API¶
Rendered reference for AdvisoryHub's machine-consumable HTTP surface: the
internal /api/ JSON namespace, the GitHub App webhook receiver, the public
project picker, and the health probes. Server-rendered HTML/HTMX UI routes are
deliberately not part of this contract.
The machine-readable source of truth is openapi.yaml
(OpenAPI 3.0.3), kept honest by drift guards in
api/tests/test_openapi_spec.py: the document must validate, and every
/api/ route must match the Django URLconf bidirectionally — paths and
methods. info.version tracks the application version in lockstep
(dev/release.sh bumps it, dev/check_release_versions.sh gates it).
Authentication in one line: /api/ uses the OIDC-established session cookie
(plus the CSRF header on unsafe methods, no API tokens), the webhook uses an
HMAC-SHA256 signature, and the intake picker and health probes are
unauthenticated. Details in the rendered description below and in
permissions.
AdvisoryHub API 0.1.0¶
AdvisoryHub is a private application for authoring, reviewing, publishing, and auditing security advisories for Eclipse Foundation projects. This document describes its machine-consumable endpoints only; there is no public anonymous API surface.
Authentication. The /api/ namespace is an internal, same-origin
JSON API: it authenticates with the Django session cookie established via
the OIDC browser flow (there are no API tokens), and every unsafe method
additionally requires the CSRF token header. Unauthenticated requests get
a JSON 401 not_authenticated instead of a login redirect. The GitHub
webhook authenticates with an HMAC signature; the project picker and
health probes are unauthenticated.
Request bodies. /api/ POST endpoints accept either
application/json or application/x-www-form-urlencoded bodies (form
values arrive as strings — JSON is recommended, and required for
non-string semantics such as booleans).
Errors. JSON errors share the Error shape
{"error": "<machine_code>", "message": "<human text>"}. Two deliberate
exceptions: unknown resources return Django's default HTML 404 page
(except where noted), and a request with an HTTP method not listed here
returns an empty 405 with an Allow header.
Servers¶
| Description | URL |
|---|---|
| Paths are absolute from the deployment root. | / |
Advisories¶
GET /api/advisories/¶
List advisories visible to the authenticated user
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
page |
query | integer | 1 | No | 1-indexed page; values below 1 are coerced to 1. |
page_size |
query | integer | 25 | No | Page size, clamped to 1–100. |
project |
query | string | No | Filter to a single project by UUID. | |
q |
query | string | No | Case-insensitive substring match over summary, details, advisory_id, and aliases. | |
review_status |
query | No | Filter by review sub-state. Not validated server-side — an unknown value simply matches nothing. | ||
state |
query | No | Filter by lifecycle state (validated; unknown values are a 400). |
Responses
{
"results": [
{
"advisory_id": "string",
"project": "string",
"state": "triage",
"review_status": "none",
"summary": "string",
"modified_at": "2022-04-13T15:42:05.901Z",
"published_at": "2022-04-13T15:42:05.901Z",
"republish_required": true
}
],
"total": 0,
"page": 0,
"page_size": 0
}
Schema of the response body
{
"type": "object",
"required": [
"results",
"total",
"page",
"page_size"
],
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AdvisorySummary"
}
},
"total": {
"type": "integer",
"description": "Total matches across all pages."
},
"page": {
"type": "integer"
},
"page_size": {
"type": "integer"
}
}
}
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: NotAuthenticated.
GET /api/advisories/{advisory_id}/¶
Retrieve one advisory
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
advisory_id |
path | string | No | AdvisoryHub identifier (e.g. `ECL-q2f7-38cm-9wrx`). Malformed ids miss the URL pattern entirely and 404 at the resolver. |
Responses
{
"advisory_id": "string",
"project": {
"id": "ab597204-159a-4431-b0ca-f5e7aed31190",
"slug": "string",
"name": "string",
"is_mature_publisher": true
},
"state": "triage",
"review_status": "none",
"summary": "string",
"details": "string",
"aliases": [
"string"
],
"references": [
{
"type": "ADVISORY",
"url": "string"
}
],
"affected": [
{
"package": {
"name": "string",
"ecosystem": "string"
},
"ranges": [
{
"type": "string",
"events": [
{
"introduced": "string",
"fixed": "string",
"last_affected": "string",
"limit": "string"
}
]
}
],
"versions": [
"string"
]
}
],
"severity": [
{
"type": "CVSS_V2",
"score": "string"
}
],
"cwe_ids": [
"string"
],
"credits": [
{
"name": "string",
"contact": [
"string"
],
"type": "FINDER"
}
],
"republish_required": true,
"withdrawn_reason": "string",
"dismissed_reason": "string",
"created_at": "2022-04-13T15:42:05.901Z",
"modified_at": "2022-04-13T15:42:05.901Z",
"published_at": "2022-04-13T15:42:05.901Z",
"submitted_for_review_at": "2022-04-13T15:42:05.901Z",
"url": "string"
}
Schema of the response body
{
"type": "object",
"required": [
"advisory_id",
"project",
"state",
"review_status",
"summary",
"details",
"aliases",
"references",
"affected",
"severity",
"cwe_ids",
"credits",
"republish_required",
"withdrawn_reason",
"dismissed_reason",
"created_at",
"modified_at",
"published_at",
"submitted_for_review_at",
"url"
],
"properties": {
"advisory_id": {
"type": "string",
"pattern": "^ECL(-[23456789cfghjmpqrvwx]{4}){3}$"
},
"project": {
"$ref": "#/components/schemas/ProjectRef"
},
"state": {
"$ref": "#/components/schemas/AdvisoryState"
},
"review_status": {
"$ref": "#/components/schemas/ReviewStatus"
},
"summary": {
"type": "string"
},
"details": {
"type": "string"
},
"aliases": {
"type": "array",
"items": {
"type": "string"
},
"description": "External identifiers (CVE, GHSA, ...)."
},
"references": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Reference"
}
},
"affected": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AffectedEntry"
}
},
"severity": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SeverityEntry"
}
},
"cwe_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "CWE identifiers (e.g. `CWE-79`)."
},
"credits": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Credit"
}
},
"republish_required": {
"type": "boolean"
},
"withdrawn_reason": {
"type": "string",
"description": "Empty unless the advisory was withdrawn."
},
"dismissed_reason": {
"type": "string",
"description": "Empty unless the advisory was dismissed."
},
"created_at": {
"type": "string",
"format": "date-time"
},
"modified_at": {
"type": "string",
"format": "date-time"
},
"published_at": {
"type": "string",
"format": "date-time",
"nullable": true
},
"submitted_for_review_at": {
"type": "string",
"format": "date-time",
"nullable": true
},
"url": {
"type": "string",
"description": "Relative URL of the advisory's HTML detail page."
}
}
}
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
Comments¶
GET /api/advisories/{advisory_id}/comments/¶
List comments on an advisory
Description
Comments are never published or disclosed externally; they are visible only to users with access to the advisory inside AdvisoryHub. Internal comments are omitted for users without internal-comment visibility. Author emails are masked unless the caller may see user emails on this advisory (INV-PRIVACY-4).
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
advisory_id |
path | string | No | AdvisoryHub identifier (e.g. `ECL-q2f7-38cm-9wrx`). Malformed ids miss the URL pattern entirely and 404 at the resolver. |
Responses
{
"results": [
{
"id": 0,
"author": "string",
"body": "string",
"is_redacted": true,
"is_internal": true,
"created_at": "2022-04-13T15:42:05.901Z",
"edited_at": "2022-04-13T15:42:05.901Z"
}
]
}
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
Refer to the common response description: RateLimited.
POST /api/advisories/{advisory_id}/comments/¶
Add a comment
Description
Rate limited to 30 requests/minute per user.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
csrfToken |
header | string | N/A | No | Django CSRF token (double-submit with the `__Host-csrftoken` cookie; `csrftoken` in dev/test). Required alongside the session cookie on every unsafe method of the /api/ namespace. |
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
advisory_id |
path | string | No | AdvisoryHub identifier (e.g. `ECL-q2f7-38cm-9wrx`). Malformed ids miss the URL pattern entirely and 404 at the resolver. |
Request body
Schema of the request body
{
"type": "object",
"required": [
"body"
],
"properties": {
"body": {
"type": "string",
"minLength": 1,
"description": "Comment text; surrounding whitespace is stripped."
},
"is_internal": {
"type": "boolean",
"default": false,
"description": "When false (the default) the comment is visible to everyone with access to the advisory; when true it is hidden from users without internal-comment visibility. Either way the comment is never published or disclosed externally. With a form-encoded body any non-empty string is truthy — use JSON for reliable boolean semantics."
}
}
}
Schema of the request body
{
"type": "object",
"required": [
"body"
],
"properties": {
"body": {
"type": "string",
"minLength": 1,
"description": "Comment text; surrounding whitespace is stripped."
},
"is_internal": {
"type": "boolean",
"default": false,
"description": "When false (the default) the comment is visible to everyone with access to the advisory; when true it is hidden from users without internal-comment visibility. Either way the comment is never published or disclosed externally. With a form-encoded body any non-empty string is truthy — use JSON for reliable boolean semantics."
}
}
}
Responses
{
"id": 0,
"author": "string",
"body": "string",
"is_redacted": true,
"is_internal": true,
"created_at": "2022-04-13T15:42:05.901Z",
"edited_at": "2022-04-13T15:42:05.901Z"
}
Schema of the response body
{
"type": "object",
"required": [
"id",
"author",
"body",
"is_redacted",
"is_internal",
"created_at",
"edited_at"
],
"properties": {
"id": {
"type": "integer"
},
"author": {
"type": "string",
"nullable": true,
"description": "Author email, masked unless the caller may see user emails on this advisory (INV-PRIVACY-4); null for authorless comments."
},
"body": {
"type": "string",
"description": "Redaction-aware text (`is_redacted` replaces the body)."
},
"is_redacted": {
"type": "boolean"
},
"is_internal": {
"type": "boolean"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"edited_at": {
"type": "string",
"format": "date-time",
"nullable": true
}
}
}
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
Refer to the common response description: RateLimited.
Access grants¶
GET /api/advisories/{advisory_id}/grants/¶
List active grants and pending invitations
Description
Owner-gated (requires grant-management permission).
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
advisory_id |
path | string | No | AdvisoryHub identifier (e.g. `ECL-q2f7-38cm-9wrx`). Malformed ids miss the URL pattern entirely and 404 at the resolver. |
Responses
{
"grants": [
{
"id": 0,
"principal_type": "user",
"principal_id": 0,
"principal_label": "string",
"permission": "viewer",
"created_at": "2022-04-13T15:42:05.901Z"
}
],
"pending": [
{
"id": 0,
"email": "string",
"permission": "viewer",
"expires_at": "2022-04-13T15:42:05.901Z",
"redeemed_at": "2022-04-13T15:42:05.901Z"
}
]
}
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
Refer to the common response description: RateLimited.
POST /api/advisories/{advisory_id}/grants/¶
Grant access or invite by email
Description
Grants viewer or collaborator to a user (by email) or a group (by name). An email with no matching account creates a pending invitation redeemed on the recipient's first login. owner is never grantable (INV-AUTH-3). Rate limited to 20 requests/hour per user.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
csrfToken |
header | string | N/A | No | Django CSRF token (double-submit with the `__Host-csrftoken` cookie; `csrftoken` in dev/test). Required alongside the session cookie on every unsafe method of the /api/ namespace. |
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
advisory_id |
path | string | No | AdvisoryHub identifier (e.g. `ECL-q2f7-38cm-9wrx`). Malformed ids miss the URL pattern entirely and 404 at the resolver. |
Request body
Schema of the request body
{
"type": "object",
"required": [
"principal",
"permission"
],
"properties": {
"principal": {
"type": "string",
"enum": [
"user",
"group"
]
},
"permission": {
"type": "string",
"enum": [
"viewer",
"collaborator"
],
"description": "`owner` is rejected with `invalid_permission` (INV-AUTH-3)."
},
"email": {
"type": "string",
"format": "email",
"description": "Required when `principal=user`."
},
"group": {
"type": "string",
"description": "Required when `principal=group` (exact group name)."
}
}
}
Schema of the request body
{
"type": "object",
"required": [
"principal",
"permission"
],
"properties": {
"principal": {
"type": "string",
"enum": [
"user",
"group"
]
},
"permission": {
"type": "string",
"enum": [
"viewer",
"collaborator"
],
"description": "`owner` is rejected with `invalid_permission` (INV-AUTH-3)."
},
"email": {
"type": "string",
"format": "email",
"description": "Required when `principal=user`."
},
"group": {
"type": "string",
"description": "Required when `principal=group` (exact group name)."
}
}
}
Responses
{
"created": "grant",
"grant": {
"id": 0,
"principal_type": "user",
"principal_id": 0,
"principal_label": "string",
"permission": "viewer",
"created_at": "2022-04-13T15:42:05.901Z"
},
"invitation": {
"id": 0,
"email": "string",
"permission": "viewer",
"expires_at": "2022-04-13T15:42:05.901Z",
"redeemed_at": "2022-04-13T15:42:05.901Z"
}
}
Schema of the response body
{
"type": "object",
"required": [
"created"
],
"properties": {
"created": {
"type": "string",
"enum": [
"grant",
"invitation"
]
},
"grant": {
"$ref": "#/components/schemas/Grant"
},
"invitation": {
"$ref": "#/components/schemas/Invitation"
}
},
"description": "Exactly one of `grant` / `invitation` is present, matching the `created` discriminator."
}
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: RateLimited.
DELETE /api/advisories/{advisory_id}/grants/{grant_id}/¶
Revoke a grant
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
csrfToken |
header | string | N/A | No | Django CSRF token (double-submit with the `__Host-csrftoken` cookie; `csrftoken` in dev/test). Required alongside the session cookie on every unsafe method of the /api/ namespace. |
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
advisory_id |
path | string | No | AdvisoryHub identifier (e.g. `ECL-q2f7-38cm-9wrx`). Malformed ids miss the URL pattern entirely and 404 at the resolver. | |
grant_id |
path | integer | No |
Responses
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
Publication¶
GET /api/advisories/{advisory_id}/publication/¶
List publication tasks for an advisory
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
advisory_id |
path | string | No | AdvisoryHub identifier (e.g. `ECL-q2f7-38cm-9wrx`). Malformed ids miss the URL pattern entirely and 404 at the resolver. |
Responses
{
"tasks": [
{
"id": 0,
"advisory_id": "string",
"status": "queued",
"attempts": 0,
"commit_sha": "string",
"last_error": "string",
"created_at": "2022-04-13T15:42:05.901Z",
"started_at": "2022-04-13T15:42:05.901Z",
"finished_at": "2022-04-13T15:42:05.901Z",
"artifacts": [
{
"kind": "osv",
"path": "string"
}
]
}
]
}
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
POST /api/advisories/{advisory_id}/publish/¶
Queue publication of an advisory
Description
Pins the latest AdvisoryVersion on a new PublicationTask and enqueues the async OSV+CSAF export. The advisory's state flips to published only after the Git push succeeds (INV-LIFECYCLE-3). Requires a fresh step-up authentication when STEP_UP_REQUIRED is enabled. Rate limited to 10 requests/hour per user. No request body. For GHSA-linked advisories publication is system-driven (INV-GHSA-3): owners receive 403 (the EF feed mirrors the GHSA automatically); only global admins may call this as a break-glass, still gated on the linked GHSA being published upstream.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
csrfToken |
header | string | N/A | No | Django CSRF token (double-submit with the `__Host-csrftoken` cookie; `csrftoken` in dev/test). Required alongside the session cookie on every unsafe method of the /api/ namespace. |
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
advisory_id |
path | string | No | AdvisoryHub identifier (e.g. `ECL-q2f7-38cm-9wrx`). Malformed ids miss the URL pattern entirely and 404 at the resolver. |
Responses
{
"id": 0,
"advisory_id": "string",
"status": "queued",
"attempts": 0,
"commit_sha": "string",
"last_error": "string",
"created_at": "2022-04-13T15:42:05.901Z",
"started_at": "2022-04-13T15:42:05.901Z",
"finished_at": "2022-04-13T15:42:05.901Z",
"artifacts": [
{
"kind": "osv",
"path": "string"
}
]
}
Schema of the response body
{
"type": "object",
"required": [
"id",
"advisory_id",
"status",
"attempts",
"commit_sha",
"last_error",
"created_at",
"started_at",
"finished_at",
"artifacts"
],
"properties": {
"id": {
"type": "integer"
},
"advisory_id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"queued",
"running",
"succeeded",
"failed"
]
},
"attempts": {
"type": "integer"
},
"commit_sha": {
"type": "string",
"description": "Empty until a push succeeded."
},
"last_error": {
"type": "string",
"description": "Redacted failure summary; empty unless failed."
},
"created_at": {
"type": "string",
"format": "date-time"
},
"started_at": {
"type": "string",
"format": "date-time",
"nullable": true
},
"finished_at": {
"type": "string",
"format": "date-time",
"nullable": true
},
"artifacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"path"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"osv",
"csaf",
"cve"
]
},
"path": {
"type": "string"
}
}
}
}
}
}
{
"error": "step_up_required",
"message": "Re-authenticate before publishing.",
"step_up_url": "/oidc/step-up/"
}
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: RateLimited.
POST /api/publication/tasks/{task_id}/retry/¶
Retry a failed publication task
Description
Creates a fresh task for the same pinned AdvisoryVersion. Only failed tasks are retryable. Step-up and rate limit as for publish. No request body.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
csrfToken |
header | string | N/A | No | Django CSRF token (double-submit with the `__Host-csrftoken` cookie; `csrftoken` in dev/test). Required alongside the session cookie on every unsafe method of the /api/ namespace. |
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
task_id |
path | integer | No |
Responses
{
"id": 0,
"advisory_id": "string",
"status": "queued",
"attempts": 0,
"commit_sha": "string",
"last_error": "string",
"created_at": "2022-04-13T15:42:05.901Z",
"started_at": "2022-04-13T15:42:05.901Z",
"finished_at": "2022-04-13T15:42:05.901Z",
"artifacts": [
{
"kind": "osv",
"path": "string"
}
]
}
Schema of the response body
{
"type": "object",
"required": [
"id",
"advisory_id",
"status",
"attempts",
"commit_sha",
"last_error",
"created_at",
"started_at",
"finished_at",
"artifacts"
],
"properties": {
"id": {
"type": "integer"
},
"advisory_id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"queued",
"running",
"succeeded",
"failed"
]
},
"attempts": {
"type": "integer"
},
"commit_sha": {
"type": "string",
"description": "Empty until a push succeeded."
},
"last_error": {
"type": "string",
"description": "Redacted failure summary; empty unless failed."
},
"created_at": {
"type": "string",
"format": "date-time"
},
"started_at": {
"type": "string",
"format": "date-time",
"nullable": true
},
"finished_at": {
"type": "string",
"format": "date-time",
"nullable": true
},
"artifacts": {
"type": "array",
"items": {
"type": "object",
"required": [
"kind",
"path"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"osv",
"csaf",
"cve"
]
},
"path": {
"type": "string"
}
}
}
}
}
}
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
Refer to the common response description: RateLimited.
GET /api/publication/tasks/{task_id}/artifact/{kind}/¶
Preview a generated publication artifact
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
kind |
path | string | No | Artifact kind; only OSV and CSAF artifacts are previewable. | |
task_id |
path | integer | No |
Responses
Schema of the response body
{
"type": "object",
"required": [
"kind",
"path",
"content"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"osv",
"csaf"
]
},
"path": {
"type": "string",
"description": "Path of the file inside the publication repo."
},
"content": {
"type": "object",
"description": "The OSV or CSAF JSON document."
}
}
}
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
Dashboard tasks¶
POST /api/dashboard/cve/{task_id}/transition/¶
Transition a CVE request task
Description
Admin/security-team only.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
csrfToken |
header | string | N/A | No | Django CSRF token (double-submit with the `__Host-csrftoken` cookie; `csrftoken` in dev/test). Required alongside the session cookie on every unsafe method of the /api/ namespace. |
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
task_id |
path | integer | No |
Request body
Schema of the request body
Schema of the request body
Responses
{
"id": 0,
"advisory_id": "string",
"status": "queued",
"cve_id": "string",
"assignee": "string",
"requested_by": "string",
"created_at": "2022-04-13T15:42:05.901Z",
"finished_at": "2022-04-13T15:42:05.901Z"
}
Schema of the response body
{
"type": "object",
"required": [
"id",
"advisory_id",
"status",
"cve_id",
"assignee",
"requested_by",
"created_at",
"finished_at"
],
"properties": {
"id": {
"type": "integer"
},
"advisory_id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"queued",
"reserved",
"rejected",
"cancelled"
]
},
"cve_id": {
"type": "string",
"nullable": true
},
"assignee": {
"type": "string",
"nullable": true,
"description": "Email; masked for non-admin callers."
},
"requested_by": {
"type": "string",
"nullable": true,
"description": "Email; masked for non-admin callers."
},
"created_at": {
"type": "string",
"format": "date-time"
},
"finished_at": {
"type": "string",
"format": "date-time",
"nullable": true
}
}
}
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
POST /api/dashboard/review/{task_id}/decide/¶
Decide a review task
Description
Admin/security-team only.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
csrfToken |
header | string | N/A | No | Django CSRF token (double-submit with the `__Host-csrftoken` cookie; `csrftoken` in dev/test). Required alongside the session cookie on every unsafe method of the /api/ namespace. |
sessionCookie |
cookie | string | N/A | No | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. |
task_id |
path | integer | No |
Request body
Responses
{
"id": 0,
"advisory_id": "string",
"status": "open",
"submitted_by": "string",
"reviewer": "string",
"decision_notes": "string",
"created_at": "2022-04-13T15:42:05.901Z",
"decided_at": "2022-04-13T15:42:05.901Z"
}
Schema of the response body
{
"type": "object",
"required": [
"id",
"advisory_id",
"status",
"submitted_by",
"reviewer",
"decision_notes",
"created_at",
"decided_at"
],
"properties": {
"id": {
"type": "integer"
},
"advisory_id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"open",
"approved",
"changes_requested",
"withdrawn"
]
},
"submitted_by": {
"type": "string",
"nullable": true,
"description": "Email; masked for non-admin callers."
},
"reviewer": {
"type": "string",
"nullable": true,
"description": "Email; masked for non-admin callers."
},
"decision_notes": {
"type": "string",
"nullable": true
},
"created_at": {
"type": "string",
"format": "date-time"
},
"decided_at": {
"type": "string",
"format": "date-time",
"nullable": true
}
}
}
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Refer to the common response description: NotAuthenticated.
Refer to the common response description: Forbidden.
Refer to the common response description: NotFoundHtml.
GHSA webhook¶
POST /ghsa/webhook/¶
Receive a GitHub App webhook delivery
Description
CSRF-exempt, session-free receiver authenticated by an HMAC-SHA256 signature over the raw request body (sha256=<hex> in X-Hub-Signature-256, keyed with the app's webhook secret, compared in constant time before any body parsing). Deliveries are idempotent on X-GitHub-Delivery; accepted events are processed asynchronously.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
X-GitHub-Delivery |
header | string | No | Unique delivery id; replays are detected on this value. | |
X-GitHub-Event |
header | string | No | GitHub event name (e.g. `repository_advisory`). | |
X-Hub-Signature-256 |
header | string | No | HMAC-SHA256 of the raw body, keyed with the webhook secret. |
Request body
Responses
Intake¶
GET /report/projects.json¶
Project picker entries for the public report form
Description
Unauthenticated autocomplete data source, capped at 200 entries. Rate limited to 60 requests/minute per client IP.
Input parameters
| Parameter | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
q |
query | string | No | Case-insensitive substring match over slug and name. |
Responses
Response headers
| Name | Description | Schema |
|---|---|---|
Cache-Control |
public, max-age=300 |
string |
Refer to the common response description: RateLimited.
Health¶
GET /healthz¶
Liveness probe
Responses
GET /readyz¶
Readiness probe
Description
Checks the database and cache, plus optionally the Celery broker (READYZ_INCLUDE_BROKER) and the publication repository (READYZ_INCLUDE_PUB_REPO).
Responses
Schema of the response body
{
"type": "object",
"required": [
"status",
"failures"
],
"properties": {
"status": {
"type": "string",
"enum": [
"fail"
]
},
"failures": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Failing check name (`db`, `cache`, `broker`, `publication_repo`) to exception class name."
}
}
}
Schemas¶
Advisory¶
| Name | Type | Description |
|---|---|---|
advisory_id |
string | |
affected |
Array<AffectedEntry> | |
aliases |
Array<string> | External identifiers (CVE, GHSA, ...). |
created_at |
string(date-time) | |
credits |
Array<Credit> | |
cwe_ids |
Array<string> | CWE identifiers (e.g. `CWE-79`). |
details |
string | |
dismissed_reason |
string | Empty unless the advisory was dismissed. |
modified_at |
string(date-time) | |
project |
ProjectRef | |
published_at |
string(date-time) | null | |
references |
Array<Reference> | |
republish_required |
boolean | |
review_status |
ReviewStatus | |
severity |
Array<SeverityEntry> | |
state |
AdvisoryState | |
submitted_for_review_at |
string(date-time) | null | |
summary |
string | |
url |
string | Relative URL of the advisory's HTML detail page. |
withdrawn_reason |
string | Empty unless the advisory was withdrawn. |
AdvisoryListPage¶
| Name | Type | Description |
|---|---|---|
page |
integer | |
page_size |
integer | |
results |
Array<AdvisorySummary> | |
total |
integer | Total matches across all pages. |
AdvisoryState¶
Type: string
AdvisorySummary¶
| Name | Type | Description |
|---|---|---|
advisory_id |
string | |
modified_at |
string(date-time) | |
project |
string | Project slug. |
published_at |
string(date-time) | null | |
republish_required |
boolean | A content edit landed after the last publication. |
review_status |
ReviewStatus | |
state |
AdvisoryState | |
summary |
string |
AffectedEntry¶
| Name | Type | Description |
|---|---|---|
package |
Properties: name, ecosystem |
|
ranges |
Array<Properties: type, events> |
|
versions |
Array<string> |
Comment¶
| Name | Type | Description |
|---|---|---|
author |
string | null | Author email, masked unless the caller may see user emails on this advisory (INV-PRIVACY-4); null for authorless comments. |
body |
string | Redaction-aware text (`is_redacted` replaces the body). |
created_at |
string(date-time) | |
edited_at |
string(date-time) | null | |
id |
integer | |
is_internal |
boolean | |
is_redacted |
boolean |
CommentCreate¶
| Name | Type | Description |
|---|---|---|
body |
string | Comment text; surrounding whitespace is stripped. |
is_internal |
boolean | When false (the default) the comment is visible to everyone with access to the advisory; when true it is hidden from users without internal-comment visibility. Either way the comment is never published or disclosed externally. With a form-encoded body any non-empty string is truthy — use JSON for reliable boolean semantics. |
Credit¶
| Name | Type | Description |
|---|---|---|
contact |
Array<string> | |
name |
string | |
type |
string |
CveTask¶
| Name | Type | Description |
|---|---|---|
advisory_id |
string | |
assignee |
string | null | Email; masked for non-admin callers. |
created_at |
string(date-time) | |
cve_id |
string | null | |
finished_at |
string(date-time) | null | |
id |
integer | |
requested_by |
string | null | Email; masked for non-admin callers. |
status |
string |
CveTransition¶
| Name | Type | Description |
|---|---|---|
cve_id |
string | The reserved CVE id (with `status=reserved`). |
notes |
string | |
status |
string |
Error¶
| Name | Type | Description |
|---|---|---|
error |
string | Stable machine-readable code. |
message |
string | Human-readable explanation. |
Grant¶
| Name | Type | Description |
|---|---|---|
created_at |
string(date-time) | |
id |
integer | |
permission |
string | |
principal_id |
integer | |
principal_label |
string | null | User email (masked unless the caller may see user emails) or group name; null when the principal no longer exists. |
principal_type |
string |
GrantCreate¶
| Name | Type | Description |
|---|---|---|
email |
string(email) | Required when `principal=user`. |
group |
string | Required when `principal=group` (exact group name). |
permission |
string | `owner` is rejected with `invalid_permission` (INV-AUTH-3). |
principal |
string |
Invitation¶
| Name | Type | Description |
|---|---|---|
email |
string | Masked unless the caller may see user emails. |
expires_at |
string(date-time) | null | |
id |
integer | |
permission |
string | |
redeemed_at |
string(date-time) | null |
ProjectPickerEntry¶
| Name | Type | Description |
|---|---|---|
name |
string | |
slug |
string |
ProjectRef¶
| Name | Type | Description |
|---|---|---|
id |
string(uuid) | |
is_mature_publisher |
boolean | |
name |
string | |
slug |
string |
PublicationTask¶
| Name | Type | Description |
|---|---|---|
advisory_id |
string | |
artifacts |
Array<Properties: kind, path> |
|
attempts |
integer | |
commit_sha |
string | Empty until a push succeeded. |
created_at |
string(date-time) | |
finished_at |
string(date-time) | null | |
id |
integer | |
last_error |
string | Redacted failure summary; empty unless failed. |
started_at |
string(date-time) | null | |
status |
string |
Reference¶
| Name | Type | Description |
|---|---|---|
type |
string | |
url |
string(uri) | http/https/ftp/ftps only. |
ReviewDecision¶
| Name | Type | Description |
|---|---|---|
decision |
string | |
notes |
string |
ReviewStatus¶
Type: string
ReviewTask¶
| Name | Type | Description |
|---|---|---|
advisory_id |
string | |
created_at |
string(date-time) | |
decided_at |
string(date-time) | null | |
decision_notes |
string | null | |
id |
integer | |
reviewer |
string | null | Email; masked for non-admin callers. |
status |
string | |
submitted_by |
string | null | Email; masked for non-admin callers. |
SeverityEntry¶
| Name | Type | Description |
|---|---|---|
score |
string | CVSS vector string, or one of negligible/low/medium/high/critical for `Ubuntu`. |
type |
string |
Common responses¶
This section describes common responses that are reused across operations.
NotAuthenticated¶
No authenticated session (not_authenticated).
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Forbidden¶
The caller lacks the required permission (forbidden).
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
NotFoundHtml¶
Unknown resource. Django's default error page — text/html, not JSON.
RateLimited¶
Rate limit exceeded (rate_limited).
Schema of the response body
{
"type": "object",
"required": [
"error",
"message"
],
"properties": {
"error": {
"type": "string",
"description": "Stable machine-readable code."
},
"message": {
"type": "string",
"description": "Human-readable explanation."
}
},
"additionalProperties": true,
"description": "Shared JSON error envelope. Some errors carry extra fields (e.g. `step_up_url` on `step_up_required`)."
}
Common parameters¶
This section describes common parameters that are reused across operations.
AdvisoryId¶
| Name | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
advisory_id |
path | string | No |
TaskId¶
| Name | In | Type | Default | Nullable | Description |
|---|---|---|---|---|---|
task_id |
path | integer | No |
Security schemes¶
| Name | Type | Scheme | Description |
|---|---|---|---|
| sessionCookie | apiKey | Django session established via the OIDC browser flow. Production uses the `__Host-` prefixed cookie name; dev/test use plain `sessionid`. There are no API tokens — this is an internal, same-origin API. | |
| csrfToken | apiKey | Django CSRF token (double-submit with the `__Host-csrftoken` cookie; `csrftoken` in dev/test). Required alongside the session cookie on every unsafe method of the /api/ namespace. |
Tags¶
| Name | Description |
|---|---|
| Advisories | Read access to advisories visible to the authenticated user. |
| Comments | Per-advisory discussion threads. |
| Access grants | Per-advisory viewer/collaborator grants and pending email invitations (owner is derived from project security-team membership and is never grantable — INV-AUTH-3). |
| Publication | OSV+CSAF export pipeline status and controls. An advisory's state flips to published only after a successful Git push (INV-LIFECYCLE-3). |
| Dashboard tasks | Admin/security-team task transitions (CVE requests, reviews). |
| GHSA webhook | Inbound GitHub App webhook receiver. |
| Intake | Public report-form support endpoints. |
| Health | Liveness/readiness probes for orchestration. |