ac-co.ai docs

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:

EndpointPath
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=S256

The 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.ai

The 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.

On this page