Skip to main content
Every Filed API call is a single HTTP POST to the same endpoint, with a JSON body and a Bearer token. This page covers the request shape, a first request, variables, and the error model, so you can call any operation in the rest of these docs once you know how the wire looks.
https://router.apps.filed.com/graphql

Request anatomy

A request is one HTTP POST with a JSON body of two keys, query and variables, and two headers, Content-Type: application/json and Authorization: Bearer YOUR_WORKSPACE_TOKEN.
POST /graphql HTTP/1.1
Host: router.apps.filed.com
Content-Type: application/json
Authorization: Bearer YOUR_WORKSPACE_TOKEN

{
  "query": "<GraphQL document>",
  "variables": { }
}
query
String!
required
A GraphQL document: one (or more) query / mutation / fragment definitions. For a single operation you usually send one operation and omit the operation name. When you send several operations, give each a name and pass "operationName" alongside query and variables.
variables
Object
A JSON object of values for the operation’s $variables. Pass complex or user-supplied values here rather than string-interpolating them into query, so the GraphQL server validates their types and you avoid injection mistakes. Omit it (or send {}) for operations that take no arguments.
Authorization
String
required
Bearer followed by a workspaceToken from Authentication. Omit only for @public operations such as health and the token exchange.
Content-Type
String
required
application/json. The body is always JSON, never form-encoded.
A successful response is JSON with a data object (and, on partial failure, an errors array, see Error responses):
{
  "data": { },
  "errors": [ ]
}

A first request

Start with health. It is @public, so it needs no token, and it confirms the endpoint is reachable.
cURL
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -d '{ "query": "query Health { health { id ai platform } }" }'
{
  "data": {
    "health": {
      "id": "health",
      "ai": "ok",
      "platform": "ok"
    }
  }
}
Now add a token and run me to confirm the token works and see who you are in the workspace:
cURL
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 workspace { id name } } } }" }'
me returns the Me union; with a workspaceToken it resolves to WorkspaceUser, which carries your role and the workspace you are scoped to (see me for the full type and User member).

Variables

Pass $variables for any operation that takes arguments, so values are type-checked by the server instead of string-interpolated. The workspace.clients field takes a ClientFilters input, an offset, a limit, and a SortBy. Send the document once with $variables placeholders, then send the values in the variables JSON object.
query ListClients($filters: ClientFilters, $limit: Int, $sortBy: SortBy) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: $filters, limit: $limit, sortBy: $sortBy) {
          id
          name
          externalId
          status
        }
      }
    }
  }
}
cURL
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "query ListClients($filters: ClientFilters, $limit: Int, $sortBy: SortBy) { me { ... on WorkspaceUser { workspace { clients(filters: $filters, limit: $limit, sortBy: $sortBy) { id name externalId status } } } } }",
    "variables": {
      "filters": { "status": ["active"], "search": "jane" },
      "limit": 20,
      "sortBy": { "field": "createdAt", "order": "DESC" }
    }
  }'
Always send user-supplied or dynamic values through variables, never by splicing them into the query string. The server coerces and validates the types, and you avoid quoting bugs and injection risk.
See clients for the full ClientFilters input and tasks for TaskFilters. The same $variable pattern applies to every mutation in the API, for example createClient(input: $input).

Error responses

On failure the response is JSON with an errors array. Each entry has at least a message, and most also carry an extensions object with a code. When the operation failed before any data could be produced, data is null (or omitted); when a field resolver fails mid-operation, that field is null in data and the matching entry is in errors with a path pointing at it. The two error shapes you will see most often:

Validation error (malformed query)

A query that references a field the type does not have fails GraphQL validation before any resolver runs. data is omitted and errors carries one entry per invalid field, with extensions.code of GRAPHQL_VALIDATION_FAILED.
cURL
curl -X POST http://localhost:7020/graphql \
  -H "Content-Type: application/json" \
  -d '{ "query": "query { health { id ai platform thisFieldDoesNotExist } }" }'
{
  "errors": [
    {
      "message": "Cannot query field \"thisFieldDoesNotExist\" on type \"Health\".",
      "locations": [
        { "line": 1, "column": 33 }
      ],
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED"
      }
    }
  ]
}

Authentication error (no token)

A resolver that requires a token (everything except @public operations like health and the token exchange) returns UNAUTHENTICATED for that field. The field is null in data and errors carries the entry with a path naming the field.
cURL
curl -X POST http://localhost:7020/graphql \
  -H "Content-Type: application/json" \
  -d '{ "query": "query { me { __typename } }" }'
{
  "errors": [
    {
      "message": "UNAUTHENTICATED",
      "path": ["me"],
      "extensions": {
        "code": "UNAUTHENTICATED",
        "serviceName": "platform"
      }
    }
  ],
  "data": {
    "me": null
  }
}
The live router may also include a stacktrace in extensions for server-side errors. Treat it as debugging detail, not a stable contract: log it for troubleshooting, but key your retry / surface-error logic off extensions.code and the path.

Reading errors in a client

Handle errors defensively:
  1. Check errors first. If present, at least part of the operation failed.
  2. When data is null, the whole operation failed; surface the first errors[0].message.
  3. When data is present but a field is null, look up the matching path in errors to know which field failed and why.
  4. Branch on extensions.code for the common cases:
    • GRAPHQL_VALIDATION_FAILED - your query is wrong; do not retry, fix the document.
    • UNAUTHENTICATED - re-exchange your API key for a fresh workspaceToken (see Authentication) and retry once.
    • Other codes (for example INTERNAL_SERVER_ERROR) - safe to retry with backoff.
Always inspect errors before data. A non-empty errors array means the operation did not fully succeed, even when data looks populated, because individual fields can fail while the rest resolve.

Next steps

  • Authentication - get the workspaceToken you send as Bearer.
  • health and me - the two queries from the first-request example above, documented in full.
  • clients and tasks - the workspace-scoped resources you reach through me { ... on WorkspaceUser { workspace { ... } } }.
  • Uploading documents - the other wire (tus) you use to stage files before attaching them to a client.