# Upload and use files

Upload bytes once, then use a stable file ID for downloads and attachments.

Use the file operations to bring a PDF, image or other document into Brigg. You receive a stable `file_id`; storage paths and provider credentials stay inside Brigg.

Give the API key access to `documents.CreateUpload`, `documents.GetUploadPart`, `documents.CompleteUpload` and the operations that will use the file. Choose an [idempotency key](https://developer.brigg.app/reliability) for each command and reuse it when retrying that command.

## Upload from your server

Download the [Node.js server helper](https://developer.brigg.app/client.mjs) into your integration as `brigg.mjs`. For TypeScript, also save the [type declarations](https://developer.brigg.app/client.d.mts) beside it as `brigg.d.mts`. It has no third-party dependencies. Use Node.js 22 or later and keep the API key on your server.

```typescript
import { createBriggClient } from "./brigg.mjs";

const brigg = createBriggClient({
  apiKey: process.env.BRIGG_API_KEY!,
  baseUrl: "https://api.brigg.app",
});

const file = await brigg.uploadFile("./Report.pdf", {
  contentType: "application/pdf",
  visibility: "workspace",
  requestId: "report-2026-11-upload",
});

console.log(file.file_id);
```

This streams the local file, handles transfers of any supported size, waits for scanning and returns the verified file metadata. You do not split files, store part receipts or handle temporary URLs. A `Blob` is also accepted; supply its `filename` in the options.

Keep `requestId` with your integration's job and reuse it with the **same unchanged file and options** after an interruption. The helper checks for an already completed transfer before sending bytes. A different file or an expired upload needs a new request ID. The returned `file_id` remains the same for that upload.

Pass an `AbortSignal` as `signal` to cancel a transfer or scan wait. Cancellation stops the client; use `documents.CancelUpload` if you also want to cancel the upload. Blocked scans, quota failures and other errors are returned to your code. `BriggApiError` includes `code`, `status`, `details`, the server's `requestId` and `retryAfter` when available.

The helper uses the operations below and the same permissions and limits. Bytes go directly to the transfer service, so a large document does not need to fit inside a JSON API request.

## Upload with HTTP

The following steps are useful when using another language or implementing resumable and parallel transfers yourself.

### Start an upload

Send the original filename, media type and exact byte length. A workspace-only call must choose `workspace` visibility. With member delegation, use `personal` to keep the resulting document in that member’s private files. Personal MCP connections may also choose `personal`.

```sh
curl 'https://api.brigg.app/v1/operations/documents.CreateUpload' \
  -H "Authorization: Bearer $BRIGG_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: agreement-upload-2026-11-10' \
  --data '{"filename":"Agreement.pdf","content_type":"application/pdf","size_bytes":48231,"visibility":"workspace"}'
```

This example's fictional PDF is 48,231 bytes. Use the size of your actual file. The response describes how to transfer it:

```json
{
  "upload_id": "upl_example",
  "state": "uploading",
  "expires_at": 1794387600000,
  "mode": "single",
  "part_size": 48231,
  "part_count": 1
}
```

An upload lasts 24 hours. Only the credential that created it may read its status, issue transfer URLs, complete it or cancel it. After completion, normal file and record permissions govern the file.

### Transfer the bytes

Request a transfer URL for part 1:

```sh
curl 'https://api.brigg.app/v1/operations/documents.GetUploadPart' \
  -H "Authorization: Bearer $BRIGG_API_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"upload_id":"upl_example","part_number":1}'
```

The response includes `url`, `method: "PUT"`, `content_type`, `size_bytes` and `expires_at`. Send the exact bytes to that URL with the returned media type. Do not send your Brigg API key to the transfer URL.

```sh
curl "$UPLOAD_URL" \
  -X PUT \
  -H 'Content-Type: application/pdf' \
  --data-binary '@Agreement.pdf'
```

A transfer URL is a temporary credential. Keep it out of logs and public pages. If it expires before the transfer finishes, request another URL for the same part and retry.

For `mode: "multipart"`, split the file into `part_size` byte chunks. The final chunk contains the remainder. Request and upload parts `1` through `part_count`. Brigg collects their transfer receipts when you complete the upload. Parts may be transferred in parallel and an interrupted part can be retried independently.

### Complete and scan

After all bytes have transferred, complete the upload with its ID. The same request works for single and multipart uploads:

```sh
curl 'https://api.brigg.app/v1/operations/documents.CompleteUpload' \
  -H "Authorization: Bearer $BRIGG_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: agreement-complete-2026-11-10' \
  --data '{"upload_id":"upl_example"}'
```

Existing clients may still send the optional `parts` array with part numbers and ETags. New integrations can omit it. An `upload_incomplete` error means one or more parts have not arrived with their expected byte lengths.

Brigg checks the byte length, file type and malware scan before creating the file. A refusal whose `details.error` is `file_scan_pending` means scanning is still running: wait the `retry_after` seconds in the error details, then retry completion with the same input and idempotency key. A blocked file cannot be completed.

Successful completion returns:

```json
{
  "file_id": "f_example",
  "filename": "Agreement.pdf",
  "content_type": "application/pdf",
  "size_bytes": 48231,
  "visibility": "workspace",
  "scan_status": "clean"
}
```

Repeating completion returns the same file. Store `file_id` in your integration. You can read the upload's state through `documents.GetUpload` or cancel an unfinished upload through `documents.CancelUpload`.

## Attach or download the file

[Attach document](https://developer.brigg.app/reference/documents/AttachDocument) accepts the completed `file_id` and the target record's `entry_id`. The key needs the attachment operation and access to the target resource type. Attaching a document follows Brigg's existing visibility rules; it does not grant arbitrary access to a private file.

Use [Get file](https://developer.brigg.app/reference/documents/GetFile) for current metadata and [Get file download](https://developer.brigg.app/reference/documents/GetFileDownload) for a temporary download URL. Each request checks current access. The download points to the verified version of the file, so an old upload URL cannot replace the bytes behind that download.

The server helper can obtain and use a fresh download URL in one call:

```typescript
import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";

const response = await brigg.downloadFile(file.file_id);
await pipeline(Readable.fromWeb(response.body), createWriteStream("./Downloaded.pdf", { flags: "wx" }));
```

This streams the download without buffering the whole file in memory. It checks current access each time and never sends the Brigg API key to the file-transfer service.

Next, [send a PDF for signature](https://developer.brigg.app/signing) or [import contacts](https://developer.brigg.app/imports).

See the [files and uploads reference](https://developer.brigg.app/reference/areas/documents/files-and-uploads) for all file operations.

## One document identity

Every document has a stable `file_id`, whether it is an uploaded file, collaborative text or a generated document. Find it through [List documents](https://developer.brigg.app/reference/documents/ListDocuments) or [Search documents](https://developer.brigg.app/reference/documents/SearchDocuments). [Get document](https://developer.brigg.app/reference/documents/GetDocument) returns its kind and supported reads and exports for the acting member.

| Document kind | Bring it into Brigg | Get its content out |
|---|---|---|
| `file` | Upload original bytes using the flow above. | Download the original bytes with GetFileDownload, or ExportDocument with `format: "original"`. |
| `text_document` | CreateTextDocument accepts Markdown. ConvertDocumentToTextDocument can copy a plain-text upload or a generated document's prose into a new text document. | ReadTextDocument returns current Markdown. ExportDocument renders Markdown, PDF or Word. |
| `generated_document` | Created in Brigg from a template or a document workflow. | ExportDocument renders PDF or Word from the selected saved state. |

Uploading a PDF or Word file preserves that file; it does not make the content collaboratively editable. Conversion to a text document supports plain text and generated prose, **not PDF or Word import**. Conversion creates a separate document and leaves the original unchanged; styling and layout are not copied into Markdown.

GetDocument and ExportDocument require [member delegation](https://developer.brigg.app/authentication#member-delegation). Use the same approved member when inspecting, exporting and downloading private content. Workspace credentials can still upload and download shared original files without delegation.

### Export and download

Inspect `capabilities.export.formats` before choosing a format. The public API uses the same `file_id` throughout; internal bundle and storage identifiers are unnecessary.

```typescript
const brigg = createBriggClient({
  apiKey: process.env.BRIGG_API_KEY!,
  baseUrl: "https://api.brigg.app",
  actAs: process.env.BRIGG_ACT_AS!,
});

const exported = await brigg.call("documents.ExportDocument", {
  file_id: "f_project_notes",
  format: "pdf",
}, { requestId: "project-notes-export-2026-11-10" });

const response = await brigg.downloadFile(exported.file_id);
await pipeline(Readable.fromWeb(response.body), createWriteStream("./Project-notes.pdf", { flags: "wx" }));
```

Use the stream imports shown in the download example above. `exported.source.file_id` identifies the unchanged source; `exported.file_id` identifies the downloadable result. A rendered export is a new private file owned by the acting member and is not automatically filed into Documents. Exporting `original` returns the original file ID and makes no copy.

The default revision is `saved`. For generated documents this excludes private drafts; if no saved state exists, you receive `no_saved_version`. Set `revision: "current"` explicitly to include the acting member's own draft. For collaborative text, both selections capture live content and fail if that content cannot be flushed. To export an exact historical state, pass an accessible `version_id` as `revision`; Brigg never silently substitutes another version.

The result's `source.revision` is an opaque content token; `source.version_id`, when present, is a historical version you can select later. They serve different purposes. Retrying with the same idempotency key returns the same export; a new snapshot needs a new key.

### Edit text without losing changes

[Read text document](https://developer.brigg.app/reference/documents/ReadTextDocument) returns current Markdown and a `revision`. Send that token as `expected_revision` with [Edit text document](https://developer.brigg.app/reference/documents/EditTextDocument) or [Replace text document](https://developer.brigg.app/reference/documents/ReplaceTextDocument). The comparison and edit happen atomically against live content. If someone edits first, you receive `409 revision_conflict` and your change is not applied.

Re-read, reconcile your edit, then submit a new command with a new idempotency key. Omitting `expected_revision` deliberately edits the latest content; a full replacement can then overwrite intervening changes. A revision token is not a history version ID.

## Private files

To upload into a member's private files, use a credential with [member delegation](https://developer.brigg.app/authentication#member-delegation), set the helper's `actAs` to their account ID, and choose `visibility: "personal"`. Keep that identity for all calls in the upload. A different member or credential cannot take over an open upload. Workspace uploads continue to use `visibility: "workspace"`.

