Skip to content

Authentication

Every request except a few public ones (/up, /v1/branding, the token endpoints and image redirects) needs an Authorization header. The API accepts two schemes:

Authorization: JWT <token> # user, admin and service JWTs
Authorization: Bearer <api key> # application API keys

Anything else gets 401 Unauthorized with an empty JSON body.

Credential Who uses it Scheme Scope
User JWT Mobile and web apps acting for a signed-in person JWT That user. Admin users can also administer every audience.
Application API key Your server, acting as an app of one audience Bearer One audience’s GraphQL endpoint.
Service JWT Workers and automation JWT One audience, limited to the token’s scopes.

Apps sign people in with Facebook, then exchange the Facebook token for an AudienceKit JWT:

POST /v1/token
Content-Type: application/json
{
"facebook_token": "<Facebook user access token>",
"host": "admin.hotmess.social",
"device": {
"identifier": "<stable device identifier>",
"type": "apple",
"model": "iPhone16,1",
"version": "1.0",
"build": "42"
}
}

host is optional. On an audience’s host (admin.<audience domain>) the Facebook token must come from that audience’s Facebook app; otherwise it must come from the AudienceKit app. GET /v1/branding?host=… returns the app ID to use as facebook_app_id.

The API exchanges the token for a long-lived one, creates or updates the user from their Facebook profile, records the device and a new session, and returns:

{
"token": "<jwt>",
"access_token": "<jwt>",
"refresh_token": "<jwt>",
"user": { "id": "…", "name": "…" }
}

Send it as Authorization: JWT <jwt>. The JWT is signed with HS256. Its claims are built at sign-in by the transforms in api/app/services/login_claims/: the user’s id and app-scoped fb_id, a jti that identifies the session, a role of user or admin, fb_app_id (the Facebook app used), and, for a sign-in on an audience’s host, audience_id. A JWT with audience_id only works on that audience’s endpoint.

A user JWT works only while its session exists. Sessions end when someone removes one of our Facebook apps: every app’s Deauthorize callback is POST /v1/facebook/deauthorize, which signs that Facebook user out everywhere and forgets their Facebook token. Their data stays until they ask Facebook to delete it, which calls POST /v1/facebook/data_deletion. Both verify Facebook’s signed_request against the AudienceKit app and every audience’s apps.

To register a device for push notifications, call POST /v1/token/device with device_type, vendor_identifier and notification_token.

An admin is a user whose isAdmin flag is set; their JWTs carry role: admin. Admins can:

  • with a platform JWT (no audience_id), call POST /v1/tenants/graphql to list and create audiences and manage their domains,
  • run every GraphQL mutation (mutations refuse anyone else with “Administrator access required”),
  • call the research endpoints for any audience, and hold every service scope.

An application belongs to one audience and has an api_key. Send it as a bearer token:

Terminal window
curl https://api.audiencekit.io/v1/audience/$AUDIENCE_ID/graphql \
-H "Authorization: Bearer $AUDIENCE_KIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"{ venues { id page { name } } }"}'

A key only works on its own audience’s endpoint; any other audience returns 403 Forbidden. Requests made with a key have no current user, so me returns null.

Machines that keep an audience’s data fresh use service tokens. A service token is a secret shaped like akst_<prefix>_<secret> that belongs to one audience and carries scopes. Exchange it for a one-hour JWT:

POST /v1/token/service
Content-Type: application/json
{ "service_token": "akst_…" }
{
"token": "<jwt>",
"token_type": "JWT",
"expires_at": "2026-10-06T06:00:00Z",
"audience_id": "…",
"audience": "LGBT",
"scopes": ["research:read", "places:write", "events:write"]
}

The API checks on every request that the token behind a service JWT hasn’t been revoked or expired. An unknown, revoked or expired secret returns 401 with {"error": "invalid_service_token"}, and a missing scope returns 403 with {"error": "insufficient_scope", "required": "…"}.

Scope Allows
research:read GET /v1/research/snapshot and GET /v1/research/batches
places:write Upserting venues through POST /v1/research/batch (needs events:write too)
events:write Upserting events through POST /v1/research/batch (needs places:write too)

See Automate with service tokens to create and manage them.