Create a workspace-scoped API key in the Filed web app and exchange it for a
short-lived
workspaceToken. This is the only call you make without a Bearer
token.curl -X POST https://router.apps.filed.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation ExchangeApiKey($apiKey: String!) { exchangeSurfaceRefreshTokenForAccessTokens(refreshToken: $apiKey) { userToken workspaceToken } }",
"variables": { "apiKey": "YOUR_API_KEY" }
}'
Save the returned
workspaceToken; you send it as Authorization: Bearer YOUR_WORKSPACE_TOKEN on every later request. Full details, including access
levels and key expiry, are in Authentication.Run
me with the workspaceToken to confirm the token works and to
see which workspace it is scoped to. me returns the Me union, which resolves
to WorkspaceUser for a workspaceToken, so select fields with an inline
fragment:curl -X POST https://router.apps.filed.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
-d '{ "query": "query Me { me { __typename ... on WorkspaceUser { id role user { id name email } workspace { id name } } } }" }'
A successful response shows your workspace id and name. If you get an
UNAUTHENTICATED error, re-exchange your API key for a fresh token (they last
about 30 minutes).Filed does not accept file bytes over GraphQL. Stage each file with the resumable
tus upload endpoint first, then pass the returned upload ID to
createClient (next step). A minimal upload is two requests: a POST that
creates the upload and returns its location, then a PATCH that sends the bytes.# 1. Create the upload. Metadata values are base64-encoded.
# filename=w2_1040.pdf filetype=application/pdf intent=client-document
LOCATION=$(curl -sS -D - -o /dev/null -X POST https://web.apps.filed.com/api/uploads \
-H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
-H "Tus-Resumable: 1.0.0" \
-H "Upload-Length: $(wc -c < w2_1040.pdf)" \
-H "Upload-Metadata: filename dzJfMTA0MC5wZGY=,filetype YXBwbGljYXRpb24vcGRm,intent Y2xpZW50LWRvY3VtZW50" \
| tr -d '\r' | awk '/^Location:/ {print $2}')
UPLOAD_ID="${LOCATION##*/}"
# 2. Send the bytes.
curl -sS -X PATCH "$LOCATION" \
-H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
-H "Tus-Resumable: 1.0.0" \
-H "Upload-Offset: 0" \
-H "Content-Type: application/offset+octet-stream" \
--data-binary @w2_1040.pdf
The upload ID is the last path segment of the upload’s
Location URL. Keep
it for the next step. Full tus details, including chunking and retries, are in
Uploading documents.Pass the upload ID to
createClient to create a
client and kick off binder ingestion in one call. The mutation returns
{ client { id }, taskId }: the client.id identifies the new client, and the
taskId is the binder ingestion task you poll next.curl -X POST https://router.apps.filed.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
-d '{
"query": "mutation CreateClient($input: CreateClientInput!) { createClient(input: $input) { client { id name externalId status returnType taxYear } taskId } }",
"variables": {
"input": {
"name": "Jane Taxpayer",
"externalId": "PMS-10432",
"returnType": "F1040",
"taxYear": 2025,
"uploadIds": ["YOUR_UPLOAD_ID"]
}
}
}'
{
"data": {
"createClient": {
"client": {
"id": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
"name": "Jane Taxpayer",
"externalId": "PMS-10432",
"status": "active",
"returnType": "F1040",
"taxYear": 2025
},
"taskId": "018f9c2b-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
}
}
}
Save both
client.id and taskId. If you created the client without documents,
taskId is null and there is nothing to poll. Full input and return type
details are in Clients.Filed ingests the staged documents into the client’s binder as a background task
of type
BINDER. Read the task through the client’s tasks(type: BINDER) field
and poll until status is COMPLETED (or FAILED).curl -X POST https://router.apps.filed.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
-d '{
"query": "query ClientTaskStatus($clientId: ID!, $type: TaskType) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { tasks(type: $type) { id status startedAt completedAt errorMessage subTasks { type status } } } } } } }",
"variables": { "clientId": "YOUR_CLIENT_ID", "type": "BINDER" }
}'
{
"data": {
"me": {
"workspace": {
"clients": [
{
"tasks": [
{
"id": "018f9c2b-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
"status": "COMPLETED",
"startedAt": "2026-07-04T09:15:00.000Z",
"completedAt": "2026-07-04T09:17:42.000Z",
"errorMessage": null,
"subTasks": [
{ "type": "CONVERT_DOCUMENTS", "status": "COMPLETED" },
{ "type": "CLASSIFY_SUBDOCS", "status": "COMPLETED" },
{ "type": "EXTRACT_SUBDOCS", "status": "COMPLETED" },
{ "type": "EXPORT_AND_INDEX", "status": "COMPLETED" }
]
}
]
}
]
}
}
}
}
Poll on an interval (for example every few seconds) until
status is no longer
RUNNING. COMPLETED means the documents are filed in the binder; FAILED
means ingestion did not succeed, and errorMessage explains why.There is no
task(id:) query; you read a task through its client. Full task,
filter, and result type details are in Tasks.Next steps
- Learn the request anatomy and error model.
- Add more documents to an existing client with
addClientDocuments. - Manage clients (rename, archive, delete, assign) in the Clients reference.
- Trigger and follow other task types (
TAX_PREP,TAX_REVIEW,TAX_ADVISOR) in the Tasks reference.