Appearance
API
Table of Contents
Factorial's API supports deterministic integrations and data changes outside the user interface. It provides nearly all functionality available through the Factorial application. See Integrations for guidance on combining the API with Factorial's pollable event stream.
API Keys
Factorial authenticates API requests with API keys. Create a key from the Keys page, copy it when it is displayed, and send it in the HTTP Authorization header:
http
Authorization: Bearer <api-key>Factorial displays the secret only when the key is created and stores a hash rather than the original value. Keys expire after three months and may be revoked from the Keys page. Use the key's description to identify its purpose, owner, and environment.
Treat an API key like a password. Store it in a secrets manager, exclude it from source control and logs, and rotate it before expiration. Revoke and replace it immediately if it may have been exposed.
API Keys and Service Accounts
For an integration, automation, or AI agent, create a dedicated service-account user. Do not use an employee's account for a long-lived integration. Use a separate service account for each independent workload and assign only the roles it needs.
API key permissions are a snapshot
When an API key is created, Factorial copies the creating user's effective permissions into the key. The key continues to use that copied permission set. Later changes to the user's roles or to the permissions within those roles do not update an existing key.
Changing the service account's roles is therefore not enough to change an existing key. Set the intended roles, create a replacement key, update the integration, and revoke the old key.
Bulk Endpoints
Nearly all of Factorial API endpoints are bulk-friendly. For example, the API does not provide an endpoint that creates one procedure. It provides endpoints that create, update, or delete multiple procedures in one request.
Querying Records
Query endpoints return records that match a set of filters. Send a POST request with a JSON query body to the resource's query path, such as /api/v1/tasks/query. An empty body returns the first page of all records.
The following resources support queries:
POST /api/v1/tasks/queryreturns tasks from both projects and work orders.POST /api/v1/queues/queryreturns queues.POST /api/v1/tags/queryreturns tags.
Query Body
A query body may contain the following fields:
filters: a list of conditions. Each filter names a fieldf, an operatoro, and a valuev. A filter may also follow relationships withr, such as["TAGS"]to filter tasks by tag name.matchesAny: whentrue, a record matches if any filter matches. By default, a record must match every filter.search: text matched against the resource's searchable fields.sortBy: a sort option, such asleast_recentormost_recent.pagination:nextsets the page size, andcursorcontinues from a previous page. A page contains at most 300 records.
Operators are EQ, NEQ, IN, NOTIN, LIKE, ILIKE, NOTLIKE, NOTILIKE, GT, GTE, LT, LTE, ISNULL, and ISNOTNULL.
Set not to true to exclude records that match a filter. A negated relationship filter excludes a record when any related record matches. For example, the following query returns tasks in a queue that are still to do and have neither an on-hold tag nor an awaiting-parts tag. Untagged tasks are included.
json
{
"filters": [
{ "f": "queue_id", "o": "EQ", "v": "<queue-id>" },
{ "f": "status", "o": "EQ", "v": "TODO" },
{ "r": ["TAGS"], "f": "name", "o": "IN", "v": ["on-hold", "awaiting-parts"], "not": true }
],
"sortBy": "least_recent",
"pagination": { "next": 50 }
}Query Response
Every query endpoint returns a page of records:
json
{
"items": [],
"next_cursor": "",
"has_next_page": false,
"previous_cursor": "",
"has_previous_page": false
}When has_next_page is true, send the same query again with pagination.cursor set to next_cursor. The sortBy value must not change between pages.
Each task includes its id, kind (PROJECT or WORK_ORDER), status, title, description, queue_id, assignee_id, work_order_id, effective_step_id, and tags. effective_step_id is the step the task runs: its procedure's step, or the step of the last redline applied to it. GET /api/v1/tasks/{id} returns the same fields for one task, along with its comments, a markdown field, and a files field.
markdown is the whole task as a Markdown document: the title, the description, and the content of each block on the task. The description field holds only the task's description, so use markdown to read everything someone wrote.
files lists the uploaded files the task's content shows, in the order they appear. Each entry has an id, name, content_type, size_bytes, url, and source. source says where the file appears: document for an image in a text block, gallery for a file in a file gallery, and inline_pdf for an inline PDF. The markdown field links each of these files by its /files/{id}/content path, so a client that downloads them can point those links at its copies.
Downloading Files
GET /api/v1/files/{id}/content returns a file's contents, with the file's content type and name in the response headers. The url of each entry in a task's files points to this endpoint. Unlike most endpoints, it is not a bulk endpoint: each request returns one file, so a client fetches several files with one request per file.
Responses include an ETag header. A client that already has a file can send it back in If-None-Match and receive 304 Not Modified instead of the contents.
Updating Tasks
Tasks belong to either a project or a work order. The endpoints below accept both kinds of task in the same request. The API key must have permission to update project tasks, work order tasks, or both, depending on the tasks in the request.
PATCH /api/v1/all-tasks/update-queue moves tasks into a queue. Set queue_id to null to remove the tasks from their queue.
json
{ "ids": ["<task-id>"], "queue_id": null }PATCH /api/v1/tasks/status sets the status of tasks. Status values are TODO, IN_PROGRESS, COMPLETED, and CANCELED.
json
{ "task_ids": ["<task-id>"], "status": "IN_PROGRESS" }Both endpoints validate every task before making changes. If any task does not exist, the request fails and no tasks change; the status endpoint also rejects a request that lists the same task more than once. Updating a task the key may not change returns 403, and the message names the permission involved, such as Projects for project tasks or Work Orders for work order tasks.
API Reference
The complete API reference is available from the API Docs section of the Factorial documentation center.