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

# Start an upload

> Creates a connection-owned upload with a ten-minute lifetime. Compute the file's lowercase SHA-256 first, then PUT its raw bytes to the returned URL with the returned Content-Type and Content-Length headers. Do not forward your API key or OAuth token to that URL. Call complete_attachment_upload afterward. Requires a client capable of reading the local file and making an HTTP PUT; MCP does not transfer bytes inside tool arguments. Supports JPEG, PNG, WebP, GIF, MP3, Ogg, WAV, M4A, WebM, MP4, MOV, PDF, plain text and CSV up to 16 MiB. Office files and archives are not supported by this upload flow. The same idempotency key and file metadata reuse the upload without extending its expiry; changed metadata returns 409. A completed retry returns its ready attachment. Uploading never sends a message. Expired uploads require a new idempotency key.




## OpenAPI

````yaml POST /attachments/uploads
openapi: 3.1.0
info:
  title: Protodesk API
  version: 0.1.0
  description: >
    The Protodesk Platform API: sync your customers in, attach your own business
    objects (orders, bookings, listings) to conversations, post and read
    messages across channels, and receive every workspace event via signed
    webhooks or a resumable SSE stream.


    Authenticate every request with a workspace-scoped API key: `Authorization:
    Bearer pk_live_...`. Create endpoints accept an `Idempotency-Key` header so
    retries are always safe. Lists use opaque cursor pagination (`data` /
    `hasMore` / `nextCursor`).


    Task-oriented guides: [docs.protodesk.io](https://docs.protodesk.io).
servers:
  - url: https://api.protodesk.io/v1
    description: Production
  - url: http://localhost:8080/v1
    description: Local development
security: []
tags:
  - name: Evelin
    description: >-
      Human-owned AI connections using the existing Evelin runtime. Generation
      never sends a customer message.
  - name: Customers
    description: People or companies contacting a workspace across channels.
  - name: Conversations
    description: Customer conversations attached to channels and external business objects.
  - name: Messages
    description: Customer, agent, AI, system, and internal messages inside conversations.
  - name: Attachments
    description: Scoped file transfer for messages.
  - name: Channel identities
    description: >-
      The same customer across WhatsApp, email, chat, and more — linked
      identities per channel.
  - name: Statuses & priorities
    description: The workspace's conversation statuses and priority levels.
  - name: Assignments
    description: >-
      Who a conversation has been assigned to, over time. Assign via POST
      /conversations/{conversationId}/assign.
  - name: Webhooks
    description: Webhook subscriptions for receiving workspace events.
  - name: Realtime
    description: Server-sent event stream of workspace events.
  - name: API Keys
    description: Create, list, and revoke workspace API keys.
  - name: Account
    description: The workspace and scopes behind your API key.
  - name: System
    description: Service metadata and health endpoints.
paths:
  /attachments/uploads:
    post:
      tags:
        - Attachments
      summary: Start an attachment upload
      description: >
        Creates a connection-owned upload with a ten-minute lifetime. Compute
        the file's lowercase SHA-256 first, then PUT its raw bytes to the
        returned URL with the returned Content-Type and Content-Length headers.
        Do not forward your API key or OAuth token to that URL. Call
        complete_attachment_upload afterward. Requires a client capable of
        reading the local file and making an HTTP PUT; MCP does not transfer
        bytes inside tool arguments. Supports JPEG, PNG, WebP, GIF, MP3, Ogg,
        WAV, M4A, WebM, MP4, MOV, PDF, plain text and CSV up to 16 MiB. Office
        files and archives are not supported by this upload flow. The same
        idempotency key and file metadata reuse the upload without extending its
        expiry; changed metadata returns 409. A completed retry returns its
        ready attachment. Uploading never sends a message. Expired uploads
        require a new idempotency key.
      operationId: createAttachmentUpload
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 8
            maxLength: 128
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - filename
                - contentType
                - sizeBytes
                - sha256
              properties:
                filename:
                  type: string
                  minLength: 1
                  maxLength: 255
                contentType:
                  type: string
                  enum:
                    - image/jpeg
                    - image/png
                    - image/webp
                    - image/gif
                    - audio/mpeg
                    - audio/ogg
                    - audio/wav
                    - audio/mp4
                    - audio/webm
                    - video/mp4
                    - video/webm
                    - video/quicktime
                    - application/pdf
                    - text/plain
                    - text/csv
                sizeBytes:
                  type: integer
                  minimum: 1
                  maximum: 16777216
                sha256:
                  type: string
                  pattern: ^[a-f0-9]{64}$
      responses:
        '200':
          description: This upload already completed. Reuse the attachment.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AttachmentUpload'
        '201':
          description: >-
            Pending upload. PUT the bytes before expiresAt, then complete using
            the same connection.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AttachmentUpload'
        default:
          $ref: '#/components/responses/Error'
      security:
        - ApiKeyAuth: []
components:
  schemas:
    AttachmentUpload:
      type: object
      required:
        - uploadId
        - status
      properties:
        uploadId:
          type: string
        status:
          type: string
          enum:
            - pending
            - ready
        url:
          type: string
          format: uri
          description: Temporary PUT URL, present only while pending.
        method:
          type: string
          enum:
            - PUT
        headers:
          type: object
          properties:
            Content-Type:
              type: string
            Content-Length:
              type: string
        expiresAt:
          type: string
          format: date-time
        maxSizeBytes:
          type: integer
        attachment:
          type: object
          description: >-
            Present only when ready. Use this object in a message's attachments
            array.
          required:
            - url
            - filename
            - contentType
            - sizeBytes
          properties:
            url:
              type: string
              description: Durable /api/media/ reference; not a public download URL.
            filename:
              type: string
            contentType:
              type: string
            sizeBytes:
              type: integer
    ErrorEnvelope:
      type: object
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/Error'
    Error:
      type: object
      required:
        - code
        - message
        - requestId
      properties:
        code:
          type: string
          example: route.not_found
        message:
          type: string
          example: Not found
        requestId:
          type: string
          example: req_x6q5vl75f5d53m7oet2k4r5w6a
  responses:
    Error:
      description: >-
        Error response. All /v1 responses carry RateLimit-Limit,
        RateLimit-Remaining, and RateLimit-Reset headers. A 429
        (rate_limit.exceeded) additionally carries Retry-After. Request bodies
        exceeding the route-class limit (1KB reads, 64KB writes) return 413
        (request.body_too_large).
      headers:
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
        RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  headers:
    RequestId:
      description: Request id for tracing this request in logs and support.
      schema:
        type: string
        example: req_x6q5vl75f5d53m7oet2k4r5w6a
    RateLimitLimit:
      description: >-
        Request budget for the route class (read or write) applied to this
        request.
      schema:
        type: integer
        example: 40
    RateLimitRemaining:
      description: Requests remaining in the current budget after this request.
      schema:
        type: integer
        example: 39
    RateLimitReset:
      description: Seconds until the budget refills to its full limit.
      schema:
        type: integer
        example: 1
    RetryAfter:
      description: Seconds to wait before retrying, sent with 429 responses.
      schema:
        type: integer
        example: 1
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: Protodesk API key
      description: >
        Use a workspace API key in the Authorization header. Keys use
        pk_live_<key_id>_<secret> for production and pk_test_<key_id>_<secret>
        for test mode.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.