# Attach uploaded files to a job

```http
PUT https://secure.yoprint.com/v1/api/store/{team_slug}/sales_order/{hash_id}/job/{id}/attach
```

- API version: v1
- Operation ID: `v1_public_sales_order_job_attach`
- Section: Production › [Jobs](https://developer.yoprint.com/reference/jobs.md)
- Web page: https://developer.yoprint.com/reference/jobs/v1-sales-order-job-attach/
- Authentication: `Authorization: Bearer YOUR_API_KEY`

Attaches files you uploaded first to a job, as mockups or production files. `{id}` is the job's `id`.

There is no separate v2 operation for this; in v2 you can also send files when you create or update a job. The answer is the job. Each job file carries its latest approval in `current_approval`; add `?omit_media_approval_items=1` to leave out that approval's `items` when you don't need them. `{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

- `collection` is `mockup` or `production_file`; mockups also need `show_in_pdf`.
- Each id must be a finished upload that is not attached anywhere yet.

## 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`.
- `id` (string, required): The job's `id` (a hash id). Example `K6iF8n29iwOZnj`.

## Query parameters

- `omit_media_approval_items` (string, optional): Send `1` (or `true`, `on`, `yes`) to leave `items` out of each job file's `current_approval`, which makes the answer much smaller for jobs with many files on one approval. Every other key stays, in the same order, and the job's `pending_approval` keeps its items. Leave it out and the answer is unchanged. Only the query string counts: a body field of this name is ignored. One of: `"1"`, `"true"`, `"on"`, `"yes"`. Example `1`.

## Request body

Content type `application/json`, required.

Fields:

- `attachment` (array of objects, required): At least 1 item.
  - Each item:
    - `collection` (string, required): One of: `"mockup"`, `"production_file"`.
    - `id` (string, required): The upload's `id`.
    - `position` (number, required): Its place among the job's files.
    - `job_service` (any, optional): The job service id (or ids) the file belongs to.
    - `name` (string, optional)
    - `show_in_pdf` (boolean, optional): Required for mockups: show it on the order PDF.

### Example request

```bash
curl -X PUT 'https://secure.yoprint.com/v1/api/store/your-team/sales_order/V4keXwwxMQyXw7/job/K6iF8n29iwOZnj/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": [
      {
        "collection": "mockup",
        "id": "n5C1IPCmxDAqfy",
        "position": 1,
        "show_in_pdf": true
      }
    ]
  }'
```

## Response

`200` Successful response.

Fields:

- `data` (object, required): A job.
  - `id` (string, required)
  - `order_id` (string, required)
  - `scoped_id` (string, required): The job number people see, such as `JOB-1001`.
  - `status_id` (string, required): The job's production status.
  - `assigned_users` (array of objects, optional)
    - Each item:
      - `avatar_url` (string | null, optional)
      - `email` (string, optional)
      - `id` (string, optional)
      - `name` (string, optional)
      - `timezone` (string | null, optional): IANA time zone, for example `America/Chicago`.
  - `due_date` (string (date-time) | null, optional): Production due. ISO 8601 with offset, or null.
  - `impression_count` (string | number | integer | null, optional): Item count times decoration locations.
  - `inventory_status` (string | null, optional)
  - `is_rush` (boolean, optional)
  - `item_count` (string | number | integer | null, optional): Total quantity of the job's lines (a decimal string or number).
  - `mockup` (array of objects, optional): Mockups.
    - 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.
      - `current_approval` (object | null, optional): The latest approval this file was sent in, as the approval operations return it, or null. When you send `omit_media_approval_items=1` in the query string it has no `items` key; every other key stays.
        - `id` (string, required)
        - `status` (string | null, required): `pending` (created, not emailed), `sent-to-customer`, `viewed-by-customer`, `need-changes` or `completed` (approved). When it was completed without a `status`, the Record answer shows `null` and later reads show `""`.
        - `type` (string, required): `order` for a whole-order approval, `job` for an artwork approval. One of: `"order"`, `"job"`.
        - `approved_by` (string, optional): `contact` when a contact of the order's customer answered in the customer portal (see `approved_by_contact_id`), `user` when a team member recorded the answer (as Record the customer's answer does), `customer` otherwise, including before any answer. One of: `"contact"`, `"user"`, `"customer"`.
        - `approved_by_contact_id` (string | null, optional): The contact who answered in the customer portal, or null (always null when a team member recorded the answer).
        - `approved_by_user` (string | null, optional): The team member who recorded the answer, or null.
        - `completed` (boolean, optional): true once the customer (or you, for them) answered: approved or changes needed.
        - `completed_at` (string (date-time) | null, optional): When the customer answered. ISO 8601 with offset, or null.
        - `created_at` (string (date-time) | null, optional): Created. ISO 8601 with offset, or null.
        - `customer_last_viewed_contact_id` (string | null, optional): The contact who last opened it in the customer portal, or null when that isn't known.
        - `customer_last_viewed_date` (string (date-time) | null, optional): When the customer last opened it. ISO 8601 with offset, or null.
        - `items` (array of objects, optional): Job approvals: the mockups under review.
        - `job_id` (string | null, optional)
        - `notes_from_customer` (string | null, optional)
        - `notes_to_customer` (string | null, optional)
        - `order_id` (string, optional)
        - `order_scoped_id` (string, optional)
        - `signature` (string | null, optional): The signer's typed name.
      - `current_approval_status` (string | null, optional): `pending-approval` while the latest approval this file was sent in is open; once that approval is answered, this file's own result from it; null when the file was never sent for approval.
      - `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)
      - `job_id` (string, optional): The job.
      - `job_service` (array of strings, optional): The ids of the job's decoration services this file belongs to.
      - `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)
  - `name` (string | null, optional)
  - `note` (string | null, optional)
  - `order_scoped_id` (string, optional): The order number.
  - `pending_approval` (object | null, optional): The job's open approval (the first one not yet answered), or null. It always keeps its `items`, with or without `omit_media_approval_items`. Same fields as `data.mockup[].current_approval` above.
  - `pipelines` (array of objects, optional): Work pipelines (production steps).
  - `position` (integer | null, optional)
  - `pricing_original_total` (decimal pair, optional): The same before pricing rules. A decimal pair: the amount is `value` ÷ 10^`dp` (`{ "value": 12500000, "dp": 6 }` is 12.50). Kept with 6 decimal places; an amount never set can come as `{ "value": 0, "dp": 0 }`. Read `dp` every time: it can differ between fields and records. See https://developer.yoprint.com/concepts/money-and-quantities.md.
  - `pricing_rule_applied` (object | null, optional): The pricing rule that set the job's decoration price, or null when no rule did. List the jobs of a sales order or quote, Get one job and Rearrange the jobs answer with the full summary below. Other answers carry only `type`, the two ids and the two totals.
    - `after` (string, optional): Full summary only: the price after the rule, as a decimal string.
    - `after_total` (decimal pair, optional): The job's decoration price after the rule. 6 decimal places. A decimal pair: the amount is `value` ÷ 10^`dp` (`{ "value": 1250, "dp": 2 }` is 12.50). Read `dp` every time: it can differ between fields and records. See https://developer.yoprint.com/concepts/money-and-quantities.md.
    - `base` (string, optional): Full summary only: the decoration price the rule started from, as a decimal string.
    - `base_type` (string, optional): Full summary only: always `service_price` on a job. One of: `"service_price"`.
    - `normal_price` (string | null, optional): Full summary only: the price with no rule; on a job the same as `base`.
    - `original_total` (decimal pair, optional): The job's decoration price before the rule. 6 decimal places. A decimal pair: the amount is `value` ÷ 10^`dp` (`{ "value": 1250, "dp": 2 }` is 12.50). Read `dp` every time: it can differ between fields and records. See https://developer.yoprint.com/concepts/money-and-quantities.md.
    - `pricing_group_id` (string | null, optional): The pricing group whose rule applied.
    - `pricing_group_name` (string | null, optional): Full summary only: the group's name now.
    - `pricing_group_removed` (boolean | null, optional): Full summary only: true when the group was removed since. The job keeps its price.
    - `pricing_rule_id` (string | null, optional): The rule that applied.
    - `rule` (object | null, optional): Full summary only: the rule as the group holds it now, with its current name and position (1 is the first rule checked). Null when the rule no longer exists.
      - `id` (string, optional)
      - `label` (string | null, optional)
      - `position` (integer, optional)
    - `type` (string, optional): `service` on a job.
  - `pricing_total` (decimal pair, optional): Decoration price per unit that the job adds to each of its lines. A decimal pair: the amount is `value` ÷ 10^`dp` (`{ "value": 12500000, "dp": 6 }` is 12.50). Kept with 6 decimal places; an amount never set can come as `{ "value": 0, "dp": 0 }`. Read `dp` every time: it can differ between fields and records. See https://developer.yoprint.com/concepts/money-and-quantities.md.
  - `production_file` (array of objects, optional): Production files. Same fields as `data.mockup` above.
  - `sales_order` (object, optional): A summary of the job's order.
    - `is_rush` (boolean, optional)
    - `name` (string | null, optional)
    - `object_type` (string, optional)
    - `status_id` (string | null, optional)
  - `services` (array of objects, optional): Decoration services: `service_id`, `location_*`, `option`, `position`.
  - `services_still_valid` (boolean, optional): false when the job's decoration services need repair.
  - `start_date` (string (date-time) | null, optional): Production start. ISO 8601 with offset, or null.
  - `status` (object | null, optional): A status, as the status operations return it.
    - `color` (string, required): A hex color, such as `#2196f3`.
    - `customer_label` (string, required): What customers see in the customer portal. When none is set: `Pending` for `start`, `In Progress` for `in_progress` and `pre_production`, `Completed` for `end`.
    - `id` (string, required)
    - `label` (string, required): The status name people see.
    - `position` (integer, required): Sort order within its list, lowest first.
    - `role` (string, required): Which list the status belongs to: `order`, `quote`, `job`, `shipment`, `purchase_order`, `pipeline` or `customer_message`.
    - `trashed` (boolean, required): true for a deleted status.
    - `type` (string, required): `start` is the first status of its list and `end` the last; `pre_production` and `in_progress` are the ones in between. One of: `"start"`, `"pre_production"`, `"in_progress"`, `"end"`.
    - `pipeline_id` (string, optional): The task preset, for `pipeline` statuses only; absent for other roles.
  - `unique_key` (string | null, optional)
