SourceHut offers a number of APIs via GraphQL. This page documents the traits common to all of our GraphQL APIs.

List of GraphQL APIs

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!

Another easy way to play with GraphQL is via the "graphql" subcommand of the hut CLI tool for SourceHut.

Authentication strategies

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

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.

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. For details, consult RFC 6749 and the meta.sr.ht documentation.

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:

{
    "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:

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

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

Requesting with cURL

Here is a simple request:

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. See 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.

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 — 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. 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.

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:

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

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

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 — 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:

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:

{
  "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:

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

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

Such as:

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. 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.

Webhook authenticity

The X-Payload-Signature and X-Payload-Nonce headers can be used to verify the authenticity of the webhook payload.

X-Payload-Signature is a base64-encoded ed25519 signature of the request body concatenated with the X-Payload-Nonce header. The public key (also base64 encoded) is:

uX7KWyyDNMaBma4aVbJ/cbUQpdjqczuCyK/HxzV/u+4=

The public key for an arbitrary SourceHut instance is available via any GraphQL API's api-meta.json (e.g. lists.sr.ht/query/api-meta.json).

To verify a webhook payload in Python, for example, the following code would suffice:

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 = request.headers["X-Payload-Signature"]
signature = base64.b64decode(signature)
nonce = request.headers["X-Payload-Nonce"].encode()

public_key.verify(signature, payload + nonce)
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.

About this wiki

commit aa91af4fa09eb84be3388f5d8ff4c5bb3059ae5e
Author: Runxi Yu <me@runxiyu.org>
Date:   2025-03-15T15:39:35+08:00

lists.sr.ht: HTML emails are rejected by most lists, not always

Signed-off-by: Runxi Yu <me@runxiyu.org>
References: https://git.sr.ht/~sircmpwn/lists.sr.ht/commit/d2470931a39c6816db9427abfd03b3b3093987e3
Clone this wiki
https://git.sr.ht/~sircmpwn/sr.ht-docs (read-only)
git@git.sr.ht:~sircmpwn/sr.ht-docs (read/write)