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

# Verify PUTs landed in R2 and record the files

> Requires files:["write"]. Same per-class project resolution as POST /uploads (projectId). Idempotent per path. Every requested path comes back with its canonical public URL — the ONLY URL form the plugin should persist.



## OpenAPI

````yaml /api/openapi.json post /uploads/confirm
openapi: 3.1.0
info:
  title: PixGlory Machine API
  version: 1.0.0
  description: >-
    The versioned /api/v1/* machine API — bearer-key authenticated, for the
    WordPress plugin and future integrations. Hand-written and maintained
    alongside apps/start/src/routes/api/v1/*; see machine-api-v1.md for the
    human-readable walkthrough. THIS FILE IS THE SOURCE OF TRUTH where the two
    disagree.
servers:
  - url: https://pixglory.com/api/v1
security:
  - bearerKey: []
paths:
  /uploads/confirm:
    post:
      summary: Verify PUTs landed in R2 and record the files
      description: >-
        Requires files:["write"]. Same per-class project resolution as POST
        /uploads (projectId). Idempotent per path. Every requested path comes
        back with its canonical public URL — the ONLY URL form the plugin should
        persist.
      operationId: confirmUploads
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ConfirmUploadsRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConfirmUploadsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/QuotaExceeded'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  schemas:
    ConfirmUploadsRequest:
      type: object
      required:
        - paths
      properties:
        paths:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: string
            minLength: 1
            maxLength: 255
            description: A single path segment — no '/', no '..'.
        projectId:
          type: string
          format: uuid
          description: >-
            Same rule as POST /uploads's projectId: required for an api-class
            key, optional-must-match for a wordpress key.
    ConfirmUploadsResponse:
      type: object
      required:
        - projectId
        - confirmedFileCount
        - skippedFileCount
        - totalBytes
        - files
      properties:
        projectId:
          type: string
        confirmedFileCount:
          type: integer
        skippedFileCount:
          type: integer
        totalBytes:
          type: integer
        files:
          type: array
          items:
            $ref: '#/components/schemas/ConfirmedUploadFile'
    ConfirmedUploadFile:
      type: object
      required:
        - path
        - publicUrl
      properties:
        path:
          type: string
        publicUrl:
          type: string
          format: uri
          description: >-
            CANONICAL public URL (cdn.pixglory.com/...) — never a custom-domain
            URL. This is what the plugin should persist.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - message
            - code
          properties:
            message:
              type: string
            code:
              type: string
            details: {}
  responses:
    BadRequest:
      description: >-
        Validation failure, an operation that can't succeed yet (e.g. a PUT that
        never landed), a wordpress key with no bound project, or an api-class
        key that omitted a required projectId.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            notBound:
              value:
                error:
                  message: >-
                    This key isn't bound to a CDN project. Create a new key with
                    a project selected.
                  code: KEY_NOT_BOUND_TO_PROJECT
            missingProjectId:
              value:
                error:
                  message: >-
                    This key has no bound project. Pass "projectId" in the
                    request body.
                  code: MISSING_PROJECT_ID
    Unauthorized:
      description: Missing, malformed, unknown, expired, or disabled key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missing:
              value:
                error:
                  message: 'Missing API key. Send it as `Authorization: Bearer <key>`.'
                  code: MISSING_API_KEY
            invalid:
              value:
                error:
                  message: Invalid or expired API key.
                  code: INVALID_API_KEY
    QuotaExceeded:
      description: The org's CDN storage or file-count quota would be exceeded.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            storage:
              value:
                error:
                  message: >-
                    Storage quota exceeded: 490000000/500000000 bytes used on
                    the free tier
                  code: CDN_STORAGE_QUOTA_EXCEEDED
                  details:
                    service: cdn
                    tier: free
                    metric: cdnStorage
                    limit: 500000000
                    used: 490000000
    Forbidden:
      description: >-
        The key is valid but either lacks a required permission, or is a real
        key of a class this endpoint doesn't accept.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            insufficient:
              value:
                error:
                  message: This key does not have the required permissions.
                  code: INSUFFICIENT_PERMISSIONS
            wrongClass:
              value:
                error:
                  message: >-
                    This endpoint requires a wordpress-class key; the presented
                    key is class "api".
                  code: WRONG_KEY_CLASS
    TooManyRequests:
      description: >-
        Rate limited — ours or the underlying key plugin's own. Honour
        Retry-After.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds until the window resets.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            rateLimited:
              value:
                error:
                  message: Too many requests, please try again shortly.
                  code: RATE_LIMIT
  securitySchemes:
    bearerKey:
      type: http
      scheme: bearer
      bearerFormat: pxg_... or pxg_wp_...
      description: >-
        An API key minted from the dashboard (POST /api/api-keys), of either
        class: `api` (prefix `pxg_`) or `wordpress` (prefix `pxg_wp_`). Which
        class a given route accepts is documented per-endpoint below; presenting
        a real key of a class the route doesn't accept is `403 WRONG_KEY_CLASS`,
        never folded into `401 INVALID_API_KEY`. No cookie fallback exists.

````