- `status` (integer, required)
- `success` (boolean, required)

Example response:

```json
{
  "data": {
    "assigned_users": [],
    "id": "K6iF8n29iwOZnj",
    "is_rush": false,
    "mockup": [
      {
        "collection": "mockup",
        "created_at": "2026-11-03T09:00:00+00:00",
        "current_approval": null,
        "current_approval_status": null,
        "file_name": "front-logo.png",
        "id": "n5C1IPCmxDAqfy",
        "is_image": true,
        "job_id": "K6iF8n29iwOZnj",
        "job_service": [],
        "mime_type": "image/png",
        "name": "Front logo",
        "position": 1,
        "show_in_pdf": true,
        "size": 20480,
        "url": "https://example.com/files/front-logo.png"
      }
    ],
    "name": "Front logo",
    "order_id": "V4keXwwxMQyXw7",
    "order_scoped_id": "SO1001",
    "pipelines": [],
    "pricing_total": {
      "dp": 6,
      "value": 0
    },
    "production_file": [],
    "scoped_id": "JOB-1001",
    "services": [],
    "status": {
      "color": "#9e9e9e",
      "customer_label": "Pending",
      "id": "YOqAGjroQEo2ZK",
      "label": "Artwork",
      "position": 1,
      "role": "job",
      "trashed": false,
      "type": "start"
    },
    "status_id": "YOqAGjroQEo2ZK"
  },
  "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: The job or an upload does not exist. Fix: Check the ids.
- `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: A required field is missing. Fix: Send `collection`, `position` and, for mockups, `show_in_pdf`.
- `500` A business rule refused the request (see the operation's errors), or something failed inside YoPrint. Refusals use the error envelope with a code such as `action_not_allowed`; an unexpected failure answers `{"message": "Server Error"}`. Repeating a refusal gets the same answer; after an unexpected failure, read the record back before you retry a change.
  - When: An id is a file already attached to something else, not a fresh upload (`{"message": "Server Error"}`). Fix: Upload the file again and send the new id.

## Notes

- The job is found by its id alone; make sure it belongs to the order in the path.
- Files are attached one at a time: when one fails, the ones before it stay attached.
- A file already on this job is not attached twice; its `show_in_pdf`, `name` and position are updated from the entry.

## Related

- [Upload a file](https://developer.yoprint.com/reference/uploads/v1-async-upload-store.md): `POST /v1/api/store/{team_slug}/async_upload`
- [Reorder the files of a job](https://developer.yoprint.com/reference/jobs/v1-sales-order-job-rearrange.md): `PUT /v1/api/store/{team_slug}/sales_order/{hash_id}/job/{id}/rearrange`
- [Update a job](https://developer.yoprint.com/reference/jobs/v2-job-update.md): `PUT /v2/api/store/{team_slug}/v2/job/{hash_id}`
