1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
|
---
title: API Conventions
---
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.
## 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!
## Authentication
All services are authenticated with OAuth via meta.sr.ht. For more information,
consult [the meta.sr.ht OAuth documentation](/meta.sr.ht/oauth-api.md).
## 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
A named action to be completed asynchronously.
### /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
short form contains less information — an ID at the minimum, but often more -
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
}
```
To retrieve the next page of results, add `?start=:id` to your request URL,
using the `:id` given by `"next"`.
## 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",
"reason": "example is required"
}
]
}
```
`"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
frontend — which may not necessarily affect the API — but major versions only
increment on breaking API changes. Any API whose major version is 0 makes no
guarantees about interface stability.
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.
**Response**
```json
{
"version": "1.2.3"
}
```
## Standard resources
### User resource
**full form**
```json
{
"canonical_name": "~username",
"name": "username",
"email": "email",
"url": "url" or null,
"location": "location" or null,
"bio": "bio" or null
}
```
**short form**
```json
{
"canonical_name": "~username",
"name": "username"
}
```
## 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.
Periodically polling the API is discouraged — use webhooks instead.
### 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`.
### 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
base64-encoded Ed25519 signature given by the `X-Payload-Signature` header. The
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)
nonce = headers["X-Payload-Nonce"].encode()
public_key.verify(signature, payload + nonce)
```
### 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**
The new [subscription resource](#subscription-resource).
### GET /api/.../webhooks
List of [subscription resources](#subscription-resource).
### GET /api/.../webhooks/:id
Retrieves a [subscription resource](#subscription-resource).
### GET /api/.../webhooks/:id/deliveries
List of [delivery resources](#delivery-resource) for this subscription.
### GET /api/.../webhooks/:id/deliveries/:id
Retrieves a [delivery resource](#delivery-resource) by UUID.
|