API Reference

The Qwick Cert REST API. All endpoints live under /api/v1 and return a consistent JSON envelope.

Base URL

https://app.qwickcert.com/api/v1

All endpoints described below are relative to this base. For example, POST /sign-digest means POST https://app.qwickcert.com/api/v1/sign-digest.


Authentication

Every API request must include an Authorization header with either a user JWT (from the CLI login flow) or an API key.

User JWT

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

API Key

Authorization: Bearer qwick_ak_xxxxxxxxxxxxxxxxxxxx

API key permissions

API keys are scoped with specific permissions. A key with only sign permission can call /sign-digest but not /apikeys. Available permissions: sign, read_audit.

Required headers

HeaderDescription
AuthorizationBearer token (JWT or API key)
Content-Typeapplication/json for POST/PUT requests

Response headers

HeaderDescription
X-Qwick-CLI-UpdateCLI update status: required, recommended, or optional
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the rate limit window resets

Response format

Every response is a JSON object with a consistent envelope. The top-level shape depends on whether the request succeeded or failed.

Success (2xx)

{
  "data": {
    "operation_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed"
  },
  "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "api_version": "v1"
}

Error (4xx / 5xx)

{
  "error": {
    "code": "QWICK_AUTH_301",
    "message": "Access token expired or invalid",
    "details": {},
    "docs": "https://docs.qwickcert.com/errors/QWICK_AUTH_301"
  },
  "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "api_version": "v1"
}
FieldTypeDescription
dataobjectResponse payload (present on success, absent on error)
errorobjectError details (present on failure, absent on success)
request_iduuidUnique ID for this request (useful for support)
api_versionstringAlways "v1"

Signing

POST/api/v1/sign-digest

Sign a pre-computed Authenticode digest. The server signs the hash via Azure Trusted Signing and returns a PKCS#7 signature blob with an RFC 3161 timestamp. Your binary never leaves your machine.

Request body

ParameterTypeRequiredDescription
digeststringYesBase64-encoded SHA-256 Authenticode digest (32 bytes)
algorithmstringYesHash algorithm used. Currently only "sha256"
file_namestringYesOriginal file name (for audit trail)
file_sizenumberYesOriginal file size in bytes
organization_slugstringYesTarget organization slug
batch_iduuidNoGroup multiple sign requests under one batch
reasonstringNoSigning reason (required if org policy enforces it)
client_channelstringNoSource: "cli", "github_action", "api"
client_versionstringNoCLI or SDK version string

Example request

curl -X POST https://app.qwickcert.com/api/v1/sign-digest \
  -H "Authorization: Bearer qwick_ak_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "digest": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
    "algorithm": "sha256",
    "file_name": "MyApp.exe",
    "file_size": 2048576,
    "organization_slug": "acme-corp",
    "client_channel": "cli",
    "client_version": "1.2.0"
  }'

Success response (200)

{
  "data": {
    "operation_id": "550e8400-e29b-41d4-a716-446655440000",
    "signature": "MIIRoQYJKoZIhvcNAQcCoIIRkjCCEY4C...",
    "certificate_chain": [
      "MIIFwTCCBGmgAwIBAgITMwAAA...",
      "MIIFrDCCBJSgAwIBAgIQB..."
    ],
    "timestamp": "2026-03-09T14:23:45Z",
    "signed_at": "2026-03-09T14:23:44.892Z"
  },
  "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "api_version": "v1"
}

Error responses

StatusCodeMeaning
400QWICK_API_600Invalid request body (missing digest, bad algorithm, etc.)
401QWICK_AUTH_301Token expired or invalid
403QWICK_AUTH_303Insufficient role (requires signer or higher)
403QWICK_BILLING_700Plan limit reached
429QWICK_BILLING_702Free tier annual signing limit reached
503QWICK_SIGN_102Azure credential still provisioning (retry in a few seconds)
502QWICK_SIGN_103Azure signing service error

Signing Operations

GET/api/v1/signing-operations

List signing operations for an organization with optional filters. Powers the audit dashboard.

Query parameters

