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

# Reserve an attachment upload

> Reserves storage for a file and returns where to send its bytes, for callers that
would rather not route the file through this API. Use
[Upload attachment](/api/attachments/create) instead to send the file in one request.

Three steps:

1. `POST` the file's `filename`, `byte_size` and `checksum` here. The response carries
   a `signed_id`, an `upload_url` and the `upload_headers` the next step must send.
2. `PUT` the file's bytes to `upload_url`, carrying every header in `upload_headers`
   unchanged. The bytes go straight to storage and never pass through this API.
3. Pass the `signed_id` in `attachments` on
   [Create ticket](/api/tickets/create) or
   [Create comment](/api/comments/create).

`checksum` is the base64-encoded MD5 digest of the file, which is the form
`Content-MD5` takes - not the hex digest. Generate it with
`openssl dgst -md5 -binary report.pdf | base64`. The storage service verifies it, so a
wrong digest fails at step 2.

A reservation holds no file until step 2 completes. Attaching a `signed_id` whose bytes
never arrived is rejected rather than left as a broken attachment, and a reservation
that is never used is cleaned up automatically.


<Info>Replace `{your-subdomain}` with your workspace's subdomain. <br /> Learn how to find your subdomain in [Workspace subdomain](/api/workspace-subdomain).</Info>

## Sending the file

The response tells you where to send the bytes. Send them with a `PUT`, carrying every header
from `upload_headers` unchanged.

```bash theme={"system"}
FILE=$HOME/Downloads/report.pdf

# 1. Reserve the upload. `wc -c` pads its output on BSD and macOS, so trim it.
curl -X POST "https://<subdomain>.neetodesk.com/api/external/v2/attachment_uploads" \
  -H "X-Api-Key: <your_api_key>" \
  -H "Content-Type: application/json" \
  -d "{
        \"filename\": \"$(basename "$FILE")\",
        \"byte_size\": $(wc -c < "$FILE" | tr -d ' '),
        \"checksum\": \"$(openssl dgst -md5 -binary "$FILE" | base64)\",
        \"content_type\": \"application/pdf\"
      }"

# 2. Send the bytes to the upload_url from the response. Pass each header from
#    upload_headers exactly as the response gave it, values included.
curl -X PUT "<upload_url>" \
  -H "Content-Type: <upload_headers.Content-Type>" \
  -H "Content-MD5: <upload_headers.Content-MD5>" \
  --data-binary "@$FILE"

# 3. Attach it.
curl -X POST "https://<subdomain>.neetodesk.com/api/external/v2/tickets" \
  -H "X-Api-Key: <your_api_key>" \
  -H "Content-Type: application/json" \
  -d '{
        "email": "customer@example.com",
        "subject": "Printer is jammed",
        "description": "Report attached.",
        "attachments": ["<signed_id>"]
      }'
```

<Warning>
  `checksum` is the **base64-encoded** MD5 digest, not the hex digest. The storage service
  verifies it, so a hex digest fails at step 2 rather than at step 1.
</Warning>

<Note>
  Send the headers from `upload_headers` rather than composing your own. Their values are
  covered by the upload signature, so an altered or missing one is refused. Which headers
  appear depends on how the workspace stores files, so read them from the response rather
  than hardcoding the set above.
</Note>


## OpenAPI

````yaml bundled/attachments.yaml POST /attachment_uploads
openapi: 3.0.3
info:
  title: NeetoDesk APIs
  version: 2.0.0
servers:
  - description: NeetoDesk APIs
    url: https://{your-subdomain}.neetodesk.com/api/external/v2
    variables:
      your-subdomain:
        default: spinkart
        description: >-
          Replace **spinkart** with your [workspace's
          subdomain](/api/workspace-subdomain).
security: []
paths:
  /attachment_uploads:
    post:
      summary: Reserve an attachment upload
      description: >
        Reserves storage for a file and returns where to send its bytes, for
        callers that

        would rather not route the file through this API. Use

        [Upload attachment](/api/attachments/create) instead to send the file in
        one request.


        Three steps:


        1. `POST` the file's `filename`, `byte_size` and `checksum` here. The
        response carries
           a `signed_id`, an `upload_url` and the `upload_headers` the next step must send.
        2. `PUT` the file's bytes to `upload_url`, carrying every header in
        `upload_headers`
           unchanged. The bytes go straight to storage and never pass through this API.
        3. Pass the `signed_id` in `attachments` on
           [Create ticket](/api/tickets/create) or
           [Create comment](/api/comments/create).

        `checksum` is the base64-encoded MD5 digest of the file, which is the
        form

        `Content-MD5` takes - not the hex digest. Generate it with

        `openssl dgst -md5 -binary report.pdf | base64`. The storage service
        verifies it, so a

        wrong digest fails at step 2.


        A reservation holds no file until step 2 completes. Attaching a
        `signed_id` whose bytes

        never arrived is rejected rather than left as a broken attachment, and a
        reservation

        that is never used is cleaned up automatically.
      parameters:
        - $ref: '#/components/parameters/api_key_header'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ReserveAttachmentUploadRequest'
      responses:
        '201':
          description: Created - Upload reserved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AttachmentUpload'
        '422':
          description: >
            Unprocessable Entity - `filename`, `byte_size` or `checksum` is
            missing or

            malformed, or `byte_size` is over the workspace upload limit (50 MB
            by default).
components:
  parameters:
    api_key_header:
      in: header
      name: X-Api-Key
      description: >-
        Use the X-Api-Key header to provide your workspace API key. Refer to
        [Authentication](/api/authentication) for more information.
      required: true
      schema:
        type: string
        default: your-api-key
  schemas:
    ReserveAttachmentUploadRequest:
      type: object
      required:
        - filename
        - byte_size
        - checksum
      properties:
        filename:
          type: string
          description: Name the file should carry in the workspace.
          example: report.pdf
        byte_size:
          type: integer
          description: Size of the file in bytes.
          example: 20481
        checksum:
          type: string
          description: >
            Base64-encoded MD5 digest of the file, which is the form
            `Content-MD5` takes.

            Not the hex digest. Generate it with

            `openssl dgst -md5 -binary report.pdf | base64`.
          example: Y2hlY2tzdW1leGFtcGxl
        content_type:
          type: string
          description: Media type of the file. Inferred from the filename when omitted.
          example: application/pdf
    AttachmentUpload:
      type: object
      properties:
        signed_id:
          type: string
          description: >
            Pass this to `attachments` on ticket or comment create once the
            file's bytes have

            been sent to `upload_url`. It is only accepted in the workspace that
            reserved it,

            and attaching it before the bytes arrive is rejected.
          example: eyJfcmFpbHMiOnsiZGF0YSI6IjEyMyJ9fQ==
        upload_url:
          type: string
          description: >-
            Send the file's bytes here with an HTTP `PUT`. The URL is
            short-lived.
          example: >-
            https://neeto-desk.s3.amazonaws.com/3ieihu6erxcialumauhj1pqs07hr?X-Amz-Signature=...
        upload_headers:
          type: object
          additionalProperties:
            type: string
          description: >
            Headers the `PUT` must carry, exactly as given. Send them unchanged
            - the

            signature covers them, so an altered or missing header is refused by
            the storage

            service.
          example:
            Content-Type: application/pdf
            Content-MD5: Y2hlY2tzdW1leGFtcGxl
        filename:
          type: string
          example: report.pdf
        content_type:
          type: string
          example: application/pdf
        byte_size:
          type: integer
          example: 20481
        checksum:
          type: string
          example: Y2hlY2tzdW1leGFtcGxl

````