Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Currents Dev Test Results API
version: 1.0.0
contact:
name: Currents Support
url: https://currents.dev
description: 'Operations tagged Test Results across 2 of this provider''s published API definitions: currents-dev-openapi.json, currents-dev-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://api.currents.dev/v1
description: Production API
- url: https://api-staging.currents.dev/v1
description: Staging API
- url: http://localhost:4000/v1
description: Local development
security:
- BearerAuth: []
tags:
- name: Test Results
description: 'Query historical test execution results.
Test results provide detailed information about individual test executions
across multiple runs. You can filter by date range, branch, tags, git author,
and outcome (`status[]` values use the `RecordedTestOutcome` schema).'
paths:
/test-results/{signature}:
get:
summary: Get test results
description: Get historical test execution results for a specific test signature
operationId: getTestResults
security:
- BearerAuth: []
- OAuth2:
- results:read
tags:
- Test Results
parameters:
- $ref: '#/components/parameters/SignaturePath'
- $ref: '#/components/parameters/DateStartQuery'
- $ref: '#/components/parameters/DateEndQuery'
- $ref: '#/components/parameters/TestResultsLimitQuery'
- $ref: '#/components/parameters/StartingAfterQuery'
- $ref: '#/components/parameters/EndingBeforeQuery'
- $ref: '#/components/parameters/TagsQuery'
- $ref: '#/components/parameters/BranchesQuery'
- $ref: '#/components/parameters/AuthorsQuery'
- $ref: '#/components/parameters/GroupsQuery'
- $ref: '#/components/parameters/EnvironmentsQuery'
- name: branch[]
in: query
required: false
deprecated: true
description: Deprecated. Use branches[] instead. Filter by git branch (can be specified multiple times using bracket notation, e.g., branch[]=main&branch[]=develop). Each value supports a trailing `*` for prefix search. Each branch value is limited to 250 characters (exact or wildcard).
schema:
type: array
items:
$ref: '#/components/schemas/BranchFilterQueryValue'
style: form
explode: true
example:
- main
- release/*
- name: tag[]
in: query
required: false
deprecated: true
description: Deprecated. Use tags[] instead. Filter by run tags (can be specified multiple times using bracket notation, e.g., tag[]=tag1&tag[]=tag2)
schema:
type: array
items:
type: string
style: form
explode: true
example:
- tag1
- tag2
- name: status[]
in: query
required: false
description: Filter by outcome(s). Each value is a `RecordedTestOutcome` (see schemas). Multiple values OR together. Combine with `flaky` using AND when both are set.
schema:
type: array
items:
$ref: '#/components/schemas/RecordedTestOutcome'
style: form
explode: true
example:
- passed
- failed
- name: flaky
in: query
required: false
description: Filter by flaky status. When true, returns only flaky tests. When false, returns only non-flaky tests. When omitted, returns all tests regardless of flaky status. Can be combined with status filter using AND logic (e.g., status[]=passed&flaky=true returns only flaky passed tests).
schema:
type: boolean
- name: annotations
in: query
required: false
description: 'Filter by test annotations. The parameter value must be a JSON-stringified array of objects: `[{ "type": "string", "description": "string" | ["string"] or null }]`. description is optional; omit or set to null to match any value for that annotation.'
schema:
type: string
example: '[{"type":"owner","description":["John Doe"]},{"type":"skip"}]'
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/TestResultsResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'413':
description: 'Payload Too Large - Response size exceeds limits. Use Accept-Encoding: gzip header.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
servers:
- url: https://api.currents.dev/v1
description: Production API
- url: https://api-staging.currents.dev/v1
description: Staging API
- url: http://localhost:4000/v1
description: Local development
components:
parameters:
GroupsQuery:
name: groups[]
in: query
required: false
description: Filter by groups (can be specified multiple times using bracket notation, e.g., groups[]=group1&groups[]=group2)
schema:
type: array
items:
type: string
style: form
explode: true
example:
- group1
- group2
TagsQuery:
name: tags[]
in: query
required: false
description: Filter by tags (can be specified multiple times using bracket notation, e.g., tags[]=value1&tags[]=value2)
schema:
type: array
items:
type: string
style: form
explode: true
example:
- tag1
- tag2
SignaturePath:
name: signature
in: path
required: true
description: The test signature
schema:
type: string
example: abc123def456
EnvironmentsQuery:
name: environments[]
in: query
required: false
description: Filter by environment (can be specified multiple times using bracket notation, e.g., environments[]=staging&environments[]=production)
schema:
type: array
items:
type: string
style: form
explode: true
example:
- staging
- production
StartingAfterQuery:
name: starting_after
in: query
required: false
description: Cursor for pagination. Returns items after this cursor value.
schema:
type: string
DateEndQuery:
name: date_end
in: query
required: true
description: End date in ISO 8601 format
schema:
type: string
format: date-time
example: '2024-01-31T23:59:59.999Z'
EndingBeforeQuery:
name: ending_before
in: query
required: false
description: Cursor for pagination. Returns items before this cursor value.
schema:
type: string
TestResultsLimitQuery:
name: limit
in: query
required: false
description: 'Maximum number of items to return (default: 10, max: 100)'
schema:
type: integer
minimum: 1
maximum: 100
default: 10
BranchesQuery:
name: branches[]
in: query
required: false
description: Filter by git branch. Use trailing `*` for prefix match (e.g. `release/*`). Max 250 characters per value.
schema:
type: array
items:
$ref: '#/components/schemas/BranchFilterQueryValue'
style: form
explode: true
example:
- main
- release/*
AuthorsQuery:
name: authors[]
in: query
required: false
description: Filter by git authors (can be specified multiple times using bracket notation, e.g., authors[]=author1&authors[]=author2)
schema:
type: array
items:
type: string
style: form
explode: true
example:
- author1
- author2
DateStartQuery:
name: date_start
in: query
required: true
description: Start date in ISO 8601 format
schema:
type: string
format: date-time
example: '2024-01-01T00:00:00.000Z'
schemas:
TestResultsResponse:
type: object
required:
- status
- has_more
- data
properties:
status:
type: string
enum:
- OK
has_more:
type: boolean
data:
type: array
items:
$ref: '#/components/schemas/TestResult'
BranchFilterQueryValue:
description: 'Branch filter value: an exact branch name, or a trailing `*` for prefix match (e.g. `release/*`). Max 250 characters.'
oneOf:
- type: string
description: Exact branch name (must not contain `*` or spaces).
pattern: ^[!-)+-~]+$
maxLength: 250
- type: string
description: 'Prefix match: branch prefix followed by `*`.'
pattern: ^[!-)+-~]+\*$
maxLength: 250
ErrorResponse:
type: object
required:
- status
- error
properties:
status:
type: string
enum:
- FAILED
error:
type: string
description: Error message
code:
type: string
description: Stable machine-readable discriminator, present on refusals a client is expected to branch on (for example `AI_CONSENT_REQUIRED`). The `error` text is free-form; branch on `code`.
candidates:
type: array
description: 'With `code: ambiguous_target`: the runs, spec files or tests the request matched. Send one of their IDs.'
items:
type: object
properties:
runId:
type: string
ciBuildId:
type: string
instanceId:
type: string
groupId:
type: string
spec:
type: string
machineId:
type:
- string
- 'null'
testId:
type: string
title:
type: array
items:
type: string
TestResult:
type: object
properties:
cursor:
type: string
signature:
type: string
framework:
type: string
createdAt:
type: string
format: date-time
projectId:
type: string
groupId:
type: string
runId:
type: string
instanceId:
type: string
spec:
type: string
machineId:
type: string
tags:
type: array
items:
type: string
title:
type: array
items:
type: string
testId:
type: string
displayError:
type:
- string
- 'null'
commit:
type: object
properties:
branch:
type: string
authorEmail:
type: string
authorName:
type: string
sha:
type: string
message:
type: string
duration:
type: integer
flaky:
type: boolean
expectedStatus:
type:
- string
- 'null'
status:
$ref: '#/components/schemas/RecordedTestOutcome'
attempts:
type: array
items:
type: object
properties:
attemptId:
type: string
state:
type: string
error:
type:
- object
- 'null'
properties:
message:
type: string
stack:
type: string
location:
type: object
startedAt:
type: string
format: date-time
duration:
type: integer
annotations:
type:
- array
- 'null'
items:
type: object
properties:
type:
type: string
description:
type: string
RecordedTestOutcome:
type: string
description: 'Final per-test outcome stored in Currents.
- **passed** — Runner reported success.
- **failed** — Runner reported failure (assertions, errors, timeouts, etc.).
- **pending** — Intentional skip or disabled test (e.g. `test.skip()` / `it.skip()`). In **Tests Explorer** (`GET /tests/{projectId}`) this increments `metrics.ignored` and does **not** increment `metrics.failures`.
- **skipped** — Runner reported a skip without a normal pass/fail (often interrupted or never executed as pass/fail). In **Tests Explorer** this increments `metrics.failures` together with `failed` (there is no separate `skipped` counter in that response). On **run and spec** payloads, `stats.skipped` is still reported separately from `stats.failures` and `stats.pending`.'
enum:
- passed
- failed
- pending
- skipped
responses:
Forbidden:
description: 'The credential authenticated but is not permitted to use this endpoint. Five refusals share the status: a `read` API key on an endpoint that writes, an OAuth token that was not granted the scope the endpoint needs (`insufficient_scope`), an OAuth token that was granted it but whose holder no longer has a role permitting it (`insufficient_role`), an OAuth token on an endpoint that accepts an API key and nothing else (`api_key_required`), and an API key on an endpoint that answers for a user (`user_credential_required`). Discriminate on `code` rather than on the message, which is free-form. The first refusal predates the codes and sets none. Only `insufficient_scope` carries the challenge header, because it is the only one a client can act on by re-authorizing.'
headers:
WWW-Authenticate:
description: 'Present on the `insufficient_scope` refusal only, naming the scope to re-authorize for (RFC 6750 3.1): `Bearer error="insufficient_scope", scope="webhooks:read"`. Absent on `insufficient_role`, where the scope is already granted, and on `api_key_required` and `user_credential_required`, where no scope would help.'
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
apiKeyIsReadOnly:
summary: A read API key on an endpoint that writes
value:
status: FAILED
error: 'Insufficient permissions: write access required'
scopeNotGranted:
summary: An OAuth token missing the endpoint's scope
value:
status: FAILED
error: 'Insufficient permissions: this endpoint requires the webhooks:read scope'
code: insufficient_scope
scopeAboveRole:
summary: An OAuth token holding the scope, whose holder was since demoted
value:
status: FAILED
error: 'Insufficient permissions: the webhooks:write scope requires the admin role in this organization'
code: insufficient_role
apiKeyRequired:
summary: An OAuth token on an endpoint that takes an API key
value:
status: FAILED
error: This endpoint accepts an API key, not an OAuth access token
code: api_key_required
userCredentialRequired:
summary: An API key on an endpoint that answers for a user
value:
status: FAILED
error: This endpoint requires an OAuth access token or a personal access token; an API key belongs to an organization, not a user
code: user_credential_required
Unauthorized:
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
status: FAILED
error: Invalid authentication credentials
BadRequest:
description: Invalid request parameters
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
status: FAILED
error: 'Invalid request body: name is required'
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
status: FAILED
error: Not found
securitySchemes:
RecordKey:
type: apiKey
in: header
name: x-currents-key
description: An organization's record key, the one a reporter sends. Accepted only by the two attachment routes under `/runs`, `POST /runs/{runId}/context-links`, `POST /sessions`, `POST /sessions/{sessionId}/attachments` and, for a session, `POST /share`, so a CI job can attach files, link its failed and flaky tests, record a session and share it with the key it already has.
BearerAuth:
type: http
scheme: bearer
description: 'An organization API key, sent as `Authorization: Bearer <key>`. A key carries one scope for the whole API: a `read` key reaches the GET endpoints, a `write` key reaches all of them. A `crnts_pat_` personal access token is accepted as a Bearer while its owner remains an eligible administrator in its active organization. Each operation requires the corresponding scope stored on that token.'
OAuth2:
type: oauth2
description: 'An access token from the Currents authorization server, for an application acting as a user in one organization. Unlike an API key it is confined per endpoint: the operation lists the scope it needs, and a token that was not granted that scope gets 403.
**The endpoints below are production''s.** They are written out so this specification can be exercised against production, and they are wrong for every other deployment — a token minted by `id.currents.dev` is refused by a staging or on-premise API, whose `OAUTH_ISSUER` and `OAUTH_API_AUDIENCE` are its own. Against anything but production, and in a client generated from this document, discover them instead: `GET {api origin}/.well-known/oauth-protected-resource`, then the `authorization_servers[0]` document.
**The audience named here is the REST API''s.** `POST /mcp` is a second resource server on the same origin, identified by `{api origin}/mcp` and described by its own document at `/.well-known/oauth-protected-resource/mcp`. Every operation below refuses a token minted for it, and it refuses every token minted for these, so a client of this specification wants the audience above and not that one.'
flows:
authorizationCode:
authorizationUrl: https://id.currents.dev/api/auth/oauth2/authorize
tokenUrl: https://id.currents.dev/api/auth/oauth2/token
refreshUrl: https://id.currents.dev/api/auth/oauth2/token
scopes:
projects:read: See which projects exist and their names
projects:write: Change project configuration, including a project's tags
results:read: See runs, specs and failure detail — errors, stacks and artifacts
analytics:read: 'See aggregate metrics: flake and failure rates, error counts, durations and trends'
actions:read: See quarantine and skip rules, and the tests they affect
actions:write: Create, edit, enable, disable and archive quarantine and skip rules
issues:write: Create and link issues in a connected issue tracker, and list its projects and issue types
runs:write: Cancel and reset runs, and permanently delete runs and their artifacts
webhooks:read: See webhook configuration, including destination URLs and headers
webhooks:write: Create, edit and delete webhooks
shares:write: Create public links to test results that anyone with the link can open until they expire
x-refined-from:
- currents-dev-openapi.json
- currents-dev-openapi.yml