aboutsummaryrefslogtreecommitdiffstats
path: root/graphql.md
blob: b002937a8236163b843772091592291fe82b35fd (plain) (blame)
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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
---
title: GraphQL on SourceHut
---

SourceHut offers a number of APIs via [GraphQL](https://graphql.org). This page
documents the traits common to all of our GraphQL APIs.

**NOTICE**: GraphQL support is a work-in-progress. Its completion is a key
priority for the SourceHut beta. If you're looking for documentation related to
our legacy APIs, which have broader coverage among our services, see [API
conventions](api-conventions.md).

# List of GraphQL APIs

*This list will be expanded as GraphQL is rolled out for more services.*

- [builds.sr.ht](/builds.sr.ht/graphql.md)
- [git.sr.ht](/git.sr.ht/graphql.md)
- [hg.sr.ht](/hg.sr.ht/graphql.md)
- [lists.sr.ht](/lists.sr.ht/graphql.md)
- [meta.sr.ht](/meta.sr.ht/graphql.md)
- [pages.sr.ht](https://srht.site)
- [todo.sr.ht](/todo.sr.ht/graphql.md)

# GraphQL playground

Each service provides a "playground" where you can run GraphQL queries to test
and learn about the system. The canonical reference for each GraphQL schema is
also available in the playground.

**NOTICE**: The GraphQL playgrounds are wired up to your *production* data. Any
queries you perform will affect your real data!

- [builds.sr.ht playground](https://builds.sr.ht/graphql)
- [git.sr.ht playground](https://git.sr.ht/graphql)
- [hg.sr.ht playground](https://hg.sr.ht/graphql)
- [meta.sr.ht playground](https://meta.sr.ht/graphql)
- [todo.sr.ht playground](https://todo.sr.ht/graphql)
- [lists.sr.ht playground](https://lists.sr.ht/graphql)

# Authentication strategies

GraphQL authentication is based on OAuth 2.0 and is compatible with
[RFC 6749][RFC 6749]. Detailed documentation on our OAuth 2.0 implementation is
available in the [meta.sr.ht documentation][meta oauth].

[RFC 6749]: https://tools.ietf.org/html/rfc6749
[meta oauth]: /meta.sr.ht/oauth.md

In short, there are two primary modes of authentication:

- Personal access tokens
- OAuth Bearer tokens

The former is suited to users who are writing their own scripts, CLI programs
with no web component, and so on. Personal access tokens are available from
[meta.sr.ht/oauth2](https://meta.sr.ht/oauth2).

The latter is useful for third-parties who wish to provide a streamlined
authentication process. You should first register for an OAuth 2.0 client at
[meta.sr.ht/oauth2](https://meta.sr.ht/oauth2). For details, consult [RFC
6749][RFC 6749] and the [meta.sr.ht documentation][meta oauth].

In either case, once a token is obtained, it is used by setting the
`Authorization` header to `Bearer <token>`, e.g.
`Authorization: Bearer AI+ym2EAAAAAAAAIc2lyY21wd26a8JLR48pyNs2ImxWYjgi9YVGxssyt5qk4YyV7BhHXAg`

## Access scopes

It is possible (and strongly encouraged) for the user to limit the scope of
access that is provided by an authentication token. The access scopes supported
by each service, and the required scopes to utilize each resolver, are
documented in that service's GraphQL schema.

# Performing GraphQL Queries

All of our GraphQL services accept queries at `/query`. To perform your query,
submit a JSON payload to this endpoint as an HTTP POST request with the
following schema:

```json
{
    "query": "your GraphQL query...",
    "variables": {
        "foo": "bar"
    }
}
```

The `variables` field is optional, if your query requires no variables. A simple
query which is supported on all APIs is:

```json
{
    "query": "{ version { major, minor, patch } }"
}
```

Your request shall have the `Content-Type` set to `application/json`.

## Requesting with cURL

Here is a simple request:

```sh
oauth_token=your oauth token
curl \
  --oauth2-bearer "$oauth_token" \
  -H 'Content-Type: application/json' \
  -d '{"query": "{ version { major, minor, patch } }"}' \
  https://meta.sr.ht/query
```

Obtain a personal access token from
[meta.sr.ht/oauth2](https://meta.sr.ht/oauth2). See [Authentication
strategies](#authentication-strategies) for details.

## Uploading files

Some GraphQL resolvers accept file uploads, via the `Upload` type. Our
implementation is compatible with the [GraphQL multipart request
specification](https://github.com/jaydenseric/graphql-multipart-request-spec).

# Query complexity limits

To limit abuse, we calculate the complexity of your query before executing it,
and apply an upper limit. As a general rule of thumb, the complexity is
a function of how many resources your request implicates. For example, consider
the following (silly) query:

```
query {
  me {
    sshKeys {
      results {
        user {
          sshKeys {
            results {
              user { 
                canonicalName
              }
            }
          }
        }
      }
    }
  }
}
```

Each field adds 1 to your complexity, unless it represents a relationship like
sshKeys — in which case it is multiplied by the number of results you request.
The total complexity of your request is capped to 200 by default; some services
permit more.

Additionally, the total time spent processing your request is capped to 3
seconds by default, though more time is permitted for resolvers handling file
uploads.

## Cursors

The number of results returned from a cursored resolver is limited to a certain
cap, and is used to spread your work out over several requests. Consider this
example:

```
query {
  me {
    sshKeys {
      cursor
      results {
        fingerprint
      }
    }
  }
}
```

The `cursor` field returns an opaque string which can be used to return
additional results, or `null` if there are none. The following request returns
another page:

```
query {
  me {
    sshKeys(cursor: $cursor) {
      cursor
      results {
        fingerprint
      }
    }
  }
}
```

You may perform repeated GraphQL queries to obtain all results. The default
limit for results returned from a single request is 25. Some resolvers accept a
`Filter` parameter which allows you to request a different number of results
&mdash; be aware of the complexity limits while tuning this number.

# API stability guarantees

The `version` resolver provides API versioning information which is compatible
with [semantic versioning](https://semver.org). The *major* version increments
when the API is changed in a backwards-incompatible way; *minor* when new
features are added, and *patch* when bugs are fixed. Changes presumed to be
backwards-compatible include:

- Adding new types
- Adding new resolvers
- Adding new fields to existing types
- Adding new members to enums
- Adding new optional parameters to existing resolvers
- Adding new optional fields to existing input types

The special version `0.0.0` indicates an API which is still undergoing its
initial design work, and provides no stability guarantees whatsoever.

Two additional fields are provided by the `version` resolver: `deprecationDate`
and `features`. The former, if not null, indicates the date at which a major
version increment is planned. Interested parties may want to monitor this value
and incorporate it into their planning. The latter, which is not available for
all APIs, enumerates the status of optional features applicable to this
SourceHut installation.

# Webhooks

SourceHut supports GraphQL-native webhooks. These can be configured to send an
HTTP request to a server operated by the user whenever certain events occur on
your account, such as adding an SSH key, updating your bio, pushing to a git
repository, and so on.

*Webhook support is fairly new and is still under development for many of our
APIs.*

We have a general introduction to webhooks on [the sourcehut blog][webhooks blog].

[webhooks blog]: https://sourcehut.org/blog/2021-08-25-graphql-native-webhooks/

## Introduction

APIs which support webhooks provide a `webhook` resolver for queries which may
be used to prepare a JSON payload which will be sent to your URL via HTTP POST.
This is of type `WebhookPayload`, which has some common features:

```graphql
interface WebhookPayload {
  uuid: String!
  event: WebhookEvent!
  date: Time!
}
```

Specific events will extend this interface with data describing the event which
occured. For example:

```graphql
type ProfileUpdateEvent implements WebhookPayload {
  uuid: String!
  event: WebhookEvent!
  date: Time!

  profile: User!
}
```

*Note: the specific interface for each kind of webhook is adjusted to the needs
of each service. Consult the GraphQL schema for each service to learn more.*

This event describes a profile update, in which the user modified some detail of
their personal information &mdash; their email address, bio, location, etc. If
you, for example, wish to be notified when a user's email address changes, you
can write a GraphQL query like this:

```graphql
query {
  webhook {
    uuid
    event
    date
    ... on ProfileUpdateEvent {
      profile { id, email }
    }
  }
}
```

When the user's email address changes, we will execute this GraphQL query on the
server and POST the resulting JSON to the URL you provide. This query produces
a payload something like this:

```json
{
  "data": {
    "webhook": {
      "uuid":
      "date":
      "profile": {
        "id": 1234,
        "email": "jane@example.org"
      }
    }
  }
}
```

The other GraphQL resolvers are available to you when you write your webhook
query, which can allow you to fetch additional information without making any
additional API requests.

## Configuring webhooks

To register your webhook, use the appropriate mutation. For instance, to
register this profile webhook, use the following interface:

```graphql
input ProfileWebhookInput {
  url: String!
  events: [WebhookEvent!]!
  query: String!
}

mutation {
  createWebhook(config: ProfileWebhookInput!): WebhookSubscription!
}
```

Such as:

```graphql
mutation {
  createWebhook(config: {
    url: "https://example.org/webhook/1234"
    events: [PROFILE_UPDATE],
    query: """
      query {
        webhook {
          uuid
          event
          date
          ... on ProfileUpdateEvent {
            profile { id, email }
          }
        }
      }
    """
  }) { id }
}
```

## Webhook authentication

Webhook queries are executed with the same credentials as the webhook was
originally configured with. This must be a personal access token or a bearer
token, as outlined in [Authentication](#authentication). Webhooks cannot be
configured from the GraphQL playground. When the original authentication method
becomes invalid (such as the expiration of or revocation of an OAuth 2.0 bearer
token), the webhook is disabled.

Webhooks are executed in read-only mode. Webhook queries cannot execute
mutations.

## Auditing and debugging webhooks

Each API provides a means of examining the webhook configuration, either by a
specific webhook's ID, or by fetching the list of all configured webhooks. Each
client is limited to only viewing or configuring the webhooks which were
associated with their credentials, except for personal access tokens, which
users may use to audit all webhooks associated with their account.

SourceHut stores a record of all webhook deliveries associated with each
subscription, which you may query via GraphQL, as well as the response status,
body, and headers returned by the remote server. These records may not be kept
indefinitely, but will be stored for at least 90 days.

Additionally, the webhook object has a "sample" resolver which provides a sample
webhook payload based on the GraphQL query you configured with it.

The precise details of these APIs are specific to each service. Consult their
respective GraphQL schemas for more information.