quadraticc

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.