Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Currents Dev Sessions API
version: 1.0.0
contact:
name: Currents Support
url: https://currents.dev
description: 'Operations tagged Sessions 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: Sessions
description: 'Browser sessions an agent recorded: a title, how it ended, the git commit and pull request, and attachments such as a trace, screenshots and a video.
A session is not a run. It does not appear in run lists or in analytics, and it has no spec files or tests. Share one with `POST /share` and `sessionId`.'
paths:
/sessions:
post:
summary: Create a session
description: 'Records a browser session and signs an upload URL for each attachment declared in the request. Upload each file with a PUT to its `uploadUrl`, sending the headers in `uploadHeaders`; when `sizeBytes` was sent, the body must be exactly that long. The session is complete when created: there is nothing to finish. Besides a bearer token, this route accepts a record key in the `x-currents-key` header.'
operationId: createSession
security:
- BearerAuth: []
- OAuth2:
- runs:write
- RecordKey: []
tags:
- Sessions
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSessionRequest'
responses:
'200':
description: The session ID and upload URLs, in the order of the request
content:
application/json:
schema:
$ref: '#/components/schemas/SessionAttachmentsResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
description: Recording is suspended or the subscription has expired
'429':
description: Too many requests for the organization (`rate_limited`)
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
/sessions/{sessionId}:
get:
summary: Get a session
description: The session and its attachments, oldest first, each with a read URL signed for an hour.
operationId: getSession
security:
- BearerAuth: []
- OAuth2:
- results:read
tags:
- Sessions
parameters:
- $ref: '#/components/parameters/SessionIdPath'
responses:
'200':
description: The session
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum:
- OK
data:
$ref: '#/components/schemas/Session'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
summary: Delete a session
description: The session stops being readable at once. Its attachments, share links and share assets are removed in the background.
operationId: deleteSession
security:
- BearerAuth: []
- OAuth2:
- runs:write
tags:
- Sessions
parameters:
- $ref: '#/components/parameters/SessionIdPath'
responses:
'200':
description: Deleted
content:
application/json:
schema:
type: object
required:
- status
properties:
status:
type: string
enum:
- OK
'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
/sessions/{sessionId}/attachments:
get:
summary: List the attachments of a session
operationId: listSessionAttachments
security:
- BearerAuth: []
- OAuth2:
- results:read
tags:
- Sessions
parameters:
- $ref: '#/components/parameters/SessionIdPath'
responses:
'200':
description: The attachments, oldest first
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum:
- OK
data:
type: object
properties:
attachments:
type: array
items:
$ref: '#/components/schemas/SessionAttachment'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
post:
summary: Add attachments to a session
description: 'Signs an upload URL for each attachment. A session takes at most 200 attachments. The body takes no target fields such as `instanceId` or `testId`: a session has none. Besides a bearer token, this route accepts a record key in the `x-currents-key` header.'
operationId: addSessionAttachments
security:
- BearerAuth: []
- OAuth2:
- runs:write
- RecordKey: []
tags:
- Sessions
parameters:
- $ref: '#/components/parameters/SessionIdPath'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- attachments
additionalProperties: false
properties:
attachments:
type: array
minItems: 1
maxItems: 50
items:
$ref: '#/components/schemas/AttachmentRequest'
responses:
'200':
description: Upload URLs, in the order of the request
content:
application/json:
schema:
$ref: '#/components/schemas/SessionAttachmentsResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
description: Too many requests for the organization (`rate_limited`)
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
/projects/{projectId}/sessions:
get:
summary: List the sessions of a project
description: Newest first. Filters match as they do on the run list; `search` looks at the title, branch, commit message and pull request.
operationId: getProjectSessions
security:
- BearerAuth: []
- OAuth2:
- results:read
tags:
- Sessions
parameters:
- $ref: '#/components/parameters/ProjectIdPath'
- $ref: '#/components/parameters/LimitQuery'
- $ref: '#/components/parameters/StartingAfterQuery'
- $ref: '#/components/parameters/EndingBeforeQuery'
- $ref: '#/components/parameters/BranchesQuery'
- $ref: '#/components/parameters/TagsQuery'
- $ref: '#/components/parameters/TagOperatorQuery'
- $ref: '#/components/parameters/RunFeedPrIdQuery'
- name: search
in: query
required: false
description: Case-insensitive substring search over a session's title, branch, commit message, and pull request id and title. Printable ASCII only; leading and trailing whitespace is trimmed, and the trimmed value must be 1–200 characters.
schema:
type: string
pattern: ^[ -~]+$
- $ref: '#/components/parameters/OptionalDateStartQuery'
- $ref: '#/components/parameters/OptionalDateEndQuery'
responses:
'200':
description: A page of sessions, without their attachments
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum:
- OK
has_more:
type: boolean
data:
type: array
items:
$ref: '#/components/schemas/SessionListItem'
'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
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
RunFeedPrIdQuery:
name: pr_id
in: query
required: false
description: Filter runs by normalized pull request id (stored as meta.pr.id). Printable ASCII only, max 128 characters.
schema:
type: string
minLength: 1
maxLength: 128
pattern: ^[!-~]+$
example: '42'
OptionalDateEndQuery:
name: date_end
in: query
required: false
description: Filter runs created before this ISO 8601 date-time. A date without a time (`2024-12-31`) means midnight UTC of that day, so the range ends before that day.
schema:
type: string
example: '2024-12-31T23:59:59Z'
SessionIdPath:
name: sessionId
in: path
required: true
description: The session ID
schema:
type: string
LimitQuery:
name: limit
in: query
required: false
description: 'Maximum number of items to return (default: 10, max: 50)'
schema:
type: integer
minimum: 1
maximum: 50
default: 10
StartingAfterQuery:
name: starting_after
in: query
required: false
description: Cursor for pagination. Returns items after this cursor value.
schema:
type: string
TagOperatorQuery:
name: tag_operator
in: query
required: false
description: Logical operator for tag filtering. AND requires all tags to be present (default), OR requires any tag to be present.
schema:
type: string
enum:
- AND
- OR
default: AND
example: AND
OptionalDateStartQuery:
name: date_start
in: query
required: false
description: Filter runs created on or after this ISO 8601 date-time. A date without a time (`2024-01-01`) means midnight UTC of that day.
schema:
type: string
example: '2024-01-01T00:00:00Z'
EndingBeforeQuery:
name: ending_before
in: query
required: false
description: Cursor for pagination. Returns items before this cursor value.
schema:
type: string
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/*
schemas:
SessionListItem:
type: object
properties:
sessionId:
type: string
projectId:
type: string
title:
type: string
status:
type: string
enum:
- passed
- failed
error:
type:
- string
- 'null'
tags:
type: array
items:
type: string
durationMs:
type: integer
git:
type: object
properties:
sha:
type:
- string
- 'null'
branch:
type:
- string
- 'null'
message:
type:
- string
- 'null'
authorName:
type:
- string
- 'null'
authorEmail:
type:
- string
- 'null'
remoteOrigin:
type:
- string
- 'null'
pr:
type:
- object
- 'null'
properties:
id:
type:
- string
- 'null'
link:
type:
- string
- 'null'
title:
type:
- string
- 'null'
source:
type:
- string
- 'null'
destination:
type:
- string
- 'null'
attachmentCount:
type: integer
createdAt:
type: string
format: date-time
cursor:
type: string
description: Pass as `starting_after` or `ending_before`
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
SessionAttachmentsResponse:
type: object
required:
- status
- data
properties:
status:
type: string
enum:
- OK
data:
type: object
required:
- sessionId
- attachments
- uploadExpiresInSeconds
properties:
sessionId:
type: string
uploadExpiresInSeconds:
type: integer
example: 600
attachments:
type: array
description: In the order of the request
items:
type: object
required:
- attachmentId
- name
- type
- uploadUrl
- uploadHeaders
properties:
attachmentId:
type: string
name:
type: string
type:
type: string
uploadUrl:
type: string
uploadHeaders:
type: object
additionalProperties:
type: string
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
SessionAttachment:
type: object
required:
- attachmentId
- name
- type
- contentType
- sizeBytes
- caption
- meta
- createdAt
- readUrl
properties:
attachmentId:
type: string
name:
type: string
type:
type: string
enum:
- trace
- screenshot
- video
- attachment
contentType:
type:
- string
- 'null'
sizeBytes:
type:
- integer
- 'null'
caption:
type:
- string
- 'null'
meta:
type: object
additionalProperties:
type: string
createdAt:
type: string
format: date-time
readUrl:
type: string
description: Signed, valid for an hour; an attachment downloads under its own name.
Session:
allOf:
- $ref: '#/components/schemas/SessionListItem'
- type: object
properties:
attachments:
type: array
items:
$ref: '#/components/schemas/SessionAttachment'
CreateSessionRequest:
type: object
required:
- projectId
- title
- status
additionalProperties: false
properties:
projectId:
type: string
title:
type: string
minLength: 1
maxLength: 1024
description: What the session set out to show
status:
type: string
enum:
- passed
- failed
error:
type: string
maxLength: 10000
description: For a failed session
durationMs:
type: integer
minimum: 0
maximum: 86400000
tags:
type: array
items:
type: string
minLength: 1
maxLength: 255
maxItems: 20
commit:
type: object
properties:
sha:
type: string
branch:
type: string
message:
type: string
authorName:
type: string
authorEmail:
type: string
remoteOrigin:
type: string
pr:
type: object
description: The pull request, by `link` or `id`
properties:
link:
type: string
id:
type: string
ci:
type: object
description: CI variables the pull request is read from when `pr` is not sent
properties:
provider:
type: string
buildNumber:
type: string
params:
type: object
attachments:
type: array
maxItems: 50
description: Like an attachment request, but `sizeBytes` is optional. Without it the upload URL does not pin the size or type.
items:
type: object
required:
- name
- type
- contentType
properties:
name:
type: string
minLength: 1
maxLength: 1024
type:
type: string
enum:
- trace
- screenshot
- video
- attachment
description: 'Allowed per level: attachment on a run, spec file and test; screenshot, video and attachment on a spec file; trace, screenshot, video and attachment on an attempt.'
contentType:
type: string
description: A trace is application/zip, a screenshot is image/png, image/jpeg, image/webp or image/gif, a video is video/*.
example: image/png
sizeBytes:
type: integer
minimum: 1
maximum: 1073741824
description: Signed into the upload URL, so storage refuses another size. Organizations that store files in their own S3 bucket behind CloudFront get an upload URL without this check.
caption:
type: string
maxLength: 1000
meta:
type: object
additionalProperties:
type: string
maxLength: 256
description: At most 20 keys of letters, digits, "_" and "-", and 2 KB in total.
AttachmentRequest:
type: object
required:
- name
- type
- contentType
- sizeBytes
properties:
name:
type: string
minLength: 1
maxLength: 1024
type:
type: string
enum:
- trace
- screenshot
- video
- attachment
description: 'Allowed per level: attachment on a run, spec file and test; screenshot, video and attachment on a spec file; trace, screenshot, video and attachment on an attempt.'
contentType:
type: string
description: A trace is application/zip, a screenshot is image/png, image/jpeg, image/webp or image/gif, a video is video/*.
example: image/png
sizeBytes:
type: integer
minimum: 1
maximum: 1073741824
description: Signed into the upload URL, so storage refuses another size. Organizations that store files in their own S3 bucket behind CloudFront get an upload URL without this check.
caption:
type: string
maxLength: 1000
meta:
type: object
additionalProperties:
type: string
maxLength: 256
description: At most 20 keys of letters, digits, "_" and "-", and 2 KB in total.
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