Coasty runs API

Autonomous task runs — the agent drives a task to completion on a machine.

OpenAPI Specification

coasty-runs-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: Coasty Public keys runs API
  version: 1.0.0
  summary: Computer Use Agents, scheduled automation, and managed VMs.
  description: "# Coasty Public API\n\nCoasty is a Computer Use Agent (CUA) platform: predict actions from screenshots,\nprovision managed VMs, and run scheduled automation against them.\n\n## Authentication\n\nAll endpoints (except `/v1/triggers/webhook/{webhook_id}` and health checks) require an API key.\nPass it as either:\n\n- `X-API-Key: sk-coasty-live-...` (or `sk-coasty-test-...` for sandbox)\n- `Authorization: Bearer sk-coasty-live-...`\n\nThe external webhook endpoint does not use an API key, but it is authenticated:\nsend the HMAC-SHA256 `Coasty-Signature` credential documented on that operation.\n\nTest-mode keys (`sk-coasty-test-*`) hit the same validation paths as live keys but\nreturn mock VMs / mock action results and never bill credits — ideal for CI.\n\n## Pricing & budgeting\n\nPer-call rates (subject to change — see `lib/pricing/tiers.ts METERED_RATES`):\n\n| Endpoint | Credits |\n|---|---|\n| `POST /v1/predict` | ~5 |\n| `POST /v1/sessions` | 10 |\n| `POST /v1/sessions/{id}/predict` | ~4 |\n| `POST /v1/ground` | ~3 |\n| `POST /v1/parse` | 0 (free) |\n\nLong-running CUA jobs orchestrated through the dashboard (not this API) bill at\n10 credits/minute with a 20-credit minimum. Subscription tiers (`free | starter |\nprofessional | enterprise`) gate feature availability (e.g. custom system prompts),\nschedule counts, and the maximum trajectory length.\n\n## Errors\n\nEvery error response uses the same envelope:\n\n```json\n{\n  \"error\": {\n    \"code\": \"INSUFFICIENT_CREDITS\",\n    \"message\": \"Need 5, have 2.\",\n    \"type\": \"billing_error\",\n    \"request_id\": \"req_a1b2c3d4e5f6\",\n    \"retryable\": false,\n    \"retry_with_same_idempotency_key\": false\n  }\n}\n```\n\nInclude the `request_id` in support requests.\n\n## Idempotency\n\nOperations marked `x-idempotency: reserve-and-replay` accept `Idempotency-Key:\n<≤128 chars of [A-Za-z0-9_-:]>`. Replays\nof the same key + identical body return the original response (with\n`X-Coasty-Idempotent-Replay: true`) for 24 h. Reusing the key with a different body\nis a 422 `IDEMPOTENCY_KEY_REUSED`.\nOperations marked `x-idempotency: webhook-payload-dedup` instead deduplicate the\nsame webhook id + identical raw body for 60 seconds; they do not accept an\nIdempotency-Key.\n\n## Clients & MCP\n\nUse the HTTP API directly from any language. Official TypeScript and Python SDKs\nare not currently published; generate a client from this OpenAPI document if needed.\n- MCP server: `npx -y @coasty/mcp` (see `x-mcp-server`)\n\n## Reference\n\nComplete (machine-readable) spec is hosted at `/.well-known/openapi.json` and\n`/openapi.json` (Stripe / Vercel conventions)."
  contact:
    name: Coasty Developer Support
    url: https://coasty.ai/support
    email: founders@coasty.ai
  license:
    name: MIT
    identifier: MIT
  termsOfService: https://coasty.ai/terms
servers:
- url: https://coasty.ai
  description: Production
- url: https://coasty.ai
  description: Sandbox — use sk-coasty-test-* keys against the same host. No billing, mock VMs.
security:
- apiKey: []
- bearerAuth: []
tags:
- name: runs
  description: Autonomous task runs — the agent drives a task to completion on a machine.
