API reference

Account API

Self-serve control surface. Dashboard/admin credential — never ship it in a product build.

Credential: as_ account session · scheme as_ account session

11 operations · 11 captured against the live API.

Every example below is a real exchange recorded against a throwaway tenant, then redacted — not a shape transcribed from a schema. Ids and timestamps are stable fixtures so the same request always reads the same way. Back to the full index →

Account #

POST /v1/account/signup #

Create an account and its tenant

Signup is open self-serve, with no email verification. The email is trimmed and lowercased and must contain exactly one @. The password must be 12 to 256 bytes. Anything else is a 400. The call creates a new tenant (acct_…) owned by the account and logs you straight in, returning the same 30-day as_ session that login returns. Limited to 20 signups a minute per client IP and 400 a minute across all callers. Past that the answer is 429 rate_limited with Retry-After.

signup captured live

Request

curl -X POST https://api.ollacore.com/v1/account/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"dev@example.com","password":"<redacted>"}'
{
  "email": "dev@example.com",
  "password": "<redacted>"
}
ParameterTypeDescription
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Request fields

FieldTypeDescription
email required string (email) Your email address; it becomes the login. Stored lowercased. Not verified: there is no email step.
password required string min length 12 · max length 256 12–256 characters. Stored only as an argon2 hash.

Response · 200

{
  "tenant_id": "acct_fac91b282191cc02dd29912c8db43f1c",
  "session_token": "as_EXAMPLE_SESSION",
  "expires_at": "2026-02-14T09:30:00Z"
}
Response fields (3)
FieldTypeDescription
tenant_id required string The account's auto-provisioned tenant.
session_token required string Opaque bearer (as_...), shown once.
expires_at required string (date-time) When this session stops working: 30 days after it was issued. Log in again for a new one.
Errors · 400 · 409 · 429
StatusWhen
400An email without exactly one @, or a password outside 12–256 bytes.
409email already registered
429Over 20 signups a minute from this client address, or 400 a minute in all.
POST /v1/account/login #

Log in

Each successful login creates a new, independent session valid for 30 days. Sessions from earlier logins keep working until they expire or are logged out. A wrong password and an unknown email get the same 401. An email that is not shaped like an address gets 400 before any lookup. Limited to 30 attempts a minute per client IP and 600 a minute across all callers (429 rate_limited with Retry-After).

login captured live

Request

curl -X POST https://api.ollacore.com/v1/account/login \
  -H "Content-Type: application/json" \
  -d '{"email":"dev@example.com","password":"<redacted>"}'
{
  "email": "dev@example.com",
  "password": "<redacted>"
}
ParameterTypeDescription
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Request fields

FieldTypeDescription
email required string (email) The email you signed up with.
password required string Your password.

Response · 200

{
  "tenant_id": "acct_fac91b282191cc02dd29912c8db43f1c",
  "session_token": "as_EXAMPLE_SESSION",
  "expires_at": "2026-02-14T09:30:01Z"
}
Response fields (3)
FieldTypeDescription
tenant_id required string The account's auto-provisioned tenant.
session_token required string Opaque bearer (as_...), shown once.
expires_at required string (date-time) When this session stops working: 30 days after it was issued. Log in again for a new one.
Errors · 400 · 401 · 429
StatusWhen
400The email is not shaped like an address.
401Missing, expired or invalid credentials
429Over 30 attempts a minute from this client address, or 600 a minute in all.
POST /v1/account/logout #

Invalidate the session

Deletes only the session in the Authorization header. The account's other sessions and its API keys are untouched. Afterwards that token answers 401 everywhere, including on a repeated logout.

logout captured live

Request

curl -X POST https://api.ollacore.com/v1/account/logout \
  -H "Authorization: Bearer as_…" \
  -H "Content-Type: application/json" \
  -d '{}'
{}
ParameterTypeDescription
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Response · 200

{
  "logged_out": true
}
Response fields (1)
FieldTypeDescription
logged_out boolean True when this call removed the session. False only if a concurrent logout removed it first; either way the session no longer works.
Errors · 401
StatusWhen
401Missing, expired or invalid credentials

API keys #

GET /v1/account/api-keys #

List keys

Returns every key the account has minted, live and test, newest first. Revoked keys are included, with revoked_at set. Each entry carries id, name, environment, plan, scopes, created_at, last_used_at, revoked_at and expires_at. There is no pagination.

listKeys captured live

Request

curl -X GET https://api.ollacore.com/v1/account/api-keys \
  -H "Authorization: Bearer as_…"
ParameterTypeDescription
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Response · 200

