> ## Documentation Index
> Fetch the complete documentation index at: https://howto.paigeme.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The projects this credential may act on

> Discovery for multi-project connections (GRE-499). An MCP OAuth bearer (`mcp_at_…`) may be attached to SEVERAL projects, each with its OWN scopes; this returns them all, with `is_default` marking the one used when a call names no project. Pass a returned `id` in the `X-Paige-Project` header to target a project. A `pk_` API key is a single project's context and returns that one project as a one-element list (same shape), so a client never needs to branch on credential type.

Deliberately NOT project-selected: it stays usable when the default project is paused (which would otherwise `403 project_inactive`), so a client can always discover a healthy sibling. The `X-Paige-Project` header is ignored here.

**Required scope:** any valid key.



## OpenAPI

````yaml /api-reference/openapi.json get /v1/projects
openapi: 3.1.0
info:
  title: Paige API
  version: 1.0.0
  description: >-
    The Paige public REST API (`/v1`). Build WhatsApp automations against your
    Paige project: send messages, manage templates, read conversations, tag
    contacts, assemble broadcasts, edit bot code + flows, and register signed
    webhooks.


    ## Authentication

    Every `/v1` request authenticates with a project API key: `Authorization:
    Bearer pk_live_…`. Mint and scope keys in **Settings → API keys**. A key
    carries its own project context, so `/v1` paths never include a project id.


    ## Scopes

    Each endpoint requires one or more scopes (see each operation, and the
    `x-required-scopes` extension). Grant a key only the scopes it needs. A key
    missing a required scope gets `403 insufficient_scope`.


    ## Response envelope

    Success: `{ "success": true, "data": … }`. Error: `{ "success": false,
    "error": { "code", "message" }, "request_id" }`. Some errors add
    `error.details` with structured extras (e.g. `quota_exceeded` carries
    `limit` + `resetAt`) — read it defensively, its keys depend on the code. The
    `request_id` is also returned as the `X-Request-Id` header on every
    response.


    ## Rate limits & quota

    Requests are throttled per key (`RateLimit-*` headers; exceed → `429
    rate_limited`). WhatsApp sends also draw down a daily quota (exceed → `429
    quota_exceeded`). Both carry `Retry-After`.


    ## Idempotency

    Send an `Idempotency-Key` header on `POST /v1/messages` to make retries safe
    — a completed key replays the stored response and never re-sends.


    ## Webhook signatures

    Outbound webhook deliveries carry `X-Paige-Signature:
    t=<unix>,v1=<hmac_sha256>`. Verify with the reference `verifyPaigeSignature`
    helper (constant-time HMAC over `"<t>.<rawBody>"`, 300s tolerance).
  contact:
    name: Paige
    url: https://paigeme.dev
servers:
  - url: https://api.paigeme.dev
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: Meta
    description: Smoke / diagnostics.
  - name: Messages
    description: Send messages and read delivery status + media.
  - name: Conversations
    description: List conversations, read messages, set state.
  - name: Contacts
    description: Read contact messages; update names, tags, attributes.
  - name: Templates
    description: WhatsApp message template CRUD.
  - name: Broadcasts
    description: Segments + broadcast assembly (always pending_approval).
  - name: Build
    description: Read/edit bot code, deploy, read/generate/update flows.
  - name: Webhooks
    description: Register signed outbound webhooks.
paths:
  /v1/projects:
    get:
      tags:
        - Meta
      summary: The projects this credential may act on
      description: >-
        Discovery for multi-project connections (GRE-499). An MCP OAuth bearer
        (`mcp_at_…`) may be attached to SEVERAL projects, each with its OWN
        scopes; this returns them all, with `is_default` marking the one used
        when a call names no project. Pass a returned `id` in the
        `X-Paige-Project` header to target a project. A `pk_` API key is a
        single project's context and returns that one project as a one-element
        list (same shape), so a client never needs to branch on credential type.


        Deliberately NOT project-selected: it stays usable when the default
        project is paused (which would otherwise `403 project_inactive`), so a
        client can always discover a healthy sibling. The `X-Paige-Project`
        header is ignored here.


        **Required scope:** any valid key.
      operationId: listProjects
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    enum:
                      - true
                  data:
                    type: object
                    properties:
                      projects:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              description: Project id — pass this as `X-Paige-Project`.
                            name:
                              type: string
                            scopes:
                              type: array
                              items:
                                type: string
                              description: >-
                                Scopes granted on THIS project. Never a union
                                across the connection.
                            is_default:
                              type: boolean
                              description: Used when a read names no project.
                          required:
                            - id
                            - name
                            - scopes
                            - is_default
                    required:
                      - projects
                required:
                  - success
                  - data
        '401':
          description: >-
            Missing or invalid API key (`api_key_required` / `invalid_api_key` /
            `invalid_token` for a revoked or expired OAuth bearer).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: >-
            Rate limit (`rate_limited`) or daily send quota (`quota_exceeded`)
            exceeded. Carries `RateLimit-*` + `Retry-After` headers.
          headers:
            RateLimit-Limit:
              description: Requests permitted in the current window.
              schema:
                type: integer
            RateLimit-Remaining:
              description: Requests remaining in the current window.
              schema:
                type: integer
            RateLimit-Reset:
              description: Seconds until the window resets.
              schema:
                type: integer
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Unexpected server error (`server_error`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          $ref: '#/components/schemas/ApiError'
        request_id:
          type: string
          description: Per-request id, also returned as the X-Request-Id header.
      required:
        - success
        - error
        - request_id
    ApiError:
      type: object
      properties:
        code:
          type: string
          description: Stable, machine-readable error code.
          example: invalid_request
        message:
          type: string
          description: Human-readable message (safe to surface).
          example: Invalid request body
        details:
          type: object
          properties: {}
          description: >-
            Structured extras for codes that carry them — the keys depend on
            `code`. `quota_exceeded` carries `limit` + `resetAt`; most errors
            omit this field entirely.
      required:
        - code
        - message
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: >-
        Project API key issued in Settings → API keys. Send it as
        `Authorization: Bearer pk_live_…`.

````