Authentication
Every OSPulse API call is authenticated with a bearer token. This page covers how to create a token, where to keep it, how to rotate it without downtime, and how to read the responses you get when a token is wrong or insufficient.
The bearer scheme
OSPulse uses HTTP Bearer authentication. Present your token in the Authorization header on every request:
Authorization: Bearer {token}There is no separate API key header, no query-string credential, and no session cookie for the public API. If the header is absent or malformed, the request is rejected before it reaches any handler.
curl https://api.ospulse.app/api/v1/repositories \
-H "Authorization: Bearer $OSPULSE_TOKEN"Reuse a single client or session across calls so the header is set once. Set the token from the environment at process start — do not read it from a file that ships with your build.
Create a token
In the app
Sign in to app.ospulse.app and open API tokens in the main navigation — or go straight to app.ospulse.app/api-tokens. Give the token a name that identifies where it runs — ci-main, warehouse-sync, laptop-rob — because that name is the only thing you will have to go on when you later decide what is safe to revoke.
API tokens require an administrator
The API tokens screen is only visible to administrators. If you cannot see it in the navigation, your account does not hold an admin role — ask an administrator in your tenant to issue a token for you. See tenancy and access.
Through the API
If you already hold a valid token, you can mint further tokens programmatically with POST /api/v1/api-tokens.
curl -X POST https://api.ospulse.app/api/v1/api-tokens \
-H "Authorization: Bearer $OSPULSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-main",
"roleScopeJson": null,
"expiresAtUtc": "2027-01-31T00:00:00Z"
}'{
"apiTokenId": "8f1c4e2a-0b7d-4f16-9a3e-5c2b7d61f004",
"name": "ci-main",
"prefix": "osp_9fQ2",
"rawSecret": "osp_9fQ2...only-shown-once...",
"roleScopeJson": "{}",
"expiresAtUtc": "2027-01-31T00:00:00Z",
"createdAtUtc": "2026-07-28T09:14:02.117Z",
"replay": false
}namestringRequiredHuman-readable label for the token. Use it to record where the token runs so revocation is unambiguous later.
roleScopeJsonstringRequiredJSON document describing the roles the token carries. Nullable — send null to inherit the creating principal's roles.
expiresAtUtcstring (date-time)RequiredUTC expiry timestamp. Nullable — send null for a token that does not expire on its own, though a bounded lifetime is strongly preferred.
The response adds the fields you need to manage the token afterwards:
apiTokenIdstring (uuid)OptionalIdentifier used to revoke this token via DELETE /api/v1/api-tokens/{apiTokenId}. Safe to store and log.
prefixstringOptionalNon-secret leading fragment of the token. Use it to correlate a token in your secret store with the entry in the token list — it is what the app displays instead of the full value.
rawSecretstringOptionalThe token value itself. Returned only on this response and never retrievable again.
createdAtUtcstring (date-time)OptionalWhen the token was issued.
replaybooleanOptionaltrue when the request was recognised as a repeat of one already processed, so an existing token was returned rather than a new one being created. See Idempotency.
rawSecret is shown once
rawSecret appears in the create response and nowhere else. There is no endpoint that returns an existing token's value. If you lose it, revoke the token and create a replacement — that is the only recovery path, and it is deliberate.
List and revoke tokens
GET /api/v1/api-tokens returns the tokens on your tenant. It never returns secrets — only the metadata you need to audit them.
curl https://api.ospulse.app/api/v1/api-tokens \
-H "Authorization: Bearer $OSPULSE_TOKEN"{
"items": [
{
"apiTokenId": "8f1c4e2a-0b7d-4f16-9a3e-5c2b7d61f004",
"name": "ci-main",
"prefix": "osp_9fQ2",
"roleScopeJson": "{}",
"lastUsedAtUtc": "2026-07-28T08:59:41.002Z",
"expiresAtUtc": "2027-01-31T00:00:00Z",
"revokedAtUtc": null,
"createdAtUtc": "2026-07-28T09:14:02.117Z"
}
],
"totalEstimate": 1
}lastUsedAtUtc is the field to audit against. A token that has not been used in months is either dead weight or a credential someone forgot they issued; both are reasons to revoke it. A revokedAtUtc that is not null means the token is already dead and is retained only for the audit trail.
Revoking is a single call and takes effect immediately:
curl -X DELETE \
https://api.ospulse.app/api/v1/api-tokens/8f1c4e2a-0b7d-4f16-9a3e-5c2b7d61f004 \
-H "Authorization: Bearer $OSPULSE_TOKEN"A successful revoke returns 204 No Content with an empty body. An unknown or already-removed apiTokenId returns 404.
Storing tokens
A token is a credential with the standing access of the principal that created it. Treat it exactly as you would a database password.
- Local development — export it into your shell environment (
OSPULSE_TOKEN) or use a.envfile that is listed in.gitignore. Never a checked-in config file. - CI — use the platform's encrypted secret store: GitHub Actions repository or organisation secrets, GitLab CI/CD variables marked masked and protected, Azure DevOps variable groups backed by Key Vault. Mint a token dedicated to CI so it can be revoked without disturbing anything else.
- Servers and workloads — pull the token from a secret manager (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault) at start-up rather than baking it into an image or a Kubernetes manifest.
- Never commit a token, paste it into an issue tracker or chat, or log the
Authorizationheader. Redact that header explicitly in your HTTP logging middleware — many defaults do not.
Assume a leaked token is already in use
If a token reaches somewhere it should not — a public commit, a shared log bundle, a screenshot — revoke it first and investigate afterwards. Revocation is instantaneous and creating a replacement takes one API call, so there is no reason to delay while you assess the blast radius. Then check GET /api/v1/audit-log for what was done with it.
Rotation without downtime
Because tokens are independent credentials rather than a single tenant-wide key, rotation is additive: you create the new one before you remove the old one, so there is never a moment when nothing works.
Create the replacement
Issue a new token with a name that distinguishes it from the outgoing one, and record the returned apiTokenId and prefix.
curl -X POST https://api.ospulse.app/api/v1/api-tokens \
-H "Authorization: Bearer $OSPULSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "ci-main-2026-07", "roleScopeJson": null, "expiresAtUtc": "2027-01-31T00:00:00Z" }'Update the secret store and deploy
Write rawSecret into your secret manager or CI secret, then roll the workloads that read it. Both tokens are valid at this point, so requests in flight with the old value continue to succeed.
Confirm the new token is live
Wait until lastUsedAtUtc on the new token is populated and recent. That is positive evidence the deploy took effect, rather than an assumption.
curl https://api.ospulse.app/api/v1/api-tokens \
-H "Authorization: Bearer $OSPULSE_TOKEN"Revoke the old token
Only once the new token is demonstrably serving traffic, delete the old apiTokenId. If something was still using it, it fails now — loudly, and while you are watching.
curl -X DELETE \
https://api.ospulse.app/api/v1/api-tokens/{oldApiTokenId} \
-H "Authorization: Bearer $OSPULSE_TOKEN"Setting expiresAtUtc on every token turns rotation into something your calendar enforces rather than something you remember. A token with a six-month expiry that no one rotates fails on a known date instead of living indefinitely.
401 versus 403
The two failure codes mean genuinely different things, and conflating them will send you debugging the wrong problem.
401 Unauthorized — we do not know who you are. The header is missing or malformed, or the token is unrecognised, expired, or revoked. Retrying will not help; the credential itself has to change.
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "Unauthorized",
"status": 401,
"detail": "The supplied API token is missing, expired, or revoked.",
"instance": "/api/v1/repositories",
"correlationId": "3f8c1d92-7b45-4e0a-9c31-6d2f8b5a1e77"
}403 Forbidden — we know who you are, and you may not do this. The token authenticated successfully but the principal lacks the role for that operation, or the plan does not include the feature. Creating a token, creating an alert rule, and creating a policy all return 403 for this reason.
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.3",
"title": "Forbidden",
"status": 403,
"detail": "The authenticated principal is not permitted to perform this operation.",
"instance": "/api/v1/api-tokens",
"correlationId": "b1d0a4c6-2f39-4a58-8e77-0c9e3a5d4b12"
}The fix for a 401 is a different token. The fix for a 403 is different roles on the same token — or a plan that includes the feature. See Pricing for what each plan covers.
A 404 on a resource you believe exists is worth treating as a third case: it usually means the resource belongs to a different tenant than the token does, not that it is missing.
correlationId
Every problem response carries a correlationId. It identifies your specific request in our logs.
{
"type": "…",
"title": "…",
"status": 401,
"detail": "…",
"instance": "/api/v1/repositories",
"correlationId": "3f8c1d92-7b45-4e0a-9c31-6d2f8b5a1e77"
}Log it on every non-2xx response — it costs nothing and it is the difference between "authentication is failing sometimes" and a support engineer looking at the exact request. Quote it when you contact support or email contact@fortitude-omnis.group. Validation failures (400, 422) add an errors object mapping each field to its messages; see Errors for the full shape.
Tokens and tenancy
A token belongs to a tenant, and every call made with it is scoped to that tenant. There is no cross-tenant read: repositories, packages, scans, policies and alerts resolved through a token are always the ones the token's tenant owns. This is why an unfamiliar 404 is usually a tenancy mismatch rather than a deleted resource.
If your organisation operates several OSPulse tenants, you need one token per tenant and your client needs to select the right one — a single token cannot span them. See Tenancy for how tenants, workspaces and teams relate, and SSO and SCIM for provisioning the human accounts that create these tokens in the first place.
