---
title: "Begin a file's upload into a draft"
method: POST
path: "/hd/desks/{desk_id}/drafts/{ticket_id}/attachments/initiate"
tags: ["hd"]
---

# Begin a file's upload into a draft

`POST /hd/desks/{desk_id}/drafts/{ticket_id}/attachments/initiate`

## Request

- Base URL: `https://api.sparkvault.com/v1`
- URL: `https://api.sparkvault.com/v1/hd/desks/{desk_id}/drafts/{ticket_id}/attachments/initiate`
- Auth: HTTP bearer
- TypeScript: <https://skmtc.dev/sparkvault/apis/sparkvault-api/docs/hd/desks/:desk_id/drafts/:ticket_id/attachments/initiate/post.ts> (a function with typed inputs; its response is checked against the schema)

Begin the upload of one file into one of the customer's own drafts, within its day. The file then goes to `forge_url` by the resumable transfer every SparkVault upload runs, and joins the request once the request is sent naming its `attachment_id`.

## Path parameters

- `desk_id` string, required
- `ticket_id` string, required

## Headers

- `X-SV-Client` 'help-desk-portal', required

## Request body

- object
  - `name` string, required: The file's name, which alone decides whether a request takes the file and what type it is stored under. A request takes images (.jpg, .jpeg, .png, .gif, .webp, .heic, .heif, .bmp, .tif, .tiff, .avif), documents (.pdf, .docx, .xlsx, .pptx, .txt, .csv, .tsv, .log, .json) and recordings (.mp4, .mov, .webm, .m4a, .mp3, .wav), and no other file: a name must end in one of those extensions, in any case, and be one a file can be stored under (no path separator, control or direction character, none of `:*?"<>|`, not only dots or spaces). The file is stored, served and downloaded as the type its extension registers, whatever type the browser reports for it.
  - `size_bytes` integer, required: The file's size in bytes: the most the upload stores.

## Response `200`

The upload, begun.

- object
  - `data` HelpDeskAttachmentUpload, required
    - `attachment_id` string, required: What the message that carries the file (an operator's reply, or a customer's reply or request as it is sent) names in `attachments` once the upload is stored.
    - `ingot_id` string, required
    - `forge_url` string, required: Where the browser streams the file (the SDK's resumable upload).
    - `expires_at` integer, required: When `forge_url` stops taking the upload (epoch seconds).
    - `max_bytes` integer, required
    - `pending_until` integer, required: When the upload, sent in no message by then, is deleted (epoch seconds, the server's clock): its reap timer fires. A message attaches it until thirty minutes before the upload is a day old.
    - `version` integer: An operator's upload only: the ticket's `version` once the upload is recorded on it, exactly one past the version it held then. An operator holding the version just before (theirs + 1 is this) carries this one on; any other value means the ticket changed meanwhile: load it again. A customer is answered none.
  - `meta` ResponseMeta: Envelope metadata present on every successful response.
    - `api_version` string
    - `request_id` string
    - `response_ms` integer
    - `timestamp` integer: Unix seconds.
    - `pools` object: Storage and bandwidth capacity snapshot for the account.
    - `billing` object
      - `past_due` boolean
    - `quota` object: This minute of the account request budget. Present on authenticated success responses that passed the throttle; absent otherwise.
      - `limit` integer
      - `used` integer
      - `remaining` integer
      - `resets_at` integer: Unix seconds.

## Other responses

- `400`: ATTACHMENT_REFUSED — the file's name is missing, too long or one a file cannot be stored under, or it ends in no extension a request accepts. A request accepts images (.jpg, .jpeg, .png, .gif, .webp, .heic, .heif, .bmp, .tif, .tiff, .avif), documents (.pdf, .docx, .xlsx, .pptx, .txt, .csv, .tsv, .log, .json) and recordings (.mp4, .mov, .webm, .m4a, .mp3, .wav), and no other file. `details.reason` names which. VALIDATION_ERROR — `size_bytes` is missing or not a whole number of bytes from 1, or the body is not a JSON object. CLIENT_HEADER_REQUIRED — see above.
- `401`: CUSTOMER_AUTH_REQUIRED — no bearer token, or one that is not a customer's of this desk now: unknown, expired, revoked, another desk's, or of the sign-in mode the desk no longer uses.
- `402`: PLAN_REQUIRED or QUOTA_EXCEEDED — the organization cannot take the file right now (its plan, its storage or its bandwidth), or the customer's upload allowance refuses it: `details.reason` is `customer_upload_limit` for files past it, and `customer_uploads_off` at a desk whose allowance is 0, which takes no files from customers. No figures are given.
- `404`: NOT_FOUND — no such request, draft or file is the customer's: one that does not exist and one of another customer read the same, byte for byte.
- `409`: CONFLICT — the request kept changing while this was saved. Send it again.
- `413`: PAYLOAD_TOO_LARGE — the file is larger than the most a customer's file may hold (`details.max_bytes`).
- `429`: RATE_LIMIT_EXCEEDED — the customer has begun too many uploads on the request this hour, holds the most files waiting to be sent on it, or has sent their files or their bytes for the day; the desk has taken its customers' bytes for the day; or this client address or customer has spent its reads at the desk for the minute. `Retry-After` carries the seconds until the budget reopens, and `details` carries `limit` and `resets_at`.
- `503`: HELP_DESK_UNAVAILABLE — the desk does not take customers right now, or this customer is refused. INTERNAL_ERROR — the identity service's keys could not be read to verify a Secure Identity bearer. BUDGET_UNAVAILABLE — a budget the request spends could not be counted, so the request was refused rather than let through uncounted; `Retry-After` carries the seconds to wait before trying again.

## Changes

- **2026-10-28** `a9a10e328ba5`: 32 info
  - the response property `error/details` became required for the status `400`
  - the response property `error/details` became required for the status `401`
  - the response property `error/details` became required for the status `402`
  - the response property `error/details` became required for the status `404`
  - …28 more
- **2026-10-28** `56d7c265a77a`: 8 breaking
  - the response property `error/details` became nullable for the status `400`
  - the response property `error/details` became nullable for the status `401`
  - the response property `error/details` became nullable for the status `402`
  - the response property `error/details` became nullable for the status `404`
  - …4 more
- **2026-10-27** `605eaf4267d2`: 1 warning
  - removed the request property `content_type`
- **2026-10-23** `ad4c62db5e08`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/sparkvault/apis/sparkvault-api/changes/hd/desks/:desk_id/drafts/:ticket_id/attachments/initiate/post.md)

---

[API](https://skmtc.dev/sparkvault/apis/sparkvault-api.md) - [All operations](https://skmtc.dev/sparkvault/apis/sparkvault-api/llms.txt) - [OpenAPI document](https://skmtc.dev/sparkvault/apis/sparkvault-api/revisions/97cfadccd0c0?raw)