paths:
  /v1/runs:
    post:
      tags:
      - runs
      operationId: createRun
      summary: Start an autonomous run
      description: 'Scope: `runs:write`. Drives `task` to completion on `machine_id`, running the screenshot→act→verify loop server-side. Each agent step bills at the /v1/predict rate (~5 credits/step, scales with cua_version); a 402 is returned up front if the wallet cannot afford one step. Test keys (sk-coasty-test-*) never bill. `webhook_secret` is returned ONCE. BYOK: opt in via the body `llm` block or the X-LLM-* headers and every LLM call runs on YOUR provider key; the key is snapshotted encrypted for crash-recovery and scrubbed at terminal state.'
      security:
      - apiKey: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/IdempotencyKey'
      - $ref: '#/components/parameters/XLLMProvider'
      - $ref: '#/components/parameters/XLLMApiKey'
      - $ref: '#/components/parameters/XLLMModel'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RunRequest'
      responses:
        '200':
          description: Run created (status "queued").
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunResponse'
          headers:
            X-Coasty-Request-Id:
              $ref: '#/components/headers/CoastyRequestId'
            X-Coasty-Key-Kind:
              $ref: '#/components/headers/KeyKind'
            X-Coasty-Test-Mode:
              $ref: '#/components/headers/TestMode'
            X-Coasty-Idempotent-Replay:
              $ref: '#/components/headers/IdempotentReplay'
            Idempotency-Status:
              $ref: '#/components/headers/IdempotencyStatus'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
      x-auth-mode: api_key
      x-required-scope: runs:write
      x-required-scopes:
      - runs:write
      x-scope-policy: required
      x-billing-code: run_step
      x-offering: runs
      x-feature-flag: RUNS_API_ENABLED
      x-idempotency: reserve-and-replay
    get:
      tags:
      - runs
      operationId: listRuns
      summary: List runs
      description: 'Scope: `runs:read`. Free. Optional `status` filter and `limit` (1-200, default 20).'
      security:
      - apiKey: []
      - bearerAuth: []
      parameters:
      - name: status
        in: query
        required: false
        schema:
          type: string
          enum:
          - queued
          - running
          - awaiting_human
          - succeeded
          - failed
          - cancelled
          - timed_out
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 200
          default: 20
      responses:
        '200':
          description: A list of runs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListRunsResponse'
          headers:
            X-Coasty-Request-Id:
              $ref: '#/components/headers/CoastyRequestId'
            X-Coasty-Key-Kind:
              $ref: '#/components/headers/KeyKind'
            X-Coasty-Test-Mode:
              $ref: '#/components/headers/TestMode'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
      x-auth-mode: api_key
      x-required-scope: runs:read
      x-required-scopes:
      - runs:read
      x-scope-policy: required
      x-billing-code: null
      x-offering: runs
      x-feature-flag: RUNS_API_ENABLED
      x-idempotency: none
  /v1/runs/{run_id}:
    parameters:
    - $ref: '#/components/parameters/RunId'
    get:
      tags:
      - runs
      operationId: getRun
      summary: Get a run
      description: 'Scope: `runs:read`. Free. Returns status, result, and credits charged.'
      security:
      - apiKey: []
      - bearerAuth: []
      responses:
        '200':
          description: The run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunResponse'
          headers:
            X-Coasty-Request-Id:
              $ref: '#/components/headers/CoastyRequestId'
            X-Coasty-Key-Kind:
              $ref: '#/components/headers/KeyKind'
            X-Coasty-Test-Mode:
              $ref: '#/components/headers/TestMode'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
      x-auth-mode: api_key
      x-required-scope: runs:read
      x-required-scopes:
      - runs:read
      x-scope-policy: required
      x-billing-code: null
      x-offering: runs
      x-feature-flag: RUNS_API_ENABLED
      x-idempotency: none
  /v1/runs/{run_id}/events:
    parameters:
    - $ref: '#/components/parameters/RunId'
    get:
      tags:
      - runs
      operationId: streamRunEvents
      summary: Stream run events (SSE)
      description: 'Scope: `runs:read`. Free. A durable Server-Sent Events stream. Resume with the `Last-Event-ID` header or `?after=<seq>`. The edge may rotate a connection after 300 seconds, so reconnect with the last sequence until a terminal `done` event arrives.'
      security:
      - apiKey: []
      - bearerAuth: []
      parameters:
      - $ref: '#/components/parameters/LastEventId'
      - name: after
        in: query
        required: false
        schema:
          type: integer
          minimum: 0
      responses:
        '200':
          description: SSE stream (text/event-stream).
          content:
            text/event-stream:
              schema:
                type: string
              description: A Server-Sent Events byte stream. Each frame carries an event name/id and JSON in its data field; reconnect with Last-Event-ID.
          headers:
            X-Coasty-Request-Id:
              $ref: '#/components/headers/CoastyRequestId'
            X-Coasty-Key-Kind:
              $ref: '#/components/headers/KeyKind'
            X-Coasty-Test-Mode:
              $ref: '#/components/headers/TestMode'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
      x-auth-mode: api_key
      x-required-scope: runs:read
      x-required-scopes:
      - runs:read
      x-scope-policy: required
      x-billing-code: null
      x-offering: runs
      x-feature-flag: RUNS_API_ENABLED
      x-idempotency: none
  /v1/runs/{run_id}/cancel:
    parameters:
    - $ref: '#/components/parameters/RunId'
    post:
      tags:
      - runs
      operationId: cancelRun
      summary: Cancel a run
      description: 'Scope: `runs:write`. Free. Cancels an in-flight run.'
      security:
      - apiKey: []
      - bearerAuth: []
      responses:
        '200':
          description: The cancelled run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunResponse'
          headers:
            X-Coasty-Request-Id:
              $ref: '#/components/headers/CoastyRequestId'
            X-Coasty-Key-Kind:
              $ref: '#/components/headers/KeyKind'
            X-Coasty-Test-Mode:
              $ref: '#/components/headers/TestMode'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
      x-auth-mode: api_key
      x-required-scope: runs:write
      x-required-scopes:
      - runs:write
      x-scope-policy: required
      x-billing-code: null
      x-offering: runs
      x-feature-flag: RUNS_API_ENABLED
      x-idempotency: none
  /v1/runs/{run_id}/resume:
    parameters:
    - $ref: '#/components/parameters/RunId'
    post:
      tags:
      - runs
      operationId: resumeRun
      summary: Resume a run after human takeover
      description: 'Scope: `runs:write`. Free. Resumes a run paused in `awaiting_human`. Body is optional.'
      security:
      - apiKey: []
      - bearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ResumeRunRequest'
      responses:
        '200':
          description: The resumed run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunResponse'
          headers:
            X-Coasty-Request-Id:
              $ref: '#/components/headers/CoastyRequestId'
            X-Coasty-Key-Kind:
              $ref: '#/components/headers/KeyKind'
            X-Coasty-Test-Mode:
              $ref: '#/components/headers/TestMode'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/ServerError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
        '504':
          $ref: '#/components/responses/GatewayTimeout'
      x-auth-mode: api_key
      x-required-scope: runs:write
      x-required-scopes:
      - runs:write
      x-scope-policy: required
      x-billing-code: null
      x-offering: runs
      x-feature-flag: RUNS_API_ENABLED
      x-idempotency: none
