CI API contract
Connect a CI runner to Quadraticc at https://app.quadrati.cc. This contract covers project lookup, immutable build registration, numbered builds, release promotion, and JUnit/TRX ingestion. API calls go to the app origin, not this documentation host.
Use HTTPS and keep bearer credentials in your CI secret store. Do not print tokens or put secrets in build metadata or test reports. Treat timeouts and server failures as potentially ambiguous writes; use the replay rules below before retrying. On HTTP 429, back off before retrying.
Build registration
Builds record immutable project versions and references to externally stored artifacts. Registration does not upload, build or host those artifacts.
This document covers project bearer credentials in the unified /api/v1 API.
Shared project/build routes select bearer authentication when an Authorization header is supplied; CI responses use flat JSON objects.
Credentials and requests
Workspace administrators create a named CI credential in project Settings. Copy the one-time value into your CI secret store. Each bearer token belongs to one project; it permits minimal project lookup, immutable build creation, release promotion and test result ingestion. Revocation applies after the revocation transaction commits. Requests already in progress complete first; authorization is serialized with revocation through a workspace lock. Bearer requests ignore browser cookies and never fall back to session authentication when a token is invalid. Other API routes require browser sessions; CI credentials cannot read environment/workflow data or manage resources.
Copy the workspace and project IDs from Project identifiers in project
Settings. Set QUADRATICC_URL,
WORKSPACE_ID, PROJECT_ID, and secret QUADRATICC_TOKEN in your pipeline.
Production connections must use HTTPS.
curl --fail-with-body \
-H "Authorization: Bearer $QUADRATICC_TOKEN" \
"$QUADRATICC_URL/api/v1/workspaces/$WORKSPACE_ID/projects/$PROJECT_ID"
Returns 200 with {"id":"…","name":"…"}. It exposes no environments or secrets.
curl --fail-with-body \
-H "Authorization: Bearer $QUADRATICC_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"version":"1.2.3","commit_sha":"0123456789abcdef0123456789abcdef01234567","branch":"main","images":{"web":"example.dkr.ecr.ap-southeast-2.amazonaws.com/web@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}' \
"$QUADRATICC_URL/api/v1/workspaces/$WORKSPACE_ID/projects/$PROJECT_ID/builds"
Returns 201 with id, metadata, created_at, triggered_by_user_id (a user UUID
or null), and created_via_ci. Versions are unique within a project and metadata
cannot be edited or deleted independently. Version is required
and must follow SemVer 2.0.0, at most 100 ASCII bytes. Examples: 0.0.1, 0.0.1-abc123, 1.2.3-rc.1+build.42. A v prefix and numeric identifiers with leading zeros are rejected. Optional branch is at most 255 UTF-8 bytes without surrounding whitespace or control characters. Commit SHA must contain 40 or 64 hexadecimal characters.
The API accepts one JSON object, rejects unknown fields, and limits request bodies
to 32 KiB. No artifacts are uploaded or checked by this endpoint.
| Status | Meaning |
|---|---|
| 400 | Invalid JSON or build metadata |
| 401 | Missing, invalid, or revoked bearer token |
| 403 | Token belongs to another project/workspace, or workspace is read-only |
| 409 | Version already exists or a resource quota was reached |
| 415 | Content type is not application/json |
| 429 | API rate limit reached |
| 503 | Temporary server/database failure |
A retry after an ambiguous successful response may return 409. Do not automatically treat every conflict as success: the existing version may have different metadata. Inspect the project's build history before resolving such a conflict. Tokens, secret values, and ciphertext are excluded from audit details; build creation records identify the service account rather than its creator acting as a user.
Named images and metadata
Both build registration endpoints and browser build creation accept optional
images and metadata objects whose values are strings:
{
"version": "0.0.42",
"commit_sha": "0123456789abcdef0123456789abcdef01234567",
"images": {
"web": "123456789012.dkr.ecr.ap-southeast-2.amazonaws.com/web@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"migrations": "123456789012.dkr.ecr.ap-southeast-2.amazonaws.com/migrations@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
},
"metadata": {"migration_name": "20261018090000_AddInvoices"}
}
For /builds/next, replace version with version_prefix, for example 0.0..
There can be up to 16 images and 64 metadata fields. Image names match
[a-z][a-z0-9_-]{0,63}; values must be fully qualified, canonical sha256 digest
references, at most 2,048 bytes. Tags alone are rejected for named images.
Metadata keys match [A-Za-z_][A-Za-z0-9_.-]{0,63}; each value is at most 4,096
UTF-8 bytes with no NUL. The JSON-encoded two-map array [images,metadata] is
limited to 12,000 bytes, including escaping; the existing 32 KiB request limit
still applies. Keep secrets out: metadata is visible to authorized build viewers.
The maps are immutable and participate in numbered replay identity independently of key order. Changing either map requests a new number.
The quadraticc/build-action@v1 action accepts these JSON inputs and returns
corresponding outputs:
- uses: quadraticc/build-action@v1
with:
url: ${{ vars.QUADRATICC_URL }}
workspace-id: ${{ vars.WORKSPACE_ID }}
project-id: ${{ vars.PROJECT_ID }}
token: ${{ secrets.QUADRATICC_TOKEN }}
version-prefix: '0.0.'
images: |
{
"web": ${{ toJSON(format('{0}@{1}', vars.WEB_IMAGE, steps.web.outputs.digest)) }},
"migrations": ${{ toJSON(format('{0}@{1}', vars.MIGRATIONS_IMAGE, steps.migrations.outputs.digest)) }}
}
metadata: |
{"migration_name": ${{ toJSON(steps.migration.outputs.name) }}}
Build attribution
Attribution is set by Quadraticc; callers cannot supply either field:
| Creation path | triggered_by_user_id |
created_via_ci |
|---|---|---|
| Signed-in user creates a build | That user | false |
| CI registers a build from a linked Quadraticc workflow run | The user who requested that run | true |
| CI registers a build without a matching Quadraticc workflow run | null |
true |
The token owner's identity and GitHub's actor name are not used to guess a user. If a build arrives before its remote workflow run is recorded, attribution fills in when Quadraticc records or reconciles that run. Exact numbered replays return the saved attribution. Build artifact metadata and known attribution remain immutable. Users with build viewing permission can see attribution even without workflow viewing permission; the workflow details and link still require workflow access.
Automatically numbered builds
Use the same project CI credential with POST to
/api/v1/workspaces/{workspace}/projects/{project}/builds/next:
{
"version_prefix": "0.0.",
"commit_sha": "0123456789abcdef0123456789abcdef01234567",
"version_suffix": "-0123456789abcdef0123456789abcdef01234567",
"branch": "main",
"metadata": {"ci_run":"123","ci_attempt":"1"}
}
The server assigns 0.0.X, where X is one greater than the highest existing
canonical 0.0.X version in this project. It starts at 0.0.1 when none exist.
Numbering uses the full project history and numeric comparison, including manual
builds. Prereleases and build metadata such as 0.0.9-rc.1 contribute their patch
number. Optional version_suffix, starting with - or +, is appended after
allocation, for example 0.0.10-<commit>. The result must be valid SemVer.
Other canonical major/minor prefixes, such as 1.2., have independent sequences.
Allocation and insertion share the existing workspace transaction lock with all build writers. Concurrent distinct requests receive different patch numbers; a failed validation, quota check, or audit transaction consumes no number.
The endpoint returns 201 with the created build's normal metadata. An
identical request within the same project and version prefix returns the existing
build with 200, without creating another build or creation audit. Identity
here means exact commit SHA, branch, version suffix, named images, metadata and optional workflow
source IDs; omitted text fields are empty and absent workflow provenance is distinct
from a supplied source. Changed metadata.ci_run/metadata.ci_attempt requests a new build even when
the image is the same. The token must still be valid and the workspace writable
for an existing result to be returned.
Prefixes require canonical decimal major/minor components and a trailing dot.
The generated version retains the 100-byte limit. This endpoint accepts the same
metadata sizes and JSON/content-type rules as explicit build creation; supplying
version is rejected. The /builds endpoint continues to require an
explicit version and returns 409 on duplicates.
CI does not need to list builds or calculate the next number locally. The server inspects the existing builds while holding the allocation lock.
Optional GitHub workflow provenance
Both /builds and /builds/next accept:
"workflow_run": {"repository_id": 123456, "run_id": 987654}
Both IDs must be positive integers and the repository must be connected to this
same Quadraticc project. The project CI token remains required. These values come
from GitHub's github.repository_id and github.run_id; they are CI-supplied
provenance, not artifact attestation. A build may arrive before Quadraticc records
the remote run ID: history links appear when that run is recorded. Project build
reads include workflow_run and, when matched, workflow_run_id. One run can produce
multiple builds. Numbered exact replay includes both source IDs; changing them
requests a new build.
Release builds
Builds default to ordinary builds. Supply "is_release": true to either
registration endpoint (or check Mark as release in the UI) to create a release.
Authorized build viewers see a Release badge in build history and details.
Users with build.create can mark an existing build as a release from its details.
Release status cannot be removed; the artifact's version, commit, images,
metadata and provenance remain unchanged.
Project CI credentials can also promote an existing build in their own project:
curl --fail-with-body \
-H "Authorization: Bearer $QUADRATICC_TOKEN" \
-H 'Content-Type: application/json' \
--data '{}' \
"$QUADRATICC_URL/api/v1/workspaces/$WORKSPACE_ID/projects/$PROJECT_ID/builds/$BUILD_ID/release"
This operation is idempotent and returns 200 with the flat build record and
is_release: true. The same session-authenticated browser operation returns
204. Revoked credentials, workspace lifecycle and project scope checks apply
as for registration. Successful promotion records a build.release audit event.
Release status does not change numbered-build replay identity. Replaying a
matching /builds/next request with is_release: true promotes and returns the
original build with 200; omitting it or supplying false never demotes that
build or allocates a duplicate.
The companion action accepts is-release: 'true' for registration. To promote a
build later, supply build-id and is-release: 'true' without version inputs,
images, metadata or workflow linking. Its is-release output confirms the status;
created is false for promotion.
Test result ingestion
CI runners can POST to
/api/v1/workspaces/{workspace UUID}/projects/{project UUID}/test-results
using an existing project bearer token. Project members with build-view access
can browse reports using the project Tests page. Existing project token
scope, revocation, workspace lifecycle, admission limits and audit apply.
No new user permission model is introduced.
Send Content-Type: application/json with these fields:
| Field | Meaning |
|---|---|
upload_id |
Required retry key, 1–200 bytes. Unique per project and report file/execution/attempt. |
filename |
Required basename, at most 255 bytes. |
format |
junit or trx. |
branch |
Required branch/ref, at most 255 bytes. |
commit_sha |
Required full 40- or 64-character hex Git commit ID. |
report |
Required original XML file as a JSON string, at most 8 MiB decoded. |
workflow_run_id |
Optional Quadraticc workflow run UUID belonging to this project. |
workflow_run |
Alternative optional GitHub source: {"repository_id":123,"run_id":456}. Repository must be connected to this project. |
For example, construct JSON with jq to preserve XML escaping:
jq -n --rawfile report results.xml \
--arg upload_id "$GITHUB_RUN_ID/$GITHUB_RUN_ATTEMPT/results.xml" \
--arg branch "$GITHUB_REF_NAME" --arg commit "$GITHUB_SHA" \
'{upload_id:$upload_id, filename:"results.xml", format:"junit",
branch:$branch, commit_sha:$commit, report:$report}' > upload.json
curl --fail-with-body -H "Authorization: Bearer $QUADRATICC_PROJECT_TOKEN" \
-H 'Content-Type: application/json' --data-binary @upload.json \
"$QUADRATICC_URL/api/v1/workspaces/$WORKSPACE_ID/projects/$PROJECT_ID/test-results"
Use format:"trx" for Visual Studio TRX files. Upload multiple files separately
with distinct upload IDs; use a new ID for each CI retry attempt. An identical
retry returns HTTP 200 and the same receipt. Reusing the ID with different
content/metadata returns 409. New uploads return 201 with a flat JSON receipt:
{"id":"report UUID","total":4,"passed":1,"failed":1,"errors":1,"skipped":1}
Invalid metadata, malformed/unsupported XML, unknown TRX outcomes and invalid durations return 400; oversized reports return 413. Limits are 10,000 test cases, 64 XML levels and 200,000 XML elements per file. DTDs are rejected. Empty suites are supported. Counts come from actual cases, not producer summary attributes. A cumulative conservative JSON size budget of 32 MiB also bounds normalized cases, counting shared suite/class names for each case and allowing for JSON escaping. Reports exceeding this budget return 400. A single leading UTF-8 BOM is accepted; the original XML is retained unchanged.
JUnit nested suites, failure/error/skipped details, output and duration seconds are preserved. TRX result names, classes, durations, errors and output are normalized. Raw XML is retained for producer-specific data and later reparsing. Reports and test names are untrusted text; the Tests UI renders them as text.
Reports are indexed by project/branch/commit for future historical analysis.
test_report_workflow_links resolves GitHub repository/run IDs against
Quadraticc runs at query time, including when the upload precedes run discovery.
Provenance is supplied by the CI token holder; it is not artifact attestation.
Reports persist until their project/workspace (or explicitly linked workflow
run) is deleted. There is no retention worker. Browser read APIs require a user session.