ish Events API
The events API from ish — 1 operation(s) for events.
The events API from ish — 1 operation(s) for events.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/ishlabs-io:ishlabs-io-events-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
contact:
name: ish developer platform
url: https://docs.ishlabs.io/
description: 'The ish Developer API: the entry point for getting human feedback on whatever you''re building, in whichever format you need. Its first surface is **sessions**: you open a session for one of your people inside *your own* environment, submit what that person perceives (human-sensory observations: a rendered screen, an error message) along with the set of actions your environment currently offers, and they decide one action per turn and tell you how it felt. Your code executes the chosen action and loops. **The loop is decide-only: ish never locates, drives, or executes anything inside your environment.** You stay in control of what runs; we tell you what a person would do next and why. ## Vocabulary
The public vocabulary is **workspace**, **person**, **environment**, and **session**, and the wire uses these names directly:
- `workspace_id`: the workspace that owns the person and is billed for the session (the create path is `/workspaces/{workspace_id}/sessions`). - `person_id`: the person the session drives. Two kinds are available to you: the ones your workspace owns, and the ones published in the shared ish library. Both are listed and searchable under `/v1/workspaces/{workspace_id}/people`, and each carries an `owner` saying which it is. - `environment_id`: a registered environment in that workspace: a named target with its own operating notes and default action vocabulary. Register one and reference it, or describe an ad-hoc one inline on the session; supply exactly one of the two. **"Session" here means one API session: a single person working toward one task in your environment, turn by turn.** It is not the ish product''s notion of a study session (someone sitting down with a study), and it is unrelated to a browser session. Sessions live under `/v1/sessions`. ## Authentication
All requests use a bearer token: `Authorization: Bearer `. The token is a **workspace API key** (prefix `ish_sk_live_`), a workspace-scoped machine principal minted in Settings > Developers and shown exactly once at mint. Scopes: `sessions:run` (create, submit turns, close), `sessions:read` (read the session and trace), `environments:read` / `environments:write` for the environment registry, `tasks:read` / `tasks:write` for the task registry, `people:read` / `people:write` for the people you can run a session for, and `usage:read` for this workspace''s consumption, limits and rate-limit budgets. Keys are minted with `sessions:run`, `sessions:read`, and `tasks:read`; ask for any `:write` scope, and for `people:read` and `usage:read`, explicitly. `usage:read` is never implied by another scope: it covers the shape of the whole workspace''s operation, not just the lane a key was minted for. An ish user access token (JWT) also works on the same header for personal scripts; server integrations should use keys. ## Versioning & forward-compatibility
The API is versioned in the URL path (`/api/v1`). Several enum-typed and block-typed fields are **open unions**: new values may be added over time, and clients MUST tolerate values they do not recognize rather than failing on them. The field descriptions call these out. ## Correlation & errors
Every response carries an **`X-Request-Id`** header; quote it in support requests. Error responses additionally echo it in the body as `request_id`. Two error-body shapes exist; parse defensively, they differ:
- **`ValidationError`**: the request-validation layer (HTTP 422): a malformed body (wrong types, missing fields, an invalid observation `type`, a bad enum value, `parameters` that don''t fit the model). A field-level envelope (`error_code`, `errors[]`, `suggestions[]`; `suggestions` is CLI-oriented boilerplate; ignore it for API use). - **`Error`**: everything else, a `{ detail, request_id }` envelope. This covers the handler layer''s business-rule 422s (e.g. `exactly one image observation block is required`; `person has no renderable background`) as well as 401 / 402 / 403 / 404 / 409 / 429 / 502.'
license:
identifier: LicenseRef-Proprietary
name: Proprietary
title: ish Developer Events API
version: 1.11.0
servers:
- description: Production
url: https://api.ishlabs.io
security:
- bearerAuth: []
tags:
- name: Events
paths:
/api/v1/workspaces/{workspace_id}/events:
get:
description: 'Events for one workspace, newest first.
Retained for 30 days. The window is enforced on this read as well as by the
sweep, so a late or un-scheduled sweep can never surface an event older than
the documented lifetime.
An unknown value in `types` is refused rather than silently matching
nothing: a typo''d filter that returns an empty page reads exactly like "your
integration produced no events", which is the most expensive way for a
caller to lose a day.'
operationId: listEvents
parameters:
- in: path
name: workspace_id
required: true
schema:
format: uuid
title: Workspace Id
type: string
- description: Filter to these event types. Repeat the parameter to pass several. Omit for all types.
in: query
name: types
required: false
schema:
anyOf:
- items:
type: string
type: array
- type: 'null'
description: Filter to these event types. Repeat the parameter to pass several. Omit for all types.
title: Types
- description: Opaque cursor from `next_cursor`.
in: query
name: after
required: false
schema:
anyOf:
- type: string
- type: 'null'
description: Opaque cursor from `next_cursor`.
title: After
- in: query
name: limit
required: false
schema:
default: 25
maximum: 100
minimum: 1
title: Limit
type: integer
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/EventListResponse'
description: Successful Response
headers:
X-Request-Id:
$ref: '#/components/headers/XRequestId'
x-ratelimit-limit:
$ref: '#/components/headers/XRateLimitLimit'
x-ratelimit-remaining:
$ref: '#/components/headers/XRateLimitRemaining'
x-ratelimit-reset:
$ref: '#/components/headers/XRateLimitReset'
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: 'The bearer token is missing, malformed, unknown, or revoked (`detail.error_kind = ''invalid_api_key''`). One kind covers all four deliberately: distinguishing them would confirm which guesses are real keys.'
headers:
X-Request-Id:
$ref: '#/components/headers/XRequestId'
x-ratelimit-limit:
$ref: '#/components/headers/XRateLimitLimit'
x-ratelimit-remaining:
$ref: '#/components/headers/XRateLimitRemaining'
x-ratelimit-reset:
$ref: '#/components/headers/XRateLimitReset'
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: The API key lacks the `events:read` scope (`detail.error_kind = 'insufficient_scope'`, with `detail.required_scope = 'events:read'`). Mint a key that includes it; an ish user access token is unconstrained by scopes.
headers:
X-Request-Id:
$ref: '#/components/headers/XRequestId'
x-ratelimit-limit:
$ref: '#/components/headers/XRateLimitLimit'
x-ratelimit-remaining:
$ref: '#/components/headers/XRateLimitRemaining'
x-ratelimit-reset:
$ref: '#/components/headers/XRateLimitReset'
'404':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: The workspace (`workspace_id`) was not found, or is not reachable with this credential. `detail.error_kind` is `workspace_not_found`.
headers:
X-Request-Id:
$ref: '#/components/headers/XRequestId'
x-ratelimit-limit:
$ref: '#/components/headers/XRateLimitLimit'
x-ratelimit-remaining:
$ref: '#/components/headers/XRateLimitRemaining'
x-ratelimit-reset:
$ref: '#/components/headers/XRateLimitReset'
'422':
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/ValidationError'
- $ref: '#/components/schemas/Error'
description: Validation error. A request-shape failure uses the `ValidationError` envelope; a handler-raised domain-validation refusal uses the `Error` envelope.
headers:
X-Request-Id:
$ref: '#/components/headers/XRequestId'
x-ratelimit-limit:
$ref: '#/components/headers/XRateLimitLimit'
x-ratelimit-remaining:
$ref: '#/components/headers/XRateLimitRemaining'
x-ratelimit-reset:
$ref: '#/components/headers/XRateLimitReset'
'429':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: 'The API key''s request rate limit is exceeded (`detail.error_kind = ''rate_limited''`). Transient: retry after the `Retry-After` interval, also echoed as `detail.retry_after_seconds`. Not applied to user access tokens.'
headers:
X-Request-Id:
$ref: '#/components/headers/XRequestId'
x-ratelimit-limit:
$ref: '#/components/headers/XRateLimitLimit'
x-ratelimit-remaining:
$ref: '#/components/headers/XRateLimitRemaining'
x-ratelimit-reset:
$ref: '#/components/headers/XRateLimitReset'
security:
- bearerAuth: []
summary: List the workspace's events
tags:
- Events
components:
headers:
XRateLimitReset:
description: '**Seconds from now** until that window resets (a delta, NOT a Unix timestamp). Measured when the response is written. Omitted on the rare occasion the reset instant could not be read, while `limit` and `remaining` are still published.'
schema:
type: integer
XRequestId:
description: Per-response correlation id, present on EVERY response (success and error). Error bodies echo it as `request_id`.
schema:
type: string
XRateLimitRemaining:
description: Requests left in that window AFTER this one was counted. Pace off this rather than waiting for a `429`.
schema:
type: integer
XRateLimitLimit:
description: Ceiling for the rate-limit window this request charged. Present on responses made with an API key against a rate-limited operation, including the `429` itself; absent for user access tokens (which carry no per-key budget) and when the rate-limit backend could not be read.
schema:
type: integer
schemas:
SessionEventTask:
description: The task the session ran, as identifiers only.
properties:
id:
anyOf:
- format: uuid
type: string
- type: 'null'
description: 'The registered task, or null when the session was opened with inline instructions. Null is a real answer, not a gap: an inline session''s intent has no registry identity.'
title: Id
revision:
anyOf:
- type: integer
- type: 'null'
description: The task's revision at bind time. Two runs of one task are only comparable within a single revision of its instructions.
title: Revision
required:
- id
- revision
title: SessionEventTask
type: object
SessionEventResponse:
properties:
created_at:
format: date-time
title: Created At
type: string
data:
$ref: '#/components/schemas/SessionEventData'
id:
description: Stable id for this event; safe to dedupe on.
format: uuid
title: Id
type: string
type:
description: '`session.completed` or `session.failed`. The split is who ENDED the run, not whether the person succeeded: the person or caller ending it leaves a usable trace (including `gave_up`, which is a finding rather than a fault), while a limit cutting it short does not. Route on this; read `data.object.status` for precision. Open union; new types may be added.'
title: Type
type: string
required:
- id
- type
- created_at
- data
title: SessionEventResponse
type: object
FieldError:
description: One field-level entry in a ValidationError envelope.
properties:
allowed_values:
description: For enum/literal failures, the accepted values.
items: {}
title: Allowed Values
type: array
input:
anyOf:
- type: string
- type: number
- type: boolean
- type: 'null'
description: Offending value (truncated); omitted for containers.
title: Input
loc:
description: Path to the offending field.
items:
anyOf:
- type: string
- type: integer
title: Location
type: array
msg:
title: Message
type: string
type:
title: Error Type
type: string
required:
- loc
- msg
- type
title: FieldError
type: object
ValidationError:
description: Request body / parameter validation failure (HTTP 422). A field-level envelope so callers can pinpoint the offending field, value, and allowed values without parsing prose.
properties:
detail:
title: Detail
type: string
error:
title: Error
type: string
error_code:
description: Stable machine code, e.g. `validation_error`.
title: Error Code
type: string
errors:
items:
$ref: '#/components/schemas/FieldError'
title: Errors
type: array
retryable:
title: Retryable
type: boolean
status:
title: Status
type: integer
suggestions:
items:
type: string
title: Suggestions
type: array
required:
- error
- error_code
- status
- retryable
- detail
- errors
title: ValidationError
type: object
SessionEventObject:
description: The session this event describes.
properties:
ended_reason:
anyOf:
- type: string
- type: 'null'
description: Finer than `status` where the status is ambiguous. Two exhaustion kinds share `balance_exhausted`, and this is what separates `credits_exhausted` (top up) from `plan_allowance_exhausted` (the period allowance, which returns on renewal). Null for an ordinary ending.
title: Ended Reason
livemode:
description: False for a test session. Never act on a test session as though it were real.
title: Livemode
type: boolean
session_id:
format: uuid
title: Session Id
type: string
status:
description: 'The precise terminal status the two coarse event types flatten: `completed`, `gave_up`, `closed`, `max_turns`, `spend_cap_reached`, `balance_exhausted`. Open union: treat an unfamiliar value as ''ended somehow'' rather than failing.'
title: Status
type: string
task:
$ref: '#/components/schemas/SessionEventTask'
turn_count:
title: Turn Count
type: integer
url:
description: 'Path to the session this event describes, relative to the API root, to be fetched under your own credentials. Relative on purpose: composed per response rather than frozen into the log, so a change of API host can never strand historical events holding a dead absolute link.'
title: Url
type: string
workspace_id:
format: uuid
title: Workspace Id
type: string
required:
- session_id
- workspace_id
- status
- ended_reason
- turn_count
- livemode
- task
- url
title: SessionEventObject
type: object
Error:
description: Generic error envelope for 4xx/5xx responses other than request-shape validation.
properties:
detail:
anyOf:
- type: string
- additionalProperties: true
type: object
description: A human-readable message, OR a structured object carrying a machine-readable `error_kind` (e.g. `idempotency_conflict`, `turn_index_mismatch`, `decision_invalid`).
title: Detail
docs_url:
description: Link to this error's entry in the error catalog. Present whenever `detail` carries a registered `error_kind`; absent on unclassified errors.
title: Docs Url
type: string
request_id:
description: Correlation id; identical to the `X-Request-Id` response header. Quote it in support requests.
title: Request Id
type: string
required:
- detail
- request_id
title: Error
type: object
EventListResponse:
description: One page of events, newest first.
properties:
has_more:
description: True when more events exist beyond this page.
title: Has More
type: boolean
items:
items:
$ref: '#/components/schemas/SessionEventResponse'
title: Items
type: array
next_cursor:
anyOf:
- type: string
- type: 'null'
description: Opaque keyset cursor for the next page (pass as `after`); null on the last page. Treat it as opaque; its encoding is not part of this contract.
title: Next Cursor
required:
- items
- has_more
- next_cursor
title: EventListResponse
type: object
SessionEventData:
properties:
object:
$ref: '#/components/schemas/SessionEventObject'
required:
- object
title: SessionEventData
type: object
securitySchemes:
bearerAuth:
description: 'Workspace API key as a bearer token: `Authorization: Bearer ish_sk_live_...`. Keys are workspace-scoped machine principals minted in Settings > Developers (shown once at mint). Scopes: `sessions:run` (create a session, submit turns, close), `sessions:read` (read the session and its decision trace), `environments:read` and `environments:write` (manage the workspace''s registered environments), `tasks:read` and `tasks:write` (manage the workspace''s registered tasks), `people:read` and `people:write` (the people the workspace can run a session for), and `usage:read` (the workspace''s own consumption, spend limits and rate-limit budgets). A key is minted with `sessions:run`, `sessions:read`, and `tasks:read` by default; every other scope must be requested, `people:read` and `usage:read` included. An ish user access token also works on the same header for personal scripts.'
scheme: bearer
type: http