General

Authentication

The Capacities API uses Bearer authentication (token-based, OAuth 2.0 RFC 6750). Which method to use depends on who you are building for.

Always use HTTPS. Never expose your access token in client-side code or public repositories.

Personal API token

Use a personal token when you are building something for yourself: a local script, a personal automation, a CLI tool, or a private integration that will only ever access your own Capacities account.

Obtaining a token

Generate a personal token inside the Capacities desktop app:

  1. Open Settings
  2. Navigate to Capacities API
  3. Click Generate new token
  4. Choose the space this token should access
  5. Select if the token should be read-only or allow writing

A personal token grants access to data in the selected space. Do not share it with anyone you do not trust. If you believe your token has been compromised, revoke it immediately and generate a new one.

Personal tokens are prefixed with cap-api-. Pass the full string in the Authorization header:

Authorization: Bearer cap-api-…

Space binding

Each personal token is tied to one space — there is no spaceId field in request bodies for those routes.

Revoking a personal token

Open Settings > Capacities API and delete the token. Requests using that token immediately receive 401 Unauthorized (cap_not_authenticated).


OAuth 2.0

Use OAuth when you are building an integration, app, or service that other users will connect to their Capacities account. OAuth lets users authorise your integration without sharing their personal credentials with you.

If you are building something other people will install or connect, do not ask users to paste their personal token into your app. Use OAuth instead. For community integrations, we require OAuth authentication (Learn more).

Getting a client_id

OAuth access is available to verified clients only.

The Capacities OAuth registry is curated. Submit via email to register and include the following:

FieldDescription
Integration nameDisplayed to users on the consent screen
Short descriptionWhat your integration does
Icon URLSquare image used on the consent screen
Homepage URLPublic page for your integration or product
Redirect URI(s)Exact URI(s) your OAuth flow will redirect to
Scopes neededWhich of api:read, api:write, offline_access you require
About the integrationWhat you're building and how it uses Capacities data
Testing instructionsA link to a staging environment or steps we can follow to verify the integration works

We will reply with a stable client_id. No client secret is issued.

How it works

  1. User initiates the connection: "Connect to Capacities" from within your integration
  2. Consent screen: the user is redirected to the Capacities authorisation page, where they choose which space to grant access to and approve the requested scopes.
  3. Your integration receives a token: after approval, Capacities issues an access token scoped to exactly what the user granted. You never see their personal token or credentials.
  4. The user stays in control: users can review all active connections and revoke access at any time under Settings > Capacities API > Connections.

Discovery

Fetch server metadata to get all endpoint URLs before hard-coding any of them:

GET https://api.capacities.io/.well-known/oauth-authorization-server

This returns a standard RFC 8414 document listing authorization_endpoint, token_endpoint, scopes_supported, and more.

Scopes

Request only the permissions your integration actually needs.

ScopeWhat it allows
api:readRead the user's content (objects, pages, properties, space info)
api:writeCreate and modify content in the user's space
offline_accessObtain a refresh token to stay connected without requiring re-authorisation

Authorization Code Flow (PKCE)

1. Generate a PKCE pair

code_verifier  = 43–128 random URL-safe characters
code_challenge = BASE64URL(SHA-256(code_verifier))

2. Redirect the user to the authorisation endpoint

GET https://api.capacities.io/oauth/authorize
  ?response_type=code
  &client_id=<your_client_id>
  &redirect_uri=<your_redirect_uri>
  &scope=api:read%20api:write%20offline_access
  &resource=https://api.capacities.io
  &code_challenge=<S256_code_challenge>
  &code_challenge_method=S256
  &state=<random_state>

The resource parameter is required and must be https://api.capacities.io.

The user logs in (if not already) and selects a Capacities space to authorise. After consent, the browser redirects to your redirect_uri with ?code=…&state=….

3. Exchange the code for tokens

POST https://api.capacities.io/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<authorization_code>
&redirect_uri=<your_redirect_uri>
&client_id=<your_client_id>
&code_verifier=<your_code_verifier>

Do not send an Authorization header. These are public clients with no client secret. Send client_id in the request body only.

Response

{
  "access_token": "eyJ…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "…", // present when offline_access was granted
  "scope": "api:read api:write offline_access",
}

Refreshing a token

When an access token expires, use the refresh token to obtain a new one without user interaction:

POST https://api.capacities.io/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=<refresh_token>
&client_id=<your_client_id>

Refresh tokens rotate on every use. Store the new refresh_token from the response immediately; the previous one is revoked.

Refresh tokens expire after 6 months of inactivity or when the user removes the connection from Settings > Connected Apps. You then need to prompt the user to re-authorise.

Using the access token

Pass the access token as a Bearer token on every API request:

Authorization: Bearer eyJ…

Access tokens are short-lived JWTs that expire after 1 hour.

Revoking access

Users can remove a connection at any time from Settings > Connected Apps. Your application should handle a 401 response gracefully and prompt the user to re-authorise.

You can revoke a token programmatically using the POST /oauth/revoke endpoint.

POST /oauth/revokeRFC 7009

Immediately invalidates a refresh token or access token and removes the connection from the user's Connected Apps. Use this when the user disconnects your integration from within your own UI.

Request

POST https://api.capacities.io/oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=<refresh_token_or_access_token>&client_id=<your_client_id>
ParameterRequiredDescription
tokenYesThe refresh token or access token to revoke
token_type_hintNorefresh_token or access_token — helps resolve the token faster
client_idYesYour OAuth client ID

Response

Always 200 OK with an empty body, even if the token was already expired or not found (RFC 7009 §2.2 — prevents token enumeration).

Example — revoke a refresh token

curl -X POST https://api.capacities.io/oauth/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=<refresh_token>&token_type_hint=refresh_token&client_id=<your_client_id>"

Revoking a refresh token revokes the entire connection (all associated access tokens become invalid immediately). Revoking only an access token has the same effect — the connection row is removed and future requests with that access token will return 401. The endpoint is also discoverable via the AS metadata at /.well-known/oauth-authorization-server under revocation_endpoint.

Error reference

Errors follow RFC 6749 §5.2.

errorLikely cause
invalid_requestMissing or malformed parameter (for example, client_secret sent when not expected)
invalid_clientclient_id unknown or not registered
invalid_grantCode already used, expired, or code_verifier mismatch
invalid_scopeScope not permitted for your client_id
invalid_targetresource parameter missing or not https://api.capacities.io
unauthorized_clientRedirect URI not in your registered allow-list
Are you missing something in the documentation?

Create a ticket on our feedback board. - Let us know if you have an idea for a feature, improvement or think there is something missing.

Request additions to the documentation. - If your questions are not getting answered, let us know and we will extend the documentation.