Authentication
API keys and OAuth 2.0 — how to get a bearer token for the Public API.
Every request to api.ac-co.ai needs a bearer token:
Authorization: Bearer <token>There are two ways to get one. Pick API keys for server-to-server integrations and scripts; pick OAuth 2.0 for an app that signs in on behalf of a user.
API keys
Create a key in the accounting app, under Settings → Developers (/settings/developers). Each key:
- Is bound to the organization it was created in — every request authenticated with it acts as that organization.
- Has an
ac_…prefix and is shown to you once, at creation time — only its hash is stored, so if you lose it you have to revoke and create a new one. - Can be scoped to a specific set of scopes (recommended), or left with no scopes selected for a legacy "full access" key that can call anything your account can. Prefer picking specific scopes.
- Scopes that touch HMRC (
hmrc:read,hmrc:write) are restricted — see HMRC compliance.
Call the API with the key as a bearer token:
curl https://api.ac-co.ai/api/rpc/legalEntity/getLegalEntities \
-H "Authorization: Bearer ac_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Revoking a key immediately invalidates it — there's no grace period.
OAuth 2.0 (authorization code + PKCE)
For an application acting on behalf of a user, use the standard OAuth 2.0 authorization-code flow with PKCE against ac-co.ai's identity provider (auth.ac-co.ai). All endpoints live under /api/auth:
| Endpoint | Path |
|---|---|
| Authorization | /api/auth/oauth2/authorize |
| Token | /api/auth/oauth2/token |
| Dynamic client registration | /api/auth/oauth2/register |
| JWKS (token verification keys) | /api/auth/jwks |
Dynamic client registration
You don't need a pre-registered client_id — register one on the fly (RFC 7591):
curl -X POST https://auth.ac-co.ai/api/auth/oauth2/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My Integration",
"redirect_uris": ["https://myapp.example.com/callback"]
}'This returns a client_id (and no secret — registered clients are public clients, token_endpoint_auth_method: none, so every token still requires interactive user login + consent).
Authorize
Redirect the user to the authorize endpoint with a PKCE code challenge and the scopes you need. Set resource to https://api.ac-co.ai — this is an RFC 8707 resource indicator that tells the authorization server which API the token is for; ac-co.ai's OAuth provider mints a JWT access token whose audience (aud) matches it, and the Public API gateway strictly checks that audience on every request.
https://auth.ac-co.ai/api/auth/oauth2/authorize
?response_type=code
&client_id=<your_client_id>
&redirect_uri=https://myapp.example.com/callback
&scope=openid accounting:read entities:read offline_access
&resource=https://api.ac-co.ai
&state=<random_state>
&code_challenge=<code_challenge>
&code_challenge_method=S256The user signs in, picks an ac-co.ai account (an account chooser always appears for OAuth connectors, even if they're already signed in elsewhere in the browser), and approves consent for the requested scopes. They're redirected back to your redirect_uri with a code.
Exchange the code for a token
curl -X POST https://auth.ac-co.ai/api/auth/oauth2/token \
-d grant_type=authorization_code \
-d code=<code_from_redirect> \
-d redirect_uri=https://myapp.example.com/callback \
-d client_id=<your_client_id> \
-d code_verifier=<your_pkce_verifier> \
-d resource=https://api.ac-co.aiThe response includes an access_token (a JWT, valid for 1 hour) and, if you requested the offline_access scope, a refresh_token (valid for 30 days) you can exchange for a new access token with grant_type=refresh_token when it expires. Authorization codes themselves expire after 10 minutes.
Use the access token exactly like an API key:
curl https://api.ac-co.ai/api/rpc/accounting/listInvoices \
-H "Authorization: Bearer <access_token>"Requesting HMRC scopes
hmrc:read and hmrc:write are restricted — see HMRC compliance for what that means before including them in your scope parameter.