Eyemagnet Auth API - Integration Guide
To integrate with Eyemagnet Auth, send GraphQL requests over HTTPS using the base URL and credentials supplied by Eyemagnet.
This guide is for partners integrating an application or backend with Eyemagnet Auth. The API is GraphQL over HTTPS. It issues signed JWTs that can be used both with this API and with services that trust Eyemagnet Auth.
The API is a GraphQL authentication service, not an OAuth or OpenID Connect authorization-code endpoint. Keep client secrets in a trusted backend; do not embed them in a browser, mobile app, desktop app, or published source code.
Before you start
Eyemagnet will provide an API base URL plus credentials for the integration:
| Value | Purpose | Keep secret? |
|---|---|---|
| API base URL | Base path for GraphQL and the supporting HTTP endpoints | No |
| Client ID and client secret | Identifies the application requesting tokens | Yes — backend only |
| Key ID and key secret, if applicable | Identifies a machine or service principal | Yes — backend only |
| Allowed scopes and teams | Defines the actions and tenant context the integration may use | No |
In the examples below, replace https://auth.example.com/auth/v2 with the API base URL supplied for your environment. The GraphQL endpoint is the base URL itself (a trailing slash is also accepted).
| Endpoint | Method | Purpose |
|---|---|---|
{base} |
POST |
GraphQL operations |
{base}/.well-known/jwks.json |
GET |
Public signing keys for JWT validation |
{base}/_health |
GET |
Health check; returns HEALTHY |
The configured base path is environment-specific, so construct the JWKS and health URLs from the supplied base URL rather than hard-coding a path.
Calling GraphQL
Send JSON with a GraphQL query and optional variables object. Protected operations use an access token in the Authorization header.
POST /auth/v2 HTTP/1.1
Host: auth.example.com
Content-Type: application/json
Authorization: Bearer <access-token>
{
"query": "query CurrentAccount { me { id name email } }"
}
GraphQL can return a successful HTTP response with an errors array. Treat a response as successful only when the relevant item is present under data and there are no applicable errors. The current API response formatter returns an error code and name alongside message; tolerant GraphQL clients may also check extensions.code when they normalize errors:
| code | Meaning | Typical action |
|---|---|---|
AUTHENTICATION |
Missing or invalid authentication | Obtain a new token and retry once. |
AUTHORIZATION |
Client, credential, or team access is not allowed | Do not retry; check the integration configuration. |
SCOPE |
Token does not hold the required scope | Request or issue a token with the necessary scope. |
MEMBERSHIP |
Subject cannot act in the selected team | Select a permitted team. |
CREDENTIALS |
Username or password was rejected | Prompt the user again; do not reveal which field failed. |
TFA |
A second factor is required | Ask for the current TOTP or recovery code, then retry login. |
EXPIRED |
Bearer or refresh token has expired | Refresh or authenticate again. |
ACCOUNT_LOCKED |
Too many failed logins | Back off and let the user try later. |
PASSWORD_POLICY |
Proposed password does not meet policy | Display the returned policy message. |
The service throttles sensitive operations. Honor 429 responses and use bounded exponential backoff. Never retry invalid credentials in a loop.
Obtain an access token
An authentication response contains a short-lived bearer token and a longer lived refresh token. Always ask for JWT version v2 when minting new tokens.
Password authentication
Use this from a confidential backend after the user supplies their credentials. account.username accepts the account username or email address. Supply team when the user needs a particular team context; otherwise the service chooses an accessible default.
const response = await fetch("https://auth.example.com/auth/v2", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `
mutation PasswordLogin(
$client: ClientLogin!
$account: AccountLogin!
$team: Reference
) {
passwordAuthentication(client: $client, account: $account, team: $team) {
bearerToken(version: "v2")
refreshToken(version: "v2")
}
}
`,
variables: {
client: { id: "<client-id>", secret: "<client-secret>" },
account: { username: "person@example.com", password: "<password>" },
team: { id: "<team-id>" },
},
}),
});
const { data, errors } = await response.json();
if ( !response.ok || errors?.length ) throw new Error( errors?.[ 0 ]?.message || "Authentication failed" );
const { bearerToken, refreshToken } = data.passwordAuthentication;
Example response:
{
"data": {
"passwordAuthentication": {
"bearerToken": "eyJ...",
"refreshToken": "eyJ..."
}
}
}
By default, a bearer token is valid for up to 6 hours and a refresh token for up to 12 hours. The optional ttl parameter is in seconds and can shorten a token lifetime. Store refresh tokens only in encrypted server-side storage or a secure, HTTP-only session mechanism.
Machine-key authentication
For service-to-service work, use a provisioned key rather than a human password. A request needs both the client credentials and the key credentials. The key’s roles and explicit scopes determine what the resulting token may do.
mutation KeyLogin($client: ClientLogin!, $key: KeyLogin!) {
keyAuthentication(client: $client, key: $key) {
bearerToken(version: "v2")
refreshToken(version: "v2")
}
}
{
"client": { "id": "<client-id>", "secret": "<client-secret>" },
"key": { "id": "<key-id>", "secret": "<key-secret>" }
}
Use a dedicated key per deployment or workload. Rotate it by provisioning a replacement, deploying the new secret, and then retiring the old key. Do not share a key between unrelated services.
Other authentication methods
The following mutations are available when enabled for the client and account:
| Mutation | Use |
|---|---|
providerAuthentication |
Exchange a verified Apple, Google, Microsoft, or Canva provider token for Eyemagnet tokens. |
webAuthenticationOptions then webAuthentication |
Authenticate with a registered WebAuthn passkey. |
switchAuthentication |
Mint a token whose default team is another accessible team. |
refreshAuthentication |
Replace an expired or expiring bearer token using its matching refresh token. |
Provider authentication takes a provider token obtained using that provider’s SDK or flow. The application must verify that its integration is configured for the provider before relying on this flow.
Use and refresh tokens
Use the bearer token with every protected request:
Authorization: Bearer <bearer-token>
To refresh, send the current bearer token in the Authorization header and its matching refresh token as an argument. The bearer token may be expired, but it must be the token that the refresh token was minted alongside, and the same client must perform the refresh.
mutation Refresh($client: ClientLogin!, $refreshToken: String!) {
refreshAuthentication(client: $client, refreshToken: $refreshToken) {
bearerToken(version: "v2")
refreshToken(version: "v2")
}
}
{
"client": { "id": "<client-id>", "secret": "<client-secret>" },
"refreshToken": "<refresh-token>"
}
Replace both tokens atomically after every successful refresh. If a refresh fails, discard the pair and begin a new authentication flow. A password, passkey, or other sign-in-method change prevents an older bearer token from being refreshed. If immediate, account-level revocation is important to your service, also call attest before privileged work rather than relying on local JWT verification alone.
To change the active tenant while signed in, call switchAuthentication(client: ..., team: { id: ... }) with the current bearer token. The requested team must already be in the token subject’s accessible team tree.
Verify tokens in your service
Services that accept Eyemagnet tokens must verify the signature and standard JWT validity claims before trusting any custom claim.
- Fetch
{base}/.well-known/jwks.jsonand cache it according to its response headers. - Select the key matching the JWT header’s
kid. - Verify the token with
RS256; reject any other algorithm. - Validate
expand, when present,nbf. - Enforce the client (
iss), team (aud), and scope (sco) appropriate to the action. Do not authorize solely because a token has a valid signature.
The API also provides attest(token: String!): Boolean! for a remote validity check. It verifies the signature, validity window, and account token-revocation stamp. Local JWKS verification is usually preferable on the request path; use attest when a live revocation check is required.
JWT claims
| Claim | Meaning |
|---|---|
jti |
Unique token ID. |
iss |
Eyemagnet client ID that issued the token. |
sub |
Account ID or machine-key ID represented by the token. |
aud |
Array of accessible team IDs. The first ID is the current/default team. |
sco |
Granted scope names. A scope can be resource-limited as scope name::resourceId. |
iat, exp, nbf |
Issued-at, expiry, and optional not-before timestamps (Unix seconds). |
pln |
Optional granted feature names. |
meta |
Optional caller-requested display metadata. Do not use it for authorization. |
Common API operations
These examples use the bearer token header described above. Each resolver also enforces its own scope and team visibility rules.
Inspect the current identity
query CurrentContext {
app { id name }
me {
id
name
email
verified
memberships { team { id name } role { id name } }
}
us { id name }
}
app, me, and us require query clients, query accounts, and query us, respectively.
Page through resources
List operations return an edge with results, cursor, and total. Pass the returned cursor to retrieve the next page; a null cursor means there are no more results.
query Accounts($cursor: ID) {
accounts(cursor: $cursor, limit: 50, sort: { name: ASCENDING }) {
cursor
total
results { id name email username verified }
}
}
Typical integration-facing resources are:
| Resource | Query | Write mutation | Required scope |
|---|---|---|---|
| Accounts | accounts |
accounts, reactivateAccount |
query accounts / modify accounts |
| Teams | teams |
teams |
query teams / modify teams |
| Roles | roles |
roles |
query roles / modify roles |
| Scopes | scopes |
scopes |
query scopes / modify scopes |
| Clients | clients |
clients |
query clients / modify clients |
| Keys | keys |
keys |
query keys / modify keys |
| Machine-key authentication | — | keyAuthentication |
Scopes assigned to the key |
| Policies | policies |
policies |
query policies / modify policies |
| Features, plans, licences | features, plans, licenses |
features, plans, licenses |
query licenses / modify licenses |
Plural write mutations accept inputs and return the affected IDs. For example, creating or updating a client uses clients(inputs: [ClientInput!]!). The full input, filter, sort, and return types are available through GraphQL introspection in the API explorer. Build against the schema rather than relying on fields not explicitly selected in an example.
Scope and tenant design
Scopes are lowercase names such as query teams or modify accounts. A token is limited to the scopes assigned through the subject’s roles or key, further constrained by the client and optionally by the limits argument during authentication. Request only the scopes needed for the immediate task.
aud is also part of authorization: having a scope does not grant access to every team. The server limits returned records and mutations to the teams the token can access. When your own service validates a token, enforce both the required scope and the expected team membership.
Never use administrative account, client, role, or key mutations from an untrusted frontend. Route those operations through your backend and give each integration the smallest practical scope set.
Account lifecycle and security operations
Use the following public security mutations to support account flows:
| Flow | Mutation(s) | Notes |
|---|---|---|
| Invite a pre-created account | invite |
Requires permission to modify the target account. Sends a single-use link. |
| Redeem invitation or reset link | redeem |
Returns a short-lived setup-only bearer token. |
| Verify email | requestEmailVerification, verifyEmail |
The request response intentionally does not reveal whether the account exists. |
| Password reset | requestPasswordReset, redeem |
Do not expose account-existence details to the user. |
| Account recovery | requestAccountRecovery, redeem |
Requires the saved recovery code plus the delivered link. |
| Register a passkey | webRegistrationOptions, webRegister |
Run the WebAuthn ceremony in a browser and submit its JSON result. |
setup and resetPassword remain for older integrations but are deprecated. New integrations should use redeem, which supports password, passkey, and provider setup consistently.
Production checklist
- Use HTTPS and the production API base URL provided by Eyemagnet.
- Store the client secret, key secret, and refresh tokens in a secret manager or encrypted server-side store.
- Mint
v2JWTs and verifyRS256tokens using JWKS andkid. - Check signature, expiry, issuer, required scope, and expected team on every authorization decision.
- Use short bearer-token lifetimes and refresh safely; never log tokens, passwords, recovery codes, or client/key secrets.
- Handle GraphQL
errorsand throttling explicitly. - Test client/key rotation and user reauthentication before production launch.
For credentials, scope grants, environment URLs, or support, contact your Eyemagnet integration administrator.