openapi: 3.1.0
info:
  title: Spine API
  version: "1.0.0"
  summary: The highest-quality research API — multi-agent canvas orchestration.
  description: |
    The Spine API delivers the highest-quality research available, ranked #1 on
    GAIA Level 3. Submit a prompt (with optional files, a template, or an
    explicit block configuration) to kick off an async multi-agent canvas run,
    and poll for the generated artifacts, DAG, and task tree.

    All endpoints are authenticated via the `X-API-KEY` header. Create a key
    from the [Spine developer portal](https://platform.getspine.ai).
  contact:
    name: Spine support
    email: support@getspine.ai
    url: https://platform.getspine.ai
servers:
  - url: https://api.getspine.ai
    description: Production
security:
  - apiKey: []
tags:
  - name: Runs
    description: Create and poll canvas runs.
  - name: Canvas
    description: Inspect the generated canvas DAG and task tree.

paths:
  /v1/run:
    post:
      tags: [Runs]
      operationId: createRun
      summary: Create a canvas run
      description: |
        Submit a prompt to trigger an async canvas run. Agents orchestrate a
        DAG of blocks to satisfy the prompt; poll `GET /v1/run/{run_id}`
        until status becomes `completed`, `partial`, or `failed`.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/CreateRunRequest"
      responses:
        "200":
          description: Run accepted. Poll `poll_url` for status and results.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateRunResponse"
              examples:
                accepted:
                  value:
                    success: true
                    data:
                      run_id: 550e8400-e29b-41d4-a716-446655440000
                      status: running
                      poll_url: /v1/run/550e8400-e29b-41d4-a716-446655440000
                      estimated_duration_ms: 900000
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "500": { $ref: "#/components/responses/InternalError" }
      x-codeSamples:
        - lang: curl
          label: curl
          source: |
            curl -X POST https://api.getspine.ai/v1/run \
              -H "X-API-KEY: sk_spine_..." \
              -F "prompt=Summarize this sales data" \
              -F "template=memo" \
              -F "files=@sales-data.csv;type=text/csv"
        - lang: python
          label: Python
          source: |
            import requests

            response = requests.post(
                "https://api.getspine.ai/v1/run",
                headers={"X-API-KEY": "sk_spine_..."},
                data={"prompt": "Summarize this sales data", "template": "memo"},
                files={"files": ("sales-data.csv", open("sales-data.csv", "rb"), "text/csv")},
            )
            run = response.json()["data"]
            print(run["run_id"])
        - lang: javascript
          label: Node
          source: |
            import fs from "node:fs";

            const form = new FormData();
            form.set("prompt", "Summarize this sales data");
            form.set("template", "memo");
            form.append("files", new Blob([fs.readFileSync("sales-data.csv")]), "sales-data.csv");

            const res = await fetch("https://api.getspine.ai/v1/run", {
              method: "POST",
              headers: { "X-API-KEY": "sk_spine_..." },
              body: form,
            });
            const { data } = await res.json();
            console.log(data.run_id);

  /v1/run/{run_id}:
    get:
      tags: [Runs]
      operationId: getRun
      summary: Get run status and results
      description: |
        Poll the current status of a run. Returns progress while `running` and
        the full result (final output + downloadable artifacts + metadata)
        once `completed`.
      parameters:
        - $ref: "#/components/parameters/RunId"
      responses:
        "200":
          description: Current run state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetRunResponse"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
      x-codeSamples:
        - lang: curl
          label: curl
          source: |
            curl https://api.getspine.ai/v1/run/<run_id> \
              -H "X-API-KEY: sk_spine_..."
        - lang: python
          label: Python
          source: |
            import requests

            r = requests.get(
                f"https://api.getspine.ai/v1/run/{run_id}",
                headers={"X-API-KEY": "sk_spine_..."},
            )
            print(r.json()["data"]["status"])
        - lang: javascript
          label: Node
          source: |
            const res = await fetch(`https://api.getspine.ai/v1/run/${runId}`, {
              headers: { "X-API-KEY": "sk_spine_..." },
            });
            const { data } = await res.json();
            console.log(data.status);

  /v1/canvas/{canvas_id}/dag:
    get:
      tags: [Canvas]
      operationId: getCanvasDag
      summary: Get canvas DAG
      description: Returns the canvas block graph — nodes and edges.
      parameters:
        - $ref: "#/components/parameters/CanvasId"
      responses:
        "200":
          description: Canvas DAG.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetCanvasDagResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
      x-codeSamples:
        - lang: curl
          label: curl
          source: |
            curl https://api.getspine.ai/v1/canvas/<canvas_id>/dag \
              -H "X-API-KEY: sk_spine_..."

  /v1/canvas/{canvas_id}/tasks:
    get:
      tags: [Canvas]
      operationId: getCanvasTasks
      summary: Get task tree
      description: |
        Returns the full task hierarchy for a canvas:
        parent-task → persona-task → tool-call-task.
      parameters:
        - $ref: "#/components/parameters/CanvasId"
      responses:
        "200":
          description: Task tree grouped by completion state.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetTasksResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
      x-codeSamples:
        - lang: curl
          label: curl
          source: |
            curl https://api.getspine.ai/v1/canvas/<canvas_id>/tasks \
              -H "X-API-KEY: sk_spine_..."

  /v1/canvas/{canvas_id}/tasks/{task_id}:
    get:
      tags: [Canvas]
      operationId: getTask
      summary: Get a single task
      description: Returns a single task with its children.
      parameters:
        - $ref: "#/components/parameters/CanvasId"
        - $ref: "#/components/parameters/TaskId"
      responses:
        "200":
          description: Task detail.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetTaskResponse"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
      x-codeSamples:
        - lang: curl
          label: curl
          source: |
            curl https://api.getspine.ai/v1/canvas/<canvas_id>/tasks/<task_id> \
              -H "X-API-KEY: sk_spine_..."

components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-KEY
      description: |
        An API key from the Spine developer portal. Keys are prefixed with
        `sk_spine_` and shown only once at creation. Keep them server-side.

  parameters:
    RunId:
      name: run_id
      in: path
      required: true
      description: The run identifier returned by `POST /v1/run`.
      schema:
        type: string
        format: uuid
    CanvasId:
      name: canvas_id
      in: path
      required: true
      description: Canvas identifier, returned on a completed run.
      schema:
        type: string
        format: uuid
    TaskId:
      name: task_id
      in: path
      required: true
      description: Task identifier from the task tree.
      schema:
        type: string
        format: uuid

  responses:
    BadRequest:
      description: Malformed request (bad template, blocks, or UUID).
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          examples:
            invalidTemplate:
              value:
                success: false
                error: "Invalid template: foo. Valid templates: app, auto, deep_research, excel, landing_page, memo, report, slides"
    Unauthorized:
      description: Missing, invalid, or revoked API key.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          examples:
            missingKey:
              value:
                success: false
                error: "X-API-KEY header required"
    NotFound:
      description: Resource does not exist or belongs to another account.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          examples:
            runNotFound:
              value:
                success: false
                error: "Run not found: 550e8400-e29b-41d4-a716-446655440000"
    InternalError:
      description: Unexpected server error.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }

  schemas:
    Template:
      type: string
      description: Preset that controls which block types agents are allowed to create.
      enum: [auto, deep_research, report, slides, memo, excel, app, landing_page]

    BlockType:
      type: string
      description: One of the 17 block types agents can create.
      enum:
        - prompt-block
        - list-block
        - memo-block
        - document-block
        - excel-block
        - deep-research-block
        - image-block
        - presentation-block
        - app-block
        - table-block
        - prototype-block
        - landing-page-block
        - text-block
        - web-block
        - yt-block
        - web-research-block
        - file-block

    RunStatus:
      type: string
      enum: [running, completed, partial, failed]

    TaskType:
      type: string
      enum: [parent-task, persona-task, tool-call-task]

    CreateRunRequest:
      type: object
      required: [prompt]
      properties:
        prompt:
          type: string
          description: What you want the agents to do.
          example: Create a Q1 performance report with charts
        template:
          allOf:
            - $ref: "#/components/schemas/Template"
          description: |
            Preset block configuration. Ignored when `blocks` is provided.
            Defaults to `auto`.
        blocks:
          type: string
          description: |
            JSON-encoded array of block types to override the template, e.g.
            `["document-block","table-block","image-block"]`. Precedence:
            `blocks` > `template` > `auto`.
          example: '["document-block","table-block","web-block"]'
        agent_instructions:
          type: string
          description: |
            Behavioral guidance applied across all agent layers (tone, rigor,
            audience, citation requirements, etc.).
          example: Write for a C-suite executive audience. Cite all sources.
        webhook_url:
          type: string
          format: uri
          description: Reserved for future webhook delivery — not yet honored.
        files:
          type: array
          description: |
            Optional file attachments. Images (PNG, JPEG, GIF, WEBP, SVG)
            become image-blocks; documents (PDF, DOCX, XLSX, CSV, TXT, MD,
            YAML, JSON, ZIP) become indexed file-blocks.
          items:
            type: string
            format: binary

    CreateRunResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data:
          type: object
          required: [run_id, status, poll_url]
          properties:
            run_id: { type: string, format: uuid }
            status: { $ref: "#/components/schemas/RunStatus" }
            poll_url:
              type: string
              description: Relative URL to poll for status.
            estimated_duration_ms:
              type: integer
              description: |
                Heuristic duration estimate in milliseconds. Runs typically take
                10–20 minutes; complex research can take up to 2 hours.

    RunProgress:
      type: object
      description: Present while `status` is `running`.
      properties:
        tasks_completed: { type: integer }
        tasks_total: { type: integer }
        elapsed_ms: { type: integer }

    Artifact:
      type: object
      description: A downloadable file produced by a block on this canvas.
      required: [id, type, name, download_url]
      properties:
        id: { type: string, format: uuid }
        type:
          type: string
          description: File extension (docx, xlsx, pptx, html, png, …).
          example: docx
        name:
          type: string
          example: Q1_Report.docx
        download_url:
          type: string
          format: uri
        size_bytes:
          type: integer

    RunResult:
      type: object
      description: Present when `status` is `completed` or `partial`.
      properties:
        final_output:
          type: string
          description: Synthesized answer or agent summary.
        artifacts:
          type: array
          items: { $ref: "#/components/schemas/Artifact" }

    RunMetadata:
      type: object
      description: Present when `status` is `completed`.
      properties:
        template_used: { $ref: "#/components/schemas/Template" }
        models_used:
          type: array
          items: { type: string }
          example: [anthropic/claude-sonnet-4-6]
        credits_consumed:
          type: integer

    Run:
      type: object
      required: [run_id, status]
      properties:
        run_id: { type: string, format: uuid }
        status: { $ref: "#/components/schemas/RunStatus" }
        canvas_id: { type: string, format: uuid }
        progress: { $ref: "#/components/schemas/RunProgress" }
        duration_ms:
          type: integer
          description: Set once the run reaches a terminal state.
        result: { $ref: "#/components/schemas/RunResult" }
        metadata: { $ref: "#/components/schemas/RunMetadata" }
        errors:
          type: array
          items:
            type: object
            properties:
              error: { type: string }

    GetRunResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data: { $ref: "#/components/schemas/Run" }

    Block:
      type: object
      description: A node in the canvas DAG.
      required: [id, name, type, status]
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        type: { $ref: "#/components/schemas/BlockType" }
        status: { type: string, example: completed }
        content:
          type: string
          description: Full text output from the block.
        url:
          type: [string, "null"]
          format: uri
          description: Reference URL for blocks that point at external resources.
        sources:
          type: array
          items:
            type: object

    Edge:
      type: object
      required: [source_id, target_id]
      properties:
        source_id: { type: string, format: uuid }
        target_id: { type: string, format: uuid }

    Canvas:
      type: object
      required: [canvas_id, nodes, edges]
      properties:
        canvas_id: { type: string, format: uuid }
        canvas_name: { type: string }
        nodes:
          type: array
          items: { $ref: "#/components/schemas/Block" }
        edges:
          type: array
          items: { $ref: "#/components/schemas/Edge" }

    GetCanvasDagResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data: { $ref: "#/components/schemas/Canvas" }

    Task:
      type: object
      required: [task_id, name, task_type, status]
      properties:
        task_id: { type: string, format: uuid }
        name: { type: string }
        task_type: { $ref: "#/components/schemas/TaskType" }
        status: { type: string, example: completed }
        children:
          type: array
          items: { $ref: "#/components/schemas/Task" }

    GetTasksResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data:
          type: object
          required: [canvas_id, completed, not_completed]
          properties:
            canvas_id: { type: string, format: uuid }
            completed:
              type: array
              items: { $ref: "#/components/schemas/Task" }
            not_completed:
              type: array
              items: { $ref: "#/components/schemas/Task" }

    GetTaskResponse:
      type: object
      required: [success, data]
      properties:
        success: { type: boolean, const: true }
        data: { $ref: "#/components/schemas/Task" }

    Error:
      type: object
      required: [success, error]
      properties:
        success: { type: boolean, const: false }
        error:
          type: string
          description: Human-readable error message.
