# HTTP

How Versia uses the HTTP protocol for all communications between instances.

Versia uses the HTTP protocol for all communications between instances.

[Signatures](/signatures#where-signatures-are-required) lists the routes where requests and responses are signed.

## TLS

Requests not encrypted with TLS (HTTPS) **MUST NOT** be sent or responded to. All implementations **MUST** use TLS 1.2 or higher, and **MUST** support HTTP/2.

## Requests

**Headers**

- `Accept` (`string`): **MUST** be `application/vnd.versia+json` on every `/.versia/` route except the [inbox](/federation/endpoints#inbox) and [Instance Messaging](/extensions/instance-messaging), which do not need it. Basic `application/json` **MUST NOT** be used on those routes. The [well-known](/federation/endpoints#well-known) and [WebFinger](/federation/discovery) routes state their own.

- `Content-Type` (`string`, required): **MUST** be `application/vnd.versia+json` if the request has a body, except on the [Instance Messaging](/extensions/instance-messaging) route. Receivers **MUST** accept it with or without `charset=utf-8`.

- `Versia-Signature` (`string`, required): See [Signatures](/signatures) for more information.

- `Versia-Signed-By` (`Domain`, required): See [Signatures](/signatures).

- `Versia-Signed-At` (`number`, required): See [Signatures](/signatures).

- `User-Agent` (`string`): A string identifying the software making the request. **SHOULD** contain the name of the software, its version, and a link to its homepage.

  ```text title="Example User-Agent"
  CoolServer/1.0 (https://coolserver.example)
  ```

```http title="Example Request"
POST /.versia/v0.7/inbox HTTP/1.1
Host: bob.example
Content-Type: application/vnd.versia+json
User-Agent: CoolServer/1.0 (https://coolserver.example)
Versia-Signature: RW7hr/xNzshs446OAPnPxR1vh7ZfZH+FDfn59bLc3LD3SIeR7udR2NqsMLfb4IQvhoFRWE1IL3sdQTPPVaDwCg==
Versia-Signed-By: example.com
Versia-Signed-At: 1729241687

{"type":"Delete","created_at":"2024-10-18T08:54:47Z","author":"example.com:033wv9k6n0007","deleted_type":"pub.versia:social/Note","deleted":"example.com:033qtdbdkj006"}
```

## Encoding

Entity types, extension names and collection names go in paths as they are, so a namespaced type spans two path segments. They **MUST NOT** be percent-encoded, so that each path has one spelling for [signatures](/signatures) to cover. A receiver does not have to accept an encoded form.

```text title="Paths With Namespaced Types"
/.versia/v0.7/entities/pub.versia:groups/Group/1234
/.versia/v0.7/entities/User/1234/collections/pub.versia:likes/Likes
```

## Redirects

API endpoints **MUST NOT** redirect to other endpoints, with the following exceptions:

- When a `GET` request does not have an `Accept: application/vnd.versia+json` header, the server **MAY** redirect to an HTML representation of the resource.

API endpoints **MUST NOT** redirect from HTTP to HTTPS. This is to force all API communication to be encrypted, and to avoid accidentally leaking sensitive data over unencrypted connections.

This is forbidden:

```http
GET /.versia/v0.7/entities/User/1234 HTTP/1.1
Host: example.com
Accept: application/vnd.versia+json

HTTP/1.1 301 Moved Permanently
Location: https://example.com/users/1234
```

This is allowed:

```http
GET /.versia/v0.7/entities/User/1234 HTTP/1.1
Host: example.com
Accept: text/html

HTTP/1.1 301 Moved Permanently
Location: https://example.com/users/1234
```

## Rate Limits

An instance that rate limits a request **MUST** respond with a [`429 Too Many Requests`](https://datatracker.ietf.org/doc/html/rfc6585#section-4) status code and a [`Retry-After`](https://datatracker.ietf.org/doc/html/rfc9110#section-10.2.3) header. The sender **MUST NOT** retry the request before the time that header gives.

## Retries

A sender delivering to an [inbox](/federation/endpoints#inbox) **SHOULD** retry with increasing delays after a network failure, a `5xx` status code, a `408 Request Timeout`, a `429 Too Many Requests`, or a `2xx` response [it had to discard](/signatures#where-signatures-are-required). It **MUST** honor any `Retry-After` header. It **MUST NOT** retry after any other `4xx` status code.

Each retry **MUST** be [signed](/signatures) again, as the signature's timestamp will no longer be valid.

How long to keep trying is up to the implementation. 48 hours is a reasonable default.

## Responses

**Headers**

- `Content-Type` (`string`, required): **MUST** be `application/vnd.versia+json` if the response has a body, unless specified otherwise. Receivers **MUST** accept it with or without `charset=utf-8`.
- `Versia-Signature` (`string`, required): See [Signatures](/signatures) for more information.
- `Versia-Signed-By` (`Domain`, required): See [Signatures](/signatures).
- `Versia-Signed-At` (`number`, required): See [Signatures](/signatures).

```http title="Example Response"
# Answers GET /.versia/v0.7/instance/collections/admins, sent to bob.example
HTTP/1.1 200 OK
Content-Type: application/vnd.versia+json
Versia-Signature: 9Xkg1gy82HxAoXeXk7je0Qc9kiBWXuVRmqXXdvBUW+z3USKzCkfGrttFHkqBALPlexghmiXzMzxM98kURplxAA==
Versia-Signed-By: bob.example
Versia-Signed-At: 1729241717

{"total":0,"items":[]}
```

## Status Codes

Every federation route uses these status codes. An inbox that answers `2xx` before it has finished checking an entity cannot report a problem found later, so it drops the entity.

| Status code                  | Meaning                                                                                                                                                                                                                                                                  |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `200 OK`                     | The requested entity or collection is in the body.                                                                                                                                                                                                                       |
| `202 Accepted`               | The [inbox](/federation/endpoints#inbox) took the entity. A [repeated signature](/signatures#repeated-signatures) and an [unknown extension type](/validation#unknown-fields-and-types) also get a `2xx` status code.                                                    |
| `400 Bad Request`            | The body is not valid JSON.                                                                                                                                                                                                                                              |
| `401 Unauthorized`           | The [signature](/signatures) is missing or invalid, or its timestamp is [too old or too new](/signatures#verifying-the-signature).                                                                                                                                       |
| `403 Forbidden`              | The receiver refuses to federate with the sender's instance.                                                                                                                                                                                                             |
| `404 Not Found`              | There is no such entity, or the requesting instance [may not fetch it](/federation#access).                                                                                                                                                                              |
| `410 Gone`                   | The entity [was deleted](/entities/delete#fetching-deleted-entities).                                                                                                                                                                                                    |
| `415 Unsupported Media Type` | The `Content-Type` is wrong.                                                                                                                                                                                                                                             |
| `422 Unprocessable Entity`   | The entity is [invalid](/validation), its [author does not belong to the signer](/validation#authors-and-signers), or its [core type is unknown](/validation#unknown-fields-and-types). Also used for invalid [pagination parameters](/federation/endpoints#pagination). |
| `429 Too Many Requests`      | The sender is [rate limited](#rate-limits).                                                                                                                                                                                                                              |

The inbox **SHOULD** answer with `202 Accepted` and no body. Senders **MUST** treat any `2xx` status code from the inbox as delivered, as long as [its signature verifies](/signatures#where-signatures-are-required).

## Error Bodies

A response with a `4xx` or `5xx` status code **MAY** have a body in the format below. The status code is what counts: an instance **MUST NOT** change its behavior based on an error body.

**Fields**

- `error` (`string`, required): Short description of the problem.
- `error_description` (`string`): Longer description of the problem, for humans.
- `details` (`object`): Problems by field. Keys are paths to the field, and values are arrays of strings describing what is wrong with it.

```jsonc title="Example Error"
{
    "error": "Validation failed",
    "error_description": "The entity does not match its definition.",
    "details": {
        "created_at": ["Must be an RFC 3339 timestamp"]
    }
}
```
