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
SchemehttpsRequiredHTTPS 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.
HoststringRequiredapi.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-8RequiredUTF-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.
CompressionstringOptionalSend 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:
Content-Type: application/jsonResponses 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
| Rule | Example |
|---|---|
Fields are camelCase | overallHealthScore, capturedAtUtc, correlationId |
| Acronyms are not shouted | apiTokenId, not APITokenId |
Collection envelopes use items | items, totalEstimate, take, skip |
Path segments are kebab-case | /api/v1/alert-rules, /api/v1/vulnerability-exposures |
Query parameters are camelCase | repositoryId, 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.
{
"snapshotId": "8f0c2f66-3d3c-4c1f-9a2f-4b1c6d0f2f11",
"capturedAtUtc": "2026-03-14T09:21:47.482Z"
}FormatstringRequiredYYYY-MM-DDTHH:mm:ss[.fffffff]Z. Fractional seconds may be present and their precision is
not guaranteed — never string-compare two timestamps for equality.
TimezonestringRequiredAlways UTC, always with the Z suffix. The API neither emits nor accepts local offsets, and
does not perform timezone conversion on your behalf.
Sending timestampsstringOptionalQuery 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.
DurationsstringOptionalWhere 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.
/api/v1/repositories/3f8a1c27-9c1b-4e2a-8f6d-2a7c5e9b0d14/dependenciesOpacitystringRequiredTreat 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.
StoragestringRequiredStore as a string or a native UUID type — 36 characters when hyphenated. Never truncate.
CasestringOptionalEmitted lowercase. Compare case-insensitively if you accept identifiers from a user or an external system.
ScopestringOptionalIdentifiers 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:
| Form | Meaning |
|---|---|
"detail": "Repository not found." | A value is present. |
"detail": null | The field applies to this resource but has no value — not yet computed, not applicable, or explicitly cleared. |
| Field absent | The 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
nullto 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 PascalCase — Npm, Healthy, Critical — not
as integers. String values keep responses readable and let new members be added without
renumbering.
{
"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 permissivelystringRequiredParse 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 defaultstringRequiredEvery 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 positionstringOptionalEnum 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 exactlystringOptionalValues 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.
{
"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
| Verb | Use | Body | Repeat-safe |
|---|---|---|---|
GET | Read a resource or collection. Never changes state. | No | Yes |
POST | Create a resource, or invoke an action. | Yes | No — a repeat may create a second resource |
PUT | Replace a value wholesale. Used sparingly. | Yes | Yes — same body, same end state |
PATCH | Partially update a resource. Send only changed fields. | Yes | Yes — same body, same end state |
DELETE | Remove a resource. Returns 204. | No | Yes — 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:
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}:runThe 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.
{
"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
| Header | When | Notes |
|---|---|---|
Authorization | Every authenticated request | Bearer {token}. Omitting it returns 401. |
Content-Type | Any request with a body | application/json unless the reference says otherwise. |
Accept | Optional | application/json. The API returns JSON regardless. |
Accept-Encoding | Optional | gzip is honoured and worthwhile on large collections. |
User-Agent | Recommended | Identify your integration and version. It makes support conversations far shorter. |
Unrecognised headers are ignored. No custom X- header is required to call the API.
