# The SuperCool public API (v1): the source of truth for the reference at
# https://supercool.com/docs/api/reference. Rendered by scripts/build-docs.mjs,
# which parses this file with a small YAML-subset parser: block mappings and
# sequences, quoted and plain scalars, | and > block scalars, and flow ([...] /
# {...}, JSON-style) collections, which may span lines. Keep to that subset;
# the build fails loudly on anything else.
#
# Derived from superior-backend/app/routes/public_api.py and
# app/lib/agent_gateway/{render_public,jobs,webhooks,uploads,service}.py.
# When the routes change, change this file.

openapi: 3.1.0
info:
  title: SuperCool API
  version: 1.0.0
  summary: One agent, one endpoint. Send a message; get back finished work.
  description: |
    The SuperCool API gives your code the same agent you talk to in the SuperCool app.
    Send it a plain-language message and it plans the work, runs the tools and hands
    back the result: videos, images, websites, decks, research and writing.

    All requests go to `https://api.supercool.com/v1` with an API key in the
    `Authorization` header. Every response carries a `Request-Id` header.
  contact:
    name: SuperCool
    url: https://supercool.com/docs/api
  termsOfService: https://supercool.com/terms
externalDocs:
  description: Guides
  url: https://supercool.com/docs/api
servers:
  - url: https://api.supercool.com/v1
    description: Production
security:
  - apiKey: []
tags:
  - name: Messages
    description: Talk to your agent and follow the work it starts.
  - name: Uploads
    description: Attach large files (up to 500 MB each) to a message.
  - name: Work
    description: The chats your agent works in, the same ones you see in the app.
  - name: Files
    description: Download what the agent made.
  - name: Account
    description: Who the key belongs to, the plan, credits and limits.
  - name: Webhooks
    description: Get pushed events instead of polling.

