Skip to main content
Filed does not accept file bytes over GraphQL. Instead, you upload each file to an upload endpoint, which returns an upload ID. You then pass those upload IDs to a GraphQL mutation: createClient to create a client with initial documents, or addClientDocuments to add documents to an existing client. Filed ingests the staged files into the client’s binder as a background task. There are two ways to upload. Both return an upload ID that you attach the same way:
  • Resumable upload (recommended) uses the tus protocol. It survives network drops and handles large files by chunking, so it is the right default for production integrations.
  • Direct upload is a single POST for small files, when you do not need resumability.

Endpoints

PurposeEndpoint
Resumable upload (tus)https://web.apps.filed.com/api/uploads
Direct uploadhttps://web.apps.filed.com/api/upload/direct
GraphQLhttps://router.apps.filed.com/graphql
Uploads and GraphQL are on different hosts. Send file bytes to an upload endpoint, then send the returned upload IDs to the GraphQL endpoint. Both use the same Authorization: Bearer YOUR_WORKSPACE_TOKEN (see Authentication).
The resumable endpoint speaks tus 1.0.0. A minimal upload is two requests: a POST that creates the upload and returns its location, then a PATCH that sends the bytes. Any tus client library works; the raw requests are shown so you can see exactly what is on the wire.

Create the upload

POST /api/uploads HTTP/1.1
Host: web.apps.filed.com
Authorization: Bearer YOUR_WORKSPACE_TOKEN
Tus-Resumable: 1.0.0
Upload-Length: 20480
Upload-Metadata: filename dzJfMTA0MC5wZGY=,filetype YXBwbGljYXRpb24vcGRm,intent Y2xpZW50LWRvY3VtZW50
Upload-Length
integer
required
The exact size of the file in bytes.
Upload-Metadata
string
required
Comma-separated key base64(value) pairs, per the tus protocol. Set filename and filetype (the MIME type). Optionally set intent to select the server’s validation policy: use client-document for client files. If intent is omitted, a default policy applies.
A successful create returns 201 Created with a Location header. The upload ID is the last path segment of that location.
HTTP/1.1 201 Created
Location: https://web.apps.filed.com/api/uploads/018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a

Send the bytes

PATCH the file to the location from the previous step.
PATCH /api/uploads/018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a HTTP/1.1
Host: web.apps.filed.com
Authorization: Bearer YOUR_WORKSPACE_TOKEN
Tus-Resumable: 1.0.0
Upload-Offset: 0
Content-Type: application/offset+octet-stream

<raw file bytes>
A successful PATCH returns 204 No Content. Large files can be sent in multiple PATCH chunks, advancing Upload-Offset each time; the Filed web app uses 5 MB chunks. A per-file size cap is enforced by the server.

Upload with a tus client

In practice, use a tus client library rather than hand-writing the POST and PATCH. It handles chunking, retries, and resuming after a network drop, and exposes the finished upload’s URL, whose last path segment is the upload ID.
import { Upload } from "tus-js-client";
import fs from "node:fs";

const path = "w2_1040.pdf";
const size = fs.statSync(path).size;

const upload = new Upload(fs.createReadStream(path), {
  endpoint: "https://web.apps.filed.com/api/uploads",
  uploadSize: size,
  chunkSize: 5 * 1024 * 1024,
  retryDelays: [0, 1000, 3000, 5000],
  headers: { Authorization: "Bearer YOUR_WORKSPACE_TOKEN" },
  metadata: {
    filename: "w2_1040.pdf",
    filetype: "application/pdf",
    intent: "client-document",
  },
  onError: (error) => {
    throw error;
  },
  onSuccess: () => {
    const uploadId = upload.url.split("/").filter(Boolean).pop();
    console.log("upload ID:", uploadId);
  },
});

upload.start();
tus-js-client also runs in the browser: pass a File object instead of a stream and omit uploadSize. Install the clients with npm i tus-js-client and pip install tuspy.

Direct upload

The direct upload endpoint is in development and not yet available. Use resumable uploads today. This section describes the intended shape; the exact request and response contract may change before release.
For small files where you do not need resumability, send the file bytes in a single POST to /api/upload/direct. It returns the same kind of upload ID that a resumable upload produces, which you attach to a client the same way.
POST /api/upload/direct HTTP/1.1
Host: web.apps.filed.com
Authorization: Bearer YOUR_WORKSPACE_TOKEN
Content-Type: application/pdf

<raw file bytes>
The response returns the upload ID in its JSON body. Pass that ID in uploadIds exactly as you would a resumable upload ID (see Attach the upload IDs below).
Because it is a single request with no chunking or resume, direct upload is best for small files. For large files or unreliable networks, prefer the resumable endpoint above.

Attach the upload IDs

Pass the collected upload IDs to a GraphQL mutation. To create a new client from the files, use createClient with uploadIds. To add files to an existing client’s binder, use addClientDocuments:
mutation AddClientDocuments($input: AddClientDocumentsInput!) {
  addClientDocuments(input: $input) {
    taskId
  }
}
{
  "input": {
    "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
    "uploadIds": ["018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a"]
  }
}
The mutation returns a taskId for the binder ingestion task. Filed converts, classifies, and files each document into the client’s binder in the background. Track it with the tasks API: poll the task until its status is COMPLETED.

Troubleshooting

POST returns 413 or the upload is rejected
  • The file exceeds the size cap for its intent, or its type is not allowed by that intent’s validation policy. Use intent=client-document for client files.
PATCH returns 409 or 404
  • The Upload-Offset does not match the server’s current offset, or the upload ID has expired. Re-create the upload with a fresh POST.
createClient / addClientDocuments rejects an upload ID
  • The upload was never completed (no successful PATCH), belongs to a different workspace, or has expired. Re-upload and use the new ID.