Docs

Authentication

Authenticate with secret or publishable API keys for your own integrations, or with OAuth 2.1 and PKCE for apps that other organisations connect.

Preview· P9

This is a published preview. Names and fields may still change before general availability; changes will be listed in the changelog.

Every request to the Agoo API carries a bearer token in the Authorization header:

GET /v1/sites HTTP/1.1
Host: api.agoo.ardent.africa
Authorization: Bearer agoo_sk_test_…

The token is one of two things:

CredentialWho it's forWhat it can do
API keyYour own integrations with your own organisation, such as your HR system or CRMWhatever its scopes allow, across your organisation, in one mode (live or test)
OAuth access tokenApps that other organisations connect, and MCP clients such as AI assistantsWhatever its scopes and the signed-in person's role allow

Agoo only accepts credentials in the Authorization header. There's no query-string or cookie alternative, so keys never end up in URLs, server logs or browser history.

API keys

Secret and publishable keys

PrefixKindUse it inCan call
agoo_sk_live_Secret, liveYour servers onlyAny endpoint its scopes allow
agoo_sk_test_Secret, testYour servers and CIThe same, in test mode
agoo_pk_live_Publishable, liveBrowsers and mobile appsCreate pre-registrations and bookings; read public booking types and their free slots, and the visit types open for pre-registration
agoo_pk_test_Publishable, testBrowsers and mobile appsThe same, in test mode

A secret key can read and change your organisation's data, including personal data about visitors and staff. Treat it like a password: it belongs on a server, never in a web page, a mobile app or a kiosk.

A publishable key is safe to ship in client code because it can only do what a member of the public can already do on your booking and pre-registration pages:

Any other call with a publishable key returns forbidden, including GET /forms (full forms contain the fields your staff fill in) and GET /people (your staff directory is never readable from a web page). A visit type that isn't open for pre-registration returns not_found from GET /visit-types/{visit_type} and validation_failed from POST /visits. Admins open a type for pre-registration in Console → Settings → Forms (Forms and fields).

Live and test keys

The key decides the mode. Live and test mode share one base URL, https://api.agoo.ardent.africa/v1. A _test_ key works on a separate sandbox copy of your organisation where no SMS, WhatsApp or email is sent and nothing is billed. See Environments and test mode.

Create a key

Owners and Admins create keys.

Open Console → Developers → API keys

Choose Create key.

Name it after what uses it

For example "HR sync (production)" or "Front desk integration (CI)". The name appears in the audit trail next to everything the key does.

Choose the kind, the mode and the scopes

Choose secret or publishable, live or test, and tick only the scopes the integration needs. Publishable keys have a fixed set of permissions.

Copy the key now

Agoo shows the full key once. Store it straight away in your secret manager or deployment environment, for example as AGOO_API_KEY. After this, the console shows only the key's prefix, its last four characters and when it was last used.

Plans

Test keys work on every plan. Live secret keys need a plan that includes the API: Growth can manage webhook endpoints and read events; Pro and Enterprise get the full API. A live call above your plan returns plan_required.

Live publishable keys work in the embed script and the WordPress plugin's blocks on every plan that has booking pages, because those show your hosted pages in a frame. Calling the API directly with a publishable key, as the React components do, needs Pro or Enterprise in live mode.

You can't change a key's scopes after you create it. To give an integration more or fewer permissions, create a new key and rotate to it.

Store keys safely

  • Keep keys in environment variables or a secret manager, never in source code. Agoo's prefixes (agoo_sk_live_ and friends) make leaked keys easy for secret scanners to spot.
  • Give each integration its own key, so you can see what each one does in the audit trail and revoke one without breaking the others.
  • Give each key the fewest scopes it needs. A payroll export needs attendance:read, not people:write.
  • Never log the Authorization header. If you log requests, log the Agoo-Request-Id response header instead.
  • Never send a secret key to a browser, a mobile app or a kiosk. If a page needs to create bookings, use a publishable key; if it needs more, call your own server and let your server call Agoo.

Rotate a key

Rotate keys when someone who had access leaves, on your regular schedule, or straight away if you think a key has leaked.

Create the replacement

Create a new key with the same mode and scopes.

Deploy it

Update the secret wherever the old key is used and redeploy.

Check the old key has gone quiet

In Console → Developers → API keys, the old key's last used time stops moving once nothing uses it.

Revoke the old key

Choose Revoke on the old key.

Revoke a key

Revoking takes effect at once: every request with that key returns unauthorized, and revoking can't be undone. If a key leaks, revoke it first and create the replacement second; a few minutes of failed calls are better than an open door. Revocations are recorded in the audit trail.

