2019-04-12 17:38:03 +02:00
|
|
|
---
|
|
|
|
title: API Conventions
|
|
|
|
---
|
|
|
|
|
2019-03-03 19:57:35 +01:00
|
|
|
Each sr.ht API follows the same set of design conventions throughout. Each API
|
|
|
|
is RESTful, authenticated with [meta.sr.ht](meta.sr.ht), and has consistent
|
|
|
|
standards for pagination, response codes, and so on, throughout.
|
|
|
|
|
2019-04-30 02:32:30 +02:00
|
|
|
## API Wrappers
|
|
|
|
|
|
|
|
API wrappers for a few programming languages exist:
|
|
|
|
|
|
|
|
- [CHICKEN Scheme](https://git.sr.ht/~evhan/topham)
|
|
|
|
- [Golang](https://git.sr.ht/~sircmpwn/sourcehut-go)
|
|
|
|
|
|
|
|
Please write to [sr.ht-discuss](https://lists.sr.ht/~sircmpwn/sr.ht-discuss) to
|
|
|
|
share yours!
|
|
|
|
|
2019-03-03 19:57:35 +01:00
|
|
|
## Authentication
|
|
|
|
|
|
|
|
All services are authenticated with OAuth via meta.sr.ht. For more information,
|
2020-07-11 01:57:52 +02:00
|
|
|
consult [the meta.sr.ht OAuth documentation](/meta.sr.ht/oauth-api.md).
|
2019-03-03 19:57:35 +01:00
|
|
|
|
|
|
|
## Routing
|
|
|
|
|
|
|
|
The basic routing model is:
|
|
|
|
|
|
|
|
### /api/:resource
|
|
|
|
|
|
|
|
List of `:resources`, e.g. `/api/repos`
|
|
|
|
|
|
|
|
- **GET**: retrieve the list
|
|
|
|
- **POST**: create a new resource
|
|
|
|
|
|
|
|
### /api/:resource/:id
|
|
|
|
|
|
|
|
Manipulate a specific `:resource` by `:id`.
|
|
|
|
|
|
|
|
- **GET**: retrieve the resource
|
|
|
|
- **PUT**: modify the resource
|
|
|
|
- **DELETE**: destroy the resource
|
|
|
|
|
|
|
|
### /api/:resource/:id/:subresource
|
|
|
|
|
|
|
|
List of `:subresources` which are children of `:resource`.
|
|
|
|
|
|
|
|
OR
|
|
|
|
|
|
|
|
A singleton which is owned by `:resource`.
|
|
|
|
|
|
|
|
OR
|
|
|
|
|
2020-03-25 11:22:36 +01:00
|
|
|
A named action to be completed asynchronously.
|
2019-03-03 19:57:35 +01:00
|
|
|
|
|
|
|
### /api/:resource/:id/:subresource/:id
|
|
|
|
|
|
|
|
Manipulate a specific `:resource` by `:id`. See `/api/:resource/:id`.
|
|
|
|
|
|
|
|
### Notes
|
|
|
|
|
|
|
|
- The `:id` may be an integer or string
|
|
|
|
|
|
|
|
## Request & response format
|
|
|
|
|
|
|
|
All requests and response bodies shall be encoded with Content-Type
|
|
|
|
`application/json`.
|
|
|
|
|
|
|
|
### Resource schemas
|
|
|
|
|
|
|
|
Each resource returned by the API has its own schema, and may be given in two
|
|
|
|
forms: *full form* and *short form*. The full form is always returned when
|
|
|
|
retrieving a resource by ID, and contains the maximum amount of detail. The
|
2021-05-16 09:47:31 +02:00
|
|
|
short form contains less information — an ID at the minimum, but often more -
|
2019-03-03 19:57:35 +01:00
|
|
|
and is returned where the long form would be inconvenient, such as from a list
|
|
|
|
endpoint or for singletons embedded in a parent resource.
|
|
|
|
|
|
|
|
#### Timestamps
|
|
|
|
|
|
|
|
Timestamps are encoded as strings using the format `%Y-%m-%dT%H:%M:%S`.
|
|
|
|
|
|
|
|
### Pagination
|
|
|
|
|
|
|
|
When executing a `GET` request on a list of resources, the response format is:
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"next": :id,
|
|
|
|
"results": [ :resources... ],
|
|
|
|
"results_per_page": 50,
|
|
|
|
"total": 200
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-03-17 16:57:35 +01:00
|
|
|
To retrieve the next page of results, add `?start=:id` to your request URL,
|
|
|
|
using the `:id` given by `"next"`.
|
2019-03-03 19:57:35 +01:00
|
|
|
|
|
|
|
## Response codes
|
|
|
|
|
|
|
|
The use of HTTP response codes is standardized throughout sourcehut.
|
|
|
|
|
|
|
|
- **200**: Resource(s) found
|
|
|
|
- **201**: Resource(s) created
|
|
|
|
- **202**: Your request has been accepted and is processing
|
|
|
|
- **204**: Used when resource(s) are successfully deleted
|
|
|
|
- **400**: Your request is invalid (missing required fields?)
|
|
|
|
- **401**: You're missing the (or have an invalid) Authorization header
|
|
|
|
- **403**: You're not allowed to do what you're attempting
|
|
|
|
- **404**: Resource(s) not found
|
|
|
|
- **5xx**: Something broke on our end
|
|
|
|
|
|
|
|
### Error responses
|
|
|
|
|
|
|
|
Errors are returned with a consistent response body:
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"errors": [
|
|
|
|
{
|
|
|
|
"field": "example",
|
2020-03-25 11:22:36 +01:00
|
|
|
"reason": "example is required"
|
2019-03-03 19:57:35 +01:00
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
`"field"` is omitted when it is not applicable.
|
|
|
|
|
|
|
|
## Standard endpoints
|
|
|
|
|
|
|
|
### GET /api/version
|
|
|
|
|
|
|
|
Returns the API version installed for this service. Sourcehut uses semantic
|
|
|
|
versioning, each version being of the format "major.minor.patch". The patch
|
|
|
|
number increments with every release, the minor number increments when new
|
|
|
|
features are added, and the major version increments on breaking changes. Note
|
|
|
|
that the minor and patch versions may increment when changes are made to the web
|
2021-05-16 09:47:31 +02:00
|
|
|
frontend — which may not necessarily affect the API — but major versions only
|
2019-03-03 19:57:35 +01:00
|
|
|
increment on breaking API changes. Any API whose major version is 0 makes no
|
|
|
|
guarantees about interface stability.
|
|
|
|
|
2019-05-06 17:15:07 +02:00
|
|
|
Changes considered non-breaking are adding new API endpoints and resources,
|
|
|
|
adding members to existing resources, adding members to enumerations, and adding
|
|
|
|
optional fields to existing requests.
|
|
|
|
|
2019-03-03 19:57:35 +01:00
|
|
|
**Response**
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"version": "1.2.3"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-03-03 20:05:39 +01:00
|
|
|
## Standard resources
|
|
|
|
|
|
|
|
### User resource
|
|
|
|
|
|
|
|
**full form**
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"canonical_name": "~username",
|
|
|
|
"name": "username",
|
|
|
|
"email": "email",
|
|
|
|
"url": "url" or null,
|
|
|
|
"location": "location" or null,
|
2019-03-03 22:29:34 +01:00
|
|
|
"bio": "bio" or null
|
2019-03-03 20:05:39 +01:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
**short form**
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"canonical_name": "~username",
|
2019-03-03 22:29:34 +01:00
|
|
|
"name": "username"
|
2019-03-03 20:05:39 +01:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2019-03-03 19:57:35 +01:00
|
|
|
## Webhooks
|
|
|
|
|
|
|
|
Most resources will have webhooks which can deliver updates to your application
|
|
|
|
via HTTP POST. You'll able to subscribe to some number of events, such as (on
|
|
|
|
meta.sr.ht) `profile:update` or `ssh-key:remove`. These require the same OAuth
|
|
|
|
scopes to configure as are necessary to obtain these resources through polling -
|
|
|
|
there's no separate OAuth scope for webhooks.
|
|
|
|
|
2021-05-16 09:47:31 +02:00
|
|
|
Periodically polling the API is discouraged — use webhooks instead.
|
2019-03-03 19:57:35 +01:00
|
|
|
|
|
|
|
### Webhook delivery
|
|
|
|
|
|
|
|
When the events you've subscribed to occur, the notification will be delivered
|
|
|
|
to your URL as an HTTP POST, generally in the same format as the affected
|
|
|
|
resource is encoded via the API. `X-Webhook-Delivery` is set to a UUID assigned
|
|
|
|
to that webhook delivery, and `X-Webhook-Event` is set to the specific event
|
|
|
|
that occurred, e.g. `profile:update`.
|
|
|
|
|
2019-04-06 19:05:59 +02:00
|
|
|
### Webhook signatures
|
|
|
|
|
|
|
|
The `X-Payload-Signature` and `X-Payload-Nonce` headers can be used to verify
|
|
|
|
the authenticity of the webhook payload. Concatenate the request body with the
|
|
|
|
nonce (treat the nonce as an ASCII-encoded string) and use it to verify the
|
2020-03-25 11:22:36 +01:00
|
|
|
base64-encoded Ed25519 signature given by the `X-Payload-Signature` header. The
|
2019-04-06 19:05:59 +02:00
|
|
|
public key (also base64 encoded) is
|
|
|
|
`uX7KWyyDNMaBma4aVbJ/cbUQpdjqczuCyK/HxzV/u+4=`. Here's an example of verifying
|
|
|
|
the payload in Python:
|
|
|
|
|
|
|
|
```python
|
|
|
|
import base64
|
|
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
|
|
|
|
|
|
public_key = Ed25519PublicKey.from_public_bytes(
|
|
|
|
base64.b64decode('uX7KWyyDNMaBma4aVbJ/cbUQpdjqczuCyK/HxzV/u+4='))
|
|
|
|
|
|
|
|
payload = request.data
|
|
|
|
signature = headers["X-Payload-Signature"]
|
|
|
|
signature = base64.b64decode(signature)
|
2019-04-06 19:25:59 +02:00
|
|
|
nonce = headers["X-Payload-Nonce"].encode()
|
2019-04-06 19:05:59 +02:00
|
|
|
|
|
|
|
public_key.verify(signature, payload + nonce)
|
|
|
|
```
|
|
|
|
|
2019-03-03 19:57:35 +01:00
|
|
|
### Subscription resource
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"id": integer,
|
|
|
|
"created": "timestamp",
|
|
|
|
"events": ["event", ...],
|
|
|
|
"url": "subscription URL"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Delivery resource
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"id": integer,
|
|
|
|
"created": "timestamp",
|
|
|
|
"event": "event",
|
|
|
|
"url": "subscription URL",
|
|
|
|
"payload": "request body",
|
|
|
|
"payload_headers": "request headers",
|
|
|
|
"response": "response body",
|
|
|
|
"response_status": integer,
|
|
|
|
"response_headers": "response headers"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
- **response_status** is -2 prior to delivery, and -1 if delivery failed.
|
|
|
|
|
|
|
|
### POST /api/.../webhooks
|
|
|
|
|
|
|
|
**Request body**
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"url": "http://example.org/webhook-notify",
|
|
|
|
"events": ["profile:update", "ssh-key:remove"]
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
- **url**: the URL that should have webhook deliveries issued to it
|
|
|
|
- **events**: the list of events this subscription should include
|
|
|
|
|
|
|
|
**Response**
|
|
|
|
|
2019-03-03 20:10:01 +01:00
|
|
|
The new [subscription resource](#subscription-resource).
|
2019-03-03 19:57:35 +01:00
|
|
|
|
|
|
|
### GET /api/.../webhooks
|
|
|
|
|
2019-03-03 20:10:01 +01:00
|
|
|
List of [subscription resources](#subscription-resource).
|
2019-03-03 19:57:35 +01:00
|
|
|
|
|
|
|
### GET /api/.../webhooks/:id
|
|
|
|
|
2019-03-03 20:10:01 +01:00
|
|
|
Retrieves a [subscription resource](#subscription-resource).
|
2019-03-03 19:57:35 +01:00
|
|
|
|
|
|
|
### GET /api/.../webhooks/:id/deliveries
|
|
|
|
|
2019-03-03 20:10:01 +01:00
|
|
|
List of [delivery resources](#delivery-resource) for this subscription.
|
2019-03-03 22:41:03 +01:00
|
|
|
|
|
|
|
### GET /api/.../webhooks/:id/deliveries/:id
|
|
|
|
|
|
|
|
Retrieves a [delivery resource](#delivery-resource) by UUID.
|