GraphQL
GraphQL-Spielwiese
Dieselbe API wie REST, eine Anfrage für alles, was Sie brauchen. Nutzen Sie einen Testschlüssel: Er bleibt in diesem Tab und geht nur an unsere API.
Wo sie liegt
- Abfragen:
POST https://developers.aton-network.org/api/graphql - Abonnements:
wss://developers.aton-network.org/api/graphql/ws(graphql-transport-ws) - Schema, Version 2026-10-15: schema.graphql
- Ereignisstrom und Webhooks: AsyncAPI-Beschreibung
Jedes Feld ist auch ein REST-Aufruf, mit demselben Recht und demselben Kontingent. Eine Anfrage zieht ihre Kosten von Ihrem api-Kontingent ab, zwei Dinge auf einmal kosten also zwei.
Grenzen
| Operationen pro Anfrage | 1 (keine Bündelung) |
| Kosten pro Anfrage | 25 (jedes Feld, das unsere Datenbank liest, kostet 1, Introspektion 5) |
| Tiefe | 5 Ebenen (15 innerhalb der Introspektion) |
| Ausgewählte Felder | 300, Aliase und Fragmente dort gezählt, wo sie vorkommen |
| Größe einer Anfrage | 16 KB, 2.000 Token, Variablen 4 KB |
| Abonnements | 5 pro Socket; 10 Ereignisströme pro App und Umgebung über Server-Sent Events und 10 über WebSockets |
Fehler
Jeder Fehler hat extensions.code, denselben Code, den REST antwortet, wo es einen gibt.
| Code | Status | Warum | Was tun |
|---|---|---|---|
| invalid_key | 401 | Es wurde kein API-Schlüssel gesendet, oder er ist keiner von uns, abgelaufen oder widerrufen. | Senden Sie einen gültigen Schlüssel als Authorization: Bearer ath_test_... oder ath_live_... (über WebSocket im Payload von connection_init). |
| missing_right | neben den Daten | Dem Schlüssel fehlt das Recht, das ein Feld braucht. Dieses Feld ist null; der Rest der Antwort ist da. | Erstellen Sie im Portal einen Schlüssel mit dem Recht (app:read, usage:read, events:read), oder fragen Sie nur ab, was dieser Schlüssel lesen darf. |
| not_found | neben den Daten | Die App des Schlüssels wurde entfernt, oder eine after-ID der Ereignisse gehört nicht zu dieser App oder ist älter als 30 Tage. | Nutzen Sie einen Schlüssel einer App, die noch existiert; setzen Sie bei einer ID fort, die Sie in den letzten 30 Tagen erhalten haben, oder beginnen Sie ohne after. |
| rate_limited | 429 | Ihr api-Kontingent für diese Minute oder diesen Tag ist überschritten. Eine Anfrage zieht ihre Kosten vom Kontingent ab; zu viele offene Ereignisströme ergeben dieselbe Antwort. | Warten Sie die Sekunden aus Retry-After ab. Fragen Sie weniger Felder ab, die die Datenbank lesen, oder weniger Aliase. |
| invalid_request | 400 | Die Anfrage konnte nicht laufen: kein JSON, ein Stapel (ein Array), ein Syntaxfehler, ein Feld oder Argument, das das Schema nicht hat, mehr als eine Operation ohne operationName oder ein Abonnement über HTTP. | Lesen Sie die Meldung; das Schema liegt unter /graphql/schema.graphql. Senden Sie eine Operation pro Anfrage; Abonnements laufen über den WebSocket. |
| query_too_complex | 400 | Eine Grenze ist überschritten: Tiefe, ausgewählte Felder (Aliase und Fragmente dort gezählt, wo sie vorkommen), Introspektionstiefe oder Kosten. | Teilen Sie die Anfrage auf, oder fragen Sie weniger auf einmal ab. Die Grenzen stehen auf dieser Seite. |
| persisted_query_not_found | 200 | Den Hash einer persistierten Abfrage kennen wir noch nicht (Meldung PersistedQueryNotFound). | Senden Sie dieselbe Anfrage noch einmal mit der Abfrage und dem Hash; dann wird sie gespeichert, und danach genügt der Hash. |
| server_error | neben den Daten | Bei uns ist etwas schiefgelaufen. Details werden nicht angezeigt. | Versuchen Sie es gleich noch einmal. Wenn es weiter passiert, sagen Sie es uns auf der Support-Seite mit der Uhrzeit. |
Schema
"""
Atheron developer API, GraphQL, version 2026-10-15. A field is never removed or changed in a way that breaks a client without first being marked @deprecated, with the date and what replaces it, at least six months before.
"""
schema {
query: Query
subscription: Subscription
}
"""The developer API, version 2026-10-15. Every field is a REST call too."""
type Query {
"""
Your application as registered, for this key's environment. Needs app:read. Same as GET /api/v1/app.
"""
app: App
"""
Calls, errors and latency per day and bucket, for this key's environment. Needs usage:read. Same as GET /api/v1/usage.
"""
usage(
"""1 to 90; anything else is brought within."""
days: Int = 7
): Usage
}
"""
Your application as registered, for the environment the API key belongs to. The same fields as GET /api/v1/app.
"""
type App {
"""The name people see on the consent screen."""
name: String!
"""Where the app is: development, review or live."""
state: String!
"""confidential (a server that keeps a secret) or public (PKCE only)."""
kind: String!
"""The quota tier: free, growth, scale or partner."""
quota: String!
"""The environment of this key."""
env: Environment!
"""The OAuth client ID for this environment."""
clientId: String!
"""The redirect addresses registered for this environment."""
redirectUris: [String!]!
"""The scopes this environment's client may ask for."""
scopes: [String!]!
"""The scopes review approved for live."""
approvedScopes: [String!]!
"""Your privacy policy, shown on the consent screen."""
privacyUrl: String
"""Your homepage, shown on the consent screen."""
homepageUrl: String
"""Client secrets and certificates in force."""
credentials: Int!
"""Named testers on the test client."""
testers: Int!
"""Webhooks for this environment."""
webhooks: Int!
}
"""Which of the app's two clients a key belongs to."""
enum Environment {
"""The test client: you, your team and up to 25 named testers."""
TEST
"""The live client, after review."""
LIVE
}
"""
Calls, errors and latency per day and bucket. The same answer as GET /api/v1/usage.
"""
type Usage {
"""The environment of this key."""
env: Environment!
"""How many days the answer covers, 1 to 90."""
days: Int!
"""One row per day and bucket, oldest first."""
usage: [UsageDay!]!
}
"""One day and bucket of calls. Days are UTC."""
type UsageDay {
"""The day, YYYY-MM-DD, UTC."""
day: String!
"""token, userinfo, userinfo:sensitive, api, sandbox or sign-in."""
bucket: String!
"""Requests counted. A GraphQL request counts as its cost."""
calls: Int!
"""Of those, how many failed."""
errors: Int!
"""Mean time taken, in milliseconds."""
msAvg: Int
"""Longest time taken, in milliseconds."""
msMax: Int!
}
"""
Events as they happen, over WebSocket (graphql-transport-ws). The same events as GET /api/v1/events and your webhooks.
"""
type Subscription {
"""
Events for this key's app and environment. Needs events:read. Same as GET /api/v1/events.
"""
events(
"""Only these kinds. All kinds when left out."""
types: [EventType!]
"""Resume after this event id (kept 30 days)."""
after: ID
): Event!
}
"""
One event, with the same five fields a webhook delivery's body carries.
"""
type Event {
"""The event's id. Send it back as after to resume."""
id: ID!
"""The kind of event."""
type: EventType!
"""When it was raised, ISO 8601."""
created: String!
"""The client it concerns."""
clientId: String!
"""What happened."""
data: EventData!
}
"""The kinds of event, as webhooks name them."""
enum EventType {
USER_SIGNED_IN
CONSENT_REVOKED
MEMBERSHIP_CHANGED
PING
}
"""
What happened. Which fields are present depends on the type, exactly as in a webhook's data.
"""
type EventData {
"""The person, as the sub claim your app was given. Absent for ping."""
sub: ID
"""user.signed_in: the scopes of this sign-in."""
scopes: [String!]
"""When it happened, ISO 8601."""
at: String
}