OSPulse

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:

http
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.

bash
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.

bash
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"
      }'
json
{
  "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
}
namestringRequired

Human-readable label for the token. Use it to record where the token runs so revocation is unambiguous later.

roleScopeJsonstringRequired

JSON document describing the roles the token carries. Nullable — send null to inherit the creating principal's roles.

expiresAtUtcstring (date-time)Required

UTC 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)Optional

Identifier used to revoke this token via DELETE /api/v1/api-tokens/{apiTokenId}. Safe to store and log.

prefixstringOptional

Non-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.

rawSecretstringOptional

The token value itself. Returned only on this response and never retrievable again.

createdAtUtcstring (date-time)Optional

When the token was issued.

replaybooleanOptional

true 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.

bash
curl https://api.ospulse.app/api/v1/api-tokens \
  -H "Authorization: Bearer $OSPULSE_TOKEN"
json
{
  "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:

bash
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 .env file 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 Authorization header. 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.

bash
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.

bash
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.

bash
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.

json
{
  "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.

json
{
  "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.

json
{
  "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.

Next steps