Skip to main content

Release Evidence API

The Release Evidence API returns a structured evidence package for one release: who approved it, which checklists were completed, which entries it contains, and the commits, pull requests and CI results linked to those entries. When the organisation has a signing key, the response is signed so your compliance system can verify it independently.

Endpoint​

GET /api/v1/releases/{id}/evidence
Authorization: Bearer <organisation-scoped key>

Required permission: read:release. Project-scoped keys cannot call this endpoint (403 scope_violation).

Response body​

{
"schemaVersion": 1,
"generatedAt": "2026-10-14T10:00:00.000Z",
"status": "complete", // "complete" | "incomplete"
"release": {
"id": "rel-uuid-...",
"name": "Sprint 42",
"version": "v2.4.0",
"status": "released",
"releasedAt": "2026-10-01T00:00:00.000Z",
"riskLevel": "medium",
"projectId": "proj-uuid-...",
"projectName": "Acme Platform"
},
"approvalChain": {
"instanceId": "inst-uuid-...", // null when the release has no approval workflow
"overallStatus": "approved", // "none" when there is no approval workflow
"stages": [
{
"name": "Engineering Lead",
"approver": "Alice Smith",
"approvedAt": "2026-09-30T15:22:00.000Z",
"comment": "LGTM",
"status": "approved"
}
]
},
"qualityChecklists": [
{
"name": "Release Readiness",
"items": [
{
"label": "Regression suite passed",
"completedBy": "Alice Smith",
"completedAt": "2026-09-30T14:00:00.000Z",
"isCompleted": true
}
]
}
],
"entrySummary": {
"totalCount": 12,
"byStatus": { "done": 9, "deployed": 2, "testing": 1 },
"releasedOrCompleted": [
{ "id": "entry-uuid-...", "title": "Fix login redirect", "status": "done" }
]
},
"githubEvidence": {
"entries": [
{
"id": "entry-uuid-...",
"title": "Fix login redirect",
"status": "done",
"commits": [
{
"sha": "a1b2c3d",
"author": "Alice Smith",
"message": "fix: correct redirect after OAuth login",
"committedAt": "2026-09-29T11:00:00.000Z",
"url": "https://github.com/acme/platform/commit/a1b2c3d",
"verified": false,
"ciResult": "passed",
"testsPassed": 312,
"testsFailed": 0,
"testsSkipped": 4
}
],
"pullRequests": [
{
"number": 847,
"title": "Fix login redirect",
"mergedAt": "2026-09-29T12:00:00.000Z",
"url": "https://github.com/acme/platform/pull/847"
}
]
}
]
},
"completenessScore": 87
}

status​

  • "complete": the release's status is completed or released.
  • "incomplete": any other release status. Treat the evidence as preliminary.

entrySummary​

byStatus counts the release's entries by status. releasedOrCompleted lists the entries whose status is deployed, done or solved.

commits[].verified and ciResult​

verified is GitHub's signature verification for the commit, as recorded by Align. It is false when Align has not recorded it, for example for commits received only through the push webhook. ciResult comes from the latest CI run linked to the commit: passed, failed, pending, or null when no run is linked.

completenessScore (0–100)​

The mean of three fractions, as a percentage rounded to a whole number:

  1. The fraction of the release's entries with at least one linked commit.
  2. The fraction of entries with at least one commit whose CI result is passed.
  3. 1 if every checklist item is complete (or there are none), otherwise 0.

A release with no entries scores 0.

Signature verification​

When the organisation has an evidence signing key, every evidence response includes:

X-Align-Evidence-Signature: sha256=<hex-digest>
X-Align-Evidence-Key-Prefix: evk_9a3b1c2d

The digest is HMAC-SHA256(signingKey, responseBody), where responseBody is the exact bytes of the response body. Verify against the raw body before parsing it; re-serialising the JSON can change the bytes.

Without a signing key, the response has no signature headers.

Getting a signing key​

An admin of the customer organisation generates the key while signed in to Align (admins of a partner organisation are refused), by calling POST /api/settings/evidence-key. The response contains the full key (evk_ followed by 64 hex characters) once; store it immediately. GET /api/settings/evidence-key returns whether a key is configured and its prefix. There is no screen for this in Settings yet, and these endpoints accept a signed-in session, not an API key.

Verifying in Node.js​

const crypto = require("crypto");

function verifyEvidence(rawBody, signatureHeader, signingKey) {
if (!signatureHeader?.startsWith("sha256=")) return false;
const received = Buffer.from(signatureHeader.slice(7), "hex");
const expected = crypto
.createHmac("sha256", signingKey)
.update(rawBody)
.digest();
return (
received.length === expected.length &&
crypto.timingSafeEqual(received, expected)
);
}

Verifying in Python​

import hmac, hashlib

def verify_evidence(raw_body: bytes, signature_header: str, signing_key: str) -> bool:
if not signature_header or not signature_header.startswith("sha256="):
return False
received = bytes.fromhex(signature_header[len("sha256="):])
expected = hmac.new(signing_key.encode(), raw_body, hashlib.sha256).digest()
return hmac.compare_digest(received, expected)

Rotating the signing key​

Calling POST /api/settings/evidence-key again issues a new key and revokes the previous one, so responses are signed with the new key from then on.

  1. Generate the new key.
  2. Update your compliance system with it.
  3. Fetch the evidence for a known release and check that the signature verifies.
  4. Remove the old key from your secrets manager.