{
  "api_keys": [
    {
      "created_at": "2026-01-15T09:30:01Z",
      "environment": "live",
      "expires_at": null,
      "id": "key-id-main",
      "last_used_at": null,
      "name": "docs-example-key",
      "plan": "default",
      "revoked_at": null,
      "scopes": [
        "rooms:read",
        "rooms:write",
        "members:write",
        "tokens:mint",
        "messages:write",
        "webhooks:manage",
        "usage:read",
        "rooms:delete",
        "data:erase"
      ]
    }
  ]
}
Response fields (1)
FieldTypeDescription
api_keys array of object The account's keys, without secrets.
Errors · 401
StatusWhen
401Missing, expired or invalid credentials
POST /v1/account/api-keys #

Create a key (secret shown once)

environment must be live or test. A test key works in a separate sandbox namespace of your tenant and never sees live rooms, messages or usage. Omit scopes for the default set (rooms:read, rooms:write, members:write, tokens:mint, messages:write). You may also request webhooks:manage, usage:read, rooms:delete and data:erase. Any other scope name, or any other environment, is a 400. Keys made here are on the default plan and do not expire. Only a hash of the secret is stored, so api_key appears in this response and nowhere else.

createKey captured live

Request

curl -X POST https://api.ollacore.com/v1/account/api-keys \
  -H "Authorization: Bearer as_…" \
  -H "Content-Type: application/json" \
  -d '{"name":"docs-example-key","environment":"live","scopes":["rooms:read","rooms:write","members:write","tokens:mint","messages:write","webhooks:manage","usage:read","rooms:delete","data:erase"]}'
{
  "name": "docs-example-key",
  "environment": "live",
  "scopes": [
    "rooms:read",
    "rooms:write",
    "members:write",
    "tokens:mint",
    "messages:write",
    "webhooks:manage",
    "usage:read",
    "rooms:delete",
    "data:erase"
  ]
}
ParameterTypeDescription
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Request fields

FieldTypeDescription
name string Optional label for the key, for your own records.
environment required one of: live, test live or test. A test key works in its own data namespace and never sees live data.
scopes array of one of: rooms:read, rooms:write, members:write, tokens:mint, messages:write, webhooks:manage, usage:read, rooms:delete, data:erase Scopes to grant. Omit for the default set: rooms:read, rooms:write, members:write, tokens:mint, messages:write. usage:read, webhooks:manage, rooms:delete and data:erase must be asked for.

Response · 200

{
  "key_id": "key-id-main",
  "api_key": "ak_live_EXAMPLE.NOT_A_REAL_KEY",
  "environment": "live",
  "scopes": [
    "rooms:read",
    "rooms:write",
    "members:write",
    "tokens:mint",
    "messages:write",
    "webhooks:manage",
    "usage:read",
    "rooms:delete",
    "data:erase"
  ]
}
Response fields (4)
FieldTypeDescription
key_id required string The key's public id (ak_live_… or ak_test_…). Use it to rotate or revoke the key; it is not a credential.
api_key required string Full ak_<env>_<id>.<secret>, shown once.
environment required one of: live, test live or test. A test key sees only test data.
scopes required array of one of: rooms:read, rooms:write, members:write, tokens:mint, messages:write, webhooks:manage, usage:read, rooms:delete, data:erase What the key may do. A call outside these scopes is refused with 403 when it is made.
Errors · 400 · 401
StatusWhen
400An environment other than live or test, or an unknown scope.
401Missing, expired or invalid credentials
POST /v1/account/api-keys/{key_id}/rotate #

Rotate atomically

In one transaction, mints a replacement that carries the old key's name, environment, scopes, plan and expiry, and revokes the old key. The old secret is refused from its next request. Session tokens already minted with the old key stay valid until they expire. Not idempotent: if the response is lost, a retry gets 404 because the old key is already revoked. The replacement still exists and shows in GET /v1/account/api-keys, but its secret cannot be shown again, so revoke it and create a new key.

rotateKey captured live

Request

curl -X POST https://api.ollacore.com/v1/account/api-keys/key-id-main/rotate \
  -H "Authorization: Bearer as_…" \
  -H "Content-Type: application/json" \
  -d '{}'
{}
ParameterTypeDescription
key_id path required string The key's public id (ak_live_… or ak_test_…), as returned when it was created or listed.
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Response · 200

{
  "api_key": "ak_live_EXAMPLE.NOT_A_REAL_KEY",
  "key_id": "ak_live_616b8de93a24dfbf2c5839186fd5b29a"
}
Response fields (2)
FieldTypeDescription
key_id string The replacement key's id. The old key is revoked in the same step.
api_key string The replacement key, ak_<env>_<id>.<secret>. Shown once; store it now.
Errors · 401 · 404
StatusWhen
401Missing, expired or invalid credentials
404not an owned, live key
DELETE /v1/account/api-keys/{key_id} #

