API tokens
Give an AI agent scoped access to ultrareview: mint a token, point the agent at the self-describing API, and let it manage review settings and profiles.
Ultrareview's configuration can be driven by machines, not just the dashboard. An org admin mints a scoped API token, hands it to an agent (an AI assistant, a CI job, a script), and that agent can read and update review settings, create review profiles, and apply them, over a plain JSON API. Everything the agent does runs through the exact same validation and audit trail a dashboard edit gets.
Create a token
- Open Configuration, then the API tokens tab in the ultrareview dashboard. Only org admins can manage tokens.
- Choose Create token, name it after the client that will hold it, pick its scopes, and pick an expiry (90 days by default).
- Copy the token from the one-time reveal. It starts with
urk_and is shown exactly once; only its hash is stored.
Revoke a token from the same list at any time. Revocation is immediate, and the row stays behind, marked Revoked and still showing when it was created and last used, as audit context.
When the dashboard is unavailable
Tokens can also be minted and revoked out of band, without a browser, by someone with operator access to your installation. That path is held to exactly the same rules as the dashboard (the same scope catalog, the same label and expiry bounds, the same per-organization cap) and it refuses any organization without a live repository connection, so neither a typo nor a disconnected organization can produce a working credential. Tokens created that way appear in this list and are revoked here like any other.
It is deliberately the narrower door: possession of an operator credential proves possession, never identity, so the person named on such a token is recorded as an asserted claim rather than a verified one.
If you need a token minted or, more urgently, a leaked one killed while the dashboard is down, contact support.
Scopes
A token holds only the scopes you grant it, and each endpoint demands exactly one scope. No scope implies another.
| Scope | Grants |
|---|---|
profiles:read | Read profiles, built-ins, the org default, and per-repo pins |
profiles:write | Create, edit, and delete profiles |
profiles:apply | Apply a profile (org default or a repo pin), and nothing else |
settings:read | Read org settings, connected repositories, and per-repo settings |
settings:write | Update org settings and per-repo settings |
One honest edge: a repo's pinned profile is itself a repo setting, so settings:write can also change pins through the settings endpoint. profiles:apply exists as the narrow grant for a token that should only switch profiles.
There is deliberately no scope for managing tokens. The API can never mint, list, or revoke credentials; that stays in the dashboard, behind your login.
Point an agent at the API
Your base URL is shown on the API tokens tab; copy it from there. On the hosted service it is https://api.mobsession.ai; a self-hosted installation shows its own API host instead. Either way, GET /v1 on that host returns the full machine-readable catalog: every endpoint, the scope it needs, example bodies, and the error code table. An agent holding only the base URL and a token can discover the rest by itself.
Three documents describe it, and none of them needs a token:
curl https://api.mobsession.ai/ # the root: what this is, and where the rest lives
curl https://api.mobsession.ai/v1 # the catalog: every endpoint, scope, and error code
curl https://api.mobsession.ai/v1/openapi.json # the full OpenAPI 3.1 document
For a person rather than an agent, that last document is rendered as the browsable API reference: every endpoint with its parameters, schemas, examples and ready-to-paste code samples. It is generated from the specification above, so the two can never disagree.
To check what your own token may do:
curl -H "Authorization: Bearer urk_..." https://api.mobsession.ai/v1/token
The endpoints
Protected requests carry Authorization: Bearer urk_...; the three public documents and the health check take no token. Bodies are JSON, and a body sent without content-type: application/json is refused.
| Method and path | Scope | What it does |
|---|---|---|
GET / | none | The root document: name, docs, versions |
GET /health | none | 200 while the API is serving |
GET /v1 | none | The machine catalog: endpoints, scopes, error codes, limits |
GET /v1/openapi.json | none | The OpenAPI 3.1 document |
GET /v1/token | any token | What your token may do, and when it expires |
GET /v1/profiles | profiles:read | List profiles, authored and built-in |
POST /v1/profiles | profiles:write | Create a profile |
GET /v1/profiles/{name} | profiles:read | Read one profile |
PATCH /v1/profiles/{name} | profiles:write | Update a profile (rename included) |
DELETE /v1/profiles/{name} | profiles:write | Delete a profile (refused while a pin or lock points at it) |
POST /v1/profiles/{name}/apply | profiles:apply | Make it the org default, or pin it to one repository |
GET /v1/repos | settings:read | List connected repositories |
GET /v1/repos/{owner}/{repo} | settings:read | One repository's settings, version, and effective values |
PATCH /v1/repos/{owner}/{repo} | settings:write | Update repository settings |
GET /v1/settings | settings:read | Organization settings (everything except profiles) |
PATCH /v1/settings | settings:write | Update organization settings |
A repository is always addressed as owner/name, matched without regard to case, exactly as GitHub spells it. Each endpoint's parameters, request body and responses are in the API reference.
A worked example: create a strict security profile and apply it to a repository.
BASE=https://api.mobsession.ai/v1
AUTH="Authorization: Bearer urk_..."
curl -X POST -H "$AUTH" -H "content-type: application/json" \
-d '{"name":"strict-security","dimensions":["security","correctness"],"verificationStrictness":2}' \
"$BASE/profiles"
curl -X POST -H "$AUTH" -H "content-type: application/json" \
-d '{"target":"repo","repo":"acme/storefront"}' \
"$BASE/profiles/strict-security/apply"
What comes back
A read of a resource and a write to it answer with the same object, so you never have to follow a write with a read. A write adds one key, dryRun.
Lists page the same way everywhere: ?limit= and ?cursor=, answering { "data": [...], "hasMore": false, "nextCursor": null }. Pass nextCursor back as cursor for the next page and treat it as opaque. A limit above 100 is refused rather than quietly clamped, because a caller that asked for 500 and silently got 100 believes it has the whole list.
Dry runs
Add ?dryRun=1 to any write. It runs the complete validation, including the referential-integrity checks the dashboard enforces, answers with the state it WOULD have produced, and commits nothing. The response carries dryRun: true.
A query parameter an endpoint does not accept is refused with invalid_query_parameter, and so is a dryRun value that is not 1, true, 0 or false. That rule exists for one reason: a mistyped ?dry_run=1 that silently committed the write is the accident a dry run is supposed to prevent.
Errors
Every error is JSON in one shape: a machine code, a human message, details carrying whatever helps you fix it, and a link to this page.
{ "error": { "code": "insufficient_scope",
"message": "This endpoint requires the profiles:write scope.",
"details": { "requiredScope": "profiles:write", "tokenScopes": ["profiles:read"] },
"docs": "https://mobsession.ai/docs/api-tokens" } }
Codes: token_missing, token_invalid, token_revoked, token_expired (401); insufficient_scope (403); malformed_json, invalid_query_parameter (400); not_found (404); method_not_allowed (405); conflict, version_conflict, builtin_profile, org_config_unreadable (409); payload_too_large (413); unsupported_media_type (415); validation_failed (422); rate_limited (429, with details.retryAfterMs and a Retry-After header); internal_error (500, with a requestId to quote to support).
A 401 also names the reason in WWW-Authenticate, and a 403 names the scope it wanted, so a client that reads headers can decide whether to retry or stop without parsing the body.
Concurrency
Repository settings carry a version. Read it, send it back as expectedVersion in your PATCH, and the API refuses with version_conflict if anything changed in between. Omitting it writes against the current state atomically, which is fine for a single-step change; a multi-step agent should send it.
Security model
- Tokens are hashed at rest (SHA-256) and shown once. A database leak exposes no usable credential.
- Each token is bound to one organization and can never act outside it.
- Requests are rate limited per token: 120 requests and 30 writes per minute.
- Every write lands in the org audit trail naming the token, and settings history shows API writes distinctly from human edits.
- Reviews' provider keys (your Anthropic key and friends) are not reachable through this API, read or write.
- No CORS headers are sent on anything that takes a token, so a browser page can never call this API with your credential. The three public documents and the health check do send them, because a specification nobody can fetch is not much of a specification.
Related
- API reference: every endpoint, generated from the specification the API serves.
- Security model: how sessions, tokens and data are protected across the product.