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

# Upload Large File

> Upload files up to 200 MB. The client uploads the file body directly to Google Cloud Storage with a short-lived signed PUT URL, then registers the upload with the API.

<Info>
  Use this endpoint whenever the file is larger than 32 MB. For files up to 32 MB, the single-shot [POST /files](/en/upload-file) endpoint is simpler and still supported.
</Info>

### How it works

The file body is uploaded **directly from the client to Google Cloud Storage** using a short-lived V4-signed PUT URL — the bytes never pass through the API server, which lets the endpoint accept files up to 200 MB.

A single upload is three calls from the client:

1. **`POST /v2/files/sign`** — the API mints a signed GCS PUT URL valid for \~15 minutes and returns the headers the client must echo on the PUT.
2. **`PUT`** the file body directly to the returned `uploadUrl`. The PUT carries **exactly** the headers from `requiredHeaders` — no more, no less — they are bound into the V4 signature, so a missing, extra, or different header makes GCS reject the PUT with `403`.
3. **`POST /v2/files/register`** — the API verifies the uploaded object's size against what you declared at `/sign`, moves it server-side from the temporary staging area to its final location, deduplicates by content hash, and returns the standard `FileDTO` (same shape as [POST /files](/en/upload-file)).

### Step 2 — direct PUT to Google Cloud Storage

Send exactly the headers returned in `requiredHeaders` (currently `Content-Type` and `x-goog-if-generation-match`):

```http cURL theme={null}
curl --request PUT \
  --url 'PASTE_uploadUrl_HERE' \
  --header 'Content-Type: application/pdf' \
  --header 'x-goog-if-generation-match: 0' \
  --upload-file './report.pdf'
```

GCS returns `200` on success. The `x-goog-if-generation-match: 0` header makes the PUT **create-only** — replays return `412 Precondition Failed`.

### Step 3 — register the upload

After the PUT succeeds, finalize the upload. The declared size does not need to be sent again — the server remembers the `size` you declared at `/sign` (for \~16 minutes) and compares it against the actual uploaded object:

```http cURL theme={null}
curl --request POST \
  --url 'https://api.tess.im/v2/files/register' \
  --header 'Authorization: Bearer <TOKEN>' \
  --header 'x-workspace-id: YOUR_WORKSPACE_ID' \
  --header 'Content-Type: application/json' \
  --data '{
    "object_path": "<objectPath from /sign>",
    "filename": "report.pdf",
    "content_type": "application/pdf",
    "process": false
  }'
```

Returns `201` with the standard `FileDTO`. If a file with identical content already exists in the workspace, the API returns `200` with the existing file's `FileDTO` instead of creating a duplicate. The optional `process` field works exactly like on [POST /files](/en/upload-file).

### Supported files

Same as [POST /files](/en/upload-file) — Text, Word, Spreadsheet, PDF, Excel, PowerPoint, Image, Video, Audio, and 30+ code extensions.

### Limits

* Maximum file size per upload: **200 MB**
* One file per flow (run the three steps again for additional files)
* Storage limit: 30 files

### Errors

| Status | Meaning                                                                                                                                                   |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `/register` validation failed (missing field or malformed `object_path`)                                                                                  |
| `401`  | Missing or invalid Bearer token                                                                                                                           |
| `403`  | Caller lacks API permission for the target workspace — or, on the GCS `PUT`, the headers do not match `requiredHeaders`                                   |
| `404`  | Upload session expired (\~15-minute window) or unknown, or the object was never uploaded — start again at `/sign`                                         |
| `412`  | On the GCS `PUT`: the object already exists (the signed URL is create-only)                                                                               |
| `415`  | Unsupported `content_type` at `/register`                                                                                                                 |
| `422`  | `/sign` validation failed (missing field or `size` > 200 MB) — or the uploaded object is larger than the size declared at `/sign` (the object is deleted) |
| `429`  | Throttled                                                                                                                                                 |

### **Headers**

<ParamField header="x-workspace-id" type="integer" required>
  Workspace ID. **Required as of 2026-09-01.** Until then, if omitted, the user's selected workspace is used (deprecated). After the cutoff, a missing header returns **422**.
</ParamField>


## OpenAPI

````yaml api-reference/upload-file-v2-sign.openapi.json POST /v2/files/sign
openapi: 3.1.0
info:
  title: Tess API - Upload Large File
  version: 1.0.0
servers:
  - url: https://api.tess.im
security: []
paths:
  /v2/files/sign:
    post:
      summary: 'Upload Large File - Step 1: Sign'
      description: >-
        Step 1 of the Upload Large File flow. Mints a short-lived (~15 min)
        Google Cloud Storage V4-signed PUT URL that the client uses to upload
        the file body directly to GCS. Returns the upload URL, the object path,
        and the exact headers the client must echo on the subsequent PUT (these
        headers are bound into the V4 signature - missing, extra, or different
        headers cause GCS to reject the PUT with 403). After the PUT succeeds,
        call POST /v2/files/register to finalize the upload and receive the
        standard FileDTO.
      parameters:
        - name: x-workspace-id
          in: header
          required: true
          description: >-
            Workspace ID. Required as of 2026-09-01. Until then, if omitted, the
            user's selected workspace is used (deprecated). After the cutoff, a
            missing header returns 422.
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - filename
                - content_type
                - size
              properties:
                filename:
                  type: string
                  description: >-
                    Original filename including extension (e.g. report.pdf).
                    Used to derive the stored extension.
                content_type:
                  type: string
                  description: >-
                    MIME type of the file. Bound into the signed URL - the
                    client MUST PUT with exactly this Content-Type header.
                size:
                  type: integer
                  description: >-
                    Declared file size in bytes. The server remembers this value
                    and /register rejects the upload if the actual uploaded
                    object is larger than declared. Hard maximum: 200 MB
                    (209715200 bytes).
      responses:
        '200':
          description: Signed PUT URL minted.
          content:
            application/json:
              schema:
                type: object
                properties:
                  uploadUrl:
                    type: string
                    format: uri
                    description: >-
                      Pre-signed Google Cloud Storage PUT URL. Valid for ~15
                      minutes.
                  objectPath:
                    type: string
                    description: >-
                      GCS object path the file will land at. Pass this back to
                      POST /v2/files/register.
                  requiredHeaders:
                    type: object
                    additionalProperties:
                      type: string
                    description: >-
                      Headers the client MUST send on the PUT - bound into the
                      V4 signature. Contains exactly Content-Type (the value you
                      declared) and x-goog-if-generation-match: 0 (create-only).
                      Send these headers verbatim and do not add others.
                  finalUrl:
                    type: string
                    format: uri
                    description: >-
                      Short-lived signed download URL (~3 hours) for the
                      uploaded object. For a durable reference, use the url
                      field of the FileDTO returned by POST /v2/files/register.
        '401':
          description: Missing or invalid Bearer token.
        '403':
          description: Caller does not have API permission for the target workspace.
        '422':
          description: >-
            Validation failed (missing field, or size > 200 MB / 209715200
            bytes).
        '429':
          description: Throttled.
      security:
        - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````