components:
  headers:
    CreditsCharged:
      description: Credits charged by this response (zero for sandbox keys and stored replays).
      schema:
        type: integer
        minimum: 0
    RetryAfter:
      description: Seconds to wait before retrying. Present on retryable back-pressure and transient failures.
      schema:
        type: integer
        minimum: 0
    IdempotencyStatus:
      description: Lifecycle status of the canonical idempotent attempt when applicable.
      schema:
        type: string
        enum:
        - processing
        - completed
    CoastyRequestId:
      description: Coasty correlation identifier for this request.
      schema:
        type: string
    IdempotentReplay:
      description: true when this is a stored response replay rather than a new execution.
      schema:
        type: boolean
    TestMode:
      description: true when the request executed against the sandbox namespace.
      schema:
        type: boolean
    KeyKind:
      description: Whether the authenticated key is live or test.
      schema:
        type: string
        enum:
        - live
        - test
        - legacy
    CreditsRefunded:
      description: Credits durably returned to the wallet after a failed billed operation.
      schema:
        type: integer
        minimum: 1
  responses:
    RateLimited:
      description: Rate or concurrency limit exceeded.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: RATE_LIMIT_EXCEEDED
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    BadRequest:
      description: Invalid request body or parameters.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: VALIDATION_ERROR
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    GatewayTimeout:
      description: An upstream dependency timed out.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: UPSTREAM_TIMEOUT
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    PayloadTooLarge:
      description: The request body exceeds the endpoint limit.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: PAYLOAD_TOO_LARGE
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    ServiceUnavailable:
      description: A required service is temporarily unavailable.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: SERVICE_UNAVAILABLE
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    NotFound:
      description: Resource not found in this key's namespace.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: NOT_FOUND
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    BadGateway:
      description: An upstream dependency returned an invalid response.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: UPSTREAM_ERROR
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    Forbidden:
      description: API key lacks the required scope or tier-feature is unavailable on the caller's plan.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: INSUFFICIENT_SCOPE
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    UnprocessableEntity:
      description: The JSON shape is valid but one or more values violate the endpoint contract.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: VALIDATION_ERROR
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    Conflict:
      description: The resource state conflicts with this operation.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: CONFLICT
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    Unauthorized:
      description: 'Missing, invalid, or revoked API key. Pass `X-API-Key: sk-coasty-live-...` (or test).'
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: INVALID_API_KEY
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    PaymentRequired:
      description: Insufficient credits to perform the operation. Top up via /credits or upgrade subscription.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: INSUFFICIENT_CREDITS
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
    ServerError:
      description: Unexpected server error. Retry with exponential backoff.
      headers:
        X-Coasty-Request-Id:
          $ref: '#/components/headers/CoastyRequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Credits-Charged:
          $ref: '#/components/headers/CreditsCharged'
        X-Credits-Refunded:
          $ref: '#/components/headers/CreditsRefunded'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
          example:
            error:
              code: INTERNAL_ERROR
              message: An error occurred. See `code` for details.
              type: validation_error
              request_id: req_a1b2c3d4e5f6
              retryable: false
              retry_with_same_idempotency_key: false
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: 'Optional client-supplied key (≤128 chars, [A-Za-z0-9_-:]) for safe retries. ''Same request'' = a SHA-256 of the canonical (sorted-key) JSON body (session_id is folded in for /sessions/{id}/predict). Replays the original response for 24 h when the body hash matches (X-Coasty-Idempotent-Replay: true and X-Credits-Charged: 0). Inference replays also set body usage.credits_charged=0 and usage.billed=false; machine-snapshot bodies retain the original gross charge for auditability. A retry while the original is still running waits up to ~25 s then returns the result, otherwise 409 IDEMPOTENCY_IN_FLIGHT (retry with the SAME key). Returns 422 IDEMPOTENCY_KEY_REUSED if the body differs. Collect a lost result via GET /v1/idempotency/{key}.'
      schema:
        type: string
        maxLength: 128
        pattern: ^[A-Za-z0-9_\-:]+$
      examples:
        uuid:
          value: 550e8400-e29b-41d4-a716-446655440000
    XLLMProvider:
      name: X-LLM-Provider
      in: header
      required: false
      description: 'BYOK: which provider the X-LLM-Api-Key belongs to (anthropic | openai). Required whenever X-LLM-Api-Key is sent — a key without a provider is a 422 LLM_PROVIDER_UNSUPPORTED. Selecting a BYOK provider (here or via the body llm.provider) runs the ENTIRE harness on your own account with no silent fallback to Coasty''s platform LLM keys.'
      schema:
        type: string
        enum:
        - anthropic
        - openai
    XLLMModel:
      name: X-LLM-Model
      in: header
      required: false
      description: 'BYOK: model override for this request (equivalent to body llm.model). Defaults: claude-sonnet-4-6 (anthropic), gpt-4o (openai). Any model string your account can access; must be vision-capable.'
      schema:
        type: string
        minLength: 1
        maxLength: 512
    RunId:
      name: run_id
      in: path
      required: true
      description: Run UUID (also used for workflow runs).
      schema:
        type: string
        format: uuid
    XLLMApiKey:
      name: X-LLM-Api-Key
      in: header
      required: false
      description: 'BYOK: your own provider API key, used for this request only. Takes precedence over the key stored via PUT /v1/llm/keys/{provider}. Never logged, never echoed in any response, webhook, or idempotency replay.'
      schema:
        type: string
        minLength: 16
        maxLength: 512
    LastEventId:
      name: Last-Event-ID
      in: header
      required: false
      description: Last processed SSE sequence number. Reconnect with this value to resume without gaps.
      schema:
        type: integer
        minimum: 0
  schemas:
    ResumeRunRequest:
      type: object
      additionalProperties: false
      properties:
        note:
          type:
          - string
          - 'null'
          maxLength: 2000
          description: Recorded on the run timeline.
    ApiError:
      type: object
      required:
      - error
      properties:
        error:
          type: object
          required:
          - code
          - message
          - type
          - retryable
          - retry_with_same_idempotency_key
          - request_id
          properties:
            code:
              type: string
              description: Stable machine-readable error code (e.g. INVALID_API_KEY, INSUFFICIENT_CREDITS).
              examples:
              - INVALID_API_KEY
            message:
              type: string
              description: Human-readable explanation. May i

# --- truncated at 32 KB (42 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/coasty/refs/heads/main/openapi/coasty-runs-api-openapi.yml