Overview

Base URL, authentication, errors, pagination and rate limits, then every endpoint and webhook event.

The Outqube API is JSON over HTTPS. Every endpoint and webhook event has its own page in this section, with parameters, responses, code samples and a playground. Those pages are generated from the OpenAPI document, which you can also fetch yourself: GET https://app.outqube.com/api/v1/openapi.json needs no key.

Base URL

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

Requests and responses are JSON. Send Content-Type: application/json with a body of up to 64 KiB. Every response has a request-id header and Cache-Control: no-store.

TODO(iulian)

Is there a staging base URL to document? The OpenAPI document lists production only.

Authentication

Send an API key as a bearer token:

curl https://app.outqube.com/api/v1/suites \
  -H "Authorization: Bearer $OUTQUBE_API_KEY"

A key is oq_ followed by 43 letters and digits. It belongs to one app and has one scope, and each scope includes the ones before it:

ScopeCan
readRead runs, results, reports, evidence and tests.
triggerEverything in read, plus start, cancel and rerun runs.
manageEverything in trigger, plus manage webhooks and hooks.

A missing, unknown, revoked or expired key is a 401. A key whose scope is too low is a 403. See Authentication for making and rotating keys.

Errors

Every error has the same shape. Branch on code, not on message, whose wording can change. Quote request_id when you ask for help.

{
  "error": {
    "code": "not_found",
    "message": "Not found.",
    "request_id": "8e5b1c4a-2f7d-4a93-b6e0-5d1c3a9f7e24"
  }
}
StatusCodes
400invalid_request
401unauthorized
403forbidden
404not_found
405method_not_allowed
409conflict
422idempotency_mismatch, invalid_base_url, no_tests, unprocessable
429rate_limited
500internal
503unavailable

Each endpoint page lists the errors that endpoint can answer. See also Errors and limits.

Pagination

List endpoints return the newest items first, in pages:

{ "data": [], "next_cursor": "…" }

Use limit (1 to 100, default 20) to size a page. To get the next page, pass next_cursor back as cursor, exactly as given. The cursor is opaque. next_cursor is null on the last page.

Idempotency

POST /runs and POST /runs/{runId}/rerun take an Idempotency-Key header: any unique string of 8 to 200 printable characters. The same key with the same request returns the same run for 24 hours. The same key with a different request is a 422 with the code idempotency_mismatch. Use it in CI so a retried job doesn't start a second run.

Rate limits

Each key may make 120 requests a minute by default, in fixed one-minute windows. Each IP address has a looser limit of at least 600 requests a minute, because CI fleets often share one address. Over a limit, the API answers 429 with the code rate_limited and a Retry-After header: the seconds to wait.

TODO(iulian)

The per-key limit is configurable on the server (API_RATE_LIMIT_PER_MINUTE). Confirm that 120 is the value in production before this page is published.

Versioning

The version is in the path: /api/v1. Adding a field, an endpoint, an optional parameter or a new value to an enum (such as a run status or outcome) is not a breaking change. Write clients that ignore fields they don't know and treat an unknown enum value as "something new": keep waiting if it is a status, not passed if it is an outcome. Every change to the API is listed in the changelog.

TODO(iulian)

How much notice do you give before a breaking change or a new major version, and how are deprecated endpoints marked? The code defines none of this.

Webhook events

Outqube also calls you: see Webhooks for the events, and the verification and retry rules, under Webhooks in the sidebar.

On this page