openapi: 3.1.0
info:
  title: Autosheet API
  description: |
    Run the Autosheet Sheets Agent programmatically — trigger AI-driven edits, lookups and transformations across Google Sheets from your own code, workflow tool, or agent.

    ## Getting Started

    To start using the Autosheet API, follow these steps:

    1. **Create an account** — Sign up with Google via the [dashboard](https://dashboard.gptforwork.com).
    2. **Connect your Google account** — Navigate to the **Autosheet API** page in the dashboard and authorize Google Sheets access through the OAuth flow. This allows the agent to read and write spreadsheets on your behalf.
    3. **Generate an API key** — On the same **Autosheet API** page, create a new API key. Give it a descriptive name (e.g. "n8n integration"). The full key is shown **only once** — copy it immediately.
    4. **Make your first request** — Use the key in the `X-API-Key` header to [run an agent](#tag/agents/post/v1/{platform}/agents). You can try the endpoint directly from the panel on the right — no local setup needed.
    5. **Wait for the run to complete** — agents run asynchronously. Either [poll the agent](#tag/agents/get/v1/{platform}/agents/{id}) until its status is `idle` and the turn status is `completed`, or provide a `webhook_url` when you start the run to be notified when it finishes.

    ## Webhooks

    Instead of polling, provide a `webhook_url` when [running an agent](#tag/agents/post/v1/{platform}/agents) to receive a `POST` callback once the turn finishes.

    **Payload** — JSON body with `agent_id`, `turn_id`, `status` (`completed`, `error`, `interrupted`, or `cancelled`), and `error_message` (populated when `status` is `error` or `interrupted`, otherwise `null`).

    **Signature verification** — each request carries `X-Webhook-Signature: sha256=<hex>`, an HMAC-SHA256 of the **raw request body** keyed with your webhook secret. Fetch the secret from the [Verify API Key](#tag/authentication/get/v1/auth/verify) response (`webhook_secret` field), re-compute the HMAC over the raw body, and compare using a constant-time check (`crypto.timingSafeEqual` in Node, `hmac.compare_digest` in Python). Reject the request on mismatch.

    **Delivery** — respond with any `2xx` status to acknowledge. Non-`2xx` responses are not retried.

  version: 1.0.0
  contact:
    name: GPT for Work Support
    url: https://gptforwork.com/support

servers:
  - url: https://api.autosheet.com
    description: Production

security:
  - ApiKeyAuth: []

tags:
  - name: Agents
    description: |
      Create and manage AI agents that operate on spreadsheets.

      **Rate limits** — 500 requests/minute and 100 concurrent agent jobs per API key. Exceeded requests return `429` with `X-RateLimit-*` and `Retry-After` headers (see the `429` response on any operation below).

      **Spending limits** — agent usage consume credits from your space balance. Space- and user-level spending caps can be set in the [dashboard](https://dashboard.gptforwork.com). When a limit is reached, new agent requests will be rejected with a 429 response.
  - name: Authentication
    description: |
      All endpoints require an API key in the `X-API-Key` header. API keys are scoped to your user account and inherit your Google Sheets authorization.

      - Keys can be created, disabled, and deleted from the [dashboard](https://dashboard.gptforwork.com).
      - A disabled or deleted key will immediately return `401`.
      - Use the [Verify API Key](#tag/authentication/get/v1/auth/verify) endpoint to test your key.

paths:
  # ── Authentication ──────────────────────────────────────────────
  /v1/auth/verify:
    get:
      operationId: verifyApiKey
      summary: Verify API key
      description: |
        Validates the provided API key and returns basic account information.
        Use this endpoint to confirm your key is active and your Google account is connected.
      tags:
        - Authentication
      responses:
        '200':
          description: API key is valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifyApiKeyResponse'
              example:
                valid: true
                user:
                  email: "user@example.com"
                space:
                  id: "11223344-5566-7788-99aa-bbccddeeff00"
                  name: "My Workspace"
                google_connected: true
                webhook_secret: "whsec_a1b2c3d4e5f6..."
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ── Agents ──────────────────────────────────────────────────────
  /v1/{platform}/agents:
    post:
      operationId: runAgent
      summary: Run agent
      description: |
        Creates a new agent or adds a follow-up prompt to an existing one.

        **New agent** — provide `spreadsheet_id` and `prompt`. A new agent is created and starts processing automatically.

        **Follow-up** — also provide `agent_id` to continue a previous agent's conversation. The agent must be in `idle` status (all previous turns completed).

        **Webhook** — optionally provide a `webhook_url` to receive a `POST` callback when the job completes or fails.
        Webhook requests are signed with your API key's webhook secret (available via [Verify API Key](#tag/authentication/get/v1/auth/verify)) using the `X-Webhook-Signature: sha256=<hmac>` header.
      tags:
        - Agents
      parameters:
        - $ref: '#/components/parameters/Platform'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RunAgentRequest'
            examples:
              newAgent:
                summary: Create a new agent
                value:
                  spreadsheet_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
                  prompt: "Check each 'email' for validity. Flag issues in 'email_status' column: valid, typo_domain, disposable, invalid_format, empty."
                  sheet_name: "Sheet1"
              withModels:
                summary: New agent with specific models
                value:
                  spreadsheet_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
                  prompt: "Translate all content in column B from French to English"
                  agent_model: "managed:agent:anthropic/claude-opus-4-6"
                  bulk_model: "managed:bulk:openai/gpt-4.1"
                  bulk_search_model: "managed:search:perplexity/sonar"
              followUp:
                summary: Follow-up on an existing agent
                value:
                  agent_id: "1f7b5f98-3906-40f4-b177-8bc2327df6f1"
                  prompt: "For rows where email_status is 'typo_domain', attempt to correct the domain. Put corrected email in 'email_corrected' column."
                  sheet_name: "Sheet1"
              withWebhook:
                summary: New agent with webhook callback
                value:
                  spreadsheet_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
                  prompt: "Translate all content in column B from French to English"
                  webhook_url: "https://example.com/webhooks/gptforwork"
      responses:
        '201':
          description: Agent created or follow-up turn added
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
          links:
            GetAgent:
              operationId: getAgent
              parameters:
                id: $response.body#/id
              description: Use the returned `id` to poll agent status
            StopAgent:
              operationId: stopAgent
              parameters:
                id: $response.body#/id
              description: Use the returned `id` to stop this agent
            FollowUp:
              operationId: runAgent
              description: Use the returned `id` as `agent_id` to send a follow-up prompt
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/GoogleAuthError'
        '404':
          $ref: '#/components/responses/SpreadsheetNotFound'
        '409':
          description: Agent is still processing a previous turn (follow-up only)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: "AGENT_BUSY"
                  message: "Agent is currently processing. Wait for idle status before sending follow-up."
        '413':
          $ref: '#/components/responses/SpreadsheetTooLarge'
        '422':
          $ref: '#/components/responses/AgentProcessingError'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/{platform}/agents/{id}:
    get:
      operationId: getAgent
      summary: Get agent
      description: |
        Retrieves the current status and details of an agent.
        Use the `include` parameter to fetch full message and tool call history.
      tags:
        - Agents
      parameters:
        - $ref: '#/components/parameters/Platform'
        - $ref: '#/components/parameters/AgentId'
        - name: include
          in: query
          description: Additional data to include in the response
          schema:
            type: array
            items:
              type: string
              enum:
                - messages
                - tool_calls
                - configuration
          style: form
          explode: false
          example:
            - messages
            - tool_calls
      responses:
        '200':
          description: Agent details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
          links:
            StopAgent:
              operationId: stopAgent
              parameters:
                id: $response.body#/id
              description: Stop this agent if it's still running
            FollowUp:
              operationId: runAgent
              description: Send a follow-up prompt using this agent's `id` as `agent_id`
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/AgentNotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/{platform}/agents/{id}/stop:
    post:
      operationId: stopAgent
      summary: Stop agent
      description: |
        Stops a running agent. If the agent has an active turn, it will be marked as cancelled.
        Completed turns are not affected.
      tags:
        - Agents
      parameters:
        - $ref: '#/components/parameters/Platform'
        - $ref: '#/components/parameters/AgentId'
      responses:
        '200':
          description: Agent stopped
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Agent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/AgentNotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /v1/{platform}/agents/configuration:
    get:
      operationId: getAgentsConfiguration
      summary: List available models
      description: |
        Returns the available models for your space, grouped by purpose.
        If your space has custom model settings, those are returned. Otherwise, platform defaults are used.

        Use the model `id` values when creating an agent with a specific model override.
      tags:
        - Agents
      parameters:
        - $ref: '#/components/parameters/Platform'
      responses:
        '200':
          description: Available configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfigurationResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API key obtained from the [dashboard](https://dashboard.gptforwork.com).

        Keys are issued in the format `sk-user-<user>-<epoch>-<random>` — for example `sk-user-a1b2c3d4-1713987654-mN3oP4qR5sT6uV7w`.

  parameters:
    Platform:
      name: platform
      in: path
      required: true
      description: Target platform
      schema:
        type: string
        enum:
          - sheets
      example: sheets

    AgentId:
      name: id
      in: path
      required: true
      description: Agent ID (UUID)
      schema:
        type: string
        format: uuid
      example: 1f7b5f98-3906-40f4-b177-8bc2327df6f1

  schemas:
    # ── Auth schemas ────────────────────────────────────────────
    VerifyApiKeyResponse:
      type: object
      properties:
        valid:
          type: boolean
          description: Whether the API key is valid and active
        user:
          type: object
          properties:
            email:
              type: string
              format: email
              description: Email associated with the API key owner
        space:
          type: object
          properties:
            id:
              type: string
              format: uuid
              description: Space ID the key belongs to
            name:
              type: string
              description: Space name
        google_connected:
          type: boolean
          description: Whether Google Sheets access has been authorized
        webhook_secret:
          type: string
          description: |
            HMAC signing secret for webhook callbacks. Use this to verify that incoming webhook requests
            originate from GPT for Work by validating the `X-Webhook-Signature: sha256=<hmac>` header.

    # ── Configuration schemas ───────────────────────────────────
    ConfigurationResponse:
      type: object
      properties:
        models:
          type: object
          description: Available models and current defaults for this space
          properties:
            agent_models:
              type: array
              description: |
                Models available for agent execution. Use the `id` in the `agent_model` field when running an agent.
              items:
                $ref: '#/components/schemas/ModelOption'
            bulk_models:
              type: array
              description: Models available for bulk (standard) operations
              items:
                $ref: '#/components/schemas/ModelOption'
            bulk_search_models:
              type: array
              description: Models available for web search operations
              items:
                $ref: '#/components/schemas/ModelOption'
            defaults:
              type: object
              description: Current default model selections for this space
              properties:
                agent_model:
                  type: string
                  description: Default agent model ID
                  example: "managed:agent:openai/gpt-5.1"
                bulk_model:
                  type: string
                  description: Default bulk model ID
                  example: "managed:bulk:openai/gpt-4.1"
                bulk_search_model:
                  type: string
                  description: Default web search model ID
                  example: "managed:search:perplexity/sonar"
      example:
        models:
          agent_models:
            - id: "managed:agent:openai/gpt-5.1"
              label: "gpt-5.1"
              provider: "openai"
              is_default: true
            - id: "managed:agent:anthropic/claude-opus-4-6"
              label: "claude-opus-4-6"
              provider: "anthropic"
              is_default: false
          bulk_models:
            - id: "managed:bulk:openai/gpt-4.1"
              label: "gpt-4.1"
              provider: "openai"
              is_default: true
          bulk_search_models:
            - id: "managed:search:perplexity/sonar"
              label: "sonar"
              provider: "perplexity"
              is_default: true
          defaults:
            agent_model: "managed:agent:openai/gpt-5.1"
            bulk_model: "managed:bulk:openai/gpt-4.1"
            bulk_search_model: "managed:search:perplexity/sonar"

    ModelOption:
      type: object
      properties:
        id:
          type: string
          description: Model identifier to use in API requests
          example: "managed:agent:openai/gpt-5.1"
        label:
          type: string
          description: Human-readable model name
          example: "gpt-5.1"
        provider:
          type: string
          description: Model provider
          example: "openai"
        is_default:
          type: boolean
          description: Whether this is the current default for its category

    # ── Webhook schemas ────────────────────────────────────────
    WebhookPayload:
      type: object
      description: |
        Body of the `POST` request sent to `webhook_url` when a turn finishes. See the [Webhooks](#section/Webhooks) section for signature verification and delivery semantics.
      required:
        - agent_id
        - turn_id
        - status
      properties:
        agent_id:
          type: string
          format: uuid
          description: The agent that was running
          example: "1f7b5f98-3906-40f4-b177-8bc2327df6f1"
        turn_id:
          type: string
          format: uuid
          description: The specific turn that finished
          example: "a3c2e8d1-4b56-4f89-9012-def456789abc"
        status:
          type: string
          enum:
            - completed
            - error
            - interrupted
            - cancelled
          description: Turn outcome
        error_message:
          type: [string, "null"]
          description: Error details when status is `error` or `interrupted`, otherwise `null`

    # ── Agent schemas ───────────────────────────────────────────
    RunAgentRequest:
      type: object
      required:
        - prompt
      properties:
        agent_id:
          type: string
          format: uuid
          description: |
            ID of an existing agent to continue. When provided, the prompt is added as a follow-up turn.
            The agent must be in `idle` status. If omitted, a new agent is created and `spreadsheet_id` becomes required.
          example: "1f7b5f98-3906-40f4-b177-8bc2327df6f1"
        spreadsheet_id:
          type: string
          description: |
            Google Sheets spreadsheet ID (found in the spreadsheet URL between `/d/` and `/edit`).
            **Required** when creating a new agent (no `agent_id`). Silently ignored on follow-ups.
          example: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
        prompt:
          type: string
          description: Natural language instruction for the agent to execute
          maxLength: 10000
          example: "Translate all content in column B from French to English"
        sheet_name:
          type: string
          description: |
            Name of the sheet to operate on. Defaults to the first sheet (new agent) or the sheet from the previous turn (follow-up).
          example: "Sheet1"
        agent_model:
          type: string
          description: |
            Override the default agent model. Use an `id` from the `models.agent_models` list in [List available models](#tag/agents/get/v1/{platform}/agents/configuration).
            If not specified, the space's default agent model is used.
          example: "managed:agent:anthropic/claude-opus-4-6"
        bulk_model:
          type: string
          description: |
            Override the default bulk tool model. Use an `id` from the `models.bulk_models` list in [List available models](#tag/agents/get/v1/{platform}/agents/configuration).
            If not specified, the space's default bulk model is used.
          example: "managed:bulk:openai/gpt-4.1"
        bulk_search_model:
          type: string
          description: |
            Override the default web search model. Use an `id` from the `models.bulk_search_models` list in [List available models](#tag/agents/get/v1/{platform}/agents/configuration).
            If not specified, the space's default search model is used.
          example: "managed:search:perplexity/sonar"
        webhook_url:
          type: string
          format: uri
          description: |
            Optional URL to receive a `POST` callback when the job completes or fails.
            Requests are signed with your API key's webhook secret (see [Verify API Key](#tag/authentication/get/v1/auth/verify)) via the `X-Webhook-Signature: sha256=<hmac>` header.
          example: "https://example.com/webhooks/gptforwork"

    Agent:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique agent identifier
          example: "1f7b5f98-3906-40f4-b177-8bc2327df6f1"
        spreadsheet_id:
          type: string
          description: Google Sheets spreadsheet ID
          example: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
        status:
          $ref: '#/components/schemas/AgentStatus'
        created_at:
          type: string
          format: date-time
          description: When the agent was created
        last_activity_at:
          type: [string, "null"]
          format: date-time
          description: When the agent last had activity
        turns:
          type: array
          items:
            $ref: '#/components/schemas/AgentTurn'
          description: List of turns (prompt executions) for this agent

    TurnConfiguration:
      type: object
      description: |
        Model and settings used for this turn. Only included when requested via the `include=configuration` parameter.
        Each turn can use different models if overridden in the request.
      properties:
        agent_model:
          type: string
          description: Agent model used for this turn
          example: "managed:agent:openai/gpt-5.1"
        bulk_model:
          type: string
          description: Bulk tool model used for this turn
          example: "managed:bulk:openai/gpt-4.1"
        bulk_search_model:
          type: string
          description: Web search model used for this turn
          example: "managed:search:perplexity/sonar"
        settings:
          type: object
          description: Additional settings applied to this turn
          properties:
            reasoning:
              type: boolean
              description: Whether reasoning mode was enabled
              example: false
            verbosity:
              type: string
              enum:
                - concise
                - normal
                - verbose
              description: Agent output verbosity level
              example: "normal"

    AgentStatus:
      type: string
      enum:
        - active
        - idle
      description: |
        Agent status:
        - `active`: A turn is currently in progress
        - `idle`: All turns are completed

    AgentTurn:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique turn identifier
        status:
          $ref: '#/components/schemas/TurnStatus'
        prompt:
          type: string
          description: The prompt that was executed
        sheet_name:
          type: string
          description: The sheet this turn operated on
        created_at:
          type: string
          format: date-time
        started_at:
          type: [string, "null"]
          format: date-time
        completed_at:
          type: [string, "null"]
          format: date-time
        message_count:
          type: integer
          description: Number of messages in this turn
          example: 12
        tool_call_count:
          type: integer
          description: Number of tool calls made in this turn
          example: 5
        error_message:
          type: [string, "null"]
          description: Error explanation when status is 'error' or 'interrupted'
        messages:
          type: array
          items:
            $ref: '#/components/schemas/AgentMessage'
          description: Conversation messages (only included when requested via `include` parameter)
        tool_calls:
          type: array
          items:
            $ref: '#/components/schemas/AgentToolCall'
          description: Tool calls made during this turn (only included when requested via `include` parameter)
        configuration:
          $ref: '#/components/schemas/TurnConfiguration'

    TurnStatus:
      type: string
      enum:
        - queued
        - running
        - completed
        - cancelled
        - interrupted
        - error
      description: |
        Turn status:
        - `queued`: Waiting to start
        - `running`: Currently executing
        - `completed`: Finished successfully
        - `cancelled`: Cancelled by user
        - `interrupted`: Stopped due to external factors (e.g., balance empty)
        - `error`: Failed with an error

    AgentMessage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        role:
          type: string
          enum:
            - user
            - assistant
        content:
          type: string

    AgentToolCall:
      type: object
      properties:
        id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        tool:
          type: string
          description: Name of the tool that was called
          example: "write_range"
        status:
          type: string
          enum:
            - pending
            - running
            - completed
            - error
        message:
          type: string
          description: Result message from the tool call
        data:
          type: object
          additionalProperties: true
          description: Additional data returned by the tool

    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Machine-readable error code
            message:
              type: string
              description: Human-readable error message
          required:
            - code
            - message

  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            invalidDocumentId:
              value:
                error:
                  code: "INVALID_DOCUMENT_ID"
                  message: "Invalid spreadsheet ID format"
            missingPrompt:
              value:
                error:
                  code: "MISSING_REQUIRED_FIELD"
                  message: "Field 'prompt' is required"
            missingSpreadsheetId:
              value:
                error:
                  code: "MISSING_REQUIRED_FIELD"
                  message: "Missing required field: spreadsheet_id"
            invalidModel:
              value:
                error:
                  code: "INVALID_REQUEST"
                  message: "Invalid agent_model: \"unknown-model\". Use GET /v1/sheets/agents/configuration to list available models."

    Unauthorized:
      description: Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: "INVALID_API_KEY"
              message: "Invalid API key"

    GoogleAuthError:
      description: Google account not connected or token expired
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: "GOOGLE_AUTH_REQUIRED"
              message: "Google account not connected. Please connect at https://dashboard.gptforwork.com/connections"

    SpreadsheetNotFound:
      description: Spreadsheet not found or not accessible
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            notFound:
              value:
                error:
                  code: "SPREADSHEET_NOT_FOUND"
                  message: "Spreadsheet not found"
            noAccess:
              value:
                error:
                  code: "SPREADSHEET_ACCESS_DENIED"
                  message: "Cannot access this spreadsheet. Check sharing settings."

    AgentNotFound:
      description: Agent not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: "AGENT_NOT_FOUND"
              message: "Agent not found"

    RateLimitExceeded:
      description: Rate limit exceeded
      headers:
        X-RateLimit-Limit:
          schema:
            type: integer
          description: Request limit per minute
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: Remaining requests in current window
        X-RateLimit-Reset:
          schema:
            type: integer
          description: Unix timestamp when the rate limit resets
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: "RATE_LIMIT_EXCEEDED"
              message: "Rate limit exceeded. Retry after 60 seconds."

    SpreadsheetTooLarge:
      description: Spreadsheet exceeds size limits
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: "SPREADSHEET_TOO_LARGE"
              message: "Spreadsheet exceeds maximum size of 50000 rows."

    AgentProcessingError:
      description: Agent could not process the request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: "AGENT_PROCESSING_ERROR"
              message: "Could not process: Unable to understand the prompt. Please be more specific."

    InternalServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: "INTERNAL_ERROR"
              message: "Internal error. Job ID: abc123. Contact support."
