GraphQL
Bac à sable GraphQL
La même API qu’en REST, une seule requête pour ce qu’il vous faut. Utilisez une clé de test : elle reste dans cet onglet et ne va nulle part ailleurs que vers notre API.
Où la trouver
- Requêtes :
POST https://developers.aton-network.org/api/graphql - Abonnements :
wss://developers.aton-network.org/api/graphql/ws(graphql-transport-ws) - Schéma, version 2026-10-15 : schema.graphql
- Flux d’événements et webhooks : Description AsyncAPI
Chaque champ est aussi un appel REST, avec le même droit et le même quota. Une requête prend son coût sur votre quota api, donc demander deux choses à la fois coûte deux.
Limites
| Opérations par requête | 1 (pas de regroupement) |
| Coût par requête | 25 (chaque champ qui lit notre base de données coûte 1, l’introspection 5) |
| Profondeur | 5 niveaux (15 dans l’introspection) |
| Champs sélectionnés | 300, alias et fragments comptés là où ils apparaissent |
| Taille d’une requête | 16 Ko, 2 000 jetons, variables 4 Ko |
| Abonnements | 5 par connexion WebSocket ; 10 flux d’événements par application et environnement en Server-Sent Events, et 10 en WebSocket |
Erreurs
Chaque erreur porte extensions.code, le même code que REST répond quand il y en a un.
| Code | Statut | Pourquoi | Quoi faire |
|---|---|---|---|
| invalid_key | 401 | Aucune clé d’API n’a été envoyée, ou ce n’est pas une des nôtres, elle a expiré ou a été révoquée. | Envoyez une clé valide en Authorization : Bearer ath_test_... ou ath_live_... (en WebSocket, dans le payload de connection_init). |
| missing_right | à côté des données | La clé ne porte pas le droit dont un champ a besoin. Ce champ vaut null ; le reste de la réponse est là. | Créez une clé avec ce droit (app:read, usage:read, events:read) dans le portail, ou ne demandez que ce que cette clé peut lire. |
| not_found | à côté des données | L’application de la clé a été supprimée, ou l’identifiant after des événements n’est pas de cette application ou date de plus de 30 jours. | Utilisez une clé d’une application qui existe encore ; reprenez à partir d’un identifiant reçu dans les 30 derniers jours, ou commencez sans after. |
| rate_limited | 429 | Quota api dépassé pour cette minute ou cette journée. Une requête prend son coût sur le quota ; trop de flux d’événements ouverts donnent aussi cette réponse. | Attendez les secondes indiquées dans Retry-After. Demandez moins de champs qui lisent la base de données, ou moins d’alias. |
| invalid_request | 400 | La requête n’a pas pu s’exécuter : pas du JSON, un lot (un tableau), une erreur de syntaxe, un champ ou un argument absent du schéma, plus d’une opération sans operationName, ou un abonnement envoyé en HTTP. | Lisez le message ; le schéma est à /graphql/schema.graphql. Envoyez une opération par requête ; les abonnements passent par le WebSocket. |
| query_too_complex | 400 | Une limite est dépassée : profondeur, champs sélectionnés (alias et fragments comptés là où ils apparaissent), profondeur d’introspection ou coût. | Divisez la requête, ou demandez moins à la fois. Les limites sont sur cette page. |
| persisted_query_not_found | 200 | Le hachage d’une requête persistée ne nous est pas encore connu (message PersistedQueryNotFound). | Renvoyez la même requête avec la requête en plus du hachage ; elle est alors gardée, et le hachage seul suffit ensuite. |
| server_error | à côté des données | Un problème est survenu de notre côté. Rien n’en est montré. | Réessayez dans un instant. Si cela continue, dites-le-nous sur la page d’assistance avec l’heure. |
Schéma
"""
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
}