Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Currents Dev Tests Explorer API
version: 1.0.0
contact:
name: Currents Support
url: https://currents.dev
description: 'Operations tagged Tests Explorer 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: Tests Explorer
description: 'Explore and analyze test metrics.
The Tests Explorer provides aggregated metrics for tests within a project,
including execution counts, failure rates, flakiness rates, and duration statistics.
Outcome strings are defined in the `RecordedTestOutcome` schema (see **components/schemas**). That schema explains how each value maps to `metrics` on `GET /tests/{projectId}` (`ignored`, `failures`, etc.).
Narrative context for the dashboard is here:
Test result statuses — Tests Explorer & REST API.'
paths:
/tests/{projectId}:
get:
summary: Get tests explorer
description: Get aggregated test metrics for a project within a date range. Outcomes use the `RecordedTestOutcome` schema (see components/schemas). For narrative context, see the Test result statuses documentation page.
operationId: getTestsExplorer
security:
- BearerAuth: []
- OAuth2:
- analytics:read
tags:
- Tests Explorer
parameters:
- $ref: '#/components/parameters/ProjectIdPath'
- $ref: '#/components/parameters/DateStartQuery'
- $ref: '#/components/parameters/DateEndQuery'
- $ref: '#/components/parameters/PageQuery'
- $ref: '#/components/parameters/TestsExplorerLimitQuery'
- $ref: '#/components/parameters/TagsQuery'
- $ref: '#/components/parameters/BranchesQuery'
- $ref: '#/components/parameters/GroupsQuery'
- $ref: '#/components/parameters/AuthorsQuery'
- $ref: '#/components/parameters/EnvironmentsQuery'
- name: order
in: query
required: false
description: Field to order results by
schema:
type: string
enum:
- failures
- passes
- flakiness
- flakinessXSamples
- failRateXSamples
- duration
- durationDelta
- flakinessRateDelta
- failureRateDelta
- durationXSamples
- executions
- title
default: title
- $ref: '#/components/parameters/SortDirectionQuery'
- name: spec
in: query
required: false
description: Filter tests by spec file name (partial match)
schema:
type: string
- name: title
in: query
required: false
description: Filter tests by title (partial match)
schema:
type: string
- name: min_executions
in: query
required: false
description: Minimum number of executions to include
schema:
type: integer
minimum: 1
- name: test_state[]
in: query
required: false
description: 'Restrict default **executions** and **avg duration** slices (unless `metric_settings` overrides). Each value is a `RecordedTestOutcome` (see components/schemas). Example: `test_state[]=passed&test_state[]=failed`.'
schema:
type: array
items:
$ref: '#/components/schemas/RecordedTestOutcome'
style: form
explode: true
example:
- passed
- failed
- name: metric_settings
in: query
required: false
description: 'Overrides which outcomes feed each metric. Keys: `executions`, `avgDuration`, `flakinessRate`, `failureRate` — each value is an array of `RecordedTestOutcome`. Omitted keys use defaults. Pass as a JSON value (same encoding as `content: application/json` for this parameter).'
content:
application/json:
schema:
$ref: '#/components/schemas/TestsExplorerMetricSettings'
example:
executions:
- failed
- passed
failureRate:
- failed
- 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/TestsExplorerResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
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:
ProjectIdPath:
name: projectId
in: path
required: true
description: The project ID
schema:
type: string
example: proj_abc123
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
TestsExplorerLimitQuery:
name: limit
in: query
required: false
description: 'Maximum number of items to return (default: 50, max: 50)'
schema:
type: integer
minimum: 1
maximum: 50
default: 50
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
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'
PageQuery:
name: page
in: query
required: false
description: Page number (0-indexed)
schema:
type: integer
minimum: 0
default: 0
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'
SortDirectionQuery:
name: dir
in: query
required: false
description: 'Sort direction: `desc` (default) or `asc`.'
schema:
type: string
enum:
- asc
- desc
default: desc
schemas:
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
TestExplorerItem:
type: object
properties:
title:
type: string
description: Test title
signature:
type: string
description: Unique test signature
spec:
type: string
description: Spec file path
metrics:
type: object
description: 'Aggregates for the query window. Outcomes follow `RecordedTestOutcome` (see components/schemas): `pending` → `ignored`; `failed` and `skipped` → `failures`.'
properties:
executions:
type: integer
description: Number of executions in the window matching the executions status filter.
passes:
type: integer
description: Executions with outcome `passed`.
failures:
type: integer
description: Executions with outcome `failed` or `skipped` (no separate skipped counter).
ignored:
type: integer
description: Executions with outcome `pending` (intentional skip; “Ignored” in the dashboard).
flaky:
type: integer
description: Executions flagged flaky within the flakiness-rate status filter.
flakinessRate:
type: number
description: Flaky executions ÷ flakiness denominator (0–1).
failureRate:
type: number
description: Failed+skipped executions ÷ failure-rate denominator (0–1).
avgDurationMs:
type: number
description: Mean duration in ms over the avg-duration status filter.
flakinessVolume:
type: number
description: 'Flakiness impact: rate × flakiness-denominator executions (rounded).'
failureVolume:
type: number
description: 'Failure impact: rate × failure-rate denominator executions (rounded).'
durationVolume:
type: number
description: 'Duration impact: avg duration × executions used for duration (rounded).'
latestTag:
type:
- array
- 'null'
items:
type: string
latestAnnotations:
type: array
description: Annotations from the most recent execution of this test.
items:
type: object
properties:
type:
type: string
description: Annotation type
description:
type:
- string
- 'null'
description: Annotation description
required:
- type
lastSeen:
type:
- string
- 'null'
format: date-time
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
TestsExplorerResponse:
type: object
required:
- status
- data
properties:
status:
type: string
enum:
- OK
data:
type: object
properties:
list:
type: array
items:
$ref: '#/components/schemas/TestExplorerItem'
count:
type: integer
total:
type: integer
nextPage:
oneOf:
- type: integer
- type: boolean
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
TestsExplorerMetricSettings:
type: object
additionalProperties: false
description: Per-metric outcome filters for Tests Explorer (`metric_settings` query parameter).
properties:
executions:
type: array
items:
$ref: '#/components/schemas/RecordedTestOutcome'
avgDuration:
type: array
items:
$ref: '#/components/schemas/RecordedTestOutcome'
flakinessRate:
type: array
items:
$ref: '#/components/schemas/RecordedTestOutcome'
failureRate:
type: array
items:
$ref: '#/components/schemas/RecordedTestOutcome'
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