1. Documents
  2. Uploading Documents

Documents

Uploading Documents

POST /v1/documents creates a document and supports two upload modes based on whether the content field is provided.

Sync Upload

Include the file as a base64 data-URI in content. The API decodes the buffer, uploads to GCS immediately, and returns the document with upload_status: "completed". A single API call completes the upload.

js
        const response = await fetch("/v1/documents", {
  method: "POST",
  headers: { "X-API-KEY": apiKey, "Content-Type": "application/json" },
  body: JSON.stringify({
    filename: "invoice-2026-04.pdf",
    display_name: "Invoice April 2026",
    category: "invoice",
    visibility: "private",
    content: "data:application/pdf;base64,JVBERi0xLjQK...",
    metadata: { po_number: "PO-12345" },
  }),
});

const { data: doc } = await response.json();
// doc.upload_status === "completed"
// doc.file.url is the GCS URL (private, returns 403 without auth)

      

The MIME type is extracted from the data-URI prefix — no separate mime_type field is needed.

Best for: Files already in memory as base64, small-to-medium sizes, or when you prefer a single round-trip.

Async Upload

Omit content and provide mime_type instead. The API creates a requested DB record and returns a signed GCS PUT URL. The client PUTs the file bytes directly to GCS — the API server never proxies the bytes.

js
        // Step 1 — create the requested record
const createRes = await fetch("/v1/documents", {
  method: "POST",
  headers: { "X-API-KEY": apiKey, "Content-Type": "application/json" },
  body: JSON.stringify({
    filename: "contract.pdf",
    display_name: "Service Contract",
    category: "contract",
    mime_type: "application/pdf",
    visibility: "private",
  }),
});
const { data: doc } = await createRes.json();
// doc.upload_status === "requested"
// doc._upload_url — signed PUT URL, valid for 1 hour

// Step 2 — PUT file bytes directly to GCS (no X-API-KEY needed)
await fetch(doc._upload_url, {
  method: "PUT",
  headers: { "Content-Type": "application/pdf" },
  body: fileBytes,
});

// Step 3 — trigger verification (empty body is fine)
await fetch(`/v1/documents/${doc.id}`, {
  method: "POST",
  headers: { "X-API-KEY": apiKey, "Content-Type": "application/json" },
  body: JSON.stringify({}),
});

      

Best for: Large files, browser-based uploads, or when you want to stream bytes directly to GCS.

Upload Status

The upload_status field tracks the state of the upload.

Status Description
requested Async upload: DB record created, awaiting GCS upload
completed File confirmed in GCS (sync always lands here; async after verification)
failed 5 consecutive verification attempts failed - create a new document
expired Upload window has expired without a successful upload

Verification behavior

When POST /v1/documents/:id is called on a requested document, the API does a GCS HEAD check:

  • File is present → upload_status transitions to "completed". file.size and file.mime_type are populated from GCS metadata.
  • File not yet present → 422 "File not yet available in storage". The internal counter increments.
  • Counter reaches 5 → upload_status transitions to "failed" and 422 "Upload window expired. Create a new document." is returned. A new document must be created.

Request Fields

Field Type Required Notes
filename string yes 1-255 chars. No /, \, or ..
category DocumentCategory yes See enum reference
display_name string no 3-128 chars. Stored as file.name. Defaults to filename
visibility "public" | "private" no Defaults to "private"
content string one of Base64 data-URI for sync upload
mime_type string one of Required when content is absent (async mode)
checksum_md5 string no Client-computed MD5
checksum_sha256 string no Client-computed SHA-256
metadata object no Arbitrary key/value pairs
resources object no Resource IDs to link atomically. See Linking

Linking Resources at Creation

Pass a resources object to create links atomically with the document:

js
        {
  "filename": "pod.jpg",
  "category": "photo",
  "content": "data:image/jpeg;base64,...",
  "resources": {
    "add": ["ship_abc123", "ast_xyz789"]
  }
}

      

The resource type is inferred from the ID prefix. See Links for all accepted prefixes and the add/remove/set pattern.