Revoke

Sets the key's revoked_at. The key stays in the key list with that timestamp. The key's row is read on every request, so the next request made with this key gets 401. Session tokens it already minted stay valid until they expire. Revoking a key that is already revoked answers 404, which a retry can treat as done.

deleteKey captured live

Request

curl -X DELETE https://api.ollacore.com/v1/account/api-keys/ak_live_e8177c7859fe3139043b58fff01924bd \
  -H "Authorization: Bearer as_…"
ParameterTypeDescription
key_id path required string The key's public id (ak_live_… or ak_test_…).
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Response · 200

{
  "revoked": true
}
Response fields (1)
FieldTypeDescription
revoked boolean True once the key no longer works.
Errors · 401 · 404
StatusWhen
401Missing, expired or invalid credentials
404not an owned, live key

Directory apps #

POST /v1/account/directory-apps #

Provision an app (webhook or SMS provider)

Set exactly one of delivery_webhook_url or provider. In webhook mode the response carries delivery_secret, shown only here. Each OTP is POSTed to your URL as JSON {app_id, phone, code, expires_at} with an x-ollacore-signature: t=<unix seconds>,v1=<hex> header, where v1 is HMAC-SHA256 of <t>.<body> keyed with that secret. The URL is checked again on every delivery. In provider mode provider.kind is twilio, plivo or msg91. provider.config is checked for that vendor now, so a misconfiguration fails here rather than when a user asks for a code.

createDirectoryApp captured live

Request

curl -X POST https://api.ollacore.com/v1/account/directory-apps \
  -H "Authorization: Bearer as_…" \
  -H "Content-Type: application/json" \
  -d '{"delivery_webhook_url":"https://hooks.example.com/hook"}'
{
  "delivery_webhook_url": "https://hooks.example.com/hook"
}
ParameterTypeDescription
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Request fields

FieldTypeDescription
delivery_webhook_url string (uri) Webhook mode: where we POST each OTP, signed, for you to deliver. Must be a public https:// URL: one whose address is private, loopback or link-local is refused with 400 here, and every delivery checks again. A name that doesn't resolve yet is accepted now and refused at delivery if it still doesn't. Set this or provider, not both.
provider object Provider mode: your own SMS vendor account, which we call to send the OTP. Set this or delivery_webhook_url, not both. Credentials cannot be updated later; rotate by creating a new app.
provider.kind required if its parent is sent one of: twilio, plivo, msg91 Which SMS vendor. config holds that vendor's credentials and is validated for it now.
provider.config required if its parent is sent object Vendor-specific credentials; validated for kind at creation.

Response · 200

{
  "app_id": "da_03270a6c821fcca6c63a2049d93b62fe",
  "delivery_mode": "webhook",
  "delivery_secret": "dsec_EXAMPLE_NOT_A_REAL_SECRET",
  "delivery_webhook_url": "https://hooks.example.com/hook"
}
Response fields (4)
FieldTypeDescription
app_id string The app's public id. Safe to ship in your mobile build; the directory endpoints take it to find your tenant.
delivery_mode one of: webhook, provider How OTPs reach users: webhook or provider.
delivery_secret string, nullable HMAC secret, shown once. Webhook mode only; absent in provider mode.
delivery_webhook_url string, nullable The delivery webhook. Webhook mode only; absent in provider mode.
Errors · 400 · 401
StatusWhen
400Invalid request: both or neither of delivery_webhook_url and provider; a provider configuration of the wrong shape; or a delivery_webhook_url that is not https:// or whose address is not public (private, loopback, link-local or otherwise reserved). A name that does not resolve yet is accepted.
401Missing, expired or invalid credentials
GET /v1/account/directory-apps #

List apps and which push transports are configured

Newest first. Each entry has app_id, delivery_mode, delivery_webhook_url (webhook mode only) and created_at. It also has push_fcm and push_apns, which say whether native-push credentials are stored. The delivery secret, SMS-provider credentials and push credentials are never returned.

listDirectoryApps captured live

Request

curl -X GET https://api.ollacore.com/v1/account/directory-apps \
  -H "Authorization: Bearer as_…"
ParameterTypeDescription
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Response · 200

{
  "directory_apps": [
    {
      "app_id": "da_03270a6c821fcca6c63a2049d93b62fe",
      "created_at": "2026-01-15T09:30:01Z",
      "delivery_mode": "webhook",
      "delivery_webhook_url": "https://hooks.example.com/hook",
      "push_apns": false,
      "push_fcm": false
    }
  ]
}
Response fields (1)
FieldTypeDescription
directory_apps array of object The account's directory apps. Secrets are never returned.
Errors · 401
StatusWhen
401Missing, expired or invalid credentials
DELETE /v1/account/directory-apps/{app_id} #