ParameterTypeRequiredDescription
organization_slugstringYesOrganization slug
statusstringNoFilter: pending, signed, completed, embed_failed, failed, timeout, abandoned
sourcestringNoFilter by source: cli, github_action, dashboard, api
batch_iduuidNoFilter by signing batch ID
actor_user_iduuidNoFilter by the user who initiated signing
date_fromISO 8601NoLower bound on created_at
date_toISO 8601NoUpper bound on created_at
pagenumberNoPage number (default: 1)
page_sizenumberNoItems per page (default: 50, max: 100)

Example request

curl "https://app.qwickcert.com/api/v1/signing-operations?organization_slug=acme-corp&status=completed&page=1&page_size=10" \
  -H "Authorization: Bearer qwick_ak_xxxxxxxxxxxx"

Success response (200)

{
  "data": {
    "items": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "organization_id": "org-uuid",
        "actor_user_id": "user-uuid",
        "actor_email": "dev@acme.com",
        "file_name": "MyApp.exe",
        "file_size": 2048576,
        "digest_algorithm": "sha256",
        "status": "completed",
        "source": "cli",
        "batch_id": null,
        "signed_at": "2026-03-09T14:23:44.892Z",
        "created_at": "2026-03-09T14:23:42.100Z"
      }
    ],
    "page": 1,
    "page_size": 10,
    "total": 1
  },
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "api_version": "v1"
}

GET/api/v1/signing-operations/:id

Get details for a single signing operation by ID.

Example request

curl "https://app.qwickcert.com/api/v1/signing-operations/550e8400-e29b-41d4-a716-446655440000?organization_slug=acme-corp" \
  -H "Authorization: Bearer qwick_ak_xxxxxxxxxxxx"

Success response (200)

{
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organization_id": "org-uuid",
    "actor_user_id": "user-uuid",
    "actor_email": "dev@acme.com",
    "file_name": "MyApp.exe",
    "file_size": 2048576,
    "file_hash": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg=",
    "digest_algorithm": "sha256",
    "status": "completed",
    "source": "cli",
    "client_channel": "cli",
    "client_version": "1.2.0",
    "batch_id": null,
    "signed_at": "2026-03-09T14:23:44.892Z",
    "duration_ms": 2792,
    "created_at": "2026-03-09T14:23:42.100Z"
  },
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "api_version": "v1"
}

GET/api/v1/signing-operations/export

Export signing operations as a CSV file. Accepts the same filters as the list endpoint.

Example request

curl "https://app.qwickcert.com/api/v1/signing-operations/export?organization_slug=acme-corp&date_from=2026-01-01" \
  -H "Authorization: Bearer qwick_ak_xxxxxxxxxxxx" \
  -o signing-audit.csv

Returns a text/csv response with columns: id, file_name, file_size, status, source, actor_email, signed_at, created_at.


API Keys

GET/api/v1/apikeys

List all API keys for an organization. Returns metadata only (the full key is shown once at creation).

Query parameters

ParameterTypeRequiredDescription
organization_slugstringYesOrganization slug

Example request

curl "https://app.qwickcert.com/api/v1/apikeys?organization_slug=acme-corp" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Success response (200)

{
  "data": {
    "keys": [
      {
        "id": "key-uuid",
        "name": "CI Pipeline",
        "prefix": "qwick_ak_abc1",
        "permissions": ["sign", "read_audit"],
        "expires_at": "2026-09-09T00:00:00Z",
        "days_until_expiry": 183,
        "last_used_at": "2026-03-08T10:30:00Z",
        "use_count": 142,
        "created_at": "2026-03-01T12:00:00Z"
      }
    ]
  },
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "api_version": "v1"
}

POST/api/v1/apikeys

Create a new API key. Returns the full key exactly once — store it securely.

Query parameters

ParameterTypeRequiredDescription
organization_slugstringYesOrganization slug

Request body

ParameterTypeRequiredDescription
namestringYesHuman-readable name for the key
permissionsstring[]YesArray of permissions: "sign", "read_audit"
expires_in_monthsnumberNoMonths until expiry (default: 6, max: 24)

Example request

curl -X POST "https://app.qwickcert.com/api/v1/apikeys?organization_slug=acme-corp" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "GitHub Actions",
    "permissions": ["sign"],
    "expires_in_months": 6
  }'

Success response (201)

