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

# Create an attachment

> Registers a file and returns a signed URL to upload it, valid 15 minutes.

* **Credential:** an API key. A trial key returns `401 unauthorized`. See [Sending a file](/guides/attachments#sending-a-file).
* **Line-scoped keys:** the list of a [line-scoped key](/authentication#line-scoped-keys) is not applied: an attachment belongs to the account.
* **`Idempotency-Key`:** honored. The key belongs to the account. While the attachment is `awaiting_upload` and before its deadline, a repeat returns the same `id` with a new `upload_url`. See [Creating an attachment](/guides/attachments#creating-an-attachment).
* **Events:** none, also when the attachment becomes `ready`, `failed` or `expired`.
* **Personal data:** `filename` is shown to the contact and can hold personal data, and `upload_url` is a credential until `upload_expires_at`. Do not copy them into logs, tickets or other tools.
* **Next steps:** upload the file with one `PUT` to `upload_url`, with `required_headers`, before `upload_expires_at`. Then wait for `status: ready` before you send the attachment.

## Errors

| Status | `error.code` | When |
| - | - | - |
| 400 | `validation_failed` | `filename` is empty or longer than 255 characters, `content_type` is empty or longer than 100, or `size_bytes` is not an integer of at least 1. |
| 409 | `idempotency_terminal_state` | The `Idempotency-Key` was used for an attachment that is no longer `awaiting_upload`, or whose upload deadline passed. Create the attachment again with a new key. |
| 413 | `attachment_exceeds_storage_limit` | `size_bytes` is over 5,368,709,120 bytes (5 GiB). In the SDK, it arrives as an `UnprocessableEntityError`. |

The errors that every operation shares are in [Errors](/errors-and-rate-limits#errors).


## OpenAPI

````yaml POST /v1/attachments
openapi: 3.0.0
info:
  title: iSnap API
  version: 2.45.0
  description: >-
    The iSnap API sends and receives iMessage, RCS and SMS on real phone lines,
    manages those lines, and reports events through webhooks. Every message is
    sent to a real recipient, with an `isnap_live_` key and with an
    `isnap_test_` key alike. Retry a `429` after `Retry-After` seconds. Retry a
    `500` or a `503` up to 2 times, with exponential backoff. Do not retry any
    other `4xx`: fix the request. Retry a write only on an operation that honors
    `Idempotency-Key`, with the key of the first attempt. Responses carry
    personal data: handles, names and message text.
servers:
  - url: https://api.isnap.ai
security:
  - bearerAuth: []
paths:
  /v1/attachments:
    post:
      summary: Create an attachment
      description: >-
        Registers a file and returns a signed `upload_url`, valid for 15
        minutes: until `upload_expires_at`. Upload the file with one HTTP `PUT`
        to that URL, with the headers listed in `required_headers`. The file
        does not pass through the iSnap API.


        iSnap checks for the uploaded file every 30 seconds. When it finds the
        file, `status` changes from `awaiting_upload` to `ready`, or to
        `transcoding` for an audio file, which iSnap converts before it is
        `ready`: an audio file larger than 52,428,800 bytes (50 MiB) becomes
        `failed`. When no file is there at `upload_expires_at`, `status` becomes
        `failed`. No event announces these changes: read `GET
        /v1/attachments/{id}` until `status` is `ready`, then pass the `id` in
        `attachments` of `POST /v1/messages`. An attachment that is not `ready`
        when a message leaves is left out of that message, without an error.


        The largest accepted `size_bytes` is 5,368,709,120 (5 GiB): above it the
        API returns `413 attachment_exceeds_storage_limit`. Above 100,000,000
        bytes the call succeeds and `warnings` contains
        `large_attachment_may_delay_delivery`. Every `content_type` value is
        accepted. iSnap does not compare the uploaded file with the `size_bytes`
        and `content_type` you declare: the attachment and the messages that
        carry it show the declared values, except for a converted audio file. An
        attachment expires 72 hours after this call, whether or not it was
        uploaded or sent.


        Accepts an API key. An attachment belongs to the account, not to a line:
        the `line_ids` of a line-scoped key are not checked. Send an
        `Idempotency-Key` header to make a retry safe. The key is scoped to the
        account. While the first attachment is still `awaiting_upload` and its
        `upload_expires_at` has not passed, a retry returns the same `id` with a
        new `upload_url` and a new `upload_expires_at`, and the body of the
        retry is not compared with the first one. After that, a retry returns
        `409 idempotency_terminal_state`.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateAttachment'
      responses:
        '201':
          description: The attachment, created and waiting for the upload.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    enum:
                      - true
                    description: Always `true` on a success response.
                  data:
                    $ref: '#/components/schemas/CreateAttachmentResponse'
                  trace_id:
                    type: string
                    description: >-
                      Trace id of the request that produced this response: 32
                      lowercase hexadecimal characters. Quote it when you report
                      a problem to iSnap.
                  request_id:
                    type: string
                    description: >-
                      Id of the request that produced this response: `req_`
                      followed by 25 letters or digits.
                required:
                  - success
                  - data
                  - trace_id
                  - request_id
        '400':
          description: >-
            Invalid request. `error.code` is `validation_failed` when a
            parameter, a header or the body does not match its schema:
            `error.issues` then lists each problem.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          description: No valid credential. `error.code` is `unauthorized`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: >-
            Too many requests. `error.code` is `rate_limit_exceeded` when more
            than 100 requests came from the same network address in 60 seconds,
            whatever their API key or account: wait `error.retry_after` seconds.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: >-
            Unexpected failure. `error.code` is `internal_error`. An operation
            that takes a JSON body also returns it when the body, sent as
            `application/json`, is not valid JSON. Quote `trace_id` when you
            report it to iSnap.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
        - bearerAuth: []
components:
  schemas:
    CreateAttachment:
      type: object
      properties:
        filename:
          type: string
          minLength: 1
          maxLength: 255
          description: >-
            File name, 1 to 255 characters. It is returned on the attachment and
            on the messages that carry it.
        content_type:
          type: string
          minLength: 1
          maxLength: 100
          description: >-
            Content type of the file, 1 to 100 characters, for example
            `image/jpeg`. Every value is accepted. A value that starts with
            `audio/` makes iSnap convert the file before the attachment is
            `ready`.
        size_bytes:
          type: integer
          minimum: 1
          description: >-
            Size of the file in bytes: at least 1 and at most 5,368,709,120 (5
            GiB). Above it the API returns `413
            attachment_exceeds_storage_limit`. Above 100,000,000 the call
            succeeds and `warnings` contains
            `large_attachment_may_delay_delivery`.
          maximum: 5368709120
      required:
        - filename
        - content_type
        - size_bytes
      description: >-
        The file to register. iSnap does not check these values against the
        uploaded file.
    CreateAttachmentResponse:
      type: object
      properties:
        id:
          type: string
          description: 'Attachment id: `att_` followed by 25 letters or digits.'
        upload_url:
          type: string
          format: uri
          description: >-
            Signed URL for the upload. Send the file to it with one HTTP `PUT`
            before `upload_expires_at`.
        upload_expires_at:
          type: string
          description: 'When `upload_url` stops working: 15 minutes after the call.'
        required_headers:
          type: object
          additionalProperties:
            type: string
          description: >-
            The headers to send with the upload, with their values:
            `content-type` and `content-length`.
        status:
          type: string
          description: Always `awaiting_upload` in this response.
        warnings:
          type: array
          items:
            type: string
          description: >-
            Contains `large_attachment_may_delay_delivery` when `size_bytes` is
            above 100,000,000. Empty otherwise.
      required:
        - id
        - upload_url
        - upload_expires_at
        - required_headers
        - status
        - warnings
      description: The attachment created, with the URL to upload its file to.
    ErrorEnvelope:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
          description: Always `false` on an error response.
        data:
          nullable: true
          description: Always `null` on an error response.
        error:
          type: object
          properties:
            status:
              type: integer
              description: The HTTP status of the response.
            code:
              type: string
              description: >-
                Stable machine-readable code that names the refusal. Branch on
                this value.
            message:
              type: string
              description: >-
                Text for a person. It can change between versions: do not branch
                on it.
            retry_after:
              type: integer
              nullable: true
              description: >-
                Seconds to wait before the next attempt. Set on the `429`
                responses that know a delay, where the `Retry-After` header
                carries the same value. When it is above 60 seconds, do not wait
                in a request: schedule the call, or report the time. `null` on
                every other error.
            retry_at:
              type: string
              nullable: true
              description: >-
                ISO 8601 timestamp at which the refusal ends. Set only on `429
                rebind_cap_reached`. `null` on every other error.
            issues:
              type: array
              nullable: true
              items:
                type: object
                properties:
                  path:
                    type: array
                    items:
                      anyOf:
                        - type: string
                        - type: number
                    description: >-
                      Where the problem is: the field names and array indexes
                      that lead to it.
                  message:
                    type: string
                    description: Text for a person.
                  code:
                    type: string
                    description: >-
                      Kind of problem. On `validation_failed`, for example
                      `invalid_type`, `invalid_value`, `invalid_format`,
                      `invalid_union`, `invalid_key`, `invalid_element`,
                      `too_small`, `too_big`, `not_multiple_of`,
                      `unrecognized_keys` or `custom`. The list is open: read
                      `path` and `message`.
                  min:
                    type: integer
                    description: >-
                      Smallest accepted value. Present only on
                      `limit_out_of_bounds`, `mac_quantity_not_a_pack`,
                      `byop_reduction_strands_mac_sessions` and
                      `byop_reduction_strands_byod_lines`.
                  max:
                    type: integer
                    description: >-
                      Largest accepted value. Present only on
                      `limit_out_of_bounds` and
                      `mac_sessions_exceed_byop_lines`, and on the `too_big`
                      issue of an `Idempotency-Key` header longer than 255
                      characters.
                required:
                  - path
                  - message
                  - code
              description: >-
                Problems found in the request, one entry each. Set on most `400
                validation_failed` responses and on the refusals that name a
                field: a refused `handle` of `POST /v1/lookup` has `issues:
                null`. `null` on every other error.
          required:
            - status
            - code
            - message
            - retry_after
            - retry_at
            - issues
          description: >-
            What went wrong. The six fields are always present; `retry_after`,
            `retry_at` and `issues` are `null` when they do not apply.
        trace_id:
          type: string
          description: >-
            Trace id of the request that produced this response: 32 lowercase
            hexadecimal characters. Quote it when you report a problem to iSnap.
        request_id:
          type: string
          description: >-
            Id of the request that produced this response: `req_` followed by 25
            letters or digits.
      required:
        - success
        - data
        - error
        - trace_id
        - request_id
      description: The body of an error response.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        An API key created in the iSnap dashboard, or a trial key, sent as
        `Authorization: Bearer <key>`. An API key starts with `isnap_live_` or
        `isnap_test_`; both act on the same account, lines and billing. A trial
        key acts on its trial only.

````

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