OSPulse

Conventions

Rules that hold across the whole OSPulse API. Learn them once and every endpoint in the reference becomes predictable — field naming, timestamp format, identifier shape, enum behaviour, and what each HTTP verb is allowed to do.

Transport

SchemehttpsRequired

HTTPS only. Plain HTTP is not served. Requests are not redirected from http://, so a misconfigured client fails loudly instead of transmitting a token in clear text.

HoststringRequired

api.ospulse.app for production. The servers block in the generated OpenAPI document carries a development entry — set the base URL explicitly in generated clients.

Character encodingutf-8Required

UTF-8 for requests and responses. Do not send a charset other than UTF-8; non-UTF-8 bytes in a JSON body are rejected as malformed.

CompressionstringOptional

Send Accept-Encoding: gzip to have large collection responses compressed. Most HTTP clients do this by default.

Content type

Every request that carries a body must declare it:

text
Content-Type: application/json

Responses are application/json, with one deliberate exception: error responses follow RFC 7807 problem details and may be served as application/problem+json. Parse them as JSON either way.

Endpoints that accept file uploads — SBOM upload, for example — take multipart/form-data instead, and are marked as such in the reference. Sending an unsupported media type returns 415 Unsupported Media Type.

Download endpoints (generated reports, SBOM exports, evidence packs, CBOM documents) stream the artefact with its own content type. Do not assume JSON from a path ending in /download.

Field naming

RuleExample
Fields are camelCaseoverallHealthScore, capturedAtUtc, correlationId
Acronyms are not shoutedapiTokenId, not APITokenId
Collection envelopes use itemsitems, totalEstimate, take, skip
Path segments are kebab-case/api/v1/alert-rules, /api/v1/vulnerability-exposures
Query parameters are camelCaserepositoryId, businessCriticality, sinceUtc

Path parameters are documented in the reference in brace form — {repositoryId}, {packageId}, {scanId} — and are substituted literally, with no braces in the request.

Dates and times

Every timestamp is ISO-8601 in UTC. Fields carrying an instant are suffixed Utc so the timezone is unambiguous from the name alone: capturedAtUtc, expiresAtUtc, sinceUtc.

json
{
  "snapshotId": "8f0c2f66-3d3c-4c1f-9a2f-4b1c6d0f2f11",
  "capturedAtUtc": "2026-03-14T09:21:47.482Z"
}
FormatstringRequired

YYYY-MM-DDTHH:mm:ss[.fffffff]Z. Fractional seconds may be present and their precision is not guaranteed — never string-compare two timestamps for equality.

TimezonestringRequired

Always UTC, always with the Z suffix. The API neither emits nor accepts local offsets, and does not perform timezone conversion on your behalf.

Sending timestampsstringOptional

Query parameters and request bodies that take an instant accept the same format. Convert to UTC before serialising, and URL-encode the value when it appears in a query string — the colons in 09:21:47 must become %3A.

DurationsstringOptional

Where a duration is returned rather than an instant, it is a .NET-style hh:mm:ss.fffffff string — the health endpoint's totalDuration is the common example. Parse it as a duration, not a timestamp.

Identifiers

Resource identifiers are GUIDs (UUIDs) rendered as lowercase, hyphenated, 36-character strings. They appear in path parameters, in request bodies, and as …Id fields in responses.

text
/api/v1/repositories/3f8a1c27-9c1b-4e2a-8f6d-2a7c5e9b0d14/dependencies
OpacitystringRequired

Treat identifiers as opaque. Do not parse them, infer creation order from them, or reconstruct one from parts. The only safe operations are equality comparison and storage.

StoragestringRequired

Store as a string or a native UUID type — 36 characters when hyphenated. Never truncate.

CasestringOptional

Emitted lowercase. Compare case-insensitively if you accept identifiers from a user or an external system.

ScopestringOptional

Identifiers are unique within your tenant. An identifier from another tenant will not resolve; it returns 404, not 403, so the API does not confirm that the resource exists elsewhere.

Two identifiers are not GUIDs and should not be treated as such: SCIM resource ids under /scim/v2/, which follow the SCIM specification, and vulnerability identifiers such as CVE-2025-12345, which are external catalogue keys.

Null versus omitted

Many response fields are nullable. A field may therefore appear in three ways, and they mean different things:

FormMeaning
"detail": "Repository not found."A value is present.
"detail": nullThe field applies to this resource but has no value — not yet computed, not applicable, or explicitly cleared.
Field absentThe field was not serialised for this response shape. Treat exactly as null.