paths:
  /messages:
    post:
      operationId: createMessage
      tags: [Messages]
      summary: Send a message
      description: |
        Sends your agent a message, optionally with files. The call returns within about 30
        seconds: either the finished answer, or `status: "processing"` when the agent started
        longer work. Follow a processing message with
        [`GET /messages/{message_id}`](#get-message) (use `?wait=` to long-poll), its
        [events](#list-message-events), or a [webhook](/docs/api/webhooks).

        Returns `201` for a new message and `200` when an `Idempotency-Key` replays an
        existing one. Running out of credits is not an error: the message comes back with
        `status: "blocked"` and `reason: "out_of_credits"`. Neither is the running-jobs cap:
        work the agent can't start because 5 jobs are already running comes back as a
        `blocked` job with `reason: "too_many_running"`, and the message lists the `running` work.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageCreate'
            example: {"message": "A 15 second vertical ad for my candle shop, warm and cozy"}
      responses:
        '201':
          description: The message was accepted. It is either final already or `processing`.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'
              example: {
                "id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d",
                "object": "message",
                "status": "processing",
                "reason": null,
                "question": null,
                "final": false,
                "reply": "On it. I'm making a 15 second vertical ad with warm candlelight shots.",
                "jobs": [{
                  "job_id": "exe_9b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e",
                  "kind": "execution",
                  "role": "execution",
                  "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
                  "title": "Candle shop vertical ad",
                  "status": "running",
                  "reason": null,
                  "final": false,
                  "queued": null,
                  "resume_by": null,
                  "link": "https://supercool.com/chat/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
                  "started_at": "2026-09-30T14:02:11.402000Z",
                  "settled_at": null,
                  "files": []
                }],
                "files": [],
                "notes": [],
                "credits_used": 0,
                "created_at": "2026-09-30T14:02:03.118000Z",
                "updated_at": "2026-09-30T14:02:12.950000Z"
              }
        '200':
          description: An `Idempotency-Key` you already used with the same body. The same message, as it is now.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/TooLarge'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [invalid_request, empty_message, too_long, too_many, unauthorized, not_found, idempotency_conflict, upload_not_ready, too_large, rate_limited]

  /messages/{message_id}:
    get:
      operationId: getMessage
      tags: [Messages]
      summary: Get a message
      description: |
        One message: its status, the agent's reply, the jobs it started and the files they
        made. With `wait`, the call holds for up to that many seconds and returns as soon as
        something changes (the status, the reply arriving, or a job's status) or the message
        is final.

        Messages are kept for at least 14 days after their last job's deadline. After that
        this returns `404 not_found`; read the work instead ([`GET /work/{work_id}`](#get-work)).
      parameters:
        - $ref: '#/components/parameters/MessageId'
        - name: wait
          in: query
          required: false
          description: Long-poll for up to this many seconds (0 to 45). `0` returns immediately.
          schema:
            type: integer
            minimum: 0
            maximum: 45
            default: 0
          example: 30
      responses:
        '200':
          description: The message.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'
              example: {
                "id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d",
                "object": "message",
                "status": "completed",
                "reason": null,
                "question": null,
                "final": true,
                "reply": "On it. I'm making a 15 second vertical ad with warm candlelight shots.",
                "jobs": [{
                  "job_id": "exe_9b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e",
                  "kind": "execution",
                  "role": "execution",
                  "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
                  "title": "Candle shop vertical ad",
                  "status": "completed",
                  "reason": null,
                  "final": true,
                  "queued": null,
                  "resume_by": null,
                  "link": "https://supercool.com/chat/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
                  "started_at": "2026-09-30T14:02:11.402000Z",
                  "settled_at": "2026-09-30T14:06:40.221000Z",
                  "files": [{
                    "file_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4",
                    "name": "candle_ad.mp4",
                    "content_type": "video/mp4",
                    "kind": "video",
                    "size": 4812345,
                    "url": "https://api.supercool.sh/api/v1/files/s/eyJmIjoiNWQwYzNl...",
                    "revision": "\"5b1c0e6f9d2a4b7c8e1f3a5d7c9b0e2f\"",
                    "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
                    "label": null
                  }]
                }],
                "files": [{
                  "file_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4",
                  "name": "candle_ad.mp4",
                  "content_type": "video/mp4",
                  "kind": "video",
                  "size": 4812345,
                  "url": "https://api.supercool.sh/api/v1/files/s/eyJmIjoiNWQwYzNl...",
                  "revision": "\"5b1c0e6f9d2a4b7c8e1f3a5d7c9b0e2f\"",
                  "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f",
                  "label": null
                }],
                "notes": [],
                "credits_used": 42.5,
                "created_at": "2026-09-30T14:02:03.118000Z",
                "updated_at": "2026-09-30T14:02:12.950000Z"
              }
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [invalid_request, unauthorized, not_found, rate_limited]

  /messages/{message_id}/events:
    get:
      operationId: listMessageEvents
      tags: [Messages]
      summary: List a message's events
      description: |
        The message's update log, oldest first: progress steps, late replies, each job's
        result with its files, and state changes. Only this message's entries: another
        message's progress in the same chat isn't included. Pass back the `cursor` from the previous
        page to get only what's new. With no cursor, the log starts where the message
        started. `done` is `true` once the message has nothing more to report.

        The call waits at least one second, and up to `wait` seconds, for new entries. Only
        one wait per message runs at a time (`409 poll_in_progress` otherwise). Entries are
        kept for 7 days; a cursor older than that returns `cursor_expired: true` with a fresh
        cursor.
      parameters:
        - $ref: '#/components/parameters/MessageId'
        - name: cursor
          in: query
          required: false
          description: The `cursor` from the previous page. Opaque.
          schema:
            type: string
        - name: wait
          in: query
          required: false
          description: Wait up to this many seconds for new entries (0 to 45; at least 1 second is always waited).
          schema:
            type: integer
            minimum: 0
            maximum: 45
            default: 0
          example: 30
      responses:
        '200':
          description: A page of events.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventList'
              example: {
                "object": "list",
                "data": [
                  {"seq": 118, "type": "progress", "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "message_id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d", "at": "2026-09-30T14:03:02.000000Z", "step": "Rendering the video", "status": "started", "file_names": []},
                  {"seq": 121, "type": "result", "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "message_id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d", "at": "2026-09-30T14:06:40.000000Z", "title": "Candle shop vertical ad", "text": "Your 15 second ad is ready.", "outcome": "completed", "files": []}
                ],
                "has_more": false,
                "done": true,
                "cursor": "eyJjIjoiYXBpOnU..."
              }
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [invalid_request, bad_cursor, unauthorized, not_found, poll_in_progress, rate_limited]

  /messages/{message_id}/recover:
    post:
      operationId: recoverMessage
      tags: [Messages]
      summary: Recover a message's results
      description: |
        For work that outlived its watch (a job or message that `expired`): re-arms the watch
        so the result is looked up again and lands in the message, its events and your
        webhooks. It never resends the message or starts new work. One entry per piece of
        work: `settled` (already has its outcome), `watching` (still being watched),
        `recovering` (re-armed now) or `exhausted` (recovered too many times; read the work).
      parameters:
        - $ref: '#/components/parameters/MessageId'
      responses:
        '200':
          description: What happened to each piece of work.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Recovery'
              example: {"id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d", "object": "recovery", "work": [{"work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "state": "recovering"}]}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [unauthorized, not_found, rate_limited]

  /uploads:
    post:
      operationId: createUpload
      tags: [Uploads]
      summary: Start an upload
      description: |
        Starts a large upload (up to 500 MB). Returns a presigned POST: send a
        `multipart/form-data` POST to `url` with every entry of `fields` as form fields and
        the file last, as a field named `file`. The link is valid for 15 minutes. Then call
        [`POST /uploads/{upload_id}/complete`](#complete-upload) and pass the `upload_id` in a
        message's `upload_ids`. Uploads never attached to a message are deleted after 24 hours.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadCreate'
            example: {"filename": "product-shoot.mov", "size": 157286400, "content_type": "video/quicktime"}
      responses:
        '201':
          description: The upload was created. POST the file to `url` next.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadCreated'
              example: {
                "object": "upload",
                "upload_id": "up_0a1b2c3d4e5f60718293a4b5c6d7e8f9",
                "url": "https://supercool-uploads.s3.amazonaws.com/",
                "fields": {"key": "agent-uploads/incoming/.../product-shoot.mov", "Content-Type": "video/quicktime", "policy": "eyJleHBpcmF0aW9uIjoi...", "x-amz-signature": "3f1c..."},
                "expires_at": "2026-09-30T14:17:03.118000Z",
                "max_bytes": 157286400
              }
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '413':
          $ref: '#/components/responses/TooLarge'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [invalid_request, bad_size, unauthorized, too_large, rate_limited]

  /uploads/{upload_id}/complete:
    post:
      operationId: completeUpload
      tags: [Uploads]
      summary: Complete an upload
      description: |
        Finalizes an upload after the file was POSTed to the presigned `url`. The file is
        checked (its real size, and executables are refused) and becomes `ready` to attach.
        Calling it again on a ready upload returns the same result.
      parameters:
        - name: upload_id
          in: path
          required: true
          description: The `upload_id` from `POST /uploads` (`up_…`).
          schema:
            type: string
          example: up_0a1b2c3d4e5f60718293a4b5c6d7e8f9
      responses:
        '200':
          description: The upload is ready to attach.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Upload'
              example: {"object": "upload", "upload_id": "up_0a1b2c3d4e5f60718293a4b5c6d7e8f9", "status": "ready", "filename": "product-shoot.mov", "content_type": "video/quicktime", "size": 157286400}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/TooLarge'
        '422':
          $ref: '#/components/responses/Unprocessable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [unauthorized, not_found, not_pending, not_uploaded, changed, too_large, executable_refused, rate_limited]

  /work:
    get:
      operationId: listWork
      tags: [Work]
      summary: List recent work
      description: |
        Your agent's recent work (chats), newest first: the same list you see in the app,
        whether the work was started over the API, in the app, by phone or anywhere else.
      parameters:
        - name: limit
          in: query
          required: false
          description: How many to return (1 to 50).
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 20
          example: 10
      responses:
        '200':
          description: Recent work.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkList'
              example: {"object": "list", "data": [{"object": "work", "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "title": "Candle shop vertical ad", "preview": "Your 15 second ad is ready.", "link": "https://supercool.com/chat/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f"}]}
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [invalid_request, unauthorized, rate_limited]

  /work/{work_id}:
    get:
      operationId: getWork
      tags: [Work]
      summary: Get a piece of work
      description: |
        One piece of work: whether it's running, the latest result text (paged with
        `from_char` / `next_from_char`) and its most recent files (up to 8), each with a
        fresh download link.
      parameters:
        - name: work_id
          in: path
          required: true
          description: The `work_id` from a job, an event or `GET /work`.
          schema:
            type: string
          example: 5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f
        - name: from_char
          in: query
          required: false
          description: Read the text from this character on (use the previous `next_from_char`).
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: The work.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Work'
              example: {"object": "work", "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "title": "Candle shop vertical ad", "status": "idle", "text": "Your 15 second ad is ready. I used warm, slow push-ins on the candles...", "next_from_char": null, "link": "https://supercool.com/chat/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "files": []}
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [invalid_request, unauthorized, not_found, rate_limited]

  /files/{file_id}:
    get:
      operationId: getFile
      tags: [Files]
      summary: Get a file
      description: |
        A file's record with a fresh signed download link. Use it when a `url` has expired,
        or when a download answered `412` because the file changed since the link was made.
        File ids contain slashes (`<work_id>/<path>`); put them in the path as they are.
      parameters:
        - name: file_id
          in: path
          required: true
          description: The `file_id` from a message, job, event or work (`<work_id>/<path>`, slashes included).
          schema:
            type: string
          example: 5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4
      responses:
        '200':
          description: The file.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileObject'
              example: {"object": "file", "file_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f/candle_ad.mp4", "name": "candle_ad.mp4", "content_type": "video/mp4", "kind": "video", "size": 4812345, "url": "https://api.supercool.sh/api/v1/files/s/eyJmIjoiNWQwYzNl...", "revision": "\"5b1c0e6f9d2a4b7c8e1f3a5d7c9b0e2f\"", "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "label": null}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [unauthorized, not_found, rate_limited]

  /me:
    get:
      operationId: getAccount
      tags: [Account]
      summary: Get the account
      description: The account the API key belongs to, its plan, available credits, the agent's name, the key itself and your limits.
      responses:
        '200':
          description: The account.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Account'
              example: {"object": "account", "user_id": "64f1c2a9e4b0a1b2c3d4e5f6", "email": "you@example.com", "name": "Sam Rivera", "plan": "pro", "credits": 1840, "agent": {"name": "Nova"}, "key": {"id": "pat:6a1b2c3d4e5f", "name": "prod server"}, "limits": {"running_jobs": 5, "requests_per_minute": 600}}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [unauthorized, rate_limited]

  /webhooks:
    get:
      operationId: listWebhooks
      tags: [Webhooks]
      summary: List webhook endpoints
      description: The account's webhook endpoints (up to 10).
      responses:
        '200':
          description: The endpoints.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpointList'
              example: {"object": "list", "data": [{"object": "webhook_endpoint", "id": "whe_4c1d2e3f4a5b6c7d8e9f", "url": "https://example.com/hooks/supercool", "events": ["*"], "description": "prod", "created_at": "2026-09-30T13:40:00.000000Z", "disabled_at": null, "disabled_reason": null, "failing_since": null}]}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [unauthorized, rate_limited]
    post:
      operationId: createWebhook
      tags: [Webhooks]
      summary: Create a webhook endpoint
      description: |
        Registers a public `https` URL (port 443 or 8443) to receive events. The response
        includes the endpoint's signing `secret` (`whsec_…`). It is shown only this once:
        store it now. Omit `events` (or pass `["*"]`) to receive every event.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookCreate'
            example: {"url": "https://example.com/hooks/supercool", "events": ["message.completed", "message.failed", "message.blocked"], "description": "prod"}
      responses:
        '201':
          description: The endpoint, with its secret (shown once).
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpointWithSecret'
              example: {"object": "webhook_endpoint", "id": "whe_4c1d2e3f4a5b6c7d8e9f", "url": "https://example.com/hooks/supercool", "events": ["message.completed", "message.failed", "message.blocked"], "description": "prod", "created_at": "2026-09-30T13:40:00.000000Z", "disabled_at": null, "disabled_reason": null, "failing_since": null, "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"}
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [invalid_request, bad_url, bad_events, bad_request, unauthorized, too_many_endpoints, rate_limited]

  /webhooks/{endpoint_id}:
    get:
      operationId: getWebhook
      tags: [Webhooks]
      summary: Get a webhook endpoint
      description: One endpoint and its 20 most recent deliveries.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      responses:
        '200':
          description: The endpoint.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpointDetail'
              example: {"object": "webhook_endpoint", "id": "whe_4c1d2e3f4a5b6c7d8e9f", "url": "https://example.com/hooks/supercool", "events": ["*"], "description": "prod", "created_at": "2026-09-30T13:40:00.000000Z", "disabled_at": null, "disabled_reason": null, "failing_since": null, "recent_deliveries": [{"object": "webhook_delivery", "id": "evt_7d8e9f0a1b2c3d4e5f6a7b8c", "event": "message.completed", "status": "delivered", "attempts": 1, "last_status_code": 200, "last_error": null, "last_response": "ok", "last_ms": 184, "created_at": "2026-09-30T14:06:41.000000Z", "delivered_at": "2026-09-30T14:06:41.000000Z", "next_attempt_at": "2026-09-30T14:06:41.000000Z", "message_id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d"}]}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [unauthorized, not_found, rate_limited]
    patch:
      operationId: updateWebhook
      tags: [Webhooks]
      summary: Update a webhook endpoint
      description: |
        Changes an endpoint's URL, events or description, or turns it off and on. Send only
        the fields to change. `"enabled": true` turns a disabled endpoint back on (after you
        fixed it) and clears its failure state.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookUpdate'
            example: {"enabled": true}
      responses:
        '200':
          description: The updated endpoint.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpoint'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [invalid_request, bad_url, bad_events, bad_request, unauthorized, not_found, rate_limited]
    delete:
      operationId: deleteWebhook
      tags: [Webhooks]
      summary: Delete a webhook endpoint
      description: Deletes the endpoint. Deliveries still waiting to be sent to it are cancelled.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      responses:
        '200':
          description: Deleted.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Deleted'
              example: {"object": "webhook_endpoint", "id": "whe_4c1d2e3f4a5b6c7d8e9f", "deleted": true}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [unauthorized, not_found, rate_limited]

  /webhooks/{endpoint_id}/rotate-secret:
    post:
      operationId: rotateWebhookSecret
      tags: [Webhooks]
      summary: Rotate a signing secret
      description: |
        Replaces the endpoint's signing secret and returns the new one (shown once). Every
        delivery from now on is signed with the new secret, including retries of earlier
        events.
      parameters:
        - $ref: '#/components/parameters/EndpointId'
      responses:
        '200':
          description: The new secret.
          headers:
            Request-Id: {$ref: '#/components/headers/Request-Id'}
            RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
            RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
            RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RotatedSecret'
              example: {"object": "webhook_endpoint", "id": "whe_4c1d2e3f4a5b6c7d8e9f", "secret": "whsec_Q2hhbmdlZC1zZWNyZXQtZXhhbXBsZTEy"}
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      x-errors: [unauthorized, not_found, rate_limited]

webhooks:
  job-event:
    post:
      operationId: jobEvent
      summary: A job changed
      description: |
        Sent when one job (one piece of work) changes: `job.completed`, `job.failed`,
        `job.blocked`, `job.resumed` or `job.expired`. `data` is the job, with its
        `message_id` and files. Signed with the Standard Webhooks headers.
      parameters:
        - $ref: '#/components/parameters/WebhookIdHeader'
        - $ref: '#/components/parameters/WebhookTimestampHeader'
        - $ref: '#/components/parameters/WebhookSignatureHeader'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JobEvent'
            example: {"type": "job.completed", "timestamp": "2026-09-30T14:06:40.310000Z", "data": {"object": "job", "message_id": "msg_3f9a1c2e7b8d4e5f6a7b8c9d", "job_id": "exe_9b2c4d6e8f0a1b3c5d7e9f1a2b4c6d8e", "kind": "execution", "role": "execution", "work_id": "5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "title": "Candle shop vertical ad", "status": "completed", "reason": null, "final": true, "queued": null, "resume_by": null, "link": "https://supercool.com/chat/5d0c3e2a-8f1b-4c6d-9e7f-1a2b3c4d5e6f", "started_at": "2026-09-30T14:02:11.402000Z", "settled_at": "2026-09-30T14:06:40.221000Z", "files": []}}
      responses:
        '200':
          description: Any 2xx within 10 seconds counts as delivered. Anything else is retried.
  message-event:
    post:
      operationId: messageEvent
      summary: A message reached a status
      description: |
        Sent once each time a message reaches a status other than `processing`:
        `message.completed`, `message.partial`, `message.failed`, `message.needs_input`,
        `message.blocked` or `message.expired`. `data` is the full message, exactly as
        `GET /messages/{message_id}` returns it.
      parameters:
        - $ref: '#/components/parameters/WebhookIdHeader'
        - $ref: '#/components/parameters/WebhookTimestampHeader'
        - $ref: '#/components/parameters/WebhookSignatureHeader'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageEvent'
      responses:
        '200':
          description: Any 2xx within 10 seconds counts as delivered. Anything else is retried.

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: sc_key_…
      description: |
        An API key from the dashboard (https://supercool.com/dashboard#api), sent as
        `Authorization: Bearer sc_key_…`. Keys are server-side secrets.

  headers:
    Request-Id:
      description: This request's id (`req_…`). Find it in the dashboard's request inspector.
      schema:
        type: string
        example: req_6c2f0e1d9a8b7c6d5e4f3a2b
    RateLimit-Limit:
      description: Requests this API key may make per minute (600).
      schema:
        type: integer
    RateLimit-Remaining:
      description: Requests left in the current window.
      schema:
        type: integer
    RateLimit-Reset:
      description: Seconds until the oldest counted request leaves the window.
      schema:
        type: integer
    Retry-After:
      description: 'On every `429`: seconds to wait before retrying. Exact for the per-key budget; `60` for account-level limits (messages, waits, work reads).'
      schema:
        type: integer

  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        Any string up to 128 characters, unique per logical request. Scoped to the API key and
        kept for 7 days: sending it again with the same body returns the same message (`200`)
        and never starts work twice; with a different body it's `409 idempotency_conflict`.
      schema:
        type: string
        maxLength: 128
      example: order-8812-ad
    MessageId:
      name: message_id
      in: path
      required: true
      description: The message's `id` (`msg_…`).
      schema:
        type: string
      example: msg_3f9a1c2e7b8d4e5f6a7b8c9d
    EndpointId:
      name: endpoint_id
      in: path
      required: true
      description: The webhook endpoint's `id` (`whe_…`).
      schema:
        type: string
      example: whe_4c1d2e3f4a5b6c7d8e9f
    WebhookIdHeader:
      name: webhook-id
      in: header
      required: true
      description: The delivery's id (`evt_…`). The same across retries of one event; use it to drop duplicates.
      schema:
        type: string
    WebhookTimestampHeader:
      name: webhook-timestamp
      in: header
      required: true
      description: Unix seconds when this attempt was signed.
      schema:
        type: string
    WebhookSignatureHeader:
      name: webhook-signature
      in: header
      required: true
      description: '`v1,` + base64 HMAC-SHA256 of `{webhook-id}.{webhook-timestamp}.{body}`, keyed with the base64-decoded part of your `whsec_` secret.'
      schema:
        type: string

  responses:
    BadRequest:
      description: The request can't be processed as sent. `invalid_request` (with `problems`) when it doesn't match the schema.
      headers:
        Request-Id: {$ref: '#/components/headers/Request-Id'}
        RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
        RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
        RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example: {"error": "invalid_request", "message": "The request doesn't match what this endpoint expects; see `problems`.", "request_id": "req_6c2f0e1d9a8b7c6d5e4f3a2b", "docs": "https://supercool.com/docs/api/errors#invalid_request", "problems": [{"field": "files", "problem": "Input should be a valid list"}]}
    Unauthorized:
      description: The API key is missing, unknown or revoked.
      headers:
        Request-Id:
          $ref: '#/components/headers/Request-Id'
        WWW-Authenticate:
          description: Always `Bearer`.
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example: {"error": "unauthorized", "message": "unknown or revoked token. Send your API key as 'Authorization: Bearer sc_key_…'.", "request_id": "req_6c2f0e1d9a8b7c6d5e4f3a2b", "docs": "https://supercool.com/docs/api/errors#unauthorized"}
    NotFound:
      description: Nothing with that id for this API key or account.
      headers:
        Request-Id: {$ref: '#/components/headers/Request-Id'}
        RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
        RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
        RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example: {"error": "not_found", "message": "No message with that id for this API key.", "request_id": "req_6c2f0e1d9a8b7c6d5e4f3a2b", "docs": "https://supercool.com/docs/api/errors#not_found"}
    Conflict:
      description: The request conflicts with the current state.
      headers:
        Request-Id: {$ref: '#/components/headers/Request-Id'}
        RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
        RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
        RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example: {"error": "idempotency_conflict", "message": "That Idempotency-Key was already used for a different message. Use a new key for a new request.", "request_id": "req_6c2f0e1d9a8b7c6d5e4f3a2b", "docs": "https://supercool.com/docs/api/errors#idempotency_conflict"}
    TooLarge:
      description: A file or upload is over the size limit.
      headers:
        Request-Id: {$ref: '#/components/headers/Request-Id'}
        RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
        RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
        RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example: {"error": "too_large", "message": "Files can be up to 500 MB.", "request_id": "req_6c2f0e1d9a8b7c6d5e4f3a2b", "docs": "https://supercool.com/docs/api/errors#too_large"}
    Unprocessable:
      description: The uploaded file was refused (an executable).
      headers:
        Request-Id: {$ref: '#/components/headers/Request-Id'}
        RateLimit-Limit: {$ref: '#/components/headers/RateLimit-Limit'}
        RateLimit-Remaining: {$ref: '#/components/headers/RateLimit-Remaining'}
        RateLimit-Reset: {$ref: '#/components/headers/RateLimit-Reset'}
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example: {"error": "executable_refused", "message": "Executable files can't be attached.", "request_id": "req_6c2f0e1d9a8b7c6d5e4f3a2b", "docs": "https://supercool.com/docs/api/errors#executable_refused"}
    TooManyRequests:
      description: A rate limit was hit. Wait `Retry-After` seconds.
      headers:
        Request-Id:
          $ref: '#/components/headers/Request-Id'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimit-Limit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimit-Remaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimit-Reset'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example: {"error": "rate_limited", "message": "Too many requests for this API key; slow down and retry after the Retry-After seconds.", "request_id": "req_6c2f0e1d9a8b7c6d5e4f3a2b", "docs": "https://supercool.com/docs/api/errors#rate_limited"}

  schemas:
    MessageCreate:
      type: object
      description: 'A message to your agent. Send `message`, files, or both.'
      properties:
        message:
          type: string
          maxLength: 20000
          default: ''
          description: What you want, in plain language. Up to 20,000 characters; send longer text as a file.
        files:
          type: array
          description: 'Small files inline (up to 5, 10 MB each, 25 MB in total). Each has a `url` or `base64`. A file that can''t be fetched doesn''t fail the message: it''s listed in the message''s `notes`.'
          items:
            $ref: '#/components/schemas/InlineFile'
        upload_ids:
          type: array
          description: Finished uploads to attach (up to 10, 1 GB in total). See `POST /uploads`.
          items:
            type: string
        webhook_endpoint_id:
          type: [string, 'null']
          description: Send this message's webhook events to this registered endpoint (`whe_…`) instead of the account's endpoints.
    InlineFile:
      type: object
      properties:
        name:
          type: [string, 'null']
          description: A file name for the agent to see.
        url:
          type: [string, 'null']
          description: A public `https` URL to fetch the file from.
        base64:
          type: [string, 'null']
          description: The file's bytes, base64-encoded.
        content_type:
          type: [string, 'null']
          description: The MIME type, for `base64` files.
    Message:
      type: object
      description: A message you sent and everything that came of it.
      required: [id, object, status, final, jobs, files, notes, credits_used, created_at, updated_at]
      properties:
        id:
          type: string
          description: The message id (`msg_…`), always generated by SuperCool.
        object:
          const: message
        status:
          $ref: '#/components/schemas/MessageStatus'
        reason:
          type: [string, 'null']
          description: 'Why, for `blocked`, `failed`, `partial` and `expired`: `out_of_credits`, `hosting_fees`, `too_many_running`, `queue_expired`, `watch_expired`, `result_unknown`, `execution_failed`, `stopped`, `agent_busy`, `turn_failed` and others. See [Getting results](/docs/api/results#reasons).'
        question:
          type: [string, 'null']
          description: For `needs_input`, the agent's question. Answer by sending a new message.
        final:
          type: boolean
          description: '`true` when nothing about this message will change any more. Stop polling.'
        reply:
          type: [string, 'null']
          description: What the agent said, once it has answered.
        jobs:
          type: array
          description: The pieces of work this message started, each with its own status.
          items:
            $ref: '#/components/schemas/Job'
        files:
          type: array
          description: Every file the message produced (handed over in the reply, or made by its jobs).
          items:
            $ref: '#/components/schemas/File'
        notes:
          type: array
          description: Problems with attachments (a file that couldn't be fetched, files over the limit).
          items:
            type: string
        credits_used:
          type: number
          description: Credits this message's jobs used so far.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
          description: When the agent's turn ended (or `created_at` while it's still going).
        resume:
          const: add_credits
          description: 'Present when queued work is waiting for credits: add credits before `resume_by` and it runs by itself. Don''t resend the message.'
        resume_by:
          type: string
          format: date-time
          description: The earliest deadline among queued jobs waiting for credits (24 hours after they were queued).
        retry_after_seconds:
          type: integer
          description: 'With `reason: "agent_busy"`: retry with the same `Idempotency-Key` after this many seconds.'
        running:
          type: array
          description: 'Present when the agent tried to start work while 5 jobs were already running on the account (a job with `reason: "too_many_running"`): the work running now, and the message that started each.'
          items:
            type: object
            properties:
              job_id:
                type: string
                description: '`exe_…`'
              message_id:
                type: string
              work_id:
                type: string
    MessageStatus:
      type: string
      description: The message's status, aggregated over its jobs.
      enum: [processing, needs_input, blocked, completed, partial, failed, expired]
    Job:
      type: object
      description: One piece of work a message started (or was refused).
      properties:
        job_id:
          type: string
          description: '`exe_…` for work that ran or is queued; `ref_…` for work refused before it was queued.'
        kind:
          type: string
          enum: [execution, refusal]
        role:
          type: string
          enum: [execution, follow_up]
          description: '`follow_up` when the message joined work that was already running; its status follows that work.'
        work_id:
          type: [string, 'null']
          description: The chat the work runs in.
        title:
          type: [string, 'null']
        status:
          $ref: '#/components/schemas/JobStatus'
        reason:
          type: [string, 'null']
          description: 'For `blocked`, `failed`, `cancelled` and `expired`: `out_of_credits`, `hosting_fees`, `too_many_running`, `execution_failed`, `stopped`, `queue_expired`, `watch_expired`, `result_unknown`.'
        final:
          type: boolean
          description: '`false` while running, and for queued work waiting for credits (it can still run).'
        queued:
          type: [boolean, 'null']
          description: '`true` for work that is queued and waiting for credits; otherwise `null`.'
        resume_by:
          type: [string, 'null']
          format: date-time
          description: For queued work waiting for credits, the deadline to add them (24 hours after it was queued).
        link:
          type: [string, 'null']
          description: The work in the SuperCool app.
        started_at:
          type: [string, 'null']
          format: date-time
        settled_at:
          type: [string, 'null']
          format: date-time
        files:
          type: array
          items:
            $ref: '#/components/schemas/File'
    JobStatus:
      type: string
      enum: [running, completed, failed, cancelled, blocked, expired]
    File:
      type: object
      description: A file the agent made or handed over.
      properties:
        file_id:
          type: string
          description: '`<work_id>/<path>`. Use it with `GET /files/{file_id}` for a fresh link.'
        name:
          type: string
        content_type:
          type: [string, 'null']
        kind:
          type: string
          enum: [image, video, audio, doc, site]
        size:
          type: [integer, 'null']
          description: Bytes. `null` for a site.
        url:
          type: [string, 'null']
          description: A signed download link. It expires, and it is pinned to `revision`; a download answers `412` if the file changed since.
        revision:
          type: [string, 'null']
          description: The file's version the link is pinned to. `null` for a site (its link always opens the live site).
        work_id:
          type: [string, 'null']
        label:
          type: [string, 'null']
    FileObject:
      allOf:
        - type: object
          properties:
            object:
              const: file
        - $ref: '#/components/schemas/File'
    EventList:
      type: object
      properties:
        object:
          const: list
        data:
          type: array
          items:
            $ref: '#/components/schemas/Event'
        has_more:
          type: boolean
          description: More entries are ready; call again with `cursor` right away.
        done:
          type: boolean
          description: The message has nothing more to report.
        cursor:
          type: string
          description: Pass this back as `cursor` for the next page.
        cursor_expired:
          type: boolean
          description: Your cursor pointed at entries older than 7 days. `cursor` restarts from now.
    Event:
      type: object
      description: One entry in a message's update log. Fields beyond the common ones depend on `type`.
      properties:
        seq:
          type: integer
        type:
          type: string
          enum: [result, progress, agent_reply, state, turn_failed, turn_busy]
          description: '`result` (a job finished, with text and files), `progress` (a step started or a file landed), `agent_reply` (the agent''s reply), `state` (`resumed`, `stopped`, `expired`, `unknown`), `turn_failed`, `turn_busy`.'
        work_id:
          type: [string, 'null']
        message_id:
          type: [string, 'null']
          description: The message the entry belongs to, when known.
        at:
          type: string
          format: date-time
        job_id:
          type: string
          description: The job (`exe_…`) the entry belongs to, when known.
        title:
          type: [string, 'null']
          description: '`result`'
        text:
          type: [string, 'null']
          description: '`result`, `agent_reply`, `state`, `turn_failed`, `turn_busy`'
        outcome:
          type: string
          enum: [completed, failed, stalled_credits]
          description: '`result`'
        files:
          type: array
          description: '`result`, `agent_reply`'
          items:
            $ref: '#/components/schemas/File'
        state:
          type: string
          description: '`state`'
        step:
          type: [string, 'null']
          description: '`progress`'
        status:
          type: [string, 'null']
          description: '`progress`'
        file_names:
          type: array
          description: '`progress`: names of files that just landed (download them from the `result`).'
          items:
            type: string
        delivered_sync:
          type: [boolean, 'null']
          description: '`agent_reply`: `true` when this reply was already in the `POST /messages` response.'
        reason:
          type: [string, 'null']
          description: '`turn_failed`, `turn_busy`'
    Recovery:
      type: object
      properties:
        id:
          type: string
        object:
          const: recovery
        work:
          type: array
          items:
            type: object
            properties:
              work_id:
                type: string
              state:
                type: string
                enum: [settled, watching, recovering, exhausted]
              outcome:
                type: string
                description: For `settled`, how it ended.
    UploadCreate:
      type: object
      required: [filename, size]
      properties:
        filename:
          type: string
        size:
          type: integer
          description: The exact size in bytes (up to 500 MB). The upload is capped at this size.
        content_type:
          type: [string, 'null']
          description: The MIME type. Guessed from the file name when omitted. The POST must send the same `Content-Type` field (it is in `fields`).
    UploadCreated:
      type: object
      properties:
        object:
          const: upload
        upload_id:
          type: string
        url:
          type: string
          description: Where to POST the file (`multipart/form-data`).
        fields:
          type: object
          description: Form fields to send with the file, exactly as given.
          additionalProperties:
            type: string
        expires_at:
          type: string
          format: date-time
          description: The presigned POST works until then (15 minutes).
        max_bytes:
          type: integer
    Upload:
      type: object
      properties:
        object:
          const: upload
        upload_id:
          type: string
        status:
          type: string
          enum: [ready]
        filename:
          type: string
        content_type:
          type: string
        size:
          type: integer
    WorkList:
      type: object
      properties:
        object:
          const: list
        data:
          type: array
          items:
            $ref: '#/components/schemas/WorkSummary'
    WorkSummary:
      type: object
      properties:
        object:
          const: work
        work_id:
          type: string
        title:
          type: [string, 'null']
        preview:
          type: [string, 'null']
        link:
          type: string
    Work:
      type: object
      properties:
        object:
          const: work
        work_id:
          type: string
        title:
          type: [string, 'null']
        status:
          type: string
          enum: [running, stalled_credits, idle]
          description: '`running`, `stalled_credits` (stopped until credits are added), or `idle`.'
        text:
          type: string
          description: The latest result text, from `from_char`.
        next_from_char:
          type: [integer, 'null']
          description: Pass as `from_char` to read on; `null` when there's no more.
        link:
          type: string
        files:
          type: array
          description: The most recent files (up to 8).
          items:
            $ref: '#/components/schemas/File'
    Account:
      type: object
      properties:
        object:
          const: account
        user_id:
          type: string
        email:
          type: [string, 'null']
        name:
          type: [string, 'null']
        plan:
          type: [string, 'null']
        credits:
          type: [number, 'null']
          description: Credits available now.
        agent:
          type: object
          properties:
            name:
              type: string
        key:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
        limits:
          type: object
          properties:
            running_jobs:
              type: integer
              description: Running jobs allowed per account (5).
            requests_per_minute:
              type: integer
              description: Requests allowed per API key per minute (600).
    WebhookEventType:
      type: string
      enum: [job.completed, job.failed, job.blocked, job.resumed, job.expired, message.completed, message.partial, message.failed, message.needs_input, message.blocked, message.expired]
    WebhookCreate:
      type: object
      required: [url]
      properties:
        url:
          type: string
          description: A public `https` URL (port 443 or 8443, up to 2,000 characters).
        events:
          type: [array, 'null']
          description: Events to receive, or `["*"]` for all (the default).
          items:
            type: string
        description:
          type: [string, 'null']
          maxLength: 120
    WebhookUpdate:
      type: object
      properties:
        url:
          type: [string, 'null']
        events:
          type: [array, 'null']
          items:
            type: string
        description:
          type: [string, 'null']
        enabled:
          type: [boolean, 'null']
          description: '`true` turns the endpoint back on and clears its failure state; `false` turns it off.'
    WebhookEndpoint:
      type: object
      properties:
        object:
          const: webhook_endpoint
        id:
          type: string
          description: '`whe_…`'
        url:
          type: string
        events:
          type: array
          items:
            type: string
        description:
          type: [string, 'null']
        created_at:
          type: string
          format: date-time
        disabled_at:
          type: [string, 'null']
          format: date-time
          description: Set when the endpoint is off (by you, or after 3 days of failed deliveries).
        disabled_reason:
          type: [string, 'null']
        failing_since:
          type: [string, 'null']
          format: date-time
          description: When deliveries started failing. Cleared by the next success.
    WebhookEndpointWithSecret:
      allOf:
        - $ref: '#/components/schemas/WebhookEndpoint'
        - type: object
          properties:
            secret:
              type: string
              description: The signing secret (`whsec_…`). Shown only in this response.
    WebhookEndpointDetail:
      allOf:
        - $ref: '#/components/schemas/WebhookEndpoint'
        - type: object
          properties:
            recent_deliveries:
              type: array
              items:
                $ref: '#/components/schemas/WebhookDelivery'
    WebhookEndpointList:
      type: object
      properties:
        object:
          const: list
        data:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEndpoint'
    WebhookDelivery:
      type: object
      properties:
        object:
          const: webhook_delivery
        id:
          type: string
          description: The delivery id (`evt_…`), sent as `webhook-id`.
        event:
          $ref: '#/components/schemas/WebhookEventType'
        status:
          type: string
          enum: [pending, delivered, cancelled, dead]
        attempts:
          type: integer
        last_status_code:
          type: [integer, 'null']
        last_error:
          type: [string, 'null']
        last_response:
          type: [string, 'null']
          description: The first 4 KB of your endpoint's last response.
        last_ms:
          type: [integer, 'null']
        created_at:
          type: string
          format: date-time
        delivered_at:
          type: [string, 'null']
          format: date-time
        next_attempt_at:
          type: [string, 'null']
          format: date-time
        message_id:
          type: [string, 'null']
    RotatedSecret:
      type: object
      properties:
        object:
          const: webhook_endpoint
        id:
          type: string
        secret:
          type: string
    Deleted:
      type: object
      properties:
        object:
          const: webhook_endpoint
        id:
          type: string
        deleted:
          const: true
    JobEvent:
      type: object
      properties:
        type:
          type: string
          enum: [job.completed, job.failed, job.blocked, job.resumed, job.expired]
        timestamp:
          type: string
          format: date-time
        data:
          allOf:
            - type: object
              properties:
                object:
                  const: job
                message_id:
                  type: string
            - $ref: '#/components/schemas/Job'
    MessageEvent:
      type: object
      properties:
        type:
          type: string
          enum: [message.completed, message.partial, message.failed, message.needs_input, message.blocked, message.expired]
        timestamp:
          type: string
          format: date-time
        data:
          $ref: '#/components/schemas/Message'
    Error:
      type: object
      description: Every API error has this shape.
      required: [error, message, request_id, docs]
      properties:
        error:
          $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
          description: What went wrong, for people.
        request_id:
          type: string
          description: The request's id (`req_…`), also in the `Request-Id` header.
        docs:
          type: string
          description: A link to this error code's documentation.
        problems:
          type: array
          description: 'With `invalid_request`: what doesn''t match, one entry per problem (up to 10).'
          items:
            type: object
            properties:
              field:
                type: [string, 'null']
                description: 'The dotted location of the field (like `files.0.url` or `wait`), or `null` for the body as a whole.'
              problem:
                type: string
        cursor:
          type: string
          description: 'With `poll_in_progress`: the cursor to use when you call again.'
    ErrorCode:
      type: string
      description: A stable, machine-readable error code. Each one is documented at `https://supercool.com/docs/api/errors#<code>`.
      enum: [bad_request, invalid_request, empty_message, too_long, bad_cursor, bad_url, bad_events, bad_size, too_many, unauthorized, not_found, idempotency_conflict, poll_in_progress, too_many_endpoints, not_pending, not_uploaded, changed, upload_not_ready, too_large, executable_refused, rate_limited, internal, unavailable]
      x-http-status: {bad_request: 400, invalid_request: 400, empty_message: 400, too_long: 400, bad_cursor: 400, bad_url: 400, bad_events: 400, bad_size: 400, too_many: 400, unauthorized: 401, not_found: 404, idempotency_conflict: 409, poll_in_progress: 409, too_many_endpoints: 409, not_pending: 409, not_uploaded: 409, changed: 409, upload_not_ready: 409, too_large: 413, executable_refused: 422, rate_limited: 429, internal: 500, unavailable: 503}