IP allow-lists (Enterprise)

Enterprise organisations can limit API access to the IP ranges they list. A request from any other address returns forbidden, even with a valid key.

OAuth 2.1 for apps

Use OAuth when you build an app that other organisations connect to their Agoo, or when a person connects an MCP client such as an AI assistant. The app never sees anyone's password or API key: each organisation grants it a token, limited to the scopes it asked for, that the organisation can revoke at any time.

Agoo follows OAuth 2.1: the authorization code flow with PKCE (S256 only), exact redirect URI matching, short-lived access tokens and refresh tokens that rotate on every use. The implicit and password grants aren't supported.

EndpointURL
Authorizationhttps://app.agoo.ardent.africa/oauth/authorize
Tokenhttps://api.agoo.ardent.africa/oauth/token
Server metadata (RFC 8414)https://api.agoo.ardent.africa/.well-known/oauth-authorization-server
Dynamic client registrationhttps://api.agoo.ardent.africa/oauth/register (for MCP clients that can't use a metadata document)

The metadata document lists everything else, including the revocation endpoint, the client authentication methods the token endpoint accepts and client_id_metadata_document_supported: true, which tells MCP clients they can register with a Client ID Metadata Document. Read those values from it rather than hard-coding them.

Register your app

Register your app in Console → Developers on your own Agoo organisation (Pro or Enterprise). You get a client ID and, for apps that run on a server, a client secret, shown once.

  • Redirect URIs must match exactly, including the path and any trailing slash. Use HTTPS; http://localhost is allowed while you develop.
  • Confidential clients run on a server and keep a client secret. Public clients, such as desktop and mobile apps, have no secret and rely on PKCE alone.
  • MCP clients register themselves, so you don't need to do anything. See How MCP clients register.

How MCP clients register

AI assistants and other MCP clients connect to many servers they have no prior relationship with, so they identify themselves when they first connect. Agoo supports the two ways the MCP authorization specification (revision 2026-07-28) describes:

MethodHow it worksStatus in the MCP spec
Client ID Metadata Document (preferred)The client's client_id is an HTTPS URL with a path, such as https://assistant.example/oauth/client.json. Agoo fetches the JSON document at that URL, checks that its client_id equals the URL, and accepts only the redirect_uris it lists. No registration call is needed.Recommended
Dynamic client registration (RFC 7591)The client posts its details to https://api.agoo.ardent.africa/oauth/register and gets a client_id back.Deprecated; kept for clients without metadata documents

A metadata document needs at least client_id, client_name and redirect_uris. Agoo caches it as its HTTP cache headers allow, shows the client's name and the redirect address's host name on the consent screen, and warns when the only redirect addresses are on localhost. Not every MCP client supports metadata documents yet; those that don't use dynamic client registration, which works the same for you. Either way, an admin approves the client for your organisation before it gets a token. See Connect an MCP client.

Walk through the authorization code flow

The example below is Coastline Consult's CRM connecting to Volta Bank's Agoo, so it can invite Coastline's consultants as visitors.

Create a PKCE verifier, a challenge and a state

Make a fresh set for every sign-in, and keep the verifier and state in the person's session.

pkce.ts
import { createHash, randomBytes } from "node:crypto"

export function createPkce() {
  const codeVerifier = randomBytes(32).toString("base64url") // 43 characters
  const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url")
  const state = randomBytes(16).toString("base64url")
  return { codeVerifier, codeChallenge, state }
}

Send the person to Agoo

Redirect their browser to the authorization endpoint. Ask only for the scopes you need, separated by spaces.

Redirect (line breaks added for reading)
GET https://app.agoo.ardent.africa/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fcrm.coastline.example%2Fagoo%2Fcallback
  &scope=visits%3Aread%20visits%3Awrite%20people%3Aread
  &state=kN3vQ8tWzR1yLp6s
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

The person signs in and approves

They sign in to Agoo, choose the organisation and see your app's name and the scopes it asks for.

The first time an app is connected to an organisation, an Owner or Admin must approve it for that organisation. If the person isn't an admin, Agoo tells them to ask one. Once approved, the app appears in the organisation's console, where admins can revoke it at any time.

Handle the callback

Agoo redirects back to your redirect URI:

GET https://crm.coastline.example/agoo/callback?code=AUTHORIZATION_CODE&state=kN3vQ8tWzR1yLp6s

Check that state matches the one you stored, and stop if it doesn't. If the person declined, you get error=access_denied instead of a code. Codes are single-use and expire after a short time, so exchange them straight away.

Exchange the code for tokens

