# Upload

An upload ticket: the server's permission for you to place one file's bytes in storage, and the record of what arrived. Create one against the record you want to attach to, PUT the raw bytes to `upload_url`, then POST its `id` as `upload_id` to that record's file collection to turn the bytes into a file. Tickets are yours alone, are single-use, expire, and can be deleted while unregistered.

## Used by
- POST /api/v2/drive-items/{drive_item}/uploads: Start a file upload on a drive item (Upload)
- GET /api/v2/uploads/{upload}: Read an upload ticket (Upload)
- POST /api/v2/work-items/{work_item}/uploads: Start a file upload on a work item (Upload)

## Upload fields
- id (integer, required): Unique identifier. Send this as `upload_id` when you register the bytes.
- work_item_id (integer or null, required): The work item this ticket attaches to, or `null` when it targets a drive item.
- drive_item_id (integer or null, required): The drive item this ticket attaches to, or `null` when it targets a work item.
- collection (string ≤ 255, required): The media collection the file will land in. Always `attachments`: it is chosen by the route, and no request field can change it.
- file_name (string ≤ 255, required): The sanitized file name the file will carry. Sent when the ticket is created; control characters are stripped and `#`, `/`, `\` and spaces become hyphens, so what you read back can differ from what you sent.
- byte_size (integer, required): The size you declared when creating the ticket. A declaration, not a bound: the server counts the bytes it actually receives.
- checksum_sha256 (string or null, required): The optional SHA-256 you declared when creating the ticket. When present the server refuses bytes that do not match it.
- received_byte_size (integer or null, required): The number of bytes that actually arrived, measured by the server. Null until the upload lands.
- received_checksum_sha256 (string or null, required): The SHA-256 the server computed over the bytes it received. Null until the upload lands.
- mime_type (string or null ≤ 255, required): The media type detected from the received bytes. Detected, never declared: the API accepts no content type when creating the ticket and ignores the `Content-Type` of the upload itself.
- status (string, required): Where the ticket is in its lifecycle.
- upload_url (string or null, required): Where to PUT the file. An opaque absolute URL: send the raw file as the body of a PUT whose only header is your Authorization bearer token. No `Content-Type` is read and the body is stored exactly as sent, so a form envelope would become part of the file. Present only while status is `pending`.
- expires_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): The deadline for the PUT. After it the ticket is dead and a new one must be created.
- register_by (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required): The deadline for the registration, set when the bytes land and measured from that moment, so a slow upload does not eat into the time you have to register. Null until the upload lands.
- uploaded_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required): When the bytes arrived. Null until they do.
- committed_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required): When the bytes became a file. Null until they do.
- media_id (integer or null, required): The id of the file this ticket became. Null until it is registered, and set exactly once.
- created_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): When the ticket was created.
- updated_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Last-write timestamp; use for incremental sync.
- links (UploadLinks, required)
  - self (url, required): The canonical URI of this record.

## Variants

### UploadList

What a collection returns, and what `?view=summary` returns on a single record.

20 fields.
Identical to the detail object above.

### UploadLinks

The `links` object on this resource.

Keys: self

## Columns deliberately not published
- created_by_id: A ticket is bound to its issuer and a caller only ever sees their own.
- disk: Storage location chosen by plnnrly; a caller-chosen one could write outside your organisation.
- model_id: The other half of that target reference, withheld with it.
- model_type: Published as `work_item_id` or `drive_item_id`, the target the ticket was created on.
- organization_id: The organisation is implied by the host you authenticated against.
- permission_prefix: The permission family the ticket was created under; checked again on every step.
- storage_key: Server-derived storage key; publishing it would let a caller reach the staged bytes directly.
- storage_token: The unguessable segment of the storage key; it is never disclosed.
- ulid: The secret part of `upload_url`; `id` is the ticket's public identifier.

## Example
```json
{
  "id": 1,
  "work_item_id": 1,
  "drive_item_id": 1,
  "collection": "…",
  "file_name": "quarterly-report.pdf",
  "byte_size": 184320,
  "checksum_sha256": "…",
  "received_byte_size": 1,
  "received_checksum_sha256": "…",
  "mime_type": "…",
  "status": "pending",
  "upload_url": "…",
  "expires_at": "2026-03-04T09:15:00Z",
  "register_by": "2026-03-04T09:15:00Z",
  "uploaded_at": "2026-03-04T09:15:00Z",
  "committed_at": "2026-03-04T09:15:00Z",
  "media_id": 1,
  "created_at": "2026-03-04T09:15:00Z",
  "updated_at": "2026-03-04T09:15:00Z",
  "links": {
    "self": "https://<your-url>/api/v2/work-items/1"
  }
}
```