In requests the distinction matters more, because PATCH bodies are partial:

  • Omit a field to leave the stored value untouched.
  • Send null to clear a nullable field.
  • Send a value to replace it.

Do not round-trip a GET into a PATCH

Serialising a full resource you fetched and sending it back as a PATCH body will write every field you received, including ones another process changed since. Send only the fields you intend to modify.

Enumerations

Enum values are transmitted as strings in PascalCaseNpm, Healthy, Critical — not as integers. String values keep responses readable and let new members be added without renumbering.

json
{
  "ecosystem": "Npm",
  "riskLevel": "High",
  "state": "Completed"
}

New enum members are added over time: a new ecosystem, a new workflow state, a new severity band. Adding a member is not treated as a breaking change, so your client must tolerate values it has never seen.

Deserialise permissivelystringRequired

Parse enums into a type that can hold an unrecognised value — a plain string, or a language enum with an explicit Unknown fallback. A parser that throws on an unknown member will break on a routine, backwards-compatible release.

Never switch exhaustively without a defaultstringRequired

Every switch or match over an enum needs a default branch that degrades gracefully: display the raw string, or classify it as unknown and continue.

Do not order by member positionstringOptional

Enum members have no guaranteed ordinal. To rank severities, map the known strings to your own numeric scale and treat unknown values as the least trusted rather than the lowest.

Compare exactlystringOptional

Values are emitted with stable casing. Compare against the exact string from the reference, or normalise case on both sides — do not match on a prefix.

Correlation identifiers

Error responses carry a correlationId — a GUID identifying the exact request as it passed through the service.

json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.6.1",
  "title": "An unexpected error occurred.",
  "status": 500,
  "detail": "The request could not be completed.",
  "instance": "/api/v1/repositories/3f8a1c27-9c1b-4e2a-8f6d-2a7c5e9b0d14/scan",
  "correlationId": "9d1a7f42-6b0e-4c3a-a7f1-2c5d8e0b3f96"
}

Log it on every non-2xx response, alongside the method, path, and status. It is the single most useful thing you can give support: with a correlationId we can find your request in the service logs directly, rather than guessing from a timestamp. Without one, diagnosing an intermittent failure is guesswork.

HTTP verb semantics

VerbUseBodyRepeat-safe
GETRead a resource or collection. Never changes state.NoYes
POSTCreate a resource, or invoke an action.YesNo — a repeat may create a second resource
PUTReplace a value wholesale. Used sparingly.YesYes — same body, same end state
PATCHPartially update a resource. Send only changed fields.YesYes — same body, same end state
DELETERemove a resource. Returns 204.NoYes — a repeat returns 404

GET, PUT, PATCH, and DELETE are idempotent: sending the same request twice leaves the system in the same state as sending it once. POST is not, which is why retry strategy differs for it — see Errors.

Action endpoints

Operations that are neither pure reads nor plain resource creation are expressed as a POST to a named sub-resource, sometimes with a colon-prefixed verb:

text
POST /api/v1/repositories/{repositoryId}/scan
POST /api/v1/scans/{scanId}/cancel
POST /api/v1/policies/{policyId}/test
POST /api/v1/quantum/probe-endpoints/{endpointId}:verify
POST /api/v1/analytics/export/destinations/{id}:run

The colon form marks an action on the addressed resource rather than a child collection. It is still an ordinary POST — no special encoding, no special headers.

Asynchronous operations

Work that cannot complete inside a request returns 202 Accepted with an identifier, and you poll a companion GET for the result. Scans, SBOM imports and exports, report generation, evidence archives, quarterly summaries, and warehouse export runs all behave this way.

json
{
  "scanId": "c41d9b8e-7a52-4f0d-9b3e-1d6a2f7c8e40",
  "state": "Queued"
}

Poll the corresponding detail endpoint — GET /api/v1/scans/{scanId} in this case — until the state reaches a terminal value. Poll on an interval measured in seconds, not milliseconds, and back off as the wait lengthens. Downloading the output of a job that has not finished returns 409 Conflict.

Request headers

HeaderWhenNotes
AuthorizationEvery authenticated requestBearer {token}. Omitting it returns 401.
Content-TypeAny request with a bodyapplication/json unless the reference says otherwise.
AcceptOptionalapplication/json. The API returns JSON regardless.
Accept-EncodingOptionalgzip is honoured and worthwhile on large collections.
User-AgentRecommendedIdentify your integration and version. It makes support conversations far shorter.

Unrecognised headers are ignored. No custom X- header is required to call the API.

Next steps