# Attach uploaded files to an order

```http
PUT https://secure.yoprint.com/v2/api/store/{team_slug}/v2/sales_order/{hash_id}/media/attach
```

- API version: v2
- Operation ID: `v2_public_order_media_attach`
- Section: Sales › [Sales order files](https://developer.yoprint.com/reference/sales-order-files.md)
- Web page: https://developer.yoprint.com/reference/sales-order-files/v2-order-media-attach/
- Authentication: `Authorization: Bearer YOUR_API_KEY`

Attaches files you have already uploaded to a sales order or quote.

Upload each file first with the upload operation, then send the uploads' ids here. All of them are attached, or none.

The answer lists the attached files. `{hash_id}` is the order's `id` (for example `V4keXwwxMQyXw7`); the `ord_` unique key is not accepted here.

## Permission

Any member of the team.

## Business rules

- At least one id, no repeats.
- Each id must be a finished upload: "Please upload this media before attaching it to an order."

## Side effects

- Creates the files' previews and refreshes the order for people who have it open in the web app.

## Path parameters

- `team_slug` (string, required): Your team slug, shown as Team ID next to your API key in Profile. Example `your-team`.
- `hash_id` (string, required): The sales order's or quote's `id` (a hash id). The `ord_` unique key and the order number do not work here. Example `V4keXwwxMQyXw7`.

## Request body

Content type `application/json`, required.

Fields:

- `attachment` (array of objects, required): At least 1 item.
  - Each item:
    - `id` (string, required): The upload's `id`. Each id once.

### Example request

```bash
curl -X PUT 'https://secure.yoprint.com/v2/api/store/your-team/v2/sales_order/V4keXwwxMQyXw7/media/attach' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Accept: application/json' \
  -H 'X-Yoprint-Decimal-Format: exact-v1' \
  -H 'Content-Type: application/json' \
  -d '{
    "attachment": [
      {
        "id": "uAzgeGQMGvaVK3"
      }
    ]
  }'
```

## Response

`200` Successful response.

Fields:

- `data` (array of objects, required): The files attached by this call.
  - Each item:
    - `collection` (string, optional): `default` for an order's files; `mockup` or `production_file` for a job's.
    - `created_at` (string (date-time) | null, optional): When the file was uploaded (attaching it doesn't change this). ISO 8601 with offset, or null.
    - `dimensions` (object | null, optional): Pixel size of an image; null otherwise.
      - `height` (integer, optional)
      - `width` (integer, optional)
    - `file_name` (string, optional)
    - `id` (string, optional)
    - `is_image` (boolean, optional)
    - `mime_type` (string, optional)
    - `name` (string, optional)
    - `position` (integer | number | null, optional)
    - `preview_state` (string, optional): `ready`: previews work. `processing`: a large image whose preview is still being made. `too_large`: over 120 megapixels, no preview. `none`: not an image. One of: `"ready"`, `"processing"`, `"too_large"`, `"failed"`, `"none"`.
    - `show_in_pdf` (boolean | null, optional): Mockups: shown on the order PDF. Null on files that never had it set, such as an order's files.
    - `size` (integer | null, optional): Bytes.
    - `url` (string | null, optional): Download link.
    - `view_url` (string | null, optional)
- `status` (integer, required)
- `success` (boolean, required)

Example response:

```json
{
  "data": [
    {
      "collection": "default",
      "created_at": "2026-11-03T09:00:00+00:00",
      "file_name": "front-logo.png",
      "id": "uAzgeGQMGvaVK3",
      "is_image": true,
      "mime_type": "image/png",
      "name": "Front logo",
      "position": 1,
      "show_in_pdf": null,
      "size": 20480,
      "url": "https://example.com/files/front-logo.png"
    }
  ],
  "status": 200,
  "success": true
}
```

## Errors

- `401` The API key is missing or wrong (`unauthenticated`).
- `403` You are not allowed to do this. `api_access_not_in_plan`: your team's plan doesn't include API access (Pro and above, with an active subscription or trial), so every request answers this until the team upgrades. When your role lacks the permission the operation needs, the body is only `{"message": "Insufficient Permission"}`. Other refusals use the error envelope, with `unauthorized` or `user_is_not_authorized`.
- `404` Not found. `page_not_found`: the team slug is unknown, you are not a member of the team, or an id in the path is not one of this team's. `code: null`, with a message that starts "No query results for": there is no record with that id.
  - When: An upload id does not exist. Fix: Use the id the upload returned.
- `422` Validation failed, or the image was refused by the media rules: a mockup that is not an image a preview can be made of (`mockup_type_not_allowed`), or an image over a size limit (`image_not_allowed`), or a file that is a web page, a script or a program (`validation_failed` on `media`, or `upload_not_allowed`: "This type of file cannot be uploaded."); `error.message` is one sentence naming the limit.
  - When: No ids, a repeated id, or an id that is not an upload. Fix: Upload the file first and send its id once.

## Related

- [Upload a file](https://developer.yoprint.com/reference/uploads/v1-async-upload-store.md): `POST /v1/api/store/{team_slug}/async_upload`
- [List the files of an order](https://developer.yoprint.com/reference/sales-order-files/v2-order-media-index.md): `GET /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/media`
- [Delete a file from an order](https://developer.yoprint.com/reference/sales-order-files/v2-order-media-destroy.md): `DELETE /v2/api/store/{team_slug}/v2/sales_order/{hash_id}/media/{hash_id2}`