{
  "data": {
    "id": "key-uuid",
    "name": "GitHub Actions",
    "key": "qwick_ak_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "prefix": "qwick_ak_xxxx",
    "permissions": ["sign"],
    "expires_at": "2026-09-09T00:00:00Z",
    "created_at": "2026-03-09T15:00:00Z"
  },
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "api_version": "v1"
}

Store the key immediately

The full key value is returned only once. It cannot be retrieved again — only the prefix is stored for identification.


POST/api/v1/apikeys/:id/revoke

Revoke an API key immediately. The key will stop working within seconds.

Example request

curl -X POST "https://app.qwickcert.com/api/v1/apikeys/key-uuid/revoke?organization_slug=acme-corp" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Success response (200)

{
  "data": {
    "id": "key-uuid",
    "revoked_at": "2026-03-09T15:30:00Z"
  },
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "api_version": "v1"
}

Billing

GET/api/v1/billing/summary

Get the current subscription state, usage counts, and active discount for an organization.

Query parameters

ParameterTypeRequiredDescription
organization_slugstringYesOrganization slug

Example request

curl "https://app.qwickcert.com/api/v1/billing/summary?organization_slug=acme-corp" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Success response (200)

{
  "data": {
    "plan": "pro",
    "status": "active",
    "current_period_end": "2026-04-09T00:00:00Z",
    "cancel_at_period_end": false,
    "limits": {
      "members": 10,
      "signing_operations": "unlimited"
    },
    "usage": {
      "members": 3,
      "signing_operations_this_period": 847
    },
    "discount": {
      "name": "Annual 20%",
      "percent_off": 20,
      "ends_at": "2027-03-09T00:00:00Z"
    }
  },
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "api_version": "v1"
}

Rate limits

API requests are rate-limited per organization to protect service quality. Limits are returned in response headers.

PlanRate limitSigning concurrency
Free60 requests/min1 concurrent session
Pro300 requests/min2 concurrent sessions
Enterprise1000 requests/minCustom

When you exceed the rate limit, the API returns 429 Too Many Requests with a Retry-After header indicating seconds to wait.

Rate limit error

HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1741537424

{
  "error": {
    "code": "QWICK_API_604",
    "message": "Rate limit exceeded",
    "details": { "retry_after": 12 },
    "docs": "https://docs.qwickcert.com/errors/QWICK_API_604"
  },
  "request_id": "...",
  "api_version": "v1"
}

Error codes

All errors use structured QWICK_* codes grouped by category. Each error includes a docs URL for resolution steps.

Infrastructure (000-099)

CodeHTTPDescription
QWICK_INFRA_000500Internal server error

Signing (100-199)

CodeHTTPDescription
QWICK_SIGN_101429Queue timeout exceeded
QWICK_SIGN_102503Failed to acquire signing credential (Azure still provisioning)
QWICK_SIGN_103502Azure signing service returned an error
QWICK_SIGN_104502Transient Azure error (safe to retry)

Authentication (300-399)

CodeHTTPDescription
QWICK_AUTH_300401Invalid credentials
QWICK_AUTH_301401Access token expired or invalid
QWICK_AUTH_302401Refresh token revoked or expired
QWICK_AUTH_303403Insufficient role or permission
QWICK_AUTH_304401API key revoked or expired
QWICK_AUTH_305429Too many authentication attempts

Policy (400-499)

CodeHTTPDescription
QWICK_POLICY_4004032FA required by organization policy
QWICK_POLICY_401403IP address not in allowlist
QWICK_POLICY_402409Approval pending (approval workflow enabled)
QWICK_POLICY_403429Per-user rate limit exceeded
QWICK_POLICY_404403Outside allowed signing hours
QWICK_POLICY_407403Source not allowed by policy
QWICK_POLICY_408403File extension not allowed by policy
QWICK_POLICY_409413File size exceeds policy limit
QWICK_POLICY_415403Organization signing is locked down

API (600-699)

CodeHTTPDescription
QWICK_API_600400Request validation failed
QWICK_API_601409Resource conflict
QWICK_API_602404Resource not found in tenant scope
QWICK_API_604429Rate limit exceeded

Billing (700-799)

CodeHTTPDescription
QWICK_BILLING_700403Plan limit reached
QWICK_BILLING_701403Subscription cancelled — signing disabled
QWICK_BILLING_702429Free tier annual signing limit reached