Post the code and the verifier to the token endpoint as a form. Confidential clients also authenticate, here with HTTP Basic (client_secret_basic); public clients send client_id in the form instead.

Token request
curl https://api.agoo.ardent.africa/oauth/token \
  -u "$AGOO_CLIENT_ID:$AGOO_CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code="$AUTHORIZATION_CODE" \
  -d redirect_uri=https://crm.coastline.example/agoo/callback \
  -d code_verifier="$CODE_VERIFIER"
Response
{
  "access_token": "<access token>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "<refresh token>",
  "scope": "visits:read visits:write people:read"
}

The granted scope can be narrower than you asked for, so check it. Store the refresh token encrypted at rest. Treat both tokens as opaque strings; their format may change.

Call the API

Use the access token exactly like an API key:

curl https://api.agoo.ardent.africa/v1/visits?status=expected \
  -H "Authorization: Bearer $ACCESS_TOKEN"

An access token acts for the person who connected the app. It can do only what both its scopes and that person's role allow: a host's token can't approve visits for other hosts, whatever its scopes. A call the role doesn't allow returns forbidden; a missing scope returns scope_missing.

Refresh tokens

Access tokens last 1 hour. Before one expires, or when a call returns unauthorized, get a new one:

Refresh request
curl https://api.agoo.ardent.africa/oauth/token \
  -u "$AGOO_CLIENT_ID:$AGOO_CLIENT_SECRET" \
  -d grant_type=refresh_token \
  -d refresh_token="$REFRESH_TOKEN"

Refresh tokens rotate: every refresh returns a new refresh token, and the one you sent stops working. Save the new one before you use the new access token. If an old refresh token is ever presented again, Agoo treats it as stolen and revokes the whole grant, so the person has to connect again. Two servers refreshing the same grant at once can trigger this, so refresh in one place, under a lock.

Errors from the token endpoint follow OAuth (RFC 6749), not problem details: for example { "error": "invalid_grant" } when a code or refresh token is expired, used or revoked. When you get invalid_grant on a refresh, send the person through the authorization flow again.

Revocation

  • Your app can revoke a token it no longer needs, for example when a customer disconnects Agoo in your product. Post it to the revocation_endpoint from the server metadata (RFC 7009). Revoking a refresh token also revokes the access tokens issued from it.
  • An organisation's admins can revoke your app at any time from the console. Its tokens stop working at once and further calls return unauthorized; your app should show the customer that the connection has ended.

Choosing between keys and OAuth

You're buildingUse
A script, sync job or integration for your own organisationA secret API key
A booking widget or pre-registration form on your websiteA publishable key
A product that many organisations connect to their AgooOAuth
Something that acts as a specific person and respects their roleOAuth
An AI assistant or other MCP clientOAuth (handled for you by the MCP server)
Tests in CIA secret test key (agoo_sk_test_…)

Scopes

Scopes apply to API keys and OAuth tokens alike. Enterprise organisations can agree custom scopes with Ardent.

ScopeAllows
organisation:readRead the organisation's name, plan and wallet balance
sites:readRead sites, gates and paired kiosks
people:readRead hosts and employees
people:writeCreate, update and deactivate hosts and employees
visits:readRead visits
visits:writeCreate and update visits; check in, check out, approve, deny, cancel; resend passes
visitors:readRead visitor records
visitors:deletePermanently erase a visitor's personal data
deliveries:readRead deliveries
deliveries:writeRecord deliveries and mark them collected
bookings:readRead booking types, free slots and bookings
bookings:writeCreate, reschedule, cancel, confirm and decline bookings
attendance:readRead clock-ins and clock-outs, attendance summaries and shifts
attendance:writeRecord clock-ins and clock-outs from your own terminals
attendance:manageChange attendance settings, record acknowledgements and consents, review punches
watchlist:readRead the watchlist
watchlist:writeAdd and remove watchlist entries
rollcalls:readRead roll calls and who is accounted for
rollcalls:writeStart and close roll calls
webhooks:manageManage webhook endpoints, rotate secrets, send test events and redeliver events
events:readRead events
audit:readRead the audit trail
forms:readRead visit types, forms and their custom field schemas
files:readRead files and get short-lived download links
files:writeRegister file uploads (photos, selfies, signatures, documents)

Each endpoint in the API reference lists the scope it needs.

A write scope lets you read the objects it creates or changes in the response. For example, a key with only people:write gets the person back from POST /people and PATCH /people/{person_id}, and a key with only visits:write gets the visit back from POST /visits and the visit actions. It doesn't let you list or fetch those objects: that still needs the read scope.

On this page