Delete an app

The app_id stops working at once: otp/request and otp/verify answer 404 for it. Users belong to the tenant, not the app, so users, conversations and existing du_ sessions stay. Push tokens registered under the deleted app use the credentials of the tenant's only remaining directory app. They get no native push if the tenant has no other app or more than one.

deleteDirectoryApp captured live

Request

curl -X DELETE https://api.ollacore.com/v1/account/directory-apps/da_03270a6c821fcca6c63a2049d93b62fe \
  -H "Authorization: Bearer as_…"
ParameterTypeDescription
app_id path required string The directory app's public id.
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Response · 200

{
  "deleted": true
}
Response fields (1)
FieldTypeDescription
deleted boolean True once the app is gone. Its users and data stay in the tenant.
Errors · 401 · 404
StatusWhen
401Missing, expired or invalid credentials
404not an owned app
PUT /v1/account/directory-apps/{app_id}/push #

Set FCM/APNs credentials (write-only)

Replaces both sides: an omitted or null fcm or apns clears what was stored, so send both to keep both. fcm.service_account needs project_id, client_email and private_key. A token_uri, if present, must be https://oauth2.googleapis.com/token. apns needs a PEM p8, key_id, team_id and topic (the bundle id), with env set to sandbox or production (default production). Credentials are validated here and are write-only: no call returns them. They sign pushes to the fcm and apns tokens registered under this app.

setPush captured live placeholder credentials

Request

curl -X PUT https://api.ollacore.com/v1/account/directory-apps/da_03270a6c821fcca6c63a2049d93b62fe/push \
  -H "Authorization: Bearer as_…" \
  -H "Content-Type: application/json" \
  -d '{"fcm":{"service_account":{"project_id":"my-firebase-project","client_email":"push@my-firebase-project.iam.gserviceaccount.com","private_key":"-----BEGIN PRIVATE KEY-----\n<redacted>\n-----END PRIVATE KEY-----"}},"apns":{"p8":"-----BEGIN PRIVATE KEY-----\n<redacted>\n-----END PRIVATE KEY-----","key_id":"ABC123DEFG","team_id":"TEAM123456","topic":"com.example.chat","env":"production"}}'
{
  "fcm": {
    "service_account": {
      "project_id": "my-firebase-project",
      "client_email": "push@my-firebase-project.iam.gserviceaccount.com",
      "private_key": "-----BEGIN PRIVATE KEY-----\n<redacted>\n-----END PRIVATE KEY-----"
    }
  },
  "apns": {
    "p8": "-----BEGIN PRIVATE KEY-----\n<redacted>\n-----END PRIVATE KEY-----",
    "key_id": "ABC123DEFG",
    "team_id": "TEAM123456",
    "topic": "com.example.chat",
    "env": "production"
  }
}
ParameterTypeDescription
app_id path required string The directory app's public id.
X-Request-Id header string Your own id for this request. It is returned in the response's X-Request-Id header and attached to every log line kept for the request, so quote it when you contact support. Use printable ASCII. Without it, the server assigns a UUID.

Request fields

FieldTypeDescription
fcm object, nullable FCM credentials: service_account is the Google service-account JSON. Omit or null to clear FCM, because this call replaces both sides.
fcm.service_account required if its parent is sent object Google service-account JSON (project_id, client_email, private_key, ...)
apns object, nullable APNs credentials. Omit or null to clear APNs, because this call replaces both sides.
apns.p8 required if its parent is sent string The contents of the APNs .p8 key file (PEM).
apns.key_id required if its parent is sent string The APNs key's 10-character id.
apns.team_id required if its parent is sent string Your Apple developer team id.
apns.topic required if its parent is sent string The app's bundle id.
apns.env one of: sandbox, production default "production" production (TestFlight and App Store) or sandbox (debug builds). Defaults to production. The wrong one fails silently.

Response · 200

{
  "push_apns": true,
  "push_fcm": true
}
Response fields (2)
FieldTypeDescription
push_fcm boolean Whether FCM credentials are now stored.
push_apns boolean Whether APNs credentials are now stored.
Errors · 400 · 401 · 404
StatusWhen
400Invalid push config
401Missing, expired or invalid credentials
404No directory app with this id in your tenant.

Errors on this plane #

Every failure uses the same body — code and message. These were triggered deliberately against the live API.

401 · no Authorization header

{
  "code": "unauthorized",
  "message": "unauthorized: missing Authorization header"
}

401 · an as_ account session on the server plane

{
  "code": "unauthorized",
  "message": "unauthorized: malformed API key"
}

The full code list and retry guidance is on Errors & rate limits.