> ## 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.

# List the project's media assets

> Newest first. Each image also carries a short-lived `preview_url`; other kinds have `null` there, so fetch their bytes through `GET /v1/media/{slug}`.

**Only your own uploads by default.** Media that CUSTOMERS sent in over WhatsApp is a separate class of data (`source: "conversation_inbound"`) and is excluded. Pass `include_inbound=true` to include it — that additionally requires the `conversations:read` scope, and a key without it gets `403` rather than a quietly-filtered list.

`include_usage=true` adds `used_in`, the flows and code files referencing each slug. It costs one read per project file, so it is off by default.

Every response also carries `storage`: how many bytes this project is storing, the ceiling, and the headroom left. An upload past the ceiling is refused with `413 storage_limit_exceeded`, so read this before a bulk sync. Media customers sent in over WhatsApp counts toward the total even when it is filtered out of `assets`.

**Required scope:** `media:read`



## OpenAPI

````yaml /api-reference/openapi.json get /v1/media
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: Tables
    description: >-
      Read and write the project's own database tables. Paige platform tables
      are denied, and an update or delete must always be filtered.
  - name: Media
    description: >-
      Upload, list, retrieve and delete the project's media assets. The slug an
      upload returns is what the bot sends by.
  - name: Webhooks
    description: Register signed outbound webhooks.
paths:
  /v1/media:
    get:
      tags:
        - Media
      summary: List the project's media assets
      description: >-
        Newest first. Each image also carries a short-lived `preview_url`; other
        kinds have `null` there, so fetch their bytes through `GET
        /v1/media/{slug}`.


        **Only your own uploads by default.** Media that CUSTOMERS sent in over
        WhatsApp is a separate class of data (`source: "conversation_inbound"`)
        and is excluded. Pass `include_inbound=true` to include it — that
        additionally requires the `conversations:read` scope, and a key without
        it gets `403` rather than a quietly-filtered list.


        `include_usage=true` adds `used_in`, the flows and code files
        referencing each slug. It costs one read per project file, so it is off
        by default.


        Every response also carries `storage`: how many bytes this project is
        storing, the ceiling, and the headroom left. An upload past the ceiling
        is refused with `413 storage_limit_exceeded`, so read this before a bulk
        sync. Media customers sent in over WhatsApp counts toward the total even
        when it is filtered out of `assets`.


        **Required scope:** `media:read`
      operationId: listMedia
      parameters:
        - schema:
            type: string
            enum:
              - 'true'
              - 'false'
              - '1'
              - '0'
              - ''
            description: >-
              Also return `used_in` for each asset — the flows and code files
              referencing its slug. Costs one read per project file, so it is
              off by default.
          required: false
          description: >-
            Also return `used_in` for each asset — the flows and code files
            referencing its slug. Costs one read per project file, so it is off
            by default.
          name: include_usage
          in: query
        - schema:
            type: string
            enum:
              - 'true'
              - 'false'
              - '1'
              - '0'
              - ''
            description: >-
              Also return media customers sent in over WhatsApp. Requires the
              `conversations:read` scope as well.
          required: false
          description: >-
            Also return media customers sent in over WhatsApp. Requires the
            `conversations:read` scope as well.
          name: include_inbound
          in: query
        - schema:
            type: string
            description: >-
              Target project id, for an MCP OAuth bearer (`mcp_at_…`) attached
              to more than one project — get ids from `GET /v1/projects`.
              Matched case-insensitively.


              Omit it and a READ falls back to the connection's default project;
              a **mutation** (any non-GET) on a connection with 2+ projects is
              rejected with `400 project_required` — a write is never defaulted
              to a guessed project. A connection with exactly one project never
              needs the header.


              Scopes are checked against the SELECTED project only, never a
              union across the connection.


              For a `pk_` API key the header selects nothing — one key is one
              project's context — but it IS validated: omit it and the key's own
              project is used, send it and it must name that project, otherwise
              the call is rejected with `403 project_not_attached` (a blank
              value is `400 invalid_project_header`, as above).
          required: false
          name: X-Paige-Project
          in: header
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    enum:
                      - true
                  data:
                    type: object
                    properties:
                      storage:
                        type: object
                        properties:
                          used_bytes:
                            type: number
                            description: >-
                              Total bytes of media stored for this project,
                              across every source.
                          asset_count:
                            type: number
                            description: How many media assets that is.
                          limit_bytes:
                            type:
                              - number
                              - 'null'
                            description: >-
                              The per-project storage ceiling in bytes, or null
                              when no ceiling is configured.
                          remaining_bytes:
                            type:
                              - number
                              - 'null'
                            description: >-
                              Headroom left before an upload is refused, or null
                              when there is no ceiling.
                          by_source:
                            type: object
                            properties:
                              chat_upload:
                                type: object
                                properties:
                                  bytes:
                                    type: number
                                  count:
                                    type: number
                                required:
                                  - bytes
                                  - count
                                description: The project's own uploads.
                              conversation_inbound:
                                type: object
                                properties:
                                  bytes:
                                    type: number
                                  count:
                                    type: number
                                required:
                                  - bytes
                                  - count
                                description: >-
                                  Media customers sent in over WhatsApp. Counts
                                  toward the total.
                            required:
                              - chat_upload
                              - conversation_inbound
                            description: >-
                              The total split by where the media came from. Both
                              halves count toward `used_bytes`.
                          enforced_for:
                            type: string
                            description: >-
                              Who the ceiling is enforced against. Always
                              "api_key" — dashboard uploads are never refused
                              for storage.
                        required:
                          - used_bytes
                          - asset_count
                          - limit_bytes
                          - remaining_bytes
                          - by_source
                          - enforced_for
                      assets:
                        type: array
                        items:
                          type: object
                          properties:
                            slug:
                              type: string
                              description: >-
                                The bot-facing name. `sendMedia(to, "<slug>")`
                                and `getMediaId("<slug>")` resolve by this.
                            media_id:
                              type:
                                - string
                                - 'null'
                              description: >-
                                Meta's id for the registered copy, used when the
                                bot sends the file. **Null when registration did
                                not happen** (usually no WhatsApp number
                                connected) — the asset is still stored and Paige
                                retries on a schedule.
                            meta_status:
                              type: string
                              enum:
                                - registered
                                - pending
                                - expired
                              description: >-
                                `registered` — ready to send. `pending` — stored
                                but not registered with Meta yet. `expired` —
                                the id passed Meta's 30-day lifetime and is
                                being refreshed.
                            meta_uploaded_at:
                              type:
                                - string
                                - 'null'
                              description: >-
                                When the file was registered with Meta (ISO
                                8601), or null.
                            mime:
                              type:
                                - string
                                - 'null'
                              description: Stored mime type, e.g. `image/png`.
                            bytes:
                              type:
                                - number
                                - 'null'
                              description: Size of the stored original.
                            filename:
                              type:
                                - string
                                - 'null'
                              description: The original filename it was uploaded under.
                            kind:
                              type: string
                              enum:
                                - image
                                - video
                                - audio
                                - knowledge
                              description: >-
                                What Paige did with it. `knowledge` means it was
                                text-extracted into the bot's knowledge base
                                (PDF, DOCX, text, markdown).
                            caption:
                              type:
                                - string
                                - 'null'
                              description: The caption supplied at upload, if any.
                            source:
                              type: string
                              enum:
                                - chat_upload
                                - conversation_inbound
                              description: >-
                                `chat_upload` is your own upload.
                                `conversation_inbound` is a file a customer sent
                                in over WhatsApp — only ever returned to a key
                                that also holds `conversations:read`.
                            created_at:
                              type:
                                - string
                                - 'null'
                              description: When the asset was created (ISO 8601).
                            preview_url:
                              type:
                                - string
                                - 'null'
                              description: >-
                                Short-lived signed URL, images only. Null for
                                every other kind.
                            used_in:
                              type: array
                              items:
                                type: object
                                properties:
                                  kind:
                                    type: string
                                    enum:
                                      - flow
                                      - code
                                    description: >-
                                      Whether a flow embeds the asset or code
                                      resolves its slug.
                                  path:
                                    type: string
                                    description: >-
                                      The project file, e.g.
                                      `flows/booking.json`.
                                required:
                                  - kind
                                  - path
                              description: Present only when `include_usage=true`.
                          required:
                            - slug
                            - media_id
                            - meta_status
                            - meta_uploaded_at
                            - mime
                            - bytes
                            - filename
                            - kind
                            - caption
                            - source
                            - created_at
                            - preview_url
                    required:
                      - storage
                      - assets
                required:
                  - success
                  - data
        '400':
          description: >-
            Validation / bad request (e.g. `invalid_request`, `invalid_cursor`,
            `invalid_template` for a Meta 4xx rejection of a template payload,
            which carries Meta's own detail as the message; note
            `outside_24h_window` is 409, not 400). Also `invalid_project_header`
            (any credential: `X-Paige-Project` was sent but blank) and, for
            multi-project MCP bearers, `project_required` (a mutation, or a read
            with no default, and no `X-Paige-Project`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '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'
        '403':
          description: >-
            Key lacks the required scope, project inactive, or a write targets a
            platform-protected file (`insufficient_scope` / `project_inactive` /
            `subscription_inactive` / `protected_file`). Also
            `project_not_attached` (the `X-Paige-Project` project is not one
            this credential may act on — not attached to the MCP connection, or
            not the `pk_` key's own project; identical response whether or not
            it exists) and, for MCP bearers, `no_project_access` (the connection
            has no projects attached).
          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_…`.

````