REST reference
Use https://api.filemark.ca/api/v1 as the base URL and send Authorization: Bearer <access-token> with each request. See Authentication for tokens, Scopes for full scope names, and Conventions for shared types, headers, and retry rules.
| Resource | Operation | Method and path | Scope |
|---|---|---|---|
| Clients | List clients | GET /api/v1/clients | clients:read |
| Records | Create a client | POST /api/v1/clients | clients:write |
| Clients | Get a client | GET /api/v1/clients/{client_id} | clients:read |
| Records | Update a client | PATCH /api/v1/clients/{client_id} | clients:write |
| Clients | List a client's entities | GET /api/v1/clients/{client_id}/entities | entities:read |
| Entities | Get an entity | GET /api/v1/entities/{entity_id} | entities:read |
| Records | Update an entity | PATCH /api/v1/entities/{entity_id} | entities:write |
| Entities | List an entity's tax years | GET /api/v1/entities/{entity_id}/tax-years | tax-years:read |
| Tax years | Get a tax year | GET /api/v1/tax-years/{tax_year_id} | tax-years:read |
| Search | Search engagement records | GET /api/v1/search | engagements:read |
| Engagements | List engagements | GET /api/v1/engagements | engagements:read |
| Records | Create an engagement | POST /api/v1/engagements | engagements:write |
| Engagements | Get an engagement | GET /api/v1/engagements/{engagement_id} | engagements:read |
| Records | Update an engagement | PATCH /api/v1/engagements/{engagement_id} | engagements:write |
| Engagements | Get engagement context | GET /api/v1/engagements/{engagement_id}/context | engagements:read |
| Engagements | Get engagement history | GET /api/v1/engagements/{engagement_id}/history | engagements:read |
| Engagements | List engagement documents | GET /api/v1/engagements/{engagement_id}/documents | documents:read |
| Engagements | List engagement workpapers | GET /api/v1/engagements/{engagement_id}/workpapers | workpapers:read |
| Workpapers | Create a workpaper | POST /api/v1/engagements/{engagement_id}/workpapers | workpapers:write |
| Engagements | Get engagement review summary | GET /api/v1/engagements/{engagement_id}/review-summary | review:read |
| Engagement forms | Get an engagement form catalog | GET /api/v1/engagements/{engagement_id}/forms | engagements:read |
| Saved tax data | Get a saved trial balance | GET /api/v1/engagements/{engagement_id}/trial-balance | tax-data:read |
| Trial balance writes | Land a trial balance on an engagement | POST /api/v1/engagements/{engagement_id}/trial-balance | tax-data:write |
| Saved accounts | Get a saved engagement account | GET /api/v1/engagements/{engagement_id}/accounts/{account_id} | tax-data:read |
| Saved accounts | List saved account adjustments | GET /api/v1/engagements/{engagement_id}/accounts/{account_id}/adjustments | tax-data:read |
| Saved-state computations | Compute an engagement's saved state with cells replaced | POST /api/v1/engagements/{engagement_id}/computations/scenario | tax-data:read + tax:compute |
| Computations | List computation targets | GET /api/v1/computations | tax:compute |
| Computations | Get a computation target contract | GET /api/v1/computations/targets/{target_id} | tax:compute |
| Computations | Compute tax schedules | POST /api/v1/computations/batch | tax:compute |
| Computations | Compute a rollover or reorganization | POST /api/v1/computations/rollovers/{target} | tax:compute |
| Records | Archive a client | POST /api/v1/clients/{client_id}/archive | clients:write |
| Records | Restore an archived client | POST /api/v1/clients/{client_id}/restore | clients:write |
| Records | Create an entity | POST /api/v1/entities | entities:write |
| Records | Archive an entity | POST /api/v1/entities/{entity_id}/archive | entities:write |
| Records | Restore an archived entity | POST /api/v1/entities/{entity_id}/restore | entities:write |
| Records | Archive an engagement | POST /api/v1/engagements/{engagement_id}/archive | engagements:write |
| Records | Restore an archived engagement | POST /api/v1/engagements/{engagement_id}/restore | engagements:write |
| Records | Get mutation preconditions | GET /api/v1/engagements/{engagement_id}/mutation-preconditions | clients:write or entities:write or engagements:write or tax-data:write |
| Review | List account notes | GET /api/v1/engagements/{engagement_id}/account-notes | review:read |
| Review | Create an account note | POST /api/v1/engagements/{engagement_id}/account-notes | review:annotate |
| Review | Update an account note | PATCH /api/v1/account-notes/{note_id} | review:annotate |
| Review | Archive an account note | POST /api/v1/account-notes/{note_id}/archive | review:annotate |
| Review | List review marks | GET /api/v1/engagements/{engagement_id}/review-marks | review:read |
| Review | Create a review mark | POST /api/v1/engagements/{engagement_id}/review-marks | review:annotate |
| Review | Update a review mark | PATCH /api/v1/review-marks/{mark_id} | review:annotate |
| Tax data writes | Apply a saved tax-data action | POST /api/v1/engagements/{engagement_id}/data-actions | tax-data:write |
| Trial balance writes | Apply a pending trial-balance import | POST /api/v1/engagements/{engagement_id}/trial-balance/apply | tax-data:write |
| Trial balance writes | Classify awaiting accounts | POST /api/v1/engagements/{engagement_id}/accounts/classify-awaiting | tax-data:write |
| Portal | List an engagement's portal contacts | GET /api/v1/engagements/{engagement_id}/portal/contacts | portal:read |
| Portal | List an engagement's portal requests | GET /api/v1/engagements/{engagement_id}/portal/requests | portal:read |
| Portal | Get a portal request | GET /api/v1/portal-requests/{request_id} | portal:read |
| Portal | Preview a portal action | POST /api/v1/portal-requests/{request_id}/actions/preview | portal:write |
| Portal | Apply a portal action | POST /api/v1/portal-requests/{request_id}/actions | portal:write |
| Portal | Comment on a portal request | POST /api/v1/portal-requests/{request_id}/comments | portal:write + portal:send |
| Portal | Send, remind, or cancel a portal invitation | POST /api/v1/engagements/{engagement_id}/portal/deliveries | portal:send |
| Integrations | List integration connections | GET /api/v1/integrations | integrations:read |
| Integrations | List integration sources | GET /api/v1/integrations/{connector_id}/sources | integrations:read |
| Integrations | Preview an integration sync | POST /api/v1/engagements/{engagement_id}/integration-syncs/preview | integrations:read |
| Integrations | Apply an integration sync | POST /api/v1/engagements/{engagement_id}/integration-syncs | integrations:sync |
| Integrations | Get an integration sync | GET /api/v1/integration-syncs/{sync_id} | integrations:read |
| Export deliveries | Deliver an export artifact | POST /api/v1/export-jobs/{export_job_id}/deliveries | exports:deliver |
| Documents | Get a document | GET /api/v1/engagements/{engagement_id}/documents/{document_id} | documents:read |
| Documents | Replace a document's file | POST /api/v1/engagements/{engagement_id}/documents/{document_id}/replace | documents:write |
| Documents | Archive a document | POST /api/v1/engagements/{engagement_id}/documents/{document_id}/archive | documents:write |
| Documents | Create a document delivery | POST /api/v1/engagements/{engagement_id}/documents/{document_id}/deliveries | documents:read + documents:write |
| Documents | Start a document upload | POST /api/v1/engagements/{engagement_id}/document-uploads | documents:write |
| Documents | Complete a document upload | POST /api/v1/document-uploads/{upload_id}/complete | documents:write |
| Documents | Classify a staged document | POST /api/v1/document-uploads/{upload_id}/classify | documents:write |
| Documents | Start a document ingestion | POST /api/v1/engagements/{engagement_id}/document-ingestions | documents:write |
| Documents | Get a document ingestion | GET /api/v1/document-ingestions/{ingestion_id} | documents:write |
| Documents | Retry a document ingestion | POST /api/v1/document-ingestions/{ingestion_id}/retry | documents:write |
| Documents | Cancel a document ingestion | POST /api/v1/document-ingestions/{ingestion_id}/cancel | documents:write |
| Workpapers | Get a workpaper | GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id} | workpapers:read |
| Workpapers | Update a workpaper | PATCH /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id} | workpapers:write |
| Workpapers | Archive a workpaper | POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/archive | workpapers:write |
| Workpapers | Restore an archived workpaper | POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/restore | workpapers:write |
| Workpapers | List workpaper evidence | GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/evidence | workpapers:read |
| Workpapers | Attach or detach workpaper evidence | POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/evidence | workpapers:write |
| Workpapers | List workpaper account links | GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/links | workpapers:read |
| Workpapers | Set workpaper account links | POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/links | workpapers:write |
| Workpapers | Validate a workpaper | POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/validate | workpapers:read |
| Workpapers | Get workpaper tie-out | GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/tie-out | workpapers:read |
| Workpapers | List workpaper versions | GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/versions | workpapers:read |
| Workpapers | Generate a workpaper from the general ledger | POST /api/v1/engagements/{engagement_id}/workpapers/generate | workpapers:write |
| Workpapers | Carry a workpaper forward from the prior year | POST /api/v1/engagements/{engagement_id}/workpapers/carry-forward | workpapers:write |
| Workpapers | Request a governed narrative draft | POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/governed-drafts | workpapers:write |
Use the tax year's UUID as the engagement ID. Unknown IDs and IDs outside your organization return 404.
Pagination
GET /clients, /engagements/{id}/documents, and /engagements/{id}/workpapers use offset pagination. limit defaults to 50 and accepts 1 through 200. offset starts at zero and accepts values through 100,000. While pagination.hasMore is true, advance offset by the number of returned rows.
Entity, tax-year, and engagement lists use cursor pagination. Pass pagination.nextCursor unchanged as the next request's cursor for the same parent resource. A null cursor marks the final page.
Trial-balance cursors identify a saved snapshot. If a 409 reports that the trial balance changed, restart from the first page. If it reports that the saved trial balance cannot be represented, resolve the stored-data issue before retrying. A malformed cursor or one from another engagement returns 400.
A workpaper's detail, evidence and version-history reads page their rows, narrative blocks, evidence entries and changes the same way. Pass nextCursor unchanged as the next request's cursor. The cursor belongs to the workpaper's resourceVersion. If the workpaper changed, the read returns 409 and you restart from the first page. A malformed cursor or one from another workpaper or list returns 400.
Operation reference
Generated from the published OpenAPI schema (version 1.2.0). Replace path variables such as $CLIENT_ID with UUIDs from earlier responses. Response examples use angle brackets for placeholders and enum choices. Computation results also include the target's output cells.
List clients
GET /api/v1/clients
Lists your organization's clients by name using limit and offset pagination. The total and results reflect the clients visible to your token.
Scope: clients:read.
Request
curl --get https://api.filemark.ca/api/v1/clients \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
"https://api.filemark.ca/api/v1/clients",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL("https://api.filemark.ca/api/v1/clients");
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer, 1 to 200, default 50 | no | Page size (1-200). |
offset | query | integer, 0 to 100000, default 0 | no | Zero-based row offset (maximum 100,000). |
Response
200. One page of the organization's clients.
{
"data": [
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z"
}
],
"pagination": {
"limit": 0,
"offset": 0,
"total": 0,
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The clients on this page, ordered by name. |
data[].id | string (uuid) | yes | Stable UUID of the client. |
data[].name | string | yes | Client display name. |
data[].createdAt | string (date-time) | yes | ISO-8601 creation timestamp (UTC). |
data[].updatedAt | string (date-time) | yes | ISO-8601 last-modified timestamp (UTC). |
pagination | object | yes | Offset pagination state for this page. |
pagination.limit | integer | yes | Page size applied (1-200). |
pagination.offset | integer | yes | Zero-based row offset (max 100,000). |
pagination.total | integer | yes | Total matching resources in the organization. |
pagination.hasMore | boolean | yes | True when more rows exist past this page. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Create a client
POST /api/v1/clients
Creates a client in your organization. The name must be unique within the organization, archived clients included: a taken name is refused with 409 conflict naming the client that holds it, and an archived one is restored rather than created again. Send a UUID in Idempotency-Key; retrying with the same key returns the client it created (200 instead of 201).
Scope: clients:write.
Request
curl --request POST https://api.filemark.ca/api/v1/clients \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"name": "Acme Holdings Inc."
}'import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
"https://api.filemark.ca/api/v1/clients",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"name": "Acme Holdings Inc.",
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch("https://api.filemark.ca/api/v1/clients", {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Acme Holdings Inc."
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string, 1 to 300 characters | yes | Client display name, unique within your organization. |
Response
201. The record was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the client. |
name | string | yes | Client display name. |
createdAt | string (date-time) | yes | ISO-8601 creation timestamp (UTC). |
updatedAt | string (date-time) | yes | ISO-8601 last-modified timestamp (UTC). |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or recoverable-archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
Errors
See the shared error codes.
Get a client
GET /api/v1/clients/{client_id}
Returns the requested client. Returns 404 if the client does not exist or is outside your token's organization or client scope.
Scope: clients:read.
Request
curl https://api.filemark.ca/api/v1/clients/$CLIENT_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
CLIENT_ID = "<client-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/clients/{CLIENT_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const CLIENT_ID = "<client-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/clients/${CLIENT_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
client_id | path | string (uuid) | yes | UUID of the client to return. |
Response
200. The requested client.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the client. |
name | string | yes | Client display name. |
createdAt | string (date-time) | yes | ISO-8601 creation timestamp (UTC). |
updatedAt | string (date-time) | yes | ISO-8601 last-modified timestamp (UTC). |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Update a client
PATCH /api/v1/clients/{client_id}
Renames a client. Send the client's current resourceVersion in If-Match; a stale version is refused so a concurrent change is never overwritten.
Scope: clients:write.
Request
curl --request PATCH https://api.filemark.ca/api/v1/clients/$CLIENT_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"name": "<string>"
}'import os
import requests
CLIENT_ID = "<client-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.patch(
f"https://api.filemark.ca/api/v1/clients/{CLIENT_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"name": "<string>",
},
)
response.raise_for_status()
print(response.json())const CLIENT_ID = "<client-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/clients/${CLIENT_ID}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
client_id | path | string (uuid) | yes | UUID of the client to rename. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string, 1 to 300 characters | yes | New client display name. |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the client. |
name | string | yes | Client display name. |
createdAt | string (date-time) | yes | ISO-8601 creation timestamp (UTC). |
updatedAt | string (date-time) | yes | ISO-8601 last-modified timestamp (UTC). |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or recoverable-archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
Errors
See the shared error codes.
List a client's entities
GET /api/v1/clients/{client_id}/entities
Lists a client's legal entities by name. Pass nextCursor as cursor for the next page until hasMore is false. A cursor from another client returns 400. A missing client or one outside your token's scope returns 404.
Scope: entities:read.
Request
curl --get https://api.filemark.ca/api/v1/clients/$CLIENT_ID/entities \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
CLIENT_ID = "<client-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/clients/{CLIENT_ID}/entities",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const CLIENT_ID = "<client-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL(`https://api.filemark.ca/api/v1/clients/${CLIENT_ID}/entities`);
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
client_id | path | string (uuid) | yes | UUID of the client whose entities to list. |
limit | query | integer, 1 to 200, default 50 | no | Page size (1-200). |
cursor | query | nullable string, 1 to 4096 characters | no | Opaque cursor returned by the preceding page. |
Response
200. One cursor-paginated page of the client's entities.
{
"data": [
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "<string>",
"legalName": "<string>",
"corporationNumber": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"prescribedLsvccHistory": {
"wasPrescribed": false,
"firstPrescribedDate": "2025-12-31"
},
"dissolvedAt": "2025-12-31",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>"
}
],
"pagination": {
"limit": 0,
"nextCursor": "<string>",
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The client's entities on this page, ordered by name. |
data[].id | string (uuid) | yes | Stable UUID of the entity. |
data[].clientId | string (uuid) | yes | UUID of the entity's parent client. |
data[].name | string | yes | Entity display name. |
data[].legalName | nullable string | no | Registered legal name, when known. |
data[].corporationNumber | nullable string | no | Corporate registry number, when known. |
data[].businessNumber | nullable string | no | Canadian business number, when known. |
data[].naicsCode | nullable string | no | NAICS industry code, when assigned. |
data[].prescribedLsvccHistory | nullable object | no | Prescribed labour-sponsored venture capital corporation status (Reg. 6701), used by every return ending on or after the first prescribed date (ITA 131(11)). Null when the corporation never was one. |
data[].prescribedLsvccHistory.wasPrescribed | boolean | when prescribedLsvccHistory is set | Ever prescribed, including a former registration (Reg. 6701). |
data[].prescribedLsvccHistory.firstPrescribedDate | nullable string (date) | when prescribedLsvccHistory is set | First prescribed date; ITA 131(11) applies from it. |
data[].dissolvedAt | nullable string (date) | no | Entity dissolution date, when applicable. |
data[].incorporationJurisdiction | nullable string | no | Corporate-law jurisdiction the corporation is incorporated or continued under. Values: 'CA' for the federal CBCA, or a two-letter province/territory code. It changes only by continuance (ITA s.250(5.1)), never by fiscal period. It is not the provincial jurisdiction the corporation is taxed in, which is the per-tax-year permanent-establishment set (ITR Reg. 400(2)/402(3)) reported on Schedule 5. Null when not recorded, and also for a corporation incorporated outside Canada. |
data[].incorporationDate | nullable string (date) | no | Date the current incorporationJurisdiction took effect. This is the original incorporation date, or the date of the most recent continuance into that jurisdiction. Null when not recorded. |
data[].registeredOffice | nullable object | no | The corporation's registered office. A return whose T2 jacket holds no head office address (lines 011 to 018) states this one as its head office, and says so. Null when not recorded. |
data[].registeredOffice.addressLine | nullable string | when registeredOffice is set | Street address on one line. |
data[].registeredOffice.city | nullable string | when registeredOffice is set | City or municipality. |
data[].registeredOffice.provinceCode | nullable string | when registeredOffice is set | Two-letter province or territory code, such as ON. |
data[].registeredOffice.postalCode | nullable string | when registeredOffice is set | Postal code, such as L4W 5S2. |
data[].registeredOffice.countryCode | nullable string | when registeredOffice is set | Two-letter country code: CA. |
data[].ontarioCorporationNumber | nullable string | no | The Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded. |
pagination | object | yes | Cursor pagination state for this page. |
pagination.limit | integer | yes | Page size applied (1-200). |
pagination.nextCursor | nullable string | no | Opaque cursor for the next page. Null on the final page. |
pagination.hasMore | boolean | yes | True when another page is available. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Get an entity
GET /api/v1/entities/{entity_id}
Returns a legal entity and its registry identifiers. The parent client must be visible to your token. A missing entity or one outside that scope returns 404.
Scope: entities:read.
Request
curl https://api.filemark.ca/api/v1/entities/$ENTITY_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENTITY_ID = "<entity-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/entities/{ENTITY_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENTITY_ID = "<entity-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/entities/${ENTITY_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | path | string (uuid) | yes | UUID of the entity to return. |
Response
200. The requested entity.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "<string>",
"legalName": "<string>",
"corporationNumber": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"prescribedLsvccHistory": {
"wasPrescribed": false,
"firstPrescribedDate": "2025-12-31"
},
"dissolvedAt": "2025-12-31",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the entity. |
clientId | string (uuid) | yes | UUID of the entity's parent client. |
name | string | yes | Entity display name. |
legalName | nullable string | no | Registered legal name, when known. |
corporationNumber | nullable string | no | Corporate registry number, when known. |
businessNumber | nullable string | no | Canadian business number, when known. |
naicsCode | nullable string | no | NAICS industry code, when assigned. |
prescribedLsvccHistory | nullable object | no | Prescribed labour-sponsored venture capital corporation status (Reg. 6701), used by every return ending on or after the first prescribed date (ITA 131(11)). Null when the corporation never was one. |
prescribedLsvccHistory.wasPrescribed | boolean | when prescribedLsvccHistory is set | Ever prescribed, including a former registration (Reg. 6701). |
prescribedLsvccHistory.firstPrescribedDate | nullable string (date) | when prescribedLsvccHistory is set | First prescribed date; ITA 131(11) applies from it. |
dissolvedAt | nullable string (date) | no | Entity dissolution date, when applicable. |
incorporationJurisdiction | nullable string | no | Corporate-law jurisdiction the corporation is incorporated or continued under. Values: 'CA' for the federal CBCA, or a two-letter province/territory code. It changes only by continuance (ITA s.250(5.1)), never by fiscal period. It is not the provincial jurisdiction the corporation is taxed in, which is the per-tax-year permanent-establishment set (ITR Reg. 400(2)/402(3)) reported on Schedule 5. Null when not recorded, and also for a corporation incorporated outside Canada. |
incorporationDate | nullable string (date) | no | Date the current incorporationJurisdiction took effect. This is the original incorporation date, or the date of the most recent continuance into that jurisdiction. Null when not recorded. |
registeredOffice | nullable object | no | The corporation's registered office. A return whose T2 jacket holds no head office address (lines 011 to 018) states this one as its head office, and says so. Null when not recorded. |
registeredOffice.addressLine | nullable string | when registeredOffice is set | Street address on one line. |
registeredOffice.city | nullable string | when registeredOffice is set | City or municipality. |
registeredOffice.provinceCode | nullable string | when registeredOffice is set | Two-letter province or territory code, such as ON. |
registeredOffice.postalCode | nullable string | when registeredOffice is set | Postal code, such as L4W 5S2. |
registeredOffice.countryCode | nullable string | when registeredOffice is set | Two-letter country code: CA. |
ontarioCorporationNumber | nullable string | no | The Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Update an entity
PATCH /api/v1/entities/{entity_id}
Changes the fields you send; an explicit null clears an optional field. A filed return keeps what it filed; refused while an unfiled return is partner-signed. Send the entity's current resourceVersion in If-Match.
Scope: entities:write.
Request
curl --request PATCH https://api.filemark.ca/api/v1/entities/$ENTITY_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"name": "<string>",
"legalName": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>",
"prescribedLsvccHistory": {
"wasPrescribed": false,
"firstPrescribedDate": "2025-12-31"
}
}'import os
import requests
ENTITY_ID = "<entity-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.patch(
f"https://api.filemark.ca/api/v1/entities/{ENTITY_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"name": "<string>",
"legalName": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>",
},
"ontarioCorporationNumber": "<string>",
"prescribedLsvccHistory": {
"wasPrescribed": False,
"firstPrescribedDate": "2025-12-31",
},
},
)
response.raise_for_status()
print(response.json())const ENTITY_ID = "<entity-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/entities/${ENTITY_ID}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "<string>",
"legalName": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>",
"prescribedLsvccHistory": {
"wasPrescribed": false,
"firstPrescribedDate": "2025-12-31"
}
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | path | string (uuid) | yes | UUID of the entity to change. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
Send only the fields to change; an explicit null clears an optional field.
| Field | Type | Required | Description |
|---|---|---|---|
name | nullable string, 1 to 300 characters | no | |
legalName | nullable string, up to 300 characters | no | |
businessNumber | nullable string, up to 40 characters | no | The corporation's own CRA business number: the nine-digit BN, or the 15-character corporation income tax account (#########RC####). T2 box 001 is the RC account; from nine digits alone a return states RC0001, the program account the T2 guide (T4012, line 001) says to enter unless the CRA has advised another, and says it did. |
naicsCode | nullable string, matching ^\d{2,6}$ | no | NAICS industry code, 2 to 6 digits. An exported return states the six-digit code of the main revenue-generating activity. |
incorporationJurisdiction | nullable string, up to 2 characters | no | Corporate-law jurisdiction of incorporation or continuance: 'CA' for the federal CBCA, or a two-letter province or territory code. Null clears it. |
incorporationDate | nullable string (date) | no | YYYY-MM-DD the current jurisdiction took effect (original incorporation or the latest continuance). Not in the future. Null clears it. |
registeredOffice | nullable object | no | The registered office. A Canadian address names its province or territory by code. A return whose T2 jacket holds no head office (lines 011 to 018) states this one as its head office and says so. Null clears it. |
registeredOffice.addressLine | nullable string, up to 300 characters | when registeredOffice is set | Street address on one line. |
registeredOffice.city | nullable string, up to 100 characters | when registeredOffice is set | City or municipality. |
registeredOffice.provinceCode | nullable string, up to 2 characters | when registeredOffice is set | Two-letter province or territory code, such as ON. Required for a Canadian address. |
registeredOffice.postalCode | nullable string, up to 10 characters | when registeredOffice is set | Postal code, such as L4W 5S2. |
registeredOffice.countryCode | nullable string, up to 2 characters | when registeredOffice is set | Two-letter country code; CA or omitted for Canada. |
ontarioCorporationNumber | nullable string, matching ^\s*[0-9]{1,10}\s*$ | no | The Ontario Corporation Number on the Ontario Business Registry, digits only (at most 10), for a corporation incorporated, amalgamated or continued in Ontario or registered there. Null clears it. |
prescribedLsvccHistory | nullable object | no | Whether the corporation is or was a prescribed labour-sponsored venture capital corporation (Reg. 6701). ITA 131(11) applies from the first prescribed date to every later year-end. Null when it never was. |
prescribedLsvccHistory.wasPrescribed | boolean | when prescribedLsvccHistory is set | Ever prescribed, including a former registration (Reg. 6701). |
prescribedLsvccHistory.firstPrescribedDate | nullable string (date) | when prescribedLsvccHistory is set | First prescribed date; required for Yes (ITA 131(11)). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "<string>",
"legalName": "<string>",
"corporationNumber": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"prescribedLsvccHistory": {
"wasPrescribed": false,
"firstPrescribedDate": "2025-12-31"
},
"dissolvedAt": "2025-12-31",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the entity. |
clientId | string (uuid) | yes | UUID of the entity's parent client. |
name | string | yes | Entity display name. |
legalName | nullable string | no | Registered legal name, when known. |
corporationNumber | nullable string | no | Corporate registry number, when known. |
businessNumber | nullable string | no | Canadian business number, when known. |
naicsCode | nullable string | no | NAICS industry code, when assigned. |
prescribedLsvccHistory | nullable object | no | Prescribed labour-sponsored venture capital corporation status (Reg. 6701), used by every return ending on or after the first prescribed date (ITA 131(11)). Null when the corporation never was one. |
prescribedLsvccHistory.wasPrescribed | boolean | when prescribedLsvccHistory is set | Ever prescribed, including a former registration (Reg. 6701). |
prescribedLsvccHistory.firstPrescribedDate | nullable string (date) | when prescribedLsvccHistory is set | First prescribed date; ITA 131(11) applies from it. |
dissolvedAt | nullable string (date) | no | Entity dissolution date, when applicable. |
incorporationJurisdiction | nullable string | no | Corporate-law jurisdiction the corporation is incorporated or continued under. Values: 'CA' for the federal CBCA, or a two-letter province/territory code. It changes only by continuance (ITA s.250(5.1)), never by fiscal period. It is not the provincial jurisdiction the corporation is taxed in, which is the per-tax-year permanent-establishment set (ITR Reg. 400(2)/402(3)) reported on Schedule 5. Null when not recorded, and also for a corporation incorporated outside Canada. |
incorporationDate | nullable string (date) | no | Date the current incorporationJurisdiction took effect. This is the original incorporation date, or the date of the most recent continuance into that jurisdiction. Null when not recorded. |
registeredOffice | nullable object | no | The corporation's registered office. A return whose T2 jacket holds no head office address (lines 011 to 018) states this one as its head office, and says so. Null when not recorded. |
registeredOffice.addressLine | nullable string | when registeredOffice is set | Street address on one line. |
registeredOffice.city | nullable string | when registeredOffice is set | City or municipality. |
registeredOffice.provinceCode | nullable string | when registeredOffice is set | Two-letter province or territory code, such as ON. |
registeredOffice.postalCode | nullable string | when registeredOffice is set | Postal code, such as L4W 5S2. |
registeredOffice.countryCode | nullable string | when registeredOffice is set | Two-letter country code: CA. |
ontarioCorporationNumber | nullable string | no | The Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or recoverable-archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Conditions of the entity the caller should act on. legal_name_missing: no legal name is recorded, so the engagement cannot be computed, matched to uploaded documents or filed until legalName is set. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
See the shared error codes.
List an entity's tax years
GET /api/v1/entities/{entity_id}/tax-years
Lists an entity's tax years by year end, newest first. Pass nextCursor as cursor for the next page until hasMore is false. A cursor from another entity returns 400. A missing entity or one outside your token's scope returns 404.
Scope: tax-years:read.
Request
curl --get https://api.filemark.ca/api/v1/entities/$ENTITY_ID/tax-years \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
ENTITY_ID = "<entity-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/entities/{ENTITY_ID}/tax-years",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const ENTITY_ID = "<entity-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL(`https://api.filemark.ca/api/v1/entities/${ENTITY_ID}/tax-years`);
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | path | string (uuid) | yes | UUID of the entity whose tax years to list. |
limit | query | integer, 1 to 200, default 50 | no | Page size (1-200). |
cursor | query | nullable string, 1 to 4096 characters | no | Opaque cursor returned by the preceding page. |
Response
200. One cursor-paginated page of the entity's tax years.
{
"data": [
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"entityId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearTaxYearId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"createdAt": "2026-07-14T12:00:00Z",
"lastModifiedAt": "2026-07-14T12:00:00Z"
}
],
"pagination": {
"limit": 0,
"nextCursor": "<string>",
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The entity's tax years on this page, newest year end first. |
data[].id | string (uuid) | yes | Stable UUID of the tax-year engagement. |
data[].entityId | string (uuid) | yes | UUID of the parent entity. |
data[].clientId | string (uuid) | yes | UUID of the parent client. |
data[].yearEnd | string (date) | yes | Fiscal year-end date. |
data[].periodStart | nullable string (date) | no | Fiscal period start date. Null when no start has been recorded, for example on a reference year an import created without one. |
data[].status | one of in_progress, review, complete | yes | Current engagement workflow status. |
data[].source | one of prepared, imported | yes | Prepared binder or imported prior-year reference. |
data[].engagementType | nullable one of compilation, review, audit, tax_only | no | Engagement service type, when assigned. |
data[].priorYearTaxYearId | nullable string (uuid) | no | Linked prior tax year used for comparatives, when present. |
data[].createdAt | string (date-time) | yes | ISO-8601 creation timestamp (UTC). |
data[].lastModifiedAt | string (date-time) | yes | ISO-8601 last-modified timestamp (UTC). |
pagination | object | yes | Cursor pagination state for this page. |
pagination.limit | integer | yes | Page size applied (1-200). |
pagination.nextCursor | nullable string | no | Opaque cursor for the next page. Null on the final page. |
pagination.hasMore | boolean | yes | True when another page is available. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Get a tax year
GET /api/v1/tax-years/{tax_year_id}
Returns a tax-year engagement's period, status, and parent records. Both its client and its entity's client must be visible to your token. A missing tax year or one outside that scope returns 404.
Scope: tax-years:read.
Request
curl https://api.filemark.ca/api/v1/tax-years/$TAX_YEAR_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
TAX_YEAR_ID = "<tax-year-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/tax-years/{TAX_YEAR_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const TAX_YEAR_ID = "<tax-year-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/tax-years/${TAX_YEAR_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
tax_year_id | path | string (uuid) | yes | UUID of the tax-year engagement to return. |
Response
200. The requested tax-year engagement.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"entityId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearTaxYearId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"createdAt": "2026-07-14T12:00:00Z",
"lastModifiedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the tax-year engagement. |
entityId | string (uuid) | yes | UUID of the parent entity. |
clientId | string (uuid) | yes | UUID of the parent client. |
yearEnd | string (date) | yes | Fiscal year-end date. |
periodStart | nullable string (date) | no | Fiscal period start date. Null when no start has been recorded, for example on a reference year an import created without one. |
status | one of in_progress, review, complete | yes | Current engagement workflow status. |
source | one of prepared, imported | yes | Prepared binder or imported prior-year reference. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Engagement service type, when assigned. |
priorYearTaxYearId | nullable string (uuid) | no | Linked prior tax year used for comparatives, when present. |
createdAt | string (date-time) | yes | ISO-8601 creation timestamp (UTC). |
lastModifiedAt | string (date-time) | yes | ISO-8601 last-modified timestamp (UTC). |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Search engagement records
GET /api/v1/search
Searches current engagements by client name, entity name, fiscal year, or year end. Every query term must match. Results include the newest revision in each amendment chain and use the engagement response fields. The response is 200 with resolution set to unique for one match, ambiguous for multiple matches, or no_match for none.
Scope: engagements:read.
Request
curl --get https://api.filemark.ca/api/v1/search \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "q=Acme Holdings 2025" \
--data-urlencode "limit=10"import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
"https://api.filemark.ca/api/v1/search",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"q": "Acme Holdings 2025",
"limit": "10",
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL("https://api.filemark.ca/api/v1/search");
url.searchParams.set("q", "Acme Holdings 2025");
url.searchParams.set("limit", "10");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
q | query | string, 2 to 80 characters | yes | At most six whitespace-separated terms. Percent and underscore characters match as text, not as wildcards. |
year_end | query | nullable string (date) | no | Keep only matches whose fiscal year end is exactly this date, YYYY-MM-DD. |
status | query | nullable one of in_progress, review, complete | no | Keep only matches in this workflow status: in_progress, review, or complete. |
limit | query | integer, 1 to 25, default 10 | no | Maximum matches to return (1-25). |
Response
200. The query, its resolution, and the matching engagements.
{
"query": "Acme Holdings 2025",
"resolution": "<no_match | unique | ambiguous>",
"data": [
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>"
}
],
"pagination": {
"limit": 1,
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
query | string, 2 to 80 characters | yes | The search text as it was interpreted, with runs of whitespace collapsed to single spaces. |
resolution | one of no_match, unique, ambiguous | yes | unique when exactly one engagement matched and none were left out, ambiguous when more than one matched, and no_match when none did. All three are returned with status 200. |
data | array of objects, up to 25 items | yes | Matching engagements ordered by relevance, using the latest revision in each amendment chain. |
data[].id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
data[].client | object | yes | The client the engagement belongs to. |
data[].client.id | string (uuid) | yes | UUID of the client or entity. |
data[].client.name | string | yes | Display name of the client or entity. |
data[].client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
data[].entity | object | yes | The legal entity the engagement belongs to. |
data[].entity.id | string (uuid) | yes | UUID of the client or entity. |
data[].entity.name | string | yes | Display name of the client or entity. |
data[].entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
data[].yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
data[].periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
data[].status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
data[].source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
data[].engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
data[].priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
data[].isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
data[].lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
data[].lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
data[].lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
data[].lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
data[].lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
data[].createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
data[].updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
data[].documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
pagination | object | yes | The result limit that was applied and whether further matches were left out. |
pagination.limit | integer, 1 to 25 | yes | Result limit applied. |
pagination.hasMore | boolean | yes | True when additional matching engagements were omitted. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: A matched engagement revision is inconsistent.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
List engagements
GET /api/v1/engagements
Lists engagements by year end, newest first, including each revision of amended returns. Filter by client, entity, year end, or workflow status. Results are limited to clients visible to your token. Pass nextCursor as cursor for the next page until hasMore is false. Reusing a cursor with different filters returns 400.
Scope: engagements:read.
Request
curl --get https://api.filemark.ca/api/v1/engagements \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
"https://api.filemark.ca/api/v1/engagements",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL("https://api.filemark.ca/api/v1/engagements");
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
client_id | query | nullable string (uuid) | no | Return only engagements belonging to this client. |
entity_id | query | nullable string (uuid) | no | Return only engagements belonging to this legal entity. |
year_end | query | nullable string (date) | no | Return only engagements whose fiscal year end is exactly this date, YYYY-MM-DD. |
status | query | nullable one of in_progress, review, complete | no | Return only engagements in this workflow status: in_progress, review, or complete. |
limit | query | integer, 1 to 200, default 50 | no | Page size (1-200). |
cursor | query | nullable string, 1 to 4096 characters | no | Opaque cursor returned by the preceding page. |
Response
200. One page of engagements, newest first.
{
"data": [
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>"
}
],
"pagination": {
"limit": 0,
"nextCursor": "<string>",
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The engagements on this page, newest year end first. |
data[].id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
data[].client | object | yes | The client the engagement belongs to. |
data[].client.id | string (uuid) | yes | UUID of the client or entity. |
data[].client.name | string | yes | Display name of the client or entity. |
data[].client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
data[].entity | object | yes | The legal entity the engagement belongs to. |
data[].entity.id | string (uuid) | yes | UUID of the client or entity. |
data[].entity.name | string | yes | Display name of the client or entity. |
data[].entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
data[].yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
data[].periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
data[].status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
data[].source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
data[].engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
data[].priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
data[].isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
data[].lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
data[].lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
data[].lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
data[].lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
data[].lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
data[].createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
data[].updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
data[].documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
pagination | object | yes | Cursor pagination state for this page. |
pagination.limit | integer | yes | Page size applied (1-200). |
pagination.nextCursor | nullable string | no | Opaque cursor for the next page. Null on the final page. |
pagination.hasMore | boolean | yes | True when another page is available. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Create an engagement
POST /api/v1/engagements
Creates the original (revision 1) engagement for an entity's fiscal year: open, unfiled, unassigned, attributed to your API client. It consumes one of the organization's binder entitlements exactly as creating the year in Filemark does; when none is available the request is refused. When the entity already has an imported reference year for the year-end (a return file another tax product saved, or a prior-year trial balance, brought in for comparatives), that year is promoted to a prepared engagement in place and returned with an imported_year_promoted warning; its imported period, elections and prior-year link are kept, and a periodStart that contradicts the imported period is refused with 409. An existing prepared engagement for the year-end is a 409 duplicate. A linked prior year filed in Filemark seeds carry-forward balances. Non-blocking conditions come back in warnings. Amendments are never created here.
Scope: engagements:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"entityId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"s249_4_bElected": false,
"s249_3_1_cElected": false,
"permanentEstablishmentJurisdictions": [
"<string>"
]
}'import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
"https://api.filemark.ca/api/v1/engagements",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"entityId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"s249_4_bElected": False,
"s249_3_1_cElected": False,
"permanentEstablishmentJurisdictions": [
"<string>",
],
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch("https://api.filemark.ca/api/v1/engagements", {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"entityId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"s249_4_bElected": false,
"s249_3_1_cElected": false,
"permanentEstablishmentJurisdictions": [
"<string>"
]
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string (uuid) | yes | UUID of the legal entity the engagement is for. |
yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. Unique per entity for an original return. |
periodStart | nullable string (date) | no | First day of the taxation year (T2 box 060), YYYY-MM-DD: normally the day after the previous year-end, or the incorporation date for a first year. Send it: an omitted start is never guessed, and the engagement cannot be computed or filed until an engagement update sets it (the answer warns period_start_missing). Must be on or before yearEnd and within the 371-day statutory maximum unless an election below extends it. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm performs for the year, when known. |
priorYearEngagementId | nullable string (uuid) | no | UUID of the same entity's earlier engagement to link as the prior year. A prior year filed in Filemark seeds carry-forward balances. |
s249_4_bElected | nullable boolean | no | True when the ITA s.249(4)(b) election applies (an acquisition of control within 7 days after the year-end extends the period to 378 days). |
s249_3_1_cElected | nullable boolean | no | True when the ITA s.249(3.1)(c) seven-day alignment election applies. |
permanentEstablishmentJurisdictions | nullable array of strings, up to 13 items | no | Two-letter province or territory codes where the corporation had a permanent establishment in the year; seeds the Schedule 5 workpaper. Requires the entity's legal name: an entity without one is refused until an entity update sets legalName. |
Response
201. The record was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"recordLifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
client | object | yes | The client the engagement belongs to. |
client.id | string (uuid) | yes | UUID of the client or entity. |
client.name | string | yes | Display name of the client or entity. |
client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
entity | object | yes | The legal entity the engagement belongs to. |
entity.id | string (uuid) | yes | UUID of the client or entity. |
entity.name | string | yes | Display name of the client or entity. |
entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
recordLifecycle | object | yes | Live or recoverable-archived (distinct from the return's filing lifecycle). |
recordLifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
recordLifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
recordLifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Non-blocking conditions the write reported: an unsupported filing year, an unverified election, a failed prior-year roll-forward or permanent-establishment seed, or a missing period start (period_start_missing: send periodStart with an engagement update). |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
See the shared error codes.
Get an engagement
GET /api/v1/engagements/{engagement_id}
Returns an engagement's client, entity, period, status, and revision metadata. A missing engagement or one outside your token's organization or client scope returns 404.
Scope: engagements:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement to return. |
Response
200. Identity, period, status, and revision metadata.
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
client | object | yes | The client the engagement belongs to. |
client.id | string (uuid) | yes | UUID of the client or entity. |
client.name | string | yes | Display name of the client or entity. |
client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
entity | object | yes | The legal entity the engagement belongs to. |
entity.id | string (uuid) | yes | UUID of the client or entity. |
entity.name | string | yes | Display name of the client or entity. |
entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Update an engagement
PATCH /api/v1/engagements/{engagement_id}
Changes the workflow status, engagement type, or fiscal period of an engagement. Refused while the return is filed or partner-signed. Identity, filing, and revision fields are never writable. Send the engagement's current resourceVersion in If-Match.
Scope: engagements:write.
Request
curl --request PATCH https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"status": "<in_progress | review | complete>",
"engagementType": "<compilation | review | audit | tax_only>",
"periodStart": "2025-12-31",
"yearEnd": "2025-12-31"
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.patch(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"status": "<in_progress | review | complete>",
"engagementType": "<compilation | review | audit | tax_only>",
"periodStart": "2025-12-31",
"yearEnd": "2025-12-31",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"status": "<in_progress | review | complete>",
"engagementType": "<compilation | review | audit | tax_only>",
"periodStart": "2025-12-31",
"yearEnd": "2025-12-31"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement to change. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
Send only the fields to change. Identity, filing and revision fields are never writable.
| Field | Type | Required | Description |
|---|---|---|---|
status | nullable one of in_progress, review, complete | no | Workflow status. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm performs for the year. |
periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. |
yearEnd | nullable string (date) | no | Fiscal year-end date, YYYY-MM-DD. Changing it re-validates the period length and the entity's year-end uniqueness. |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"recordLifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
client | object | yes | The client the engagement belongs to. |
client.id | string (uuid) | yes | UUID of the client or entity. |
client.name | string | yes | Display name of the client or entity. |
client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
entity | object | yes | The legal entity the engagement belongs to. |
entity.id | string (uuid) | yes | UUID of the client or entity. |
entity.name | string | yes | Display name of the client or entity. |
entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
recordLifecycle | object | yes | Live or recoverable-archived (distinct from the return's filing lifecycle). |
recordLifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
recordLifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
recordLifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Non-blocking conditions the write reported: an unsupported filing year, an unverified election, a failed prior-year roll-forward or permanent-establishment seed, or a missing period start (period_start_missing: send periodStart with an engagement update). |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
See the shared error codes.
Get engagement context
GET /api/v1/engagements/{engagement_id}/context
Returns the requested revision, the latest revision in its amendment chain, and links to adjacent revisions. An inconsistent chain returns 409.
Scope: engagements:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/context \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/context",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/context`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of any revision in the amendment chain to resolve. It need not be the newest one. |
Response
200. The requested revision, the current revision, and their place in the amendment chain.
{
"requestedEngagement": {
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>"
},
"currentEngagement": {
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>"
},
"revision": {
"rootEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"previousEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"nextEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"currentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isCurrent": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
requestedEngagement | object | yes | The revision named by the engagement ID in the request. |
requestedEngagement.id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
requestedEngagement.client | object | yes | The client the engagement belongs to. |
requestedEngagement.client.id | string (uuid) | yes | UUID of the client or entity. |
requestedEngagement.client.name | string | yes | Display name of the client or entity. |
requestedEngagement.client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
requestedEngagement.entity | object | yes | The legal entity the engagement belongs to. |
requestedEngagement.entity.id | string (uuid) | yes | UUID of the client or entity. |
requestedEngagement.entity.name | string | yes | Display name of the client or entity. |
requestedEngagement.entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
requestedEngagement.yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
requestedEngagement.periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
requestedEngagement.status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
requestedEngagement.source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
requestedEngagement.engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
requestedEngagement.priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
requestedEngagement.isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
requestedEngagement.lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
requestedEngagement.lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
requestedEngagement.lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
requestedEngagement.lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
requestedEngagement.lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
requestedEngagement.createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
requestedEngagement.updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
requestedEngagement.documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
currentEngagement | object | yes | The newest revision of the same amendment chain. Identical to requestedEngagement when the requested revision is already the newest. |
currentEngagement.id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
currentEngagement.client | object | yes | The client the engagement belongs to. |
currentEngagement.client.id | string (uuid) | yes | UUID of the client or entity. |
currentEngagement.client.name | string | yes | Display name of the client or entity. |
currentEngagement.client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
currentEngagement.entity | object | yes | The legal entity the engagement belongs to. |
currentEngagement.entity.id | string (uuid) | yes | UUID of the client or entity. |
currentEngagement.entity.name | string | yes | Display name of the client or entity. |
currentEngagement.entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
currentEngagement.yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
currentEngagement.periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
currentEngagement.status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
currentEngagement.source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
currentEngagement.engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
currentEngagement.priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
currentEngagement.isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
currentEngagement.lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
currentEngagement.lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
currentEngagement.lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
currentEngagement.lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
currentEngagement.lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
currentEngagement.createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
currentEngagement.updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
currentEngagement.documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
revision | object | yes | Where the requested revision sits in the chain and how to reach its neighbours. |
revision.rootEngagementId | string (uuid) | yes | UUID of the first revision in the chain, the original return. |
revision.previousEngagementId | nullable string (uuid) | no | UUID of the revision the requested one amends. Null when the requested revision is the original return. |
revision.nextEngagementId | nullable string (uuid) | no | UUID of the revision that amends the requested one. Null when the requested revision is the newest. |
revision.currentEngagementId | string (uuid) | yes | UUID of the newest revision in the chain. |
revision.isCurrent | boolean | yes | True when the requested revision is the newest one in the chain. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The persisted amendment chain is inconsistent.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Get engagement history
GET /api/v1/engagements/{engagement_id}/history
Lists amendment metadata for each revision from the original return to the latest. An inconsistent chain or one exceeding 100 revisions returns 409.
Scope: engagements:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/history \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/history",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/history`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of any revision in the amendment chain to list. It need not be the newest one. |
Response
200. Amendment metadata for up to 100 revisions.
{
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"currentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"revisions": [
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
engagementId | string (uuid) | yes | UUID of the engagement that was requested. |
currentEngagementId | string (uuid) | yes | UUID of the newest revision in the chain. It is the last entry in revisions. |
revisions | array of objects, 1 to 100 items | yes | Revision metadata ordered from the original return to the latest, up to 100 entries. Longer chains return a conflict. |
revisions[].id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
revisions[].client | object | yes | The client the engagement belongs to. |
revisions[].client.id | string (uuid) | yes | UUID of the client or entity. |
revisions[].client.name | string | yes | Display name of the client or entity. |
revisions[].client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
revisions[].entity | object | yes | The legal entity the engagement belongs to. |
revisions[].entity.id | string (uuid) | yes | UUID of the client or entity. |
revisions[].entity.name | string | yes | Display name of the client or entity. |
revisions[].entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
revisions[].yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
revisions[].periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
revisions[].status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
revisions[].source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
revisions[].engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
revisions[].priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
revisions[].isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
revisions[].lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
revisions[].lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
revisions[].lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
revisions[].lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
revisions[].lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
revisions[].createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
revisions[].updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
revisions[].documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The persisted amendment chain is inconsistent.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
List engagement documents
GET /api/v1/engagements/{engagement_id}/documents
Lists processed documents for an engagement, ordered by kind and file name. Each record includes the document's name, type, source, and timestamps. Use limit and offset for pagination.
Scope: documents:read.
Request
curl --get https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/documents \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/documents",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/documents`);
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement whose documents to list. |
limit | query | integer, 1 to 200, default 50 | no | Page size (1-200). |
offset | query | integer, 0 to 100000, default 0 | no | Zero-based row offset (maximum 100,000). |
Response
200. One page of document registry metadata.
{
"data": [
{
"id": "basis:currentTb",
"fileName": "General Ledger 2025.xlsx",
"kind": "<gl | transactions | fs | balance-sheet | income-statement | retained-earnings-statement | cash-flow-statement | workpaper | supporting | tb-current | tb-prior | tb-classification | gifi-export | prior-return | portal-upload | evidence>",
"source": "<onboarding | manual | portal | connector | api | unknown>",
"uploadedAt": "2026-07-14T12:00:00Z"
}
],
"pagination": {
"limit": 1,
"offset": 0,
"total": 0,
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The documents on this page, ordered by kind, then file name, then identifier. |
data[].id | string, 1 to 256 characters | yes | Identifier of the document within this engagement. It stays stable for a given document but is not a UUID and is not unique across engagements. |
data[].fileName | string, 1 to 512 characters | yes | Name to show for the document. When the stored record carries no file name, a generic label for its kind is returned. |
data[].kind | one of gl, transactions, fs, balance-sheet, income-statement, retained-earnings-statement, cash-flow-statement, workpaper, supporting, tb-current, tb-prior, tb-classification, gifi-export, prior-return, portal-upload, evidence | yes | What the document is to the engagement. gl is a general-ledger file. transactions is a transaction list, one row per bank transaction, read into the same ledger set. fs is a financial-statement file carrying several statements the GIFI mapping was read from; balance-sheet, income-statement, retained-earnings-statement and cash-flow-statement are a file carrying that one statement. workpaper is a file attached to a workpaper. supporting is a supporting document filed against the engagement. tb-current and tb-prior are the current-year and prior-year trial balances the file was built from. tb-classification is the prior-year account classification file. gifi-export is an exported GIFI file. prior-return is the predecessor return file an imported year was read out of. portal-upload is a file the client sent through the client portal. evidence is a file attached as support for a tax attribute. |
data[].source | one of onboarding, manual, portal, connector, api, unknown | yes | How the document entered the engagement. onboarding is the onboarding pipeline, manual is a practitioner upload in the workspace, portal is a client upload through the client portal, connector is an accounting-system connection, and unknown means the record does not say. |
data[].uploadedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the document was added. Null when no time was recorded for it, or when the stored value could not be read as a timestamp. |
pagination | object | yes | Offset pagination state for this page. |
pagination.limit | integer, 1 to 200 | yes | Page size applied. |
pagination.offset | integer, 0 to 100000 | yes | Zero-based row offset applied. |
pagination.total | integer, at least 0 | yes | Total matching resources. |
pagination.hasMore | boolean | yes | True when another page is available. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
List engagement workpapers
GET /api/v1/engagements/{engagement_id}/workpapers
Lists active workpapers for an engagement, oldest first, using limit and offset pagination. Each record includes creation details, review status, the trial-balance tie-out result, and the linked account count.
Scope: workpapers:read.
Request
curl --get https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers`);
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement whose workpapers to list. |
limit | query | integer, 1 to 200, default 50 | no | Page size (1-200). |
offset | query | integer, 0 to 100000, default 0 | no | Zero-based row offset (maximum 100,000). |
Response
200. One page of workpaper metadata.
{
"data": [
{
"id": "b18d4c77-2e3f-4a51-8c0d-6f9a7b2c1d34",
"templateId": "<string>",
"kind": "<string>",
"origin": "<string>",
"status": "<string>",
"parseStatus": "<string>",
"tieOutMode": "<string>",
"tieOutStatus": "<string>",
"detectedType": "<string>",
"category": "<string>",
"version": 3,
"isReviewed": false,
"reviewedAt": "2026-07-14T12:00:00Z",
"sourceFileName": "<string>",
"linkedAccountCount": 4,
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z"
}
],
"pagination": {
"limit": 1,
"offset": 0,
"total": 0,
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The workpapers on this page, oldest first. Deleted workpapers are not included. |
data[].id | string (uuid) | yes | UUID of the workpaper. |
data[].templateId | nullable string | no | Identifier of the workpaper template the workpaper was built from. Null for a workpaper created without one. |
data[].kind | string | yes | row_grid for a workpaper of tabular rows, narrative for a written memo. |
data[].origin | string | yes | How the workpaper came into existence: uploaded from a practitioner's spreadsheet, generated from the trial balance, blank from a template, or carryforward from the prior year. |
data[].status | string | yes | draft while the workpaper is still being worked on, complete once the preparer has finished it. |
data[].parseStatus | nullable string | no | For an uploaded workpaper, how its spreadsheet read: parsed when rows were extracted, unparsed when no parser applied, or parse_failed when reading it failed. Null when the workpaper did not come from an upload. |
data[].tieOutMode | nullable string | no | How the workpaper is tied back to the trial balance: sum adds the workpaper's own rows, cell reads one nominated spreadsheet cell (used when the upload could not be parsed). Null on a narrative workpaper, which has no tie-out. |
data[].tieOutStatus | nullable string | no | Tie-out status. matched indicates a difference within tolerance, difference indicates a difference above tolerance, incomplete indicates missing inputs, and pending indicates a check awaiting computation. Null for narrative workpapers. |
data[].detectedType | nullable string | no | Workpaper type detected during upload processing. Null if no type was detected or the workpaper was created another way. |
data[].category | nullable string | no | Free-text grouping label a practitioner set on the workpaper. There is no fixed list of values. Null when none is set. |
data[].version | integer, at least 1 | yes | Content version of the workpaper, starting at 1 and increasing each time its saved content is replaced. |
data[].isReviewed | boolean | yes | True when the workpaper has been marked reviewed, which is when reviewedAt is set. |
data[].reviewedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the workpaper was marked reviewed. Null while it has not been. |
data[].sourceFileName | nullable string, up to 512 characters | no | Name of the file the workpaper was uploaded from. Null when it was not created from an upload. |
data[].linkedAccountCount | integer, at least 0 | yes | Number of trial-balance accounts linked to the workpaper. |
data[].createdAt | string (date-time) | yes | ISO-8601 in UTC. When the workpaper was created. |
data[].updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the workpaper was last modified. |
pagination | object | yes | Offset pagination state for this page. |
pagination.limit | integer, 1 to 200 | yes | Page size applied. |
pagination.offset | integer, 0 to 100000 | yes | Zero-based row offset applied. |
pagination.total | integer, at least 0 | yes | Total matching resources. |
pagination.hasMore | boolean | yes | True when another page is available. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Create a workpaper
POST /api/v1/engagements/{engagement_id}/workpapers
Creates a workpaper for the engagement from a concept template. Optional initial rows are validated against the template's declared column types. A template that permits one live instance for the tax year is refused when one already exists. Refused while the return is filed or partner-signed.
Scope: workpapers:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"templateId": "fixed-assets",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"category": "<string>",
"assumption": "<string>",
"notes": "<string>",
"linkedAccountIds": [
"<string>"
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
}
}
]
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"templateId": "fixed-assets",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"category": "<string>",
"assumption": "<string>",
"notes": "<string>",
"linkedAccountIds": [
"<string>",
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>",
},
},
],
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"templateId": "fixed-assets",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"category": "<string>",
"assumption": "<string>",
"notes": "<string>",
"linkedAccountIds": [
"<string>"
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
}
}
]
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string, 1 to 100 characters | yes | Concept template to build the workpaper from. Read the catalogue from the engagement's workpaper list; a template that permits one live instance per year is refused when one already exists. |
customName | nullable string, up to 200 characters | no | The one practitioner-set name. Required in practice for a template that permits several instances, so each instance is identifiable. |
kind | one of row_grid, narrative | no | Body shape to create. |
category | nullable string, up to 100 characters | no | |
assumption | nullable string, up to 4000 characters | no | |
notes | nullable string, up to 8000 characters | no | |
linkedAccountIds | nullable array of strings, up to 500 items | no | Saved trial-balance account ids to link. Every id must belong to this engagement's saved trial balance. |
rows | nullable array of objects | no | Grid rows. A row with a rowKey replaces that row; a row without one is appended with a server-assigned key. Cells are validated against the template's declared column types; an undeclared column key is refused. There is no row count limit beyond the request body size, so a whole grid of any stored size can be sent. |
rows[].rowKey | nullable string, up to 200 characters | when rows is set | Stable key of the row to replace. Omit to append a new row. |
rows[].sectionId | nullable string, up to 100 characters | when rows is set | Template section the row belongs to. |
rows[].cells | object | when rows is set | Cell values keyed by the template's declared column keys. |
rows[].cells.* | any | no | Members not listed here. |
Response
201. The workpaper was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"templateId": "<string>",
"name": "<string>",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"origin": "<string>",
"status": "<draft | complete>",
"category": "<string>",
"version": 1,
"isReviewed": false,
"reviewedAt": "2026-07-14T12:00:00Z",
"parseStatus": "<string>",
"detectedType": "<string>",
"sourceFileName": "<string>",
"assumption": "<string>",
"notes": "<string>",
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"linkedAccountIds": [
"<string>"
],
"columns": [
{
"key": "description",
"label": "<string>",
"type": "<text | number | date | select | cohorts>",
"options": [
"<string>"
],
"sectionId": "<string>",
"entryFields": [
{
"key": "acquisitionDate",
"label": "<string>",
"type": "<text | number | date | select>",
"options": [
"<string>"
]
}
]
}
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
},
"untypedCells": [
"<string>"
]
}
],
"rowCount": 0,
"rowsTruncated": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>",
"needsReview": false
}
],
"narrativeBlockCount": 0,
"narrativeTruncated": false,
"nextCursor": "<string>",
"completenessStatus": "<incomplete | complete>",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the workpaper. |
engagementId | string (uuid) | yes | UUID of the engagement it belongs to. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
templateId | nullable string | no | Concept template the workpaper was built from. |
name | string | yes | Practitioner-facing display name. |
customName | nullable string | no | The one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name). |
kind | one of row_grid, narrative | yes | Body shape. |
origin | string | yes | How the workpaper came to exist: blank, uploaded, generated, carryforward. |
status | one of draft, complete | yes | Preparer status. |
category | nullable string | no | Practitioner grouping label. |
version | integer, at least 1 | yes | Monotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number. |
isReviewed | boolean | yes | True once a practitioner marked it reviewed in Filemark. The API never sets it. |
reviewedAt | nullable string (date-time) | no | ISO-8601 in UTC. Null while unreviewed. |
parseStatus | nullable string | no | Outcome of parsing the uploaded source file, when there is one. |
detectedType | nullable string | no | Layout the parser detected in the source file. |
sourceFileName | nullable string, up to 512 characters | no | File name of the uploaded source, when there is one. |
assumption | nullable string | no | The preparer's stated assumption for the working paper. |
notes | nullable string | no | Preparer notes. |
tieOut | object | yes | Agreement with the linked trial-balance accounts. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
linkedAccountIds | array of strings | yes | Saved trial-balance account ids the workpaper is linked to. |
columns | array of objects | yes | Declared grid columns. Empty for a narrative workpaper. |
columns[].key | string | yes | Row key this column occupies. |
columns[].label | string | yes | Practitioner-facing column heading. |
columns[].type | one of text, number, date, select, cohorts | yes | Declared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields. |
columns[].options | nullable array of strings | no | Closed option list for a 'select' column. Null for every other type. |
columns[].sectionId | nullable string | no | Template section this column belongs to. Null for the main grid. |
columns[].entryFields | nullable array of objects | no | The declared fields of each entry of a 'cohorts' column. Null for every other type. |
columns[].entryFields[].key | string | when entryFields is set | Entry key this field occupies. |
columns[].entryFields[].label | string | when entryFields is set | Practitioner-facing field heading. |
columns[].entryFields[].type | one of text, number, date, select | when entryFields is set | Declared value type. A written entry value must match it. |
columns[].entryFields[].options | nullable array of strings | when entryFields is set | Closed option list for a 'select' field. Null for every other type. |
rows | array of objects | yes | One page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper. |
rows[].rowKey | string | yes | Stable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one. |
rows[].sectionId | nullable string | no | Template section the row sits in. Null for the main grid. |
rows[].cells | object | no | Cell values keyed by column key. Only keys the template declares appear here and only they are accepted on a write. A 'cohorts' cell is a list of entries, each carrying only its column's declared entryFields. |
rows[].cells.* | any | no | Members not listed here. |
rows[].untypedCells | array of strings | no | Declared column keys whose stored value does not fit the column's declared type, so it is not in cells. Correct the cell in Filemark or write it again through an update. Empty when every declared cell is published. |
rowCount | integer, at least 0 | yes | Total rows stored, across every page. |
rowsTruncated | boolean | yes | True when more rows follow this page; nextCursor reads them. |
narrative | array of objects | yes | One page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper. |
narrative[].id | string | yes | Stable block identifier. |
narrative[].kind | one of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divider | yes | Block kind. |
narrative[].content | nullable string | no | Plain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally. |
narrative[].level | nullable integer, 1 to 3 | no | Heading level, 1 to 3. Null for other kinds. |
narrative[].variant | nullable one of note, warning, risk, conclusion | no | Callout variant. Null for other kinds. |
narrative[].needsReview | boolean | yes | True while a carried-forward block still awaits practitioner review. |
narrativeBlockCount | integer, at least 0 | yes | Total narrative blocks stored, across every page. |
narrativeTruncated | boolean | yes | True when more narrative blocks follow this page; nextCursor reads them. |
nextCursor | nullable string, up to 4096 characters | no | Continues the body: the next page of rows for a row grid, of blocks for a narrative. Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
completenessStatus | nullable one of incomplete, complete | no | Narrative completeness checklist state. Null for a row-grid workpaper. |
lifecycle | object | yes | Live or archived (recoverable in Filemark). |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
warnings | array of objects | no | Non-blocking conditions: an unknown template, a dropped cell, a partial seed. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.
Get engagement review summary
GET /api/v1/engagements/{engagement_id}/review-summary
Returns saved review indicators for an engagement, including sign-offs, workpaper review and tie-out counts, account review status, active review marks, and acknowledged warning counts. The readiness object reports that filing readiness has not been evaluated.
Scope: review:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/review-summary \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/review-summary",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/review-summary`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement to summarize. |
Response
200. Saved review indicators.
{
"importedReviewEvidence": [
{
"id": "<string>",
"sourceRevisionId": "<string>",
"fieldPath": "<string>",
"nativeFieldId": {
"<member>": "<any>"
},
"preparation": "<any>",
"sourceActor": "<string>",
"sourceMarkedAt": "<string>",
"sourceTime": {
"<member>": "<any>"
},
"sourceValue": {
"<member>": "<any>"
},
"valueWithheld": false,
"sourceDetails": {
"<member>": "<any>"
},
"evidenceKind": "<string>",
"isHistorical": true,
"countsForSignoff": false
}
],
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementStatus": "<in_progress | review | complete>",
"lifecycleStatus": "<open | signed | filed>",
"signoffs": {
"preparerSigned": false,
"reviewerSigned": false,
"reviewerStatus": "<pending | signed | skipped>",
"partnerSigned": false
},
"workpapers": {
"total": 12,
"reviewed": 9,
"tieOutMatched": 7,
"tieOutDifference": 1,
"tieOutIncomplete": 2,
"tieOutPending": 2
},
"accountReviewStatuses": {
"followUp": 3,
"analyzed": 41,
"firstReview": 18,
"secondReview": 6
},
"activeReviewMarks": {
"total": 5,
"questions": 2,
"corrections": 1,
"firstReview": 1,
"partnerReview": 1
},
"acknowledgedWarningCount": 2,
"readiness": {
"evaluated": false,
"ready": null
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
importedReviewEvidence | array of objects | no | Source marks with their original attribution; never current approval or sign-off. |
importedReviewEvidence[].id | string | when importedReviewEvidence is set | |
importedReviewEvidence[].sourceRevisionId | string | when importedReviewEvidence is set | |
importedReviewEvidence[].fieldPath | nullable string | when importedReviewEvidence is set | |
importedReviewEvidence[].nativeFieldId | nullable object | when importedReviewEvidence is set | |
importedReviewEvidence[].nativeFieldId.* | any | no | Members not listed here. |
importedReviewEvidence[].preparation | any | when importedReviewEvidence is set | |
importedReviewEvidence[].sourceActor | nullable string | when importedReviewEvidence is set | |
importedReviewEvidence[].sourceMarkedAt | nullable string | when importedReviewEvidence is set | |
importedReviewEvidence[].sourceTime | nullable object | when importedReviewEvidence is set | |
importedReviewEvidence[].sourceTime.* | any | no | Members not listed here. |
importedReviewEvidence[].sourceValue | nullable object | when importedReviewEvidence is set | |
importedReviewEvidence[].sourceValue.* | any | no | Members not listed here. |
importedReviewEvidence[].valueWithheld | boolean | when importedReviewEvidence is set | |
importedReviewEvidence[].sourceDetails | nullable object | when importedReviewEvidence is set | |
importedReviewEvidence[].sourceDetails.* | any | no | Members not listed here. |
importedReviewEvidence[].evidenceKind | string | when importedReviewEvidence is set | |
importedReviewEvidence[].isHistorical | boolean, always true | when importedReviewEvidence is set | |
importedReviewEvidence[].countsForSignoff | boolean, always false | when importedReviewEvidence is set | |
engagementId | string (uuid) | yes | UUID of the engagement these indicators belong to. |
engagementStatus | one of in_progress, review, complete | yes | Workflow status the practitioner has set on the engagement: in_progress, review, or complete. |
lifecycleStatus | one of open, signed, filed | yes | Derived from the engagement's own record: filed once a filing time is recorded, signed when a partner sign-off exists but no filing time does, and open otherwise. |
signoffs | object | yes | Which of the three sign-offs are recorded. |
signoffs.preparerSigned | boolean | yes | True when a preparer sign-off is recorded. |
signoffs.reviewerSigned | boolean | yes | True when a reviewer sign-off is recorded. |
signoffs.reviewerStatus | one of pending, signed, skipped | yes | pending when no reviewer decision has been recorded, signed when a reviewer signed off, skipped when the reviewer step was waived for this engagement. |
signoffs.partnerSigned | boolean | yes | True when a partner sign-off is recorded. |
workpapers | object | yes | Workpaper counts by review state and tie-out result. |
workpapers.total | integer, at least 0 | yes | Workpapers on the engagement. Deleted workpapers are not counted. |
workpapers.reviewed | integer, at least 0 | yes | How many of those workpapers are marked reviewed. |
workpapers.tieOutMatched | integer, at least 0 | yes | Workpapers whose tie-out difference is within tolerance. |
workpapers.tieOutDifference | integer, at least 0 | yes | Workpapers whose tie-out difference is outside tolerance. |
workpapers.tieOutIncomplete | integer, at least 0 | yes | Workpapers awaiting inputs for a tie-out. |
workpapers.tieOutPending | integer, at least 0 | yes | Workpapers awaiting a tie-out computation. Narrative workpapers are excluded from all four tie-out counts. |
accountReviewStatuses | object | yes | Trial-balance accounts counted by the review status set on them. |
accountReviewStatuses.followUp | integer, at least 0 | yes | Accounts flagged for a follow-up question to the client. |
accountReviewStatuses.analyzed | integer, at least 0 | yes | Accounts the preparer has marked as analyzed. |
accountReviewStatuses.firstReview | integer, at least 0 | yes | Accounts marked as having passed first review. |
accountReviewStatuses.secondReview | integer, at least 0 | yes | Accounts marked as having passed second review. Accounts still at the default status are counted in none of these four. |
activeReviewMarks | object | yes | Uncleared review marks on the return's fields, by type. |
activeReviewMarks.total | integer, at least 0 | yes | Review marks that nobody has cleared yet. |
activeReviewMarks.questions | integer, at least 0 | yes | Active marks raising a question about a field. |
activeReviewMarks.corrections | integer, at least 0 | yes | Active marks asking for a field to be corrected. |
activeReviewMarks.firstReview | integer, at least 0 | yes | Active first-review marks on a field. |
activeReviewMarks.partnerReview | integer, at least 0 | yes | Active partner-review marks on a field. |
acknowledgedWarningCount | integer, at least 0 | yes | Computed warnings a practitioner has acknowledged and not since reinstated. |
readiness | object | yes | States that this response carries no filing-readiness verdict. |
readiness.evaluated | boolean, always false | no | Always false. This read reports stored review indicators and does not evaluate whether the return can be filed. |
readiness.ready | null | no | Always null. A filing-readiness verdict requires the return to be recomputed, which this read does not do. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Get an engagement form catalog
GET /api/v1/engagements/{engagement_id}/forms
Lists supported forms for the engagement's tax year and each form's supported year range. Filing requirements depend on the corporation's circumstances. An unavailable form catalog returns 503.
Scope: engagements:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/forms \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/forms",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/forms`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement whose tax year the catalog is built for. |
Response
200. The engagement's year and the forms whose window covers it.
{
"context": {
"engagementId": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"yearEnd": "2025-12-31",
"taxYear": 2025,
"revisionNumber": 1,
"source": "<prepared | imported>",
"filedAt": "2026-07-14T12:00:00Z",
"referenceOnly": false
},
"data": [
{
"targetId": "S125",
"displayName": "Schedule 125",
"formId": "T2SCH125",
"taxYearWindow": {
"from": 2010,
"through": 1900
},
"availability": "supported",
"applicabilityBasis": "tax_year_window",
"filingRequirement": "not_evaluated"
}
],
"evaluation": {
"formRevisionResolved": false,
"savedStateEvaluated": false,
"computationEvaluated": false,
"readinessEvaluated": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
context | object | yes | The engagement and revision the catalog was built for. |
context.engagementId | string (uuid) | yes | UUID of the engagement the catalog was built for. |
context.yearEnd | string (date) | yes | The engagement's fiscal year-end date, YYYY-MM-DD. |
context.taxYear | integer, 1900 to 9999 | yes | Calendar year of the year-end date, the year matched against each form's supported tax-year window. |
context.revisionNumber | integer, at least 1 | yes | Revision of the engagement the catalog was built from. 1 is the original return. |
context.source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file. |
context.filedAt | nullable string (date-time) | yes | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
context.referenceOnly | boolean | yes | True when the engagement is an imported prior-year reference, which is the same as source being imported. |
data | array of objects, up to 200 items | yes | The supported forms, with the T2 return first, then schedules and other forms in numeric order. |
data[].targetId | string, matching ^(?:S\d+[A-Z]?|T\d+[A-Z]?)$ | yes | Identifier for the form target: S followed by the schedule number for a T2 schedule, T2 for the return itself, and the CRA form number for any other form. |
data[].displayName | string, 1 to 256 characters | yes | Name to show for the form. It is derived from the target identifier. It is not the CRA's full form title. |
data[].formId | string, matching ^[A-Z0-9_-]{1,64}$ | yes | The CRA form number for this target. |
data[].taxYearWindow | object | yes | The range of tax years this form is supported for. |
data[].taxYearWindow.from | integer, 1900 to 9999 | yes | First tax year the form is supported for. |
data[].taxYearWindow.through | nullable integer, 1900 to 9999 | yes | Last tax year the form is supported for. Null when the form has no declared end year and remains supported for later years. |
data[].availability | string, always "supported" | yes | Always supported. The form supports the engagement's tax year. |
data[].applicabilityBasis | string, always "tax_year_window" | yes | Always tax_year_window. Selection is based on the engagement's tax year. |
data[].filingRequirement | string, always "not_evaluated" | yes | Always not_evaluated. This response does not decide whether the corporation has to file the form. |
evaluation | object | yes | Flags recording what this response did not do. A listed form is not necessarily populated, computed, or required. |
evaluation.formRevisionResolved | boolean, always false | yes | Always false. No year-specific revision of any form was selected. |
evaluation.savedStateEvaluated | boolean, always false | yes | Always false. Saved form data is not evaluated by the catalog lookup. |
evaluation.computationEvaluated | boolean, always false | yes | Always false. No amounts were computed for any form. |
evaluation.readinessEvaluated | boolean, always false | yes | Always false. No form was checked for whether it is ready to file. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.503: The canonical form catalog is temporarily unavailable.
Get a saved trial balance
GET /api/v1/engagements/{engagement_id}/trial-balance
Returns active accounts from the saved trial balance with their balances and GIFI codes before adjusting entries and tax adjustments. Amounts are decimal strings in the reporting currency, with debits positive and credits negative. Deleted accounts are counted in archivedAccountCount. Pass nextCursor as cursor for the next page until hasMore is false. Cursors identify the saved snapshot. If the trial balance changes between requests, the next page returns 409 and pagination must restart from the first page. A missing engagement or one outside your token's scope returns 404.
Scope: tax-data:read.
Request
curl --get https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/trial-balance \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/trial-balance",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/trial-balance`);
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement whose trial balance you want. An engagement is one corporation's taxation year. |
limit | query | integer, 1 to 200, default 50 | no | How many active accounts to return, from 1 to 200. Defaults to 50. |
cursor | query | nullable string, 1 to 4096 characters | no | The nextCursor value from the previous page. Omit it to start at the first page. A cursor stops working once the trial balance changes. |
Response
200. One page of the saved trial balance.
{
"context": {
"engagementId": "3f1c2b4a-5d6e-4f70-8912-a3b4c5d6e7f8",
"yearEnd": "2025-12-31",
"revisionNumber": 1,
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"filedAt": "2026-07-14T12:00:00Z",
"referenceOnly": false,
"reportingCurrency": "CAD",
"currencyBasis": "<canadian_currency | functional_currency | reversionary>",
"balanceConvention": "debits_positive_credits_negative"
},
"sourceRevision": {
"classifiedReportVersion": 12,
"gifiAssignmentsVersion": 4,
"lastModifiedAt": "2026-03-02T09:41:07Z",
"snapshotDigest": "sha256:9f2c1d5b0a7e43c68b1f0d2e4a6c8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e"
},
"availability": "<available | empty | not_imported>",
"projection": {
"savedBookBalancesIncluded": true,
"gifiAssignmentsIncluded": true,
"archivedAccountsIncluded": false,
"postedBookAdjustmentsIncluded": false,
"taxAdjustmentsIncluded": false,
"classificationIncluded": false,
"computationEvaluated": false,
"lineageIncluded": false,
"readinessEvaluated": false
},
"totalStoredAccountCount": 130,
"archivedAccountCount": 2,
"data": [
{
"id": "acct-6500",
"accountCode": "<string>",
"accountName": "Meals and entertainment",
"currentYearBalance": "18450.75",
"priorYearBalance": "125000.5",
"gifiCode": "<string>",
"changeStatus": "<new | removed | significant_increase | significant_decrease | minor_change | unchanged | unknown>"
}
],
"pagination": {
"limit": 50,
"total": 128,
"nextCursor": "<string>",
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
context | object | yes | The engagement, reporting currency and sign convention these balances belong to. |
context.engagementId | string (uuid) | yes | UUID of the engagement these balances belong to. An engagement is one corporation's taxation year. |
context.yearEnd | string (date) | yes | The engagement's taxation year end, YYYY-MM-DD. |
context.revisionNumber | integer, at least 1 | yes | Filing revision counter for this year end. The original engagement is 1. Each amendment opened from it adds one. |
context.status | one of in_progress, review, complete | yes | Preparation workflow state set in the product. in_progress while the engagement is being worked on, review while it sits with a reviewer, complete when preparation is finished. Filing is separate and is reported by filedAt. |
context.source | one of prepared, imported | yes | How the engagement came to exist. prepared is a full engagement worked in Filemark and can be filed. imported is a reference-only record created from an uploaded prior-year trial balance. An imported engagement supplies only comparatives and carryforward figures until it is promoted to prepared. |
context.filedAt | nullable string (date-time) | yes | When the return was marked filed, ISO-8601 in UTC. Null when the engagement has not been marked filed. Once set, the engagement is read-only in the product. |
context.referenceOnly | boolean | yes | True when source is imported, meaning the engagement is a reference-only record whose balances are comparatives. |
context.reportingCurrency | string, matching ^[A-Z]{3}$ | yes | ISO 4217 code of the currency every amount in this response is stated in. CAD unless the corporation has an effective functional currency election for the year. |
context.currencyBasis | one of canadian_currency, functional_currency, reversionary | yes | What determines the reporting currency. canadian_currency: no functional currency election applies to this year and amounts are in Canadian dollars. functional_currency: an effective election applies and amounts are in the elected currency. reversionary: the election has ended and the year is back on Canadian dollars under the reversion rules. |
context.balanceConvention | string, always "debits_positive_credits_negative" | yes | Always debits_positive_credits_negative. Every balance in this response is signed with debits positive and credits negative. |
sourceRevision | object | yes | Version counters and a fingerprint for the saved state this page was read from. |
sourceRevision.classifiedReportVersion | integer, at least 0 | yes | Saved trial-balance version, incremented whenever accounts or balances are saved. Zero before the first save. |
sourceRevision.gifiAssignmentsVersion | integer, at least 0 | yes | Write counter for the saved GIFI code assignments, on the same convention as classifiedReportVersion. |
sourceRevision.lastModifiedAt | string (date-time) | yes | When any part of the engagement was last saved, ISO-8601 in UTC. |
sourceRevision.snapshotDigest | string, matching ^sha256:[0-9a-f]{64}$ | yes | Fingerprint of the saved state this page was read from, as sha256: followed by 64 lowercase hex characters. Pagination is bound to it. If the trial balance is edited between pages, the next request with the old cursor is refused with 409 and you restart from the first page. |
availability | one of available, empty, not_imported | yes | Whether there is a trial balance to read. available: at least one active account is present. empty: the stored account list is nonempty but every account has been archived. not_imported: the stored account list is empty, including a newly created year. This describes saved accounts, not import history. |
projection | object | yes | What this response does and does not include. |
projection.savedBookBalancesIncluded | boolean, always true | yes | Always true. The balances are the saved book balances as entered or imported. |
projection.gifiAssignmentsIncluded | boolean, always true | yes | Always true. Each account carries the GIFI code saved for it on this engagement. |
projection.archivedAccountsIncluded | boolean, always false | yes | Always false. Deleted accounts are excluded from data and counted in archivedAccountCount. |
projection.postedBookAdjustmentsIncluded | boolean, always false | yes | Always false. Posted adjusting journal entries are not applied to these balances. |
projection.taxAdjustmentsIncluded | boolean, always false | yes | Always false. Workpaper tax adjustments are not applied to these balances. |
projection.classificationIncluded | boolean, always false | yes | Always false. Read a single account to get its tax classification. |
projection.computationEvaluated | boolean, always false | yes | Always false. No schedule or return computation is run to produce this response. |
projection.lineageIncluded | boolean, always false | yes | Always false. No per-amount lineage or derivation trail is returned. |
projection.readinessEvaluated | boolean, always false | yes | Always false. Filing readiness is not evaluated by this operation. |
totalStoredAccountCount | integer, at least 0 | yes | Number of accounts held on the engagement, counting archived ones. |
archivedAccountCount | integer, at least 0 | yes | Number of deleted accounts, excluded from data. |
data | array of objects, up to 200 items | yes | One page of active accounts, in the order the engagement stores them. Empty when availability is empty or not_imported. |
data[].id | string, 1 to 128 characters | yes | Identifier of the account within this engagement, used to read the account and its adjustments. It stays fixed for the life of the engagement but does not carry across years, and it is not a UUID. |
data[].accountCode | nullable string, up to 128 characters | yes | Account number as it appears in the client's chart of accounts. Null for a trial balance whose accounts are identified by name only. |
data[].accountName | string, up to 512 characters | yes | Account name from the client's chart of accounts. Escape this untrusted text before rendering. Empty when the source file has no name. |
data[].currentYearBalance | decimal string, 1 to 96 characters | yes | Closing balance for the taxation year, before adjusting entries and tax adjustments. Exact decimal string in the reporting currency, debits positive and credits negative. |
data[].priorYearBalance | nullable decimal string, 1 to 96 characters | yes | The comparative closing balance for the preceding year, on the same convention as currentYearBalance. Null when no prior-year figure is on file for the account. |
data[].gifiCode | nullable string, matching ^[0-9]{4}$ | yes | The four-digit GIFI code assigned to this account on this engagement. Null when no code has been assigned yet. |
data[].changeStatus | one of new, removed, significant_increase, significant_decrease, minor_change, unchanged, unknown | yes | How the balance moved against the prior year. new: the account appears only in the current year. removed: it appears only in the prior year. significant_increase and significant_decrease: the move passed both the absolute and the percentage threshold configured for the engagement. minor_change: the balance moved but not enough to be flagged. unchanged: the balance is identical. unknown: the saved account carries no comparison result. |
pagination | object | yes | Page size, total active account count, and the next cursor. |
pagination.limit | integer, 1 to 200 | yes | Page size applied to this request, at most 200. It can be smaller than the limit you asked for. |
pagination.total | integer, at least 0 | yes | Number of active accounts in the whole trial balance, across all pages. Archived accounts are excluded. |
pagination.nextCursor | nullable string | yes | Opaque cursor to pass as cursor on the next request. Null on the last page. It is tied to the snapshot this page came from and stops working if the trial balance changes. |
pagination.hasMore | boolean | yes | True when more accounts follow this page. nextCursor is set whenever it is true. |
Errors
In addition to the shared error codes:
400: The pagination cursor is malformed or invalid.404: The engagement does not exist in the caller's organization.409: The saved trial balance is inconsistent or changed between pages.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Land a trial balance on an engagement
POST /api/v1/engagements/{engagement_id}/trial-balance
Merge supplied books or rows through the shared trial-balance import. When answers are needed, nothing is applied: use the returned previewId and questions with the apply endpoint. Rules and prior-year carryforward run inside apply; use classify-awaiting for a bounded model round. If-Match is the engagement resourceVersion. Filed or signed returns are read-only.
Scope: tax-data:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/trial-balance \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"mode": "<books | rows>",
"source": {
"system": "<quickbooks | xero | sage | other>",
"label": "<string>"
},
"accounts": [
{
"accountCode": "1000",
"accountName": "Chequing",
"debit": 0,
"credit": 0,
"balance": 0,
"priorYearBalance": 0,
"gifiCode": "<string>"
}
]
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/trial-balance",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"mode": "<books | rows>",
"source": {
"system": "<quickbooks | xero | sage | other>",
"label": "<string>",
},
"accounts": [
{
"accountCode": "1000",
"accountName": "Chequing",
"debit": 0,
"credit": 0,
"balance": 0,
"priorYearBalance": 0,
"gifiCode": "<string>",
},
],
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/trial-balance`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"mode": "<books | rows>",
"source": {
"system": "<quickbooks | xero | sage | other>",
"label": "<string>"
},
"accounts": [
{
"accountCode": "1000",
"accountName": "Chequing",
"debit": 0,
"credit": 0,
"balance": 0,
"priorYearBalance": 0,
"gifiCode": "<string>"
}
]
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement whose account list the rows merge into. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The engagement's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
Preview and, when no answers are needed, merge the supplied books or rows.
| Field | Type | Required | Description |
|---|---|---|---|
mode | one of books, rows | no | books asks about absent accounts; rows imports only the supplied rows. |
source | object | yes | Where the rows came from. |
source.system | one of quickbooks, xero, sage, other | yes | The accounting system the rows were read from. |
source.label | nullable string, up to 200 characters | no | Free text naming the company file or report, for the practitioner. |
accounts | array of objects, 1 to 20000 items | yes | Every account with a balance, in the order the system reports them. |
accounts[].accountCode | string, up to 128 characters | no | The account's code in the client's chart. Optional when the system has no codes; then the name identifies the account. |
accounts[].accountName | string, 1 to 512 characters | yes | The account's name. |
accounts[].debit | nullable number, at least 0 or string | no | Amount in dollars. Send a decimal string to preserve the exact source amount. Combined account amounts are rounded half-up to cents on import. |
accounts[].credit | nullable number, at least 0 or string | no | Amount in dollars. Send a decimal string to preserve the exact source amount. Combined account amounts are rounded half-up to cents on import. |
accounts[].balance | nullable number or string | no | Amount in dollars. Send a decimal string to preserve the exact source amount. Combined account amounts are rounded half-up to cents on import. Debits positive, credits negative. |
accounts[].priorYearBalance | nullable number or string | no | Amount in dollars. Send a decimal string to preserve the exact source amount. Combined account amounts are rounded half-up to cents on import. Prior tax-year closing balance, debits positive, credits negative. Omit or send null when unknown; zero is a known balance. |
accounts[].gifiCode | nullable string, 1 to 16 characters | no | Caller-mapped CRA GIFI code. The import pipeline validates it before seeding an assignment. |
Response
200. The engagement after the landing, or the earlier result of the same Idempotency-Key.
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"recordLifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
],
"importStatus": "<applied | needs_answers | blocked>",
"applied": {
"accountCount": 0,
"classifiedCount": 0,
"unclassifiedCount": 0,
"gifiSeededCount": 0,
"totalDebits": "<string>",
"totalCredits": "<string>",
"balanced": false,
"warnings": [
"<string>"
]
},
"submitted": {
"totalDebits": "<string>",
"totalCredits": "<string>",
"balanced": false,
"warnings": [
"<string>"
]
},
"previewId": "<string>",
"questions": {
"mode": "<books | rows>",
"confirmationCounts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
},
"conflicts": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"existingName": "<string>",
"incomingName": "<string>",
"incomingBalance": "<string>",
"incomingCode": "<string>",
"crossKey": false
}
],
"restores": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"deletedAt": "<string>",
"incomingBalance": "<string>"
}
],
"missing": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"balance": "<string>",
"refs": {
"ajeLines": 0,
"workpapers": 0,
"groups": 0
}
}
],
"duplicates": [
{
"code": "<string>",
"combinedBalance": "<string>",
"rows": [
{
"name": "<string>",
"balance": "<string>"
}
],
"rowsCount": 0,
"rowsTruncated": false,
"plans": {
"combine": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"first": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"skip": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
]
}
}
],
"lowMatch": {
"matchedRows": 0,
"rows": 0
},
"blocked": {
"reason": "<string>",
"message": "<string>"
},
"coverage": {
"fiscalPeriod": {
"start": "<string>",
"end": "<string>"
},
"files": [
{
"glId": "<string>",
"filename": "<string>",
"accounts": [
"<string>"
],
"reportRange": {
"start": "<string>",
"end": "<string>"
},
"observedRange": {
"start": "<string>",
"end": "<string>"
}
}
],
"missingRanges": [
{
"start": "<string>",
"end": "<string>"
}
],
"requirements": [
{
"id": "<string>",
"message": "<string>"
}
]
},
"priorYearMismatch": false,
"missingCount": 0,
"missingTruncated": false,
"conflictsCount": 0,
"conflictsTruncated": false,
"restoresCount": 0,
"restoresTruncated": false,
"duplicatesCount": 0,
"duplicatesTruncated": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
client | object | yes | The client the engagement belongs to. |
client.id | string (uuid) | yes | UUID of the client or entity. |
client.name | string | yes | Display name of the client or entity. |
client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
entity | object | yes | The legal entity the engagement belongs to. |
entity.id | string (uuid) | yes | UUID of the client or entity. |
entity.name | string | yes | Display name of the client or entity. |
entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
recordLifecycle | object | yes | Live or recoverable-archived (distinct from the return's filing lifecycle). |
recordLifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
recordLifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
recordLifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Non-blocking conditions the write reported: an unsupported filing year, an unverified election, a failed prior-year roll-forward or permanent-establishment seed, or a missing period start (period_start_missing: send periodStart with an engagement update). |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
importStatus | one of applied, needs_answers, blocked | no | |
applied | nullable object | no | What this call imported: the engagement's books after the import. Null when nothing was imported (importStatus needs_answers or blocked). |
applied.accountCount | integer, at least 0 | when applied is set | Live accounts on the engagement after the import. |
applied.classifiedCount | integer, at least 0 | when applied is set | Live accounts with a classification other than unclassified. |
applied.unclassifiedCount | integer, at least 0 | when applied is set | Live accounts still awaiting classification or a successful model answer. Call classify awaiting accounts for one bounded model round. Through MCP these include the accounts whose model classification was deferred to you: answer the hostModelRequests on this result and save each classification answer with update_engagement_data, action applyClassificationAnswers. |
applied.gifiSeededCount | integer, at least 0 | when applied is set | Live accounts with a GIFI assignment, including validated source codes and chart defaults. |
applied.totalDebits | nullable string | when applied is set | Canonical decimal debit total of the submitted source rows. |
applied.totalCredits | nullable string | when applied is set | Canonical decimal credit total of the submitted source rows. |
applied.balanced | boolean | when applied is set | True when both exact source amounts and the saved cent amounts balance within a dollar. rows mode can import an unbalanced subset. |
applied.warnings | array of strings, up to 50 items | when applied is set | Parser warnings, for the practitioner. |
submitted | nullable object | no | The submitted rows' totals and parser warnings when nothing was imported (importStatus needs_answers or blocked); null once an import applies, whose summary carries them. |
submitted.totalDebits | nullable string | when submitted is set | Canonical decimal debit total of the submitted rows. |
submitted.totalCredits | nullable string | when submitted is set | Canonical decimal credit total of the submitted rows. |
submitted.balanced | boolean | when submitted is set | True when the submitted debits and credits balance within a dollar. |
submitted.warnings | array of strings, up to 50 items | when submitted is set | Parser warnings, for the practitioner. |
previewId | nullable string | no | Principal- and engagement-scoped preview for the apply capability; expires after six hours. |
questions | nullable object | no | |
questions.mode | one of books, rows | when questions is set | |
questions.confirmationCounts | object | when questions is set | |
questions.confirmationCounts.balancesUpdated | integer, at least 0 | when questions is set | |
questions.confirmationCounts.renamed | integer, at least 0 | when questions is set | |
questions.confirmationCounts.added | integer, at least 0 | when questions is set | |
questions.confirmationCounts.restored | integer, at least 0 | when questions is set | |
questions.confirmationCounts.deleted | integer, at least 0 | when questions is set | |
questions.confirmationCounts.unchanged | integer, at least 0 | when questions is set | |
questions.confirmationCounts.skipped | integer, at least 0 | when questions is set | |
questions.conflicts | array of objects, up to 5000 items | when questions is set | |
questions.conflicts[].plans | nullable map of object | when questions is set | |
questions.conflicts[].plans.{key} | object | per key | |
questions.conflicts[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.renamed | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.added | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.restored | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.deleted | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.unchanged | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.skipped | integer, at least 0 | per key | |
questions.conflicts[].accountId | string | when questions is set | |
questions.conflicts[].code | string | when questions is set | |
questions.conflicts[].existingName | string | when questions is set | |
questions.conflicts[].incomingName | string | when questions is set | |
questions.conflicts[].incomingBalance | string | when questions is set | |
questions.conflicts[].incomingCode | nullable string | when questions is set | |
questions.conflicts[].crossKey | boolean | when questions is set | |
questions.restores | array of objects, up to 5000 items | when questions is set | |
questions.restores[].plans | nullable map of object | when questions is set | |
questions.restores[].plans.{key} | object | per key | |
questions.restores[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
questions.restores[].plans.{key}.renamed | integer, at least 0 | per key | |
questions.restores[].plans.{key}.added | integer, at least 0 | per key | |
questions.restores[].plans.{key}.restored | integer, at least 0 | per key | |
questions.restores[].plans.{key}.deleted | integer, at least 0 | per key | |
questions.restores[].plans.{key}.unchanged | integer, at least 0 | per key | |
questions.restores[].plans.{key}.skipped | integer, at least 0 | per key | |
questions.restores[].accountId | string | when questions is set | |
questions.restores[].code | string | when questions is set | |
questions.restores[].name | string | when questions is set | |
questions.restores[].deletedAt | nullable string | when questions is set | |
questions.restores[].incomingBalance | string | when questions is set | |
questions.missing | array of objects, up to 5000 items | when questions is set | |
questions.missing[].plans | map of object | when questions is set | |
questions.missing[].plans.{key} | object | per key | |
questions.missing[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
questions.missing[].plans.{key}.renamed | integer, at least 0 | per key | |
questions.missing[].plans.{key}.added | integer, at least 0 | per key | |
questions.missing[].plans.{key}.restored | integer, at least 0 | per key | |
questions.missing[].plans.{key}.deleted | integer, at least 0 | per key | |
questions.missing[].plans.{key}.unchanged | integer, at least 0 | per key | |
questions.missing[].plans.{key}.skipped | integer, at least 0 | per key | |
questions.missing[].accountId | string | when questions is set | |
questions.missing[].code | string | when questions is set | |
questions.missing[].name | string | when questions is set | |
questions.missing[].balance | string | when questions is set | |
questions.missing[].refs | object | when questions is set | |
questions.missing[].refs.ajeLines | integer | when questions is set | |
questions.missing[].refs.workpapers | integer | when questions is set | |
questions.missing[].refs.groups | integer | when questions is set | |
questions.duplicates | array of objects, up to 5000 items | when questions is set | |
questions.duplicates[].code | string | when questions is set | |
questions.duplicates[].combinedBalance | string | when questions is set | |
questions.duplicates[].rows | array of objects, up to 5000 items | when questions is set | |
questions.duplicates[].rows[].name | string | when questions is set | |
questions.duplicates[].rows[].balance | string | when questions is set | |
questions.duplicates[].rowsCount | integer, at least 0 | when questions is set | Member rows in this source group, listed or not. |
questions.duplicates[].rowsTruncated | boolean | when questions is set | Only the first 5,000 member rows are listed. Review the source for the rest; combine explicitly accepts the sum of every member, including unlisted rows. |
questions.duplicates[].plans | object | when questions is set | Apply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner. |
questions.duplicates[].plans.combine | array of objects, 1 to 2 items | when questions is set | |
questions.duplicates[].plans.combine[].conflicts | map of one of rename, new_account | when questions is set | |
questions.duplicates[].plans.combine[].conflicts.{key} | one of rename, new_account | per key | |
questions.duplicates[].plans.combine[].restores | map of one of restore, skip | when questions is set | |
questions.duplicates[].plans.combine[].restores.{key} | one of restore, skip | per key | |
questions.duplicates[].plans.combine[].counts | object | when questions is set | |
questions.duplicates[].plans.combine[].counts.balancesUpdated | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.renamed | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.added | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.restored | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.deleted | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.unchanged | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.skipped | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first | array of objects, 1 to 2 items | when questions is set | |
questions.duplicates[].plans.first[].conflicts | map of one of rename, new_account | when questions is set | |
questions.duplicates[].plans.first[].conflicts.{key} | one of rename, new_account | per key | |
questions.duplicates[].plans.first[].restores | map of one of restore, skip | when questions is set | |
questions.duplicates[].plans.first[].restores.{key} | one of restore, skip | per key | |
questions.duplicates[].plans.first[].counts | object | when questions is set | |
questions.duplicates[].plans.first[].counts.balancesUpdated | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.renamed | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.added | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.restored | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.deleted | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.unchanged | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.skipped | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip | array of objects, 1 to 1 items | when questions is set | |
questions.duplicates[].plans.skip[].conflicts | map of one of rename, new_account | when questions is set | |
questions.duplicates[].plans.skip[].conflicts.{key} | one of rename, new_account | per key | |
questions.duplicates[].plans.skip[].restores | map of one of restore, skip | when questions is set | |
questions.duplicates[].plans.skip[].restores.{key} | one of restore, skip | per key | |
questions.duplicates[].plans.skip[].counts | object | when questions is set | |
questions.duplicates[].plans.skip[].counts.balancesUpdated | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.renamed | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.added | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.restored | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.deleted | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.unchanged | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.skipped | integer, at least 0 | when questions is set | |
questions.lowMatch | nullable object | when questions is set | |
questions.lowMatch.matchedRows | integer | when questions is set | |
questions.lowMatch.rows | integer | when questions is set | |
questions.blocked | nullable object | when questions is set | |
questions.blocked.reason | string | when questions is set | |
questions.blocked.message | string | when questions is set | |
questions.coverage | nullable object | when questions is set | |
questions.coverage.fiscalPeriod | object | when questions is set | |
questions.coverage.fiscalPeriod.start | nullable string | when questions is set | |
questions.coverage.fiscalPeriod.end | nullable string | when questions is set | |
questions.coverage.files | array of objects | when questions is set | |
questions.coverage.files[].glId | string | when questions is set | |
questions.coverage.files[].filename | string | when questions is set | |
questions.coverage.files[].accounts | array of strings | when questions is set | |
questions.coverage.files[].reportRange | nullable object | when questions is set | |
questions.coverage.files[].reportRange.start | nullable string | when questions is set | |
questions.coverage.files[].reportRange.end | nullable string | when questions is set | |
questions.coverage.files[].observedRange | nullable object | when questions is set | |
questions.coverage.files[].observedRange.start | nullable string | when questions is set | |
questions.coverage.files[].observedRange.end | nullable string | when questions is set | |
questions.coverage.missingRanges | array of objects | when questions is set | |
questions.coverage.missingRanges[].start | nullable string | when questions is set | |
questions.coverage.missingRanges[].end | nullable string | when questions is set | |
questions.coverage.requirements | array of objects | when questions is set | |
questions.coverage.requirements[].id | string | when questions is set | |
questions.coverage.requirements[].message | string | when questions is set | |
questions.priorYearMismatch | boolean | when questions is set | |
questions.missingCount | integer, at least 0 | when questions is set | |
questions.missingTruncated | boolean | when questions is set | Only the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account. |
questions.conflictsCount | integer, at least 0 | when questions is set | Name conflicts the import found, listed or not. |
questions.conflictsTruncated | boolean | when questions is set | Only the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest. |
questions.restoresCount | integer, at least 0 | when questions is set | Rows matching a deleted account, listed or not. |
questions.restoresTruncated | boolean | when questions is set | Only the first 5,000 rows matching a deleted account are listed; omitted answers skip the row. |
questions.duplicatesCount | integer, at least 0 | when questions is set | Duplicated account codes in the source, listed or not. |
questions.duplicatesTruncated | boolean | when questions is set | Only the first 5,000 duplicated codes are listed. Every duplicated code needs an answer: import this trial balance in Filemark to answer the rest. |
Errors
In addition to the shared error codes:
404: The engagement does not exist in the caller's scope.
Get a saved engagement account
GET /api/v1/engagements/{engagement_id}/accounts/{account_id}
Returns an active saved account's balances, classification, GIFI mapping, chart-of-accounts metadata, and adjustment summary. The summary contains book adjustment totals and a count of linked workpaper tax-adjustment sources. Use the account adjustments endpoint for individual entries and workpapers. A missing engagement, an inactive account, or an engagement outside your token's scope returns 404.
Scope: tax-data:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/accounts/$ACCOUNT_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
ACCOUNT_ID = "<account-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/accounts/{ACCOUNT_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const ACCOUNT_ID = "<account-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/accounts/${ACCOUNT_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the account belongs to. An engagement is one corporation's taxation year. |
account_id | path | string, 1 to 128 characters | yes | Identifier of the account within that engagement, as returned in the trial balance. |
Response
200. One saved account with its balances, classification, GIFI mapping, and adjustment summary.
{
"context": {
"engagementId": "3f1c2b4a-5d6e-4f70-8912-a3b4c5d6e7f8",
"yearEnd": "2025-12-31",
"revisionNumber": 1,
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"filedAt": "2026-07-14T12:00:00Z",
"referenceOnly": false,
"reportingCurrency": "CAD",
"currencyBasis": "<canadian_currency | functional_currency | reversionary>",
"balanceConvention": "debits_positive_credits_negative"
},
"sourceRevision": {
"classifiedReportVersion": 12,
"gifiAssignmentsVersion": 4,
"gifiCarriedForwardVersion": 3,
"gifiAutoAppliedAccountIdsVersion": 2,
"ajeEntriesVersion": 7,
"lastModifiedAt": "2026-03-02T09:41:07Z",
"workpaperRevisionDigest": "sha256:1a3c5e7b9d0f2468ace02468ace02468ace02468ace02468ace02468ace02468",
"snapshotDigest": "sha256:9f2c1d5b0a7e43c68b1f0d2e4a6c8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e"
},
"account": {
"id": "acct-6500",
"accountCode": "<string>",
"accountName": "Meals and entertainment",
"currentYearBalance": "18450.75",
"priorYearBalance": "125000.5",
"changeStatus": "<new | removed | significant_increase | significant_decrease | minor_change | unchanged | unknown>"
},
"classification": {
"availability": "<classified | not_classified>",
"userStatus": "<pending | modified | accepted>",
"ruleId": "<string>",
"source": "<rule | prior_year | llm | llm_failed | deferred_to_host | manual>",
"confidence": "<decimal>",
"assumption": "<string>",
"bookTreatment": "<accept_as_booked | reclassify | accrue_adjust | write_off | requires_review | unclassified>",
"taxTreatment": "<fully_deductible | non_deductible_full | partially_deductible | capital_cca | timing_difference | deduct_other_schedule | non_taxable | disclosure_only | informational | unclassified>",
"bookTreatmentSource": "<derived | manual>",
"taxTreatmentSource": "<derived | manual>",
"s1Line": "<string>",
"feedsSchedule": "<string>",
"taxAssumptionNote": "<string>",
"derivationRuleId": "<string>",
"adjustmentType": "<addition | deduction>",
"deductibilityRule": "<string>",
"deductibilityPercentage": "<decimal>",
"incomeType": "<active_business | property | rental | capital>",
"incomeTypeSource": "<derived | manual>",
"shareholderOrigin": "<borrowed_principal | unpaid_remuneration | unpaid_related_expenses | shareholder_advance>",
"shareholderOriginSource": "manual",
"salesTaxAttribution": {
"<member>": "<any>"
},
"salesTaxAttributionSource": "manual",
"foreignSource": false,
"incomeSourceBasis": "rule_default_canadian_not_incident",
"subtype": "<string>",
"conditionalOn": "<string>",
"templateId": "<string>",
"itaReferences": [
"ITA 67.1(1)"
]
},
"gifiMapping": {
"assignedGifiCode": "<string>",
"carriedForward": false,
"machineApplied": false,
"relationToCurrentDefault": "<unassigned | no_default | matches_default | overrides_default>",
"chartAccount": {
"id": "b8d2f0c4-7e11-4a63-9c05-1d2e3f4a5b6c",
"naturalKey": "6500",
"accountType": "<asset | liability | equity | revenue | expense | unassigned>",
"accountSubtype": "<string>",
"subtypeSource": "<string>",
"accountTypeSource": "<inferred | firm_template | connector | coa_file | user>",
"isContra": false,
"isActive": false,
"defaultGifiCode": "<string>",
"defaultGifiSource": "<inferred | firm_template | carried | connector | coa_file | user>",
"parentAccountCode": "<string>",
"updatedAt": "2026-02-11T18:03:55Z"
}
},
"adjustmentSummary": {
"postedBookAdjustmentTotal": "-1250",
"adjustedBookBalance": "17200.75",
"postedBookAdjustmentCount": 2,
"blockedBookAdjustmentCount": 0,
"workpaperTaxAdjustmentSourceCount": 1,
"schedule1ImpactEvaluated": false
},
"projection": {
"savedStateOnly": true,
"classificationIncluded": true,
"gifiMappingIncluded": true,
"postedBookAdjustmentsSummarized": true,
"workpaperTaxAdjustmentsSummarized": true,
"workpaperAmountsAllocatedToAccount": false,
"schedule1ImpactEvaluated": false,
"computationEvaluated": false,
"lineageIncluded": false,
"readinessEvaluated": false,
"storageMetadataIncluded": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
context | object | yes | The engagement, reporting currency and sign convention this account belongs to. |
context.engagementId | string (uuid) | yes | UUID of the engagement this account belongs to. An engagement is one corporation's taxation year. |
context.yearEnd | string (date) | yes | The engagement's taxation year end, YYYY-MM-DD. |
context.revisionNumber | integer, at least 1 | yes | Filing revision counter for this year end. The original engagement is 1. Each amendment opened from it adds one. |
context.status | one of in_progress, review, complete | yes | Preparation workflow state set in the product. in_progress while the engagement is being worked on, review while it sits with a reviewer, complete when preparation is finished. Filing is separate and is reported by filedAt. |
context.source | one of prepared, imported | yes | How the engagement came to exist. prepared is a full engagement worked in Filemark and can be filed. imported is a reference-only record created from an uploaded prior-year trial balance. An imported engagement supplies only comparatives and carryforward figures until it is promoted to prepared. |
context.filedAt | nullable string (date-time) | yes | When the return was marked filed, ISO-8601 in UTC. Null when the engagement has not been marked filed. Once set, the engagement is read-only in the product. |
context.referenceOnly | boolean | yes | True when source is imported, meaning the engagement is a reference-only record whose balances are comparatives. |
context.reportingCurrency | string, matching ^[A-Z]{3}$ | yes | ISO 4217 code of the currency every amount in this response is stated in. CAD unless the corporation has an effective functional currency election for the year. |
context.currencyBasis | one of canadian_currency, functional_currency, reversionary | yes | What determines the reporting currency. canadian_currency: no functional currency election applies to this year and amounts are in Canadian dollars. functional_currency: an effective election applies and amounts are in the elected currency. reversionary: the election has ended and the year is back on Canadian dollars under the reversion rules. |
context.balanceConvention | string, always "debits_positive_credits_negative" | yes | Always debits_positive_credits_negative. Every balance in this response is signed with debits positive and credits negative. |
sourceRevision | object | yes | Version counters and fingerprints for the saved state this response was read from. |
sourceRevision.classifiedReportVersion | integer, at least 0 | yes | Saved trial-balance version, including account classifications, incremented on every save. Zero before the first save. |
sourceRevision.gifiAssignmentsVersion | integer, at least 0 | yes | Write counter for the saved GIFI code assignments, on the same convention as classifiedReportVersion. |
sourceRevision.gifiCarriedForwardVersion | integer, at least 0 | yes | Write counter for the record of which GIFI assignments were carried forward from the prior year, on the same convention as classifiedReportVersion. |
sourceRevision.gifiAutoAppliedAccountIdsVersion | integer, at least 0 | yes | Write counter for the record of which GIFI assignments were applied in bulk from suggestions, on the same convention as classifiedReportVersion. |
sourceRevision.ajeEntriesVersion | integer, at least 0 | yes | Write counter for the saved adjusting journal entries, on the same convention as classifiedReportVersion. |
sourceRevision.lastModifiedAt | string (date-time) | yes | When any part of the engagement was last saved, ISO-8601 in UTC. |
sourceRevision.workpaperRevisionDigest | string, matching ^sha256:[0-9a-f]{64}$ | yes | Fingerprint of the engagement's workpapers and their account links at the moment of this read, as sha256: followed by 64 lowercase hex characters. A changed value means a workpaper was saved or relinked. |
sourceRevision.snapshotDigest | string, matching ^sha256:[0-9a-f]{64}$ | yes | Fingerprint of everything this response is built from, as sha256: followed by 64 lowercase hex characters. Compare it across reads to see whether anything about this account changed. |
account | object | yes | The account's code, name and balances before any adjustment. |
account.id | string, 1 to 128 characters | yes | Identifier of the account within this engagement, the same value you passed in the path. It stays fixed for the life of the engagement but does not carry across years, and it is not a UUID. |
account.accountCode | nullable string, up to 128 characters | yes | Account number as it appears in the client's chart of accounts. Null for a trial balance whose accounts are identified by name only. |
account.accountName | string, up to 512 characters | yes | Account name as it appears in the client's chart of accounts. May be an empty string when the source file carried no name. |
account.currentYearBalance | decimal string, 1 to 96 characters | yes | Closing balance for the taxation year, before adjusting entries and tax adjustments. Exact decimal string in the reporting currency, debits positive and credits negative. |
account.priorYearBalance | nullable decimal string, 1 to 96 characters | yes | The comparative closing balance for the preceding year, on the same convention as currentYearBalance. Null when no prior-year figure is on file for the account. |
account.changeStatus | one of new, removed, significant_increase, significant_decrease, minor_change, unchanged, unknown | yes | How the balance moved against the prior year. new: the account appears only in the current year. removed: it appears only in the prior year. significant_increase and significant_decrease: the move passed both the absolute and the percentage threshold configured for the engagement. minor_change: the balance moved but not enough to be flagged. unchanged: the balance is identical. unknown: the saved account carries no comparison result. |
classification | object | yes | The saved tax treatment of the account. |
classification.availability | one of classified, not_classified | yes | classified when a tax classification is saved for the account. not_classified when none is. Under not_classified every other field here is null and itaReferences is empty. |
classification.userStatus | nullable one of pending, modified, accepted | yes | Where the preparer has got to on this account. pending: the classification has not been reviewed. modified: the preparer changed it. accepted: the preparer confirmed it. Null when the account carries no review state. |
classification.ruleId | nullable string, up to 256 characters | yes | Identifier of the classification rule that matched the account, if one did. Null for a classification that did not come from a rule. |
classification.source | nullable one of rule, prior_year, llm, llm_failed, deferred_to_host, manual | yes | Where the classification came from. rule: a keyword rule matched the account. prior_year: it was carried forward from the preceding year and needs reconfirming. llm: an automated classifier suggested it. llm_failed: the automated classifier could not classify the account and it is left unclassified for manual review. deferred_to_host: the classification request went to the calling host's model and the account was left unclassified. manual: a preparer set it. Null when the account is not classified. |
classification.confidence | nullable decimal string, 1 to 96 characters | yes | The classification's score, as an exact decimal string from 0 to 1. For source llm it is the model's own, uncalibrated score: use it to rank, not as a probability. Null when the classification did not come with a score. |
classification.assumption | nullable string, up to 4000 characters | yes | The plain-language assumption the classification rests on, usually phrased as what the preparer should confirm with the client. A preparer's own wording replaces the generated one. Null when no assumption is recorded. |
classification.bookTreatment | nullable one of accept_as_booked, reclassify, accrue_adjust, write_off, requires_review, unclassified | yes | What should happen to the booked amount. accept_as_booked: leave the book entry alone. reclassify: move it to another account. accrue_adjust: adjust it for an accrual or cut-off. write_off: remove it. requires_review: a preparer has to decide. unclassified: no view has been reached. Null when the account is not classified. |
classification.taxTreatment | nullable one of fully_deductible, non_deductible_full, partially_deductible, capital_cca, timing_difference, deduct_other_schedule, non_taxable, disclosure_only, informational, unclassified | yes | How the amount is treated for tax. fully_deductible: deductible in full. non_deductible_full: added back in full. partially_deductible: deductible up to deductibilityPercentage. capital_cca: capital in nature and relieved through capital cost allowance. timing_difference: deductible, but in another period. deduct_other_schedule: relieved somewhere other than Schedule 1, named by feedsSchedule. non_taxable: not brought into income. disclosure_only: reported but with no income effect. informational: carried for context only. unclassified: no view has been reached. Null when the account is not classified. |
classification.bookTreatmentSource | nullable one of derived, manual | yes | derived when bookTreatment was computed from the classification. manual when a preparer set it, and re-running the classifier will not overwrite it. Null when the account is not classified. |
classification.taxTreatmentSource | nullable one of derived, manual | yes | derived when taxTreatment was computed from the classification. manual when a preparer set it, and re-running the classifier will not overwrite it. Null when the account is not classified. |
classification.s1Line | nullable string, up to 32 characters | yes | Schedule 1 line for the account adjustment. Null when no adjustment is routed to Schedule 1 or a preparer must resolve an ambiguous mapping. |
classification.feedsSchedule | nullable string, up to 32 characters | yes | The schedule the amount feeds, such as S1 for Schedule 1, S8 for Schedule 8, T661 for the scientific research and experimental development claim, or DivC for a Division C deduction. Null when the amount feeds no schedule. |
classification.taxAssumptionNote | nullable string, up to 4000 characters | yes | Explanation of a conditional or unresolved routing decision. Null when no note applies. |
classification.derivationRuleId | nullable string, up to 256 characters | yes | Identifier of the branch that produced bookTreatment, taxTreatment, s1Line and feedsSchedule. Null when the account is not classified. |
classification.adjustmentType | nullable one of addition, deduction | yes | Which way the Schedule 1 adjustment goes: addition adds the amount back to income, deduction takes it out. Null when the account produces no Schedule 1 adjustment. |
classification.deductibilityRule | nullable string, up to 2000 characters | yes | Plain-language statement of the rule limiting the deduction, including its statutory basis. Null when no rule is recorded. |
classification.deductibilityPercentage | nullable decimal string, 1 to 96 characters | yes | Share of the amount that is deductible, as an exact decimal string from 0 to 100. Null when the limit is not a simple percentage. |
classification.incomeType | nullable one of active_business, property, rental, capital | yes | The kind of income the account represents. It decides how the amount is split on the income schedules. active_business: income from an active business. property: investment or other property income. rental: rental income. capital: a capital item. Null when the account carries no income tag. |
classification.incomeTypeSource | nullable one of derived, manual | yes | derived when incomeType was computed from the classification, manual when a preparer set it. Null when incomeType is null. |
classification.shareholderOrigin | nullable one of borrowed_principal, unpaid_remuneration, unpaid_related_expenses, shareholder_advance | no | |
classification.shareholderOriginSource | nullable string, always "manual" | no | |
classification.salesTaxAttribution | nullable object | no | |
classification.salesTaxAttribution.* | any | no | Members not listed here. |
classification.salesTaxAttributionSource | nullable string, always "manual" | no | |
classification.foreignSource | nullable boolean | yes | True when the income is from a foreign source. Foreign income is separated on the income schedules. Null when the question has not been answered. |
classification.incomeSourceBasis | nullable string, always "rule_default_canadian_not_incident" | yes | How foreignSource was set. rule_default_canadian_not_incident: nobody stated the source, so the classification rule filed the income as Canadian-source (and property income as not incident to an active business). The return discloses that default, and asks for the source instead where it shows income or a business outside Canada. Null when the source was stated or is unanswered. |
classification.subtype | nullable string, up to 64 characters | yes | A finer tag within the income type, such as dividend, used to route the amount more precisely. Null when the classification sets none. |
classification.conditionalOn | nullable string, up to 2000 characters | yes | The factual condition that must hold for this treatment to apply. Null when the treatment is unconditional. |
classification.templateId | nullable string, up to 256 characters | yes | Identifier of the workpaper template this account calls for. Null when the account needs no workpaper. |
classification.itaReferences | array of strings, up to 32 items | yes | Income Tax Act provisions the classification relies on, as written citations. Empty when none are recorded. |
gifiMapping | object | yes | The GIFI code saved for this account and the chart-of-accounts record behind it. |
gifiMapping.assignedGifiCode | nullable string, matching ^[0-9]{4}$ | yes | The four-digit GIFI code assigned to the account for this year. Null when no code has been assigned yet. |
gifiMapping.carriedForward | boolean | yes | True when the assignment was copied forward from the prior year. |
gifiMapping.machineApplied | boolean | yes | True when the assignment was applied in bulk from a suggestion. These assignments count as unconfirmed and are not used to train later suggestions. |
gifiMapping.relationToCurrentDefault | one of unassigned, no_default, matches_default, overrides_default | yes | How this year's assignment compares with the chart-of-accounts default. unassigned: no code is assigned for this year. no_default: a code is assigned but the chart holds no default to compare it with. matches_default: the assigned code equals the default. overrides_default: the assigned code differs from the default. |
gifiMapping.chartAccount | nullable object | yes | The chart-of-accounts record behind this account. Null when the account has no chart record yet, which is usual the first time an account is seen. |
gifiMapping.chartAccount.id | string (uuid) | when chartAccount is set | UUID of the chart-of-accounts record. |
gifiMapping.chartAccount.naturalKey | string, 1 to 517 characters | when chartAccount is set | The key that identifies this account across years. It is the normalized account code. When the books carry no codes it is the text name: followed by the normalized account name. |
gifiMapping.chartAccount.accountType | one of asset, liability, equity, revenue, expense, unassigned | when chartAccount is set | The account's accounting class. unassigned when no class has been established for it. |
gifiMapping.chartAccount.accountSubtype | nullable string, up to 256 characters | when chartAccount is set | The account type as the source accounting system named it, kept verbatim. Null when the source supplied none. |
gifiMapping.chartAccount.subtypeSource | nullable string, up to 64 characters | when chartAccount is set | Which accounting system's vocabulary accountSubtype is written in. Null when accountSubtype is null. |
gifiMapping.chartAccount.accountTypeSource | one of inferred, firm_template, connector, coa_file, user | when chartAccount is set | Where accountType came from, weakest first. inferred: worked out from the account name or number. firm_template: a default from the firm's own chart template. connector: supplied by the connected accounting system. coa_file: supplied by an uploaded chart of accounts. user: set by a person, the strongest source. |
gifiMapping.chartAccount.isContra | boolean | when chartAccount is set | True when the account runs against its class, such as accumulated depreciation against fixed assets. |
gifiMapping.chartAccount.isActive | boolean | when chartAccount is set | True while the account is part of the current chart. False once it has stopped appearing in imported books. |
gifiMapping.chartAccount.defaultGifiCode | nullable string, matching ^[0-9]{4}$ | when chartAccount is set | The four-digit GIFI code that seeds each new year for this account. Null when no default has been established. |
gifiMapping.chartAccount.defaultGifiSource | nullable one of inferred, firm_template, carried, connector, coa_file, user | when chartAccount is set | Where defaultGifiCode came from, weakest first. inferred: worked out from the account name or number. firm_template: a default from the firm's own chart template. carried: copied forward from a prior year. connector: supplied by the connected accounting system. coa_file: supplied by an uploaded chart of accounts. user: set by a person, the strongest source. Null when defaultGifiCode is null. |
gifiMapping.chartAccount.parentAccountCode | nullable string, up to 128 characters | when chartAccount is set | Account number of this account's parent in the client's chart. Null when the account has no parent or the source did not say. |
gifiMapping.chartAccount.updatedAt | string (date-time) | when chartAccount is set | When the chart-of-accounts record was last written, ISO-8601 in UTC. |
adjustmentSummary | object | yes | Totals and counts for the adjustments touching this account. |
adjustmentSummary.postedBookAdjustmentTotal | decimal string, 1 to 96 characters | yes | Net effect of posted adjusting entries on this account, as a decimal string in the reporting currency. Debits are positive and credits negative. |
adjustmentSummary.adjustedBookBalance | decimal string, 1 to 96 characters | yes | The account's balance after posted adjusting entries. It is the saved balance plus postedBookAdjustmentTotal. Exact decimal string in the reporting currency. Workpaper tax adjustments are not included. |
adjustmentSummary.postedBookAdjustmentCount | integer, at least 0 | yes | How many adjusting journal entries touching this account post. |
adjustmentSummary.blockedBookAdjustmentCount | integer, at least 0 | yes | How many adjusting journal entries touching this account cannot post because they do not balance or a line does not resolve to an account. These are defects a preparer has to fix. Entries marked potential are not counted here. |
adjustmentSummary.workpaperTaxAdjustmentSourceCount | integer, at least 0 | yes | Number of linked workpapers with a tax adjustment. A workpaper may be linked to several accounts. |
adjustmentSummary.schedule1ImpactEvaluated | boolean, always false | yes | Always false. Schedule 1 contributions require a separate computation. |
projection | object | yes | What this response does and does not include. |
projection.savedStateOnly | boolean, always true | yes | Always true. The response uses saved state. |
projection.classificationIncluded | boolean, always true | yes | Always true. The account's saved classification is included. |
projection.gifiMappingIncluded | boolean, always true | yes | Always true. The account's saved GIFI assignment is included. |
projection.postedBookAdjustmentsSummarized | boolean, always true | yes | Always true. Use the account adjustments endpoint for individual adjusting entries. |
projection.workpaperTaxAdjustmentsSummarized | boolean, always true | yes | Always true. Use the account adjustments endpoint for individual workpaper tax adjustments. |
projection.workpaperAmountsAllocatedToAccount | boolean, always false | yes | Always false. Workpaper amounts apply to the whole workpaper. |
projection.schedule1ImpactEvaluated | boolean, always false | yes | Always false. Schedule 1 contributions are not computed by this operation. |
projection.computationEvaluated | boolean, always false | yes | Always false. No schedule or return computation is run to produce this response. |
projection.lineageIncluded | boolean, always false | yes | Always false. No per-amount lineage or derivation trail is returned. |
projection.readinessEvaluated | boolean, always false | yes | Always false. Filing readiness is not evaluated by this operation. |
projection.storageMetadataIncluded | boolean, always false | yes | Always false. No raw stored records, documents or internal storage details are returned. |
Errors
In addition to the shared error codes:
404: The engagement or active account does not exist in the caller's organization.409: The saved account data cannot be represented safely.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
List saved account adjustments
GET /api/v1/engagements/{engagement_id}/accounts/{account_id}/adjustments
Returns posted and blocked book adjusting entries for the account, including reasons for blocked entries, and linked workpaper tax adjustments. Each entry includes this account's lines and the totals for the entire entry. Each workpaper amount represents the whole workpaper. The response contains the complete list. A missing engagement, an inactive account, or an engagement outside your token's scope returns 404.
Scope: tax-data:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/accounts/$ACCOUNT_ID/adjustments \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
ACCOUNT_ID = "<account-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/accounts/{ACCOUNT_ID}/adjustments",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const ACCOUNT_ID = "<account-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/accounts/${ACCOUNT_ID}/adjustments`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the account belongs to. An engagement is one corporation's taxation year. |
account_id | path | string, 1 to 128 characters | yes | Identifier of the account within that engagement, as returned in the trial balance. |
Response
200. The account's posted book adjusting entries and its linked workpaper tax-adjustment sources.
{
"context": {
"engagementId": "3f1c2b4a-5d6e-4f70-8912-a3b4c5d6e7f8",
"yearEnd": "2025-12-31",
"revisionNumber": 1,
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"filedAt": "2026-07-14T12:00:00Z",
"referenceOnly": false,
"reportingCurrency": "CAD",
"currencyBasis": "<canadian_currency | functional_currency | reversionary>",
"balanceConvention": "debits_positive_credits_negative"
},
"sourceRevision": {
"classifiedReportVersion": 12,
"gifiAssignmentsVersion": 4,
"gifiCarriedForwardVersion": 3,
"gifiAutoAppliedAccountIdsVersion": 2,
"ajeEntriesVersion": 7,
"lastModifiedAt": "2026-03-02T09:41:07Z",
"workpaperRevisionDigest": "sha256:1a3c5e7b9d0f2468ace02468ace02468ace02468ace02468ace02468ace02468",
"snapshotDigest": "sha256:9f2c1d5b0a7e43c68b1f0d2e4a6c8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e"
},
"account": {
"id": "acct-6500",
"accountCode": "<string>",
"accountName": "Meals and entertainment",
"currentYearBalance": "18450.75",
"priorYearBalance": "125000.5",
"changeStatus": "<new | removed | significant_increase | significant_decrease | minor_change | unchanged | unknown>"
},
"projection": {
"postedBookAdjustmentsIncluded": true,
"blockedBookAdjustmentsIncluded": true,
"workpaperTaxAdjustmentSourcesIncluded": true,
"unrelatedBookAdjustmentLinesIncluded": false,
"workpaperAmountsAllocatedToAccount": false,
"schedule1ImpactEvaluated": false,
"computationEvaluated": false,
"readinessEvaluated": false,
"storageMetadataIncluded": false
},
"postedBookAdjustmentTotal": "-1250",
"adjustedBookBalance": "17200.75",
"bookAdjustments": [
{
"kind": "book_aje",
"id": "aje-004",
"number": 4,
"name": "Accrue December utilities",
"description": "Accrue the December utilities invoice received after year end.",
"entryType": "<adjusting | reclassifying | tax_provision | potential>",
"source": "<preparer | client>",
"entryDate": "2025-12-31",
"recurring": false,
"postingStatus": "<posted | blocked | excluded>",
"blockReason": "<unbalanced | unlinked_account>",
"totalDebits": "1250",
"totalCredits": "1250",
"accountEffect": "125000.5",
"requestedAccountLines": [
{
"id": "line-1",
"type": "<debit | credit>",
"accountId": "acct-6500",
"accountCode": "<string>",
"accountName": "Meals and entertainment",
"amount": "1250"
}
]
}
],
"workpaperTaxAdjustments": [
{
"kind": "workpaper_tax_adjustment",
"workpaperId": "7c9e1a2b-3d4f-4e56-8a7b-9c0d1e2f3a4b",
"templateId": "<string>",
"displayName": "<string>",
"version": 3,
"lifecycleStatus": "draft",
"tieOutStatus": "<matched | difference | pending | incomplete>",
"adjustmentAmount": "125000.5",
"adjustmentType": "<addition | deduction>",
"adjustmentStatus": "ok",
"requiresManualReview": false,
"linkedAccountCount": 2,
"linkedAccountIds": [
"acct-6500",
"acct-6510"
],
"allocationBasis": "workpaper_total_not_account_allocated",
"schedule1ImpactEvaluated": false
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
context | object | yes | The engagement, reporting currency and sign convention these amounts belong to. |
context.engagementId | string (uuid) | yes | UUID of the engagement this account belongs to. An engagement is one corporation's taxation year. |
context.yearEnd | string (date) | yes | The engagement's taxation year end, YYYY-MM-DD. |
context.revisionNumber | integer, at least 1 | yes | Filing revision counter for this year end. The original engagement is 1. Each amendment opened from it adds one. |
context.status | one of in_progress, review, complete | yes | Preparation workflow state set in the product. in_progress while the engagement is being worked on, review while it sits with a reviewer, complete when preparation is finished. Filing is separate and is reported by filedAt. |
context.source | one of prepared, imported | yes | How the engagement came to exist. prepared is a full engagement worked in Filemark and can be filed. imported is a reference-only record created from an uploaded prior-year trial balance. An imported engagement supplies only comparatives and carryforward figures until it is promoted to prepared. |
context.filedAt | nullable string (date-time) | yes | When the return was marked filed, ISO-8601 in UTC. Null when the engagement has not been marked filed. Once set, the engagement is read-only in the product. |
context.referenceOnly | boolean | yes | True when source is imported, meaning the engagement is a reference-only record whose balances are comparatives. |
context.reportingCurrency | string, matching ^[A-Z]{3}$ | yes | ISO 4217 code of the currency every amount in this response is stated in. CAD unless the corporation has an effective functional currency election for the year. |
context.currencyBasis | one of canadian_currency, functional_currency, reversionary | yes | What determines the reporting currency. canadian_currency: no functional currency election applies to this year and amounts are in Canadian dollars. functional_currency: an effective election applies and amounts are in the elected currency. reversionary: the election has ended and the year is back on Canadian dollars under the reversion rules. |
context.balanceConvention | string, always "debits_positive_credits_negative" | yes | Always debits_positive_credits_negative. Every balance in this response is signed with debits positive and credits negative. |
sourceRevision | object | yes | Version counters and fingerprints for the saved state this response was read from. |
sourceRevision.classifiedReportVersion | integer, at least 0 | yes | Saved trial-balance version, including account classifications, incremented on every save. Zero before the first save. |
sourceRevision.gifiAssignmentsVersion | integer, at least 0 | yes | Write counter for the saved GIFI code assignments, on the same convention as classifiedReportVersion. |
sourceRevision.gifiCarriedForwardVersion | integer, at least 0 | yes | Write counter for the record of which GIFI assignments were carried forward from the prior year, on the same convention as classifiedReportVersion. |
sourceRevision.gifiAutoAppliedAccountIdsVersion | integer, at least 0 | yes | Write counter for the record of which GIFI assignments were applied in bulk from suggestions, on the same convention as classifiedReportVersion. |
sourceRevision.ajeEntriesVersion | integer, at least 0 | yes | Write counter for the saved adjusting journal entries, on the same convention as classifiedReportVersion. |
sourceRevision.lastModifiedAt | string (date-time) | yes | When any part of the engagement was last saved, ISO-8601 in UTC. |
sourceRevision.workpaperRevisionDigest | string, matching ^sha256:[0-9a-f]{64}$ | yes | Fingerprint of the engagement's workpapers and their account links at the moment of this read, as sha256: followed by 64 lowercase hex characters. A changed value means a workpaper was saved or relinked. |
sourceRevision.snapshotDigest | string, matching ^sha256:[0-9a-f]{64}$ | yes | Fingerprint of everything this response is built from, as sha256: followed by 64 lowercase hex characters. Compare it across reads to see whether anything about this account changed. |
account | object | yes | The account these adjustments were read for, with its balances before any adjustment. |
account.id | string, 1 to 128 characters | yes | Identifier of the account within this engagement, the same value you passed in the path. It stays fixed for the life of the engagement but does not carry across years, and it is not a UUID. |
account.accountCode | nullable string, up to 128 characters | yes | Account number as it appears in the client's chart of accounts. Null for a trial balance whose accounts are identified by name only. |
account.accountName | string, up to 512 characters | yes | Account name as it appears in the client's chart of accounts. May be an empty string when the source file carried no name. |
account.currentYearBalance | decimal string, 1 to 96 characters | yes | Closing balance for the taxation year, before adjusting entries and tax adjustments. Exact decimal string in the reporting currency, debits positive and credits negative. |
account.priorYearBalance | nullable decimal string, 1 to 96 characters | yes | The comparative closing balance for the preceding year, on the same convention as currentYearBalance. Null when no prior-year figure is on file for the account. |
account.changeStatus | one of new, removed, significant_increase, significant_decrease, minor_change, unchanged, unknown | yes | How the balance moved against the prior year. new: the account appears only in the current year. removed: it appears only in the prior year. significant_increase and significant_decrease: the move passed both the absolute and the percentage threshold configured for the engagement. minor_change: the balance moved but not enough to be flagged. unchanged: the balance is identical. unknown: the saved account carries no comparison result. |
projection | object | yes | What this response does and does not include. |
projection.postedBookAdjustmentsIncluded | boolean, always true | yes | Always true. Adjusting journal entries that post to this account are listed. |
projection.blockedBookAdjustmentsIncluded | boolean, always true | yes | Always true. Entries touching this account that cannot post are listed too. |
projection.workpaperTaxAdjustmentSourcesIncluded | boolean, always true | yes | Always true. The workpapers holding a tax adjustment that are linked to this account are listed. |
projection.unrelatedBookAdjustmentLinesIncluded | boolean, always false | yes | Always false. Returned lines belong to the requested account. |
projection.workpaperAmountsAllocatedToAccount | boolean, always false | yes | Always false. Workpaper amounts apply to the whole workpaper. |
projection.schedule1ImpactEvaluated | boolean, always false | yes | Always false. Schedule 1 contributions are not computed by this operation. |
projection.computationEvaluated | boolean, always false | yes | Always false. No schedule or return computation is run to produce this response. |
projection.readinessEvaluated | boolean, always false | yes | Always false. Filing readiness is not evaluated by this operation. |
projection.storageMetadataIncluded | boolean, always false | yes | Always false. No workpaper payloads, rows, notes, attachments or internal storage details are returned. |
postedBookAdjustmentTotal | decimal string, 1 to 96 characters | yes | Net effect on this account of every adjusting journal entry that posts. Exact decimal string in the reporting currency, debits positive and credits negative. It is the sum of the accountEffect values below. |
adjustedBookBalance | decimal string, 1 to 96 characters | yes | The account's balance after posted adjusting entries. It is the saved balance plus postedBookAdjustmentTotal. Exact decimal string in the reporting currency. Workpaper tax adjustments are not included. |
bookAdjustments | array of objects, up to 5000 items | yes | All adjusting entries with lines for this account, including their posting status and this account's lines. Empty when no entries apply. |
bookAdjustments[].kind | string, always "book_aje" | yes | Always book_aje, marking this as an adjusting journal entry. |
bookAdjustments[].id | string, 1 to 128 characters | yes | Identifier of the entry within this engagement. |
bookAdjustments[].number | integer, at least 1 | yes | The entry's number in the engagement, as shown to preparers. |
bookAdjustments[].name | string, up to 512 characters | yes | Short title the preparer gave the entry. May be an empty string. |
bookAdjustments[].description | string, up to 4000 characters | yes | The preparer's longer explanation of the entry. May be an empty string. |
bookAdjustments[].entryType | one of adjusting, reclassifying, tax_provision, potential | yes | What kind of entry it is. adjusting: an ordinary correction. reclassifying: a move between accounts. tax_provision: the tax provision entry. potential: a proposed or waived entry that is tracked but never posted. The other three post. |
bookAdjustments[].source | one of preparer, client | yes | Who the entry came from. preparer for one raised in the engagement. client for one already in the client's own books. |
bookAdjustments[].entryDate | nullable string (date) | yes | The date the preparer put on the entry, YYYY-MM-DD. Null when the entry carries no date. |
bookAdjustments[].recurring | boolean | yes | True when the preparer marked the entry as one that recurs each year. |
bookAdjustments[].postingStatus | one of posted, blocked, excluded | yes | Whether the entry affects balances. posted: it does. blocked: it cannot, because of the defect named in blockReason, and a preparer has to fix it before filing. excluded: it is a potential entry, which is never posted and is not a defect. |
bookAdjustments[].blockReason | nullable one of unbalanced, unlinked_account | yes | Why the entry does not balance or resolve. unbalanced: total debits and total credits differ by a cent or more. unlinked_account: a line carrying an amount does not resolve to an account in the trial balance. Null when neither applies. An excluded entry can carry a reason too. |
bookAdjustments[].totalDebits | decimal string, 1 to 96 characters | yes | Total debits across all accounts in the entry, as a non-negative decimal string in the reporting currency. |
bookAdjustments[].totalCredits | decimal string, 1 to 96 characters | yes | Total credits across all accounts in the entry, as a non-negative decimal string in the reporting currency. Equals totalDebits for a balanced entry. |
bookAdjustments[].accountEffect | nullable decimal string, 1 to 96 characters | yes | Net effect of this entry on the account you asked for, exact decimal string in the reporting currency, debits positive and credits negative. Null when the entry does not post, in which case postingStatus is blocked or excluded. |
bookAdjustments[].requestedAccountLines | array of objects, up to 2000 items | yes | Lines for the requested account. totalDebits and totalCredits cover the entire entry across all accounts. |
bookAdjustments[].requestedAccountLines[].id | string, 1 to 128 characters | yes | Identifier of the line within its entry. |
bookAdjustments[].requestedAccountLines[].type | one of debit, credit | yes | Which side the line is posted on. A debit raises the account's balance and a credit lowers it, matching the debits positive convention. |
bookAdjustments[].requestedAccountLines[].accountId | string, 1 to 128 characters | yes | The account this line posts to, always the account you asked for. Lines of the same entry that post elsewhere are not returned. |
bookAdjustments[].requestedAccountLines[].accountCode | nullable string, up to 128 characters | yes | Account number as written on the line. Null when the line carries none. |
bookAdjustments[].requestedAccountLines[].accountName | string, up to 512 characters | yes | Account name as written on the line. May be an empty string. |
bookAdjustments[].requestedAccountLines[].amount | decimal string, 1 to 96 characters | yes | Non-negative line amount as a decimal string in the reporting currency. type identifies the debit or credit direction. |
workpaperTaxAdjustments | array of objects, up to 2000 items | yes | Every workpaper holding a tax adjustment that is linked to this account. No amount here is attributed to the account, and none is reflected in adjustedBookBalance. Empty when no such workpaper is linked. |
workpaperTaxAdjustments[].kind | string, always "workpaper_tax_adjustment" | yes | Always workpaper_tax_adjustment, marking this as a workpaper source. |
workpaperTaxAdjustments[].workpaperId | string (uuid) | yes | UUID of the workpaper. |
workpaperTaxAdjustments[].templateId | nullable string, up to 256 characters | yes | Identifier of the template the workpaper was built from. Null for a workpaper that was not built from a template. |
workpaperTaxAdjustments[].displayName | nullable string, up to 512 characters | yes | The name a preparer gave the workpaper. Null when it carries no custom name. The product then shows the template's name. |
workpaperTaxAdjustments[].version | integer, at least 1 | yes | Write counter for the workpaper, starting at 1 and increasing on every save. |
workpaperTaxAdjustments[].lifecycleStatus | string, 1 to 64 characters | yes | Workpaper preparation status, such as draft. Treat unknown values as informational. |
workpaperTaxAdjustments[].tieOutStatus | nullable one of matched, difference, pending, incomplete | yes | Whether the workpaper agrees with the balances of the accounts it is linked to. matched: it agrees within a cent. difference: it does not. pending: the check has not been run. incomplete: nothing has been entered to check, or an amount could not be read. Null for a workpaper that carries no numeric tie-out, such as a narrative one. |
workpaperTaxAdjustments[].adjustmentAmount | nullable decimal string, 1 to 96 characters | yes | The whole workpaper's adjustment amount, as an exact decimal string in the reporting currency, and not this account's share of it. Null when the workpaper records a type or status but no amount. |
workpaperTaxAdjustments[].adjustmentType | nullable one of addition, deduction | yes | Which way the adjustment goes: addition adds the amount back to income, deduction takes it out. Null when the workpaper does not say. |
workpaperTaxAdjustments[].adjustmentStatus | string, 1 to 128 characters | yes | Whether the workpaper's amount is usable. ok means it computed cleanly. not_computed means the adjustment has not been computed for this workpaper yet. Any other value means it did not compute cleanly and a preparer has to look at it, for example because the workpaper's rows could not be read or no computation exists for its template. |
workpaperTaxAdjustments[].requiresManualReview | boolean | yes | True whenever adjustmentStatus is anything other than ok. A preparer has to review the workpaper before its amount can be relied on. |
workpaperTaxAdjustments[].linkedAccountCount | integer, at least 1 | yes | How many trial-balance accounts the workpaper is linked to, counting the one you asked for. Anything above 1 means the amount covers more than this account. |
workpaperTaxAdjustments[].linkedAccountIds | array of strings, 1 to 20000 items | yes | Identifiers of every trial-balance account the workpaper is linked to, always including the one you asked for. |
workpaperTaxAdjustments[].allocationBasis | string, always "workpaper_total_not_account_allocated" | yes | Always workpaper_total_not_account_allocated. adjustmentAmount applies to the whole workpaper. |
workpaperTaxAdjustments[].schedule1ImpactEvaluated | boolean, always false | yes | Always false. Schedule 1 contributions require routing and deduplication during computation. |
Errors
In addition to the shared error codes:
404: The engagement or active account does not exist in the caller's organization.409: The saved account data cannot be represented safely.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Compute an engagement's saved state with cells replaced
POST /api/v1/engagements/{engagement_id}/computations/scenario
Runs a temporary computation using an engagement's saved inputs and the replacements in the request; nothing is saved. An input left out keeps its saved value and each input sent replaces its whole saved value, except t2Jacket, which merges into the saved jacket box by box: send t2Jacket and each of its sections as an object (t2Jacket: null is refused) and clear one box by sending it as null. Each target's input names are read with getComputationTargetContract, whose view=inputs answers the names, or one input, a page of at most about 20 KB. An input the engine refuses after admission is answered in error.details with its location and the engine's reason. The response contains a bounded summary (headline figures, each target's state, the findings), the computed results (all of them, a page selected with resultsOffset and resultsLimit, or none with view 'summary') and a hash identifying the saved input snapshot. Filing readiness is not evaluated.
Scopes: tax-data:read + tax:compute.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/computations/scenario \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"compute": [
"part_i_tax"
],
"inputs": {
"isCCPC": true
}
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/computations/scenario",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"compute": [
"part_i_tax",
],
"inputs": {
"isCCPC": True,
},
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/computations/scenario`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"compute": [
"part_i_tax"
],
"inputs": {
"isCCPC": true
}
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement whose saved inputs the computation runs against. An engagement outside your organization, or outside what a delegated token may see, answers 404. |
Request body
Targets to compute from an engagement's saved data, named cells replaced.
| Field | Type | Required | Description |
|---|---|---|---|
compute | array of strings, 1 to 100 items | yes | Schedule/result keys to compute. Dependencies run automatically. |
inputs | object | no | Replacements for the engagement's saved computation inputs, keyed by input name, for this request only; an input left out keeps its saved value. An input replaces its saved value whole, and null replaces it with no answer, except t2Jacket, which merges into the saved jacket box by box: a box sent with a value replaces that box, a box sent as null clears it, and every box not sent keeps its saved answer. t2Jacket itself, its identification, filingStatus and applicability sections, and any other object the saved jacket holds must each be sent as an object; null or any other value in their place is refused. Every jacket box is answered under t2Jacket, such as t2Jacket.applicability.associatedCCPC (T2 box 160). Read each input's name, JSON type and meaning with get_computation_target_contract (REST: GET /api/v1/computations/targets/{targetId}) for each target in compute and its dependencies: its 'inputs' view answers the input names, or one input, a page of at most about 20 KB. A wrong JSON type is refused at the input. A name no requested target publishes is refused at the deepest published name on its path, with the published names it most resembles or, when none does, a pointer to that position's 'inputs' view; a root name another public target publishes is refused naming it and the targets to add to compute (REST names every refused name). An input the engine refuses after admission, such as a cohort decomposition that does not sum to the row's additions, is refused with its location and the engine's reason, from which every string the request or the saved engagement carries, at any length and other than a single word of up to ten letters, every such amount of 100 or more and every quoted literal are withheld. The engagement owns its taxation-period and filing-lineage inputs, which cannot be replaced. |
inputs.* | any | no | Members not listed here. |
view | one of full, summary | no | 'summary' answers the headline figures (taxable income, Part I tax, provincial tax, balance-due day), each computed target's state and finding counts, and the error and warning findings, a few kilobytes without the per-target results. 'full' (the default) adds the results, which for a filing graph run past 100 KB; page them with resultsOffset and resultsLimit. Both carry the summary, and the computation is the same either way. |
resultsOffset | integer, 0 to 10000 | no | Position of the first target result to return, in target-name order, counting from 0; continue from the previous page's resultsPage.nextOffset. |
resultsLimit | nullable integer, 1 to 200 | no | Most target results to return in one page (1 to 200). Omitted: every result from resultsOffset on. |
Response
200. Computed results keyed by target, the cells this request replaced, and the lineage hash of the saved state they were applied to.
{
"data": {
"engagementId": "6f1c9a24-3b7e-4d58-9c02-8a5e71d4b3f6",
"sourceStateSha256": "<string>",
"appliedOverrides": [
"<string>"
],
"results": {
"<member>": "<any>"
},
"returnFindings": [
{
"<member>": "<any>"
}
],
"summary": {
"headline": [
{
"label": "Taxable income (T2 line 360)",
"target": "part_i_tax",
"output": "taxableIncome",
"value": 0,
"targetState": "<computed | provisional | held | not_computed>"
}
],
"targets": [
{
"target": "schedule8",
"state": "<computed | provisional | held | not_computed>",
"provisional": false,
"ready": false,
"errorCount": 0,
"warningCount": 0,
"heldBy": [
"<string>"
]
}
],
"findings": [
{
"target": "<string>",
"code": "<string>",
"severity": "<error | warning>",
"message": "<string>"
}
],
"findingCount": 0
},
"resultsPage": {
"view": "<full | summary>",
"offset": 0,
"limit": 0,
"total": 0,
"returned": 0,
"nextOffset": 0
}
},
"computeVersion": "4c1f9b0a7d2e6538ac91b47f2d5e08c3a6b1de92",
"engineSchemaVersion": "1.1.0",
"ratesVersion": "7b3a1e5c9d02f46817ac5b930e2d6f84c1957ab0de3f2681c47d905ba3e18f26",
"timestamp": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | object | yes | The computed results, the cells this request replaced, and the lineage hash of the saved state they were applied to. |
data.engagementId | string (uuid) | yes | UUID of the engagement whose saved inputs this computation was built from, the same id the request path names. |
data.sourceStateSha256 | string | yes | Lineage hash of the saved state this computation was built from. Two responses with the same hash were computed from the same saved facts, and a different hash means the engagement changed in between. |
data.appliedOverrides | array of strings | yes | The input cells this request replaced, in sorted order. Every other cell came from the engagement's saved inputs. |
data.results | object | yes | Engine output for the requested targets and their automatic dependencies, or the page of it resultsPage names (none in the summary view). Readiness is not evaluated here. |
data.results.* | any | no | The target's output cells; see the computation reference. |
data.returnFindings | array of objects | no | Questions the T2 jacket holds the return on that no requested target reports, such as the ITA 94.1 offshore investment fund property question a Yes on line 259 asks. Each finding has a code, a severity, a message and, where one input answers it, inputPaths naming that input. Empty when the jacket asks nothing; an export withholds the jacket until each is answered. |
data.returnFindings[].* | any | no | Members not listed here. |
data.summary | nullable object | no | Headline figures, each target's state and the findings, bounded, so the result can be read without the per-target results. |
data.summary.headline | array of objects | when summary is set | The figures a preparer checks first, for the targets this computation ran: Schedule 1 net income, taxable income, the Part I tax lines, each province's tax, the total, and the balance-due day. Each carries its target's state: a figure from a held or provisional target is published but not final. |
data.summary.headline[].label | string | when summary is set | What the figure is, with its T2 line where it has one. |
data.summary.headline[].target | string | when summary is set | The target whose result carries the figure. |
data.summary.headline[].output | string | when summary is set | The result member the figure is read from, dotted for a nested one. |
data.summary.headline[].value | nullable number or integer or string or boolean | when summary is set | The figure as the target's result publishes it, whatever the target's state: a held or provisional target still publishes its figures, calculated but not final (targetState says which). Null when the result carries no number, text or Yes/No answer there: the target published no result, withheld the figure because a computation it depends on published none, or computed none. A null is never a zero. |
data.summary.headline[].targetState | one of computed, provisional, held, not_computed | when summary is set | The state of the target the figure comes from (targets[].state). Under 'held' or 'provisional' the figure is not final until what holds the target is answered; 'not_computed' means the target published no result. |
data.summary.targets | array of objects | when summary is set | Every computed target in target-name order, with its state and finding counts. |
data.summary.targets[].target | string | when summary is set | The target id. |
data.summary.targets[].state | one of computed, provisional, held, not_computed | when summary is set | 'not_computed' when the engine published no result for the target; 'held' when it waits on a producer that is not ready (heldBy) or states itself blocked; 'provisional' when it computed but marks itself provisional or not ready, or carries an error finding; 'computed' otherwise. Filing readiness is not evaluated here. |
data.summary.targets[].provisional | nullable boolean | when summary is set | The result's own provisional flag. Null when it states none. |
data.summary.targets[].ready | nullable boolean | when summary is set | The result's own ready flag. Null when it states none. |
data.summary.targets[].errorCount | integer, at least 0 | when summary is set | Error findings the result carries. |
data.summary.targets[].warningCount | integer, at least 0 | when summary is set | Warning findings the result carries. |
data.summary.targets[].heldBy | array of strings | when summary is set | The producers the target waits on, when it is held by a dependency. |
data.summary.findings | array of objects, up to 25 items | when summary is set | The error and warning findings, errors first, at most 25; findingCount counts them all. |
data.summary.findings[].target | string | when summary is set | The target whose result carries the finding. |
data.summary.findings[].code | nullable string | when summary is set | The finding's code. Null when it has none. |
data.summary.findings[].severity | one of error, warning | when summary is set | Errors are listed first. |
data.summary.findings[].message | string | when summary is set | The finding's message, cut at 400 characters; the full text is in the target's result. |
data.summary.findingCount | integer, at least 0 | when summary is set | How many error and warning findings the results carry. |
data.resultsPage | nullable object | no | Which per-target results this answer carries, and the next page. |
data.resultsPage.view | one of full, summary | when resultsPage is set | The view the request asked for. |
data.resultsPage.offset | integer, at least 0 | when resultsPage is set | Position of the first result carried, in target-name order. |
data.resultsPage.limit | nullable integer | when resultsPage is set | The page size asked for. Null when every result from offset was asked for. |
data.resultsPage.total | integer, at least 0 | when resultsPage is set | How many target results the computation produced. |
data.resultsPage.returned | integer, at least 0 | when resultsPage is set | How many target results this answer carries. |
data.resultsPage.nextOffset | nullable integer | when resultsPage is set | The resultsOffset of the next page, sent with view 'full'. Null when this answer reached the last result, and always null with view 'summary', which carries no results. |
computeVersion | string | yes | Identifier of the engine build that produced this response. Two responses with equal computeVersion and ratesVersion were computed on identical engine code and identical rate tables. |
engineSchemaVersion | string | yes | Semantic version of the response envelope's shape. The major part changes when a response field is removed or renamed. The minor part changes when a field is added that existing clients can ignore. |
ratesVersion | string | yes | Content hash of the rate tables. Two responses with equal computeVersion and ratesVersion were computed on identical engine code and identical rate tables. |
timestamp | string (date-time) | yes | When this computation ran. ISO-8601 in UTC. |
Errors
In addition to the shared error codes:
404: The engagement does not exist in the caller's organization.409: The saved engagement state cannot be computed.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
List computation targets
GET /api/v1/computations
Lists batch targets and their automatic dependencies, plus rollover, reorganization, and screening targets. Each target has a published cell contract.
Scope: tax:compute.
Request
curl https://api.filemark.ca/api/v1/computations \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
"https://api.filemark.ca/api/v1/computations",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch("https://api.filemark.ca/api/v1/computations", {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Response
200. The batch targets, their dependency graph, and the rollover targets.
{
"data": {
"batchTargets": [
"part_i_tax",
"schedule1",
"schedule8"
],
"batchDependencies": {
"part_i_tax": [
"schedule1"
],
"schedule8": []
},
"rolloverTargets": [
"gaar-screen",
"section-85",
"section-86"
]
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | object | yes | The batch targets, their dependencies, and the rollover targets. |
data.batchTargets | array of strings | yes | Every schedule and calculation target POST /api/v1/computations/batch accepts, sorted by id. Each one has a published cell contract at GET /api/v1/computations/targets/{target_id}. |
data.batchDependencies | map of array of strings | yes | For each batch target, the other batch targets that run automatically with it, including their own dependencies. Every target in the chain returns its result alongside the one you asked for, and its input cells are accepted on the same request. An empty list means the target runs on its own. |
data.batchDependencies.{key} | array of strings | per key | For each batch target, the other batch targets that run automatically with it, including their own dependencies. Every target in the chain returns its result alongside the one you asked for, and its input cells are accepted on the same request. An empty list means the target runs on its own. |
data.rolloverTargets | array of strings | yes | Every rollover, reorganization, and screening target POST /api/v1/computations/rollovers/{target} accepts, sorted by id. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Get a computation target contract
GET /api/v1/computations/targets/{target_id}
Returns a target's input and output cells, JSON types, payloadContract selector, and example request. Supply payloadContract in computation requests to apply the target's strict value constraints. With view=inputs it returns one input instead, a page of at most about 20 KB: the cells at inputPath and a page of the input names directly under it.
Scope: tax:compute.
Request
curl https://api.filemark.ca/api/v1/computations/targets/part_i_tax \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
"https://api.filemark.ca/api/v1/computations/targets/part_i_tax",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch("https://api.filemark.ca/api/v1/computations/targets/part_i_tax", {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
target_id | path | string, 1 to 64 characters, matching ^[a-z][a-z0-9_-]*$ | yes | Batch or rollover target id from the computation catalog. |
view | query | one of full, inputs, default "full" | no | 'full' (the default) answers the whole contract: every input and output cell and the example request, past a megabyte for a filing target such as part_i_tax. 'inputs' answers a page of at most about 20 KB: the cells at inputPath and a page of the input names directly under it, each with the first sentence of its description (inputIndex). Read the top-level names first, then follow a member's path to the cell you need. |
inputPath | query | nullable string, up to 512 characters | no | With view 'inputs', the input to read, as a dotted path: a top-level name (assetData) or a path under one (assetData.additionCohorts.amount). [] after an array's name may be written or left out. Omitted: the top level. A path that names no published input is refused. |
inputsOffset | query | integer, 0 to 10000, default 0 | no | With view 'inputs', the position of the first name to list, counting from 0; continue from the previous page's inputIndex.nextOffset. |
inputsLimit | query | integer, 1 to 50, default 50 | no | With view 'inputs', the most names to list in one page (1 to 50, the default). |
Response
200. The target's kind, dependencies, supported taxation years, payloadContract selector, input and output cells, and example input; with view=inputs, the cells at inputPath and inputIndex.
{
"data": {
"id": "schedule1",
"kind": "<batch | rollover>",
"title": "<string>",
"group": "<string>",
"program": "<string>",
"jurisdiction": "<string>",
"status": "<string>",
"supportedTaxYears": [
{
"first": 2023,
"last": 0
}
],
"contract": {
"boundaryProfileId": "<string>",
"payloadSchemaVersion": "<string>"
},
"inputs": [
{
"path": "<string>",
"types": [
"<string>"
],
"required": false,
"requiredOnDefaultBoundary": false,
"strictPinned": false,
"<output cell>": "<any>"
}
],
"outputs": [
{
"path": "<string>",
"types": [
"<string>"
],
"<output cell>": "<any>"
}
],
"exampleInput": {
"<member>": "<any>"
},
"dependencies": [
"<string>"
],
"inputIndex": {
"path": "assetData",
"members": [
{
"name": "additionCohorts",
"path": "assetData.additionCohorts",
"types": [
"<string>"
],
"requiredOnDefaultBoundary": false,
"cells": 1,
"summary": "<string>"
}
],
"offset": 0,
"limit": 1,
"total": 0,
"returned": 0,
"nextOffset": 0
}
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | object | yes | The requested target's published contract. |
data.id | string | yes | The target's id, as the computation catalog lists it and as a compute request names it. |
data.kind | one of batch, rollover | yes | Which operation computes this target: 'batch' for POST /api/v1/computations/batch, 'rollover' for POST /api/v1/computations/rollovers/{target}. |
data.title | nullable string | no | The target's display title: the CRA form title for a schedule or form, otherwise the calculation's name. Null when no title is published. |
data.group | nullable string | no | The computation reference group this target is listed under: one of 'return', 'financial-statements', 'federal-schedules', 'provincial-schedules', 'tax-calculations', 'pools-and-continuity', 'worksheets-and-diagnostics' and 'elections-and-rollovers'. |
data.program | nullable string | no | The return or product line this target belongs to. Null for a catalogued engine node that publishes no contract document. |
data.jurisdiction | nullable string | no | The taxing jurisdiction this target computes for. Null for a catalogued engine node that publishes no contract document. |
data.status | nullable string | no | Lifecycle status of the target's contract: 'production' for a schedule target on a current form contract, and 'not_applicable' for a rollover, reorganization, screening, or internal calculation target, which has no schedule form lifecycle. 'draft' and 'deprecated' are also defined. Null for a catalogued engine node that publishes no contract document. |
data.supportedTaxYears | nullable array of objects | no | Inclusive taxation-year windows for schedule targets. Null where a year window does not apply, as for rollover targets. |
data.supportedTaxYears[].first | integer | when supportedTaxYears is set | First taxation year this target supports, as a four-digit year. The window is inclusive of this year. |
data.supportedTaxYears[].last | nullable integer | when supportedTaxYears is set | Last supported taxation year. Null means open-ended. |
data.contract | nullable object | no | The selector to send as payloadContract to validate a request against this target's pinned schema pair. |
data.contract.boundaryProfileId | string, 1 to 128 characters, matching ^[a-z][a-z0-9_.-]*$ | when contract is set | Identifier of the strict computation boundary profile. |
data.contract.payloadSchemaVersion | string, up to 64 characters, matching ^[0-9]+\.[0-9]+\.[0-9]+$ | when contract is set | Semantic version of the target payload schema pair. |
data.inputs | array of objects | yes | Every input cell this target accepts, sorted by path. A cell listed here is a cell the computation boundary admits inside the request's inputs object. Empty for a catalogued engine node that publishes no contract document. In view 'inputs', only the cells at inputPath itself, each with everything the full contract publishes for it. |
data.inputs[].path | string | yes | Dotted path of this cell inside the request's inputs object. A [] segment addresses the elements of an array. |
data.inputs[].types | array of strings | yes | Every JSON type the boundary admits at this position. |
data.inputs[].required | boolean | yes | Whether this cell is required when using the strict payload contract. See requiredOnDefaultBoundary for requirements that apply to every request. |
data.inputs[].requiredOnDefaultBoundary | boolean | yes | Whether every request needs this cell, including one with no payloadContract, which is refused when the cell is omitted or null. True for a batch target's taxYear and for the statutory scope facts a rollover cannot answer on the caller's behalf. |
data.inputs[].strictPinned | boolean | yes | Whether the strict payload contract fixes this cell to a single value. |
data.inputs[].* | any | no | Members not listed here. |
data.outputs | array of objects | yes | Every output cell this target's result can carry. Empty for a catalogued engine node that publishes no contract document, and in view 'inputs'. |
data.outputs[].path | string | yes | Dotted path of this cell inside the target's result object. A [] segment addresses the elements of an array. |
data.outputs[].types | array of strings | yes | Every JSON type this result position can carry. |
data.outputs[].* | any | no | Members not listed here. |
data.exampleInput | nullable object | no | An example request body the target accepts and computes. It is the value a caller sends as inputs. Null in view 'inputs'. |
data.exampleInput.* | any | no | Members not listed here. |
data.dependencies | nullable array of strings | no | Batch targets that run automatically with this one. The request may also carry their input cells. Null for rollovers. |
data.inputIndex | nullable object | no | In view 'inputs', the names directly under inputPath (the top level of the target's input when inputPath is omitted), a page at a time, each with its types, whether every request needs it, how many cells it holds and the first sentence of its description. Null in the full view. |
data.inputIndex.path | string | when inputIndex is set | The position read, dotted. Empty for the top level of the target's input. |
data.inputIndex.members | array of objects | when inputIndex is set | The names directly under path, in name order, from offset. |
data.inputIndex.members[].name | string | when inputIndex is set | The input's name at this position. |
data.inputIndex.members[].path | string | when inputIndex is set | Its dotted path, the value to send as inputPath to read it. An array's elements are reached through the array's name. |
data.inputIndex.members[].types | array of strings | when inputIndex is set | The JSON types the full contract publishes for the input itself; for a name it publishes only through its members, 'object' or 'array', whichever holds them. |
data.inputIndex.members[].requiredOnDefaultBoundary | boolean | when inputIndex is set | Whether every request needs this input, as the cell of the same name in the full contract says. |
data.inputIndex.members[].cells | integer, at least 1 | when inputIndex is set | How many published input cells sit at this path or under it; more than one means it has members of its own to read. |
data.inputIndex.members[].summary | nullable string | when inputIndex is set | The first sentence of its description, cut at 128 characters. Null when the contract describes it nowhere at this path. |
data.inputIndex.offset | integer, at least 0 | when inputIndex is set | Position of the first member listed, counting from 0. |
data.inputIndex.limit | integer, at least 1 | when inputIndex is set | The most members this page could list (inputsLimit, at most 50). |
data.inputIndex.total | integer, at least 0 | when inputIndex is set | How many names sit directly under path. |
data.inputIndex.returned | integer, at least 0 | when inputIndex is set | How many members this answer lists: fewer than limit at the last member, or where one more would take the page past about 20 KB. |
data.inputIndex.nextOffset | nullable integer | when inputIndex is set | The inputsOffset of the next page. Null after the last member. |
Errors
In addition to the shared error codes:
404: The computation target is not in the public catalog.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Compute tax schedules
POST /api/v1/computations/batch
Computes batch targets and their dependencies using the request's inputs. Each input must be a published cell of a requested target or dependency and use its JSON type. Supply payloadContract for strict value validation or handoff for tax-software import cells or files.
Scope: tax:compute.
Request
curl --request POST https://api.filemark.ca/api/v1/computations/batch \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"compute": [
"part_i_tax"
],
"inputs": {
"taxYear": 2025,
"fiscalStart": "2025-01-01",
"fiscalEnd": "2025-12-31",
"isCCPC": true,
"daysInYear": 365,
"t2Jacket": {
"additionalInfo": {
"isCreditUnion": false
}
},
"cooperativeCorporation136_2ThroughoutYear": false,
"isInsuranceCorp": false,
"associatedGroupAII": 0,
"priorYearGroupTCEC": 0,
"specifiedInvestmentBusinessIncome": 0,
"lifeInsurancePolicyIncome": 0,
"specifiedCorporateIncomeReviewed": true,
"supplementalLinesReviewed": true,
"specifiedPartnershipIncomeApplies": false,
"pyUCCPools": [],
"pyLossPools": []
}
}'import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
"https://api.filemark.ca/api/v1/computations/batch",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"compute": [
"part_i_tax",
],
"inputs": {
"taxYear": 2025,
"fiscalStart": "2025-01-01",
"fiscalEnd": "2025-12-31",
"isCCPC": True,
"daysInYear": 365,
"t2Jacket": {
"additionalInfo": {
"isCreditUnion": False,
},
},
"cooperativeCorporation136_2ThroughoutYear": False,
"isInsuranceCorp": False,
"associatedGroupAII": 0,
"priorYearGroupTCEC": 0,
"specifiedInvestmentBusinessIncome": 0,
"lifeInsurancePolicyIncome": 0,
"specifiedCorporateIncomeReviewed": True,
"supplementalLinesReviewed": True,
"specifiedPartnershipIncomeApplies": False,
"pyUCCPools": [],
"pyLossPools": [],
},
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch("https://api.filemark.ca/api/v1/computations/batch", {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"compute": [
"part_i_tax"
],
"inputs": {
"taxYear": 2025,
"fiscalStart": "2025-01-01",
"fiscalEnd": "2025-12-31",
"isCCPC": true,
"daysInYear": 365,
"t2Jacket": {
"additionalInfo": {
"isCreditUnion": false
}
},
"cooperativeCorporation136_2ThroughoutYear": false,
"isInsuranceCorp": false,
"associatedGroupAII": 0,
"priorYearGroupTCEC": 0,
"specifiedInvestmentBusinessIncome": 0,
"lifeInsurancePolicyIncome": 0,
"specifiedCorporateIncomeReviewed": true,
"supplementalLinesReviewed": true,
"specifiedPartnershipIncomeApplies": false,
"pyUCCPools": [],
"pyLossPools": []
}
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Request body
The targets to compute and their inputs.
| Field | Type | Required | Description |
|---|---|---|---|
compute | array of strings, 1 to 100 items | yes | Schedule/result keys to compute. Dependencies run automatically. |
inputs | object | no | Inputs for the requested batch targets and their dependencies. Each field must use a published cell name and JSON type. Invalid cells return field-level errors. Supply payloadContract to validate values against the target schema. |
inputs.* | any | no | Members not listed here. |
vendorInput | object | no | Vendor cells mapped to canonical input paths through the versioned handoff mapping. Each cell must have an unambiguous one-to-one mapping. Omit when unused. Null is invalid. |
vendorInput.vendor | one of taxcycle, taxprep | when vendorInput is set | Vendor vocabulary used by every key in cells. |
vendorInput.cells | object | when vendorInput is set | Case-sensitive import field codes or cell IDs of the selected tax software. Supply values in their canonical Filemark JSON types and units. |
vendorInput.cells.* | any | no | Members not listed here. |
payloadContract | object | no | Strict contract to validate the request against. Omit this property for the default boundary. Null is not valid. |
payloadContract.boundaryProfileId | string, 1 to 128 characters, matching ^[a-z][a-z0-9_.-]*$ | when payloadContract is set | Identifier of the strict computation boundary profile. |
payloadContract.payloadSchemaVersion | string, up to 64 characters, matching ^[0-9]+\.[0-9]+\.[0-9]+$ | when payloadContract is set | Semantic version of the target payload schema pair. |
handoff | object | no | Optional handoff projection. Formats 'json' and 'file' carry the selected vendor's import identifiers and encoded schedule values, byte-identical to the Filemark companion files, or as those files themselves, plus coverage warnings and mapping provenance. Format 'gfi' takes no vendor and returns the universal RC4088 file from computed Schedule 100/125 amounts. That format requires corporationName and businessNumber. The t2_return and carryforward targets include the complete computed jacket and normalized S141 disclosure in vendor handoff; schedule141 also supports disclosure-only handoff. When a safety gate would block a companion-file export, data.handoff.blocked carries the reason and data.results is unaffected. data.handoff.vendorEdition names the product release these cell identifiers are valid for. Confirm your install matches it before importing. Omit this property for no projection. Null is not valid. |
handoff.vendor | one of taxcycle, taxprep, ifirm | when handoff is set | Receiving tax software whose import identifiers the response's data.handoff block carries: 'taxcycle' (per-form import field codes), 'taxprep' (.csv cell IDs), or 'ifirm' (cells/setdata cell paths). Required for formats 'json' and 'file'. Omit it for format 'gfi'. |
handoff.format | one of json, file, gfi | when handoff is set | Container for those cells. 'json' is the default and returns them as structured data in data.handoff.cells, or in data.handoff.forms for 'taxcycle'. 'file' returns the receiving software's own import file, base64-encoded in data.handoff.files: one .csv for 'taxprep' and 'ifirm', one .xlsx workbook per form for 'taxcycle'. 'gfi' returns the universal RC4088 .gfi file and takes no vendor. |
handoff.language | one of en, fr | when handoff is set | Import-file language for the 'taxprep' and 'ifirm' format 'file' projections. The file's header line is [<corporation legal name>|0|<language>], with Filemark in place of the name when the computed return carries none: 'en' writes 0 and 'fr' writes 1. The default is English. |
handoff.corporationName | string, 1 to 200 characters | when handoff is set | Corporation name that identifies a format 'gfi' artifact. Optional on the selector. Format 'gfi' refuses when it is absent. |
handoff.businessNumber | string, 9 to 32 characters | when handoff is set | Corporation BN9 or RC program-account number for the format 'gfi' header. Optional on the selector. Format 'gfi' refuses when it is absent or malformed. |
calculationReferenceDate | string (date) | no | Calendar date, YYYY-MM-DD, that date-sensitive claims are computed as of: whether a claim window is still open, and whether a stated receipt, payment, assessment or claim filing date has passed. It is your statement for this computation, not filing evidence, and the same request answers the same way on any day. The response repeats it as data.calculationReferenceDate. Omit it and the server's current calendar day is used, returned as data.calculationReference. Null is not valid. |
Response
200. Results keyed by target under data.results, with the engine, schema, and rate-table versions the computation ran on.
{
"data": {
"results": {
"part_i_tax": {
"ready": false,
"provisional": false,
"warnings": [],
"<output cell>": "<any>"
}
},
"notComputed": [
"<string>"
],
"calculationReferenceDate": "2025-12-31",
"calculationReference": {
"schemaVersion": 1,
"referenceInstant": "2026-07-14T12:00:00Z",
"calendarDate": "2025-12-31",
"timeZone": "<string>",
"authority": "<advisory>"
},
"result": {
"ready": false,
"provisional": false,
"warnings": [],
"<output cell>": "<any>"
},
"handoff": {
"blocked": null,
"warnings": [],
"<output cell>": "<any>"
},
"vendorInput": {
"vendor": "<taxcycle | taxprep>",
"mappingTableVersion": "<string>",
"vendorEdition": "<string>",
"fieldMapVersion": "<string>",
"applied": [
{
"cell": "<string>",
"schedule": "<string>",
"filemarkConcept": "<string>",
"inputPath": "<string>",
"cardinality": "one_to_one"
}
]
},
"readiness": {
"ready": false,
"<output cell>": "<any>"
},
"<output cell>": "<any>"
},
"computeVersion": "4c1f9b0a7d2e6538ac91b47f2d5e08c3a6b1de92",
"engineSchemaVersion": "1.1.0",
"ratesVersion": "7b3a1e5c9d02f46817ac5b930e2d6f84c1957ab0de3f2681c47d905ba3e18f26",
"timestamp": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | object | yes | Computed target results and any requested handoff projection. Each result carries ready, provisional and warnings, and a saved-filing response may also carry readiness. A handoff projection reports blocked when draft or unready results cannot be exported safely. |
data.results | map of object | no | One result per batch target that ran, keyed by target id. Those are the targets named in compute plus the dependencies that ran with them. |
data.results.{key} | object | per key | The target's result. It carries the readiness flags, the warnings, and the published output cells. |
data.results.{key}.ready | boolean | per key | True when the target computed from complete facts, false when a required fact was missing or a finding stopped the computation. The warnings say what to supply. |
data.results.{key}.provisional | boolean | per key | True when the result rests on an unanswered fact or an assumption the engine disclosed in warnings. Treat the figures as not final until the fact is answered. A handoff export refuses provisional results. |
data.results.{key}.warnings | array of any | per key | Findings the engine raised for this target. Each carries a code, a message written for the preparer, and usually a severity of error, warning, or info. A finding may also name the box or line it concerns. |
data.results.{key}.* | any | no | The target's output cells; see the computation reference. |
data.notComputed | array of strings | no | Requested targets for which the engine produced no result. No amount or non-applicability determination is implied; supply the target's applicability and input facts. |
data.calculationReferenceDate | string (date) | no | The calculationReferenceDate the request sent, which the date-sensitive claims were computed as of. Present only when the request sent it. |
data.calculationReference | object | no | Server execution instant and calendar day used for date-sensitive batch checks when the request sent no calculationReferenceDate. Public calculations are advisory; this is not authenticated filing evidence. |
data.calculationReference.schemaVersion | integer, always 1 | when calculationReference is set | |
data.calculationReference.referenceInstant | string (date-time) | when calculationReference is set | |
data.calculationReference.calendarDate | string (date) | when calculationReference is set | |
data.calculationReference.timeZone | string | when calculationReference is set | |
data.calculationReference.authority | one of advisory | when calculationReference is set | |
data.result | object | no | The single result when the request addressed one rollover, reorganization, or screening target by path. It carries the readiness flags, the warnings, and the published output cells. |
data.result.ready | boolean | when result is set | True when the target computed from complete facts, false when a required fact was missing or a finding stopped the computation. The warnings say what to supply. |
data.result.provisional | boolean | when result is set | True when the result rests on an unanswered fact or an assumption the engine disclosed in warnings. Treat the figures as not final until the fact is answered. A handoff export refuses provisional results. |
data.result.warnings | array of any | when result is set | Findings the engine raised for this target. Each carries a code, a message written for the preparer, and usually a severity of error, warning, or info. A finding may also name the box or line it concerns. |
data.result.* | any | no | The target's output cells; see the computation reference. |
data.handoff | object | no | The handoff projection. Present only when the request sent the handoff selector. It carries the receiving product's import identifiers (cells, or forms for 'taxcycle'), or its import file (files), with the mapping table version and vendor edition they were mapped against. |
data.handoff.blocked | nullable object | when handoff is set | Null when the projection was built. Otherwise an object whose reason says why Filemark would refuse the equivalent handoff export or could not build the selected product's payload. It may also carry a code, an action, and the issues (readiness blockers) behind the refusal. The computation results are unaffected. |
data.handoff.warnings | array of any | when handoff is set | One entry per computed cell, and per continuation or attachment sheet, the selected product cannot carry, each with a code and a message that says what to transfer by hand. Branch on the code. The message is prose. |
data.handoff.* | any | no | Members not listed here. |
data.vendorInput | object | no | Mapping provenance for accepted vendor cells. Present only when vendorInput was submitted. Values are never echoed. |
data.vendorInput.vendor | one of taxcycle, taxprep | when vendorInput is set | The vendor vocabulary the submitted cells were resolved in, echoing the selector. |
data.vendorInput.mappingTableVersion | string | when vendorInput is set | Version of the Filemark mapping table the cells were resolved through. |
data.vendorInput.vendorEdition | nullable string | when vendorInput is set | The product release these cell identifiers are valid for. Null when the table does not record one for this vendor. |
data.vendorInput.fieldMapVersion | string | when vendorInput is set | Version of the vendor field map used to resolve the cells. Present only for vendors that resolve through a field map. |
data.vendorInput.applied | array of objects | when vendorInput is set | One entry per accepted vendor cell, in cell order, naming the canonical input it became. |
data.vendorInput.applied[].cell | string | when vendorInput is set | The vendor cell identifier as submitted. |
data.vendorInput.applied[].schedule | string | when vendorInput is set | The schedule the cell belongs to in the mapping table. |
data.vendorInput.applied[].filemarkConcept | string | when vendorInput is set | The Filemark concept the cell maps to. |
data.vendorInput.applied[].inputPath | string | when vendorInput is set | The dotted path inside inputs the submitted value was written to. |
data.vendorInput.applied[].cardinality | string, always "one_to_one" | when vendorInput is set | Always one_to_one. Each accepted vendor cell resolves to exactly one canonical input. |
data.readiness | object | no | Filing-readiness summary. Published only by responses computed from a saved filing. A stateless computation does not evaluate readiness and omits it. |
data.readiness.ready | boolean | when readiness is set | True when the saved filing has no open readiness blocker. |
data.readiness.* | any | no | Members not listed here. |
data.* | any | no | Members not listed here. |
computeVersion | string | yes | Identifier of the engine build that produced this response. Two responses with equal computeVersion and ratesVersion were computed on identical engine code and identical rate tables. |
engineSchemaVersion | string | yes | Semantic version of the response envelope's shape. The major part changes when a response field is removed or renamed. The minor part changes when a field is added that existing clients can ignore. |
ratesVersion | string | yes | Content hash of the rate tables. Two responses with equal computeVersion and ratesVersion were computed on identical engine code and identical rate tables. |
timestamp | string (date-time) | yes | When this computation ran. ISO-8601 in UTC. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Compute a rollover or reorganization
POST /api/v1/computations/rollovers/{target}
Computes one rollover, reorganization, or screening target using the request's inputs. Inputs must use the target's published cell names and JSON types and include the facts marked as required on the default boundary.
Scope: tax:compute.
Request
curl --request POST https://api.filemark.ca/api/v1/computations/rollovers/section-22 \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"inputs": {
"receivables": {
"faceValue": 100000,
"electedAmount": 80000,
"vendorClaimedS20_1_pBefore": 0,
"fairMarketValue": null
},
"party": {
"jointElectionFiled": true,
"soldAllOrSubstantiallyAllBusinessProperty": true,
"purchaserProposesToContinueBusiness": true,
"nonArmsLength": false
}
}
}'import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
"https://api.filemark.ca/api/v1/computations/rollovers/section-22",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"inputs": {
"receivables": {
"faceValue": 100000,
"electedAmount": 80000,
"vendorClaimedS20_1_pBefore": 0,
"fairMarketValue": None,
},
"party": {
"jointElectionFiled": True,
"soldAllOrSubstantiallyAllBusinessProperty": True,
"purchaserProposesToContinueBusiness": True,
"nonArmsLength": False,
},
},
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch("https://api.filemark.ca/api/v1/computations/rollovers/section-22", {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"inputs": {
"receivables": {
"faceValue": 100000,
"electedAmount": 80000,
"vendorClaimedS20_1_pBefore": 0,
"fairMarketValue": null
},
"party": {
"jointElectionFiled": true,
"soldAllOrSubstantiallyAllBusinessProperty": true,
"purchaserProposesToContinueBusiness": true,
"nonArmsLength": false
}
}
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
target | path | string | yes | Rollover, reorganization, or screening target id, as listed under rolloverTargets in the computation catalog. |
Request body
Inputs for one rollover, reorganization, or screening engine.
| Field | Type | Required | Description |
|---|---|---|---|
inputs | object | yes | Canonical rollover-engine inputs. Every member must be a published input cell of the target, with the published JSON type. Unpublished members and wrong-typed cells are rejected with per-field details. Cell values are validated beyond their JSON type only under a payloadContract selector. |
inputs.* | any | no | Members not listed here. |
payloadContract | object | no | Strict contract to validate the request against. Omit this property for the default boundary. Null is not valid. |
payloadContract.boundaryProfileId | string, 1 to 128 characters, matching ^[a-z][a-z0-9_.-]*$ | when payloadContract is set | Identifier of the strict computation boundary profile. |
payloadContract.payloadSchemaVersion | string, up to 64 characters, matching ^[0-9]+\.[0-9]+\.[0-9]+$ | when payloadContract is set | Semantic version of the target payload schema pair. |
Response
200. The target's result under data.result, with the engine, schema, and rate-table versions the computation ran on.
{
"data": {
"results": {
"section-22": {
"ready": false,
"provisional": false,
"warnings": [],
"<output cell>": "<any>"
}
},
"notComputed": [
"<string>"
],
"calculationReferenceDate": "2025-12-31",
"calculationReference": {
"schemaVersion": 1,
"referenceInstant": "2026-07-14T12:00:00Z",
"calendarDate": "2025-12-31",
"timeZone": "<string>",
"authority": "<advisory>"
},
"result": {
"ready": false,
"provisional": false,
"warnings": [],
"<output cell>": "<any>"
},
"handoff": {
"blocked": null,
"warnings": [],
"<output cell>": "<any>"
},
"vendorInput": {
"vendor": "<taxcycle | taxprep>",
"mappingTableVersion": "<string>",
"vendorEdition": "<string>",
"fieldMapVersion": "<string>",
"applied": [
{
"cell": "<string>",
"schedule": "<string>",
"filemarkConcept": "<string>",
"inputPath": "<string>",
"cardinality": "one_to_one"
}
]
},
"readiness": {
"ready": false,
"<output cell>": "<any>"
},
"<output cell>": "<any>"
},
"computeVersion": "4c1f9b0a7d2e6538ac91b47f2d5e08c3a6b1de92",
"engineSchemaVersion": "1.1.0",
"ratesVersion": "7b3a1e5c9d02f46817ac5b930e2d6f84c1957ab0de3f2681c47d905ba3e18f26",
"timestamp": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | object | yes | Computed target results and any requested handoff projection. Each result carries ready, provisional and warnings, and a saved-filing response may also carry readiness. A handoff projection reports blocked when draft or unready results cannot be exported safely. |
data.results | map of object | no | One result per batch target that ran, keyed by target id. Those are the targets named in compute plus the dependencies that ran with them. |
data.results.{key} | object | per key | The target's result. It carries the readiness flags, the warnings, and the published output cells. |
data.results.{key}.ready | boolean | per key | True when the target computed from complete facts, false when a required fact was missing or a finding stopped the computation. The warnings say what to supply. |
data.results.{key}.provisional | boolean | per key | True when the result rests on an unanswered fact or an assumption the engine disclosed in warnings. Treat the figures as not final until the fact is answered. A handoff export refuses provisional results. |
data.results.{key}.warnings | array of any | per key | Findings the engine raised for this target. Each carries a code, a message written for the preparer, and usually a severity of error, warning, or info. A finding may also name the box or line it concerns. |
data.results.{key}.* | any | no | The target's output cells; see the computation reference. |
data.notComputed | array of strings | no | Requested targets for which the engine produced no result. No amount or non-applicability determination is implied; supply the target's applicability and input facts. |
data.calculationReferenceDate | string (date) | no | The calculationReferenceDate the request sent, which the date-sensitive claims were computed as of. Present only when the request sent it. |
data.calculationReference | object | no | Server execution instant and calendar day used for date-sensitive batch checks when the request sent no calculationReferenceDate. Public calculations are advisory; this is not authenticated filing evidence. |
data.calculationReference.schemaVersion | integer, always 1 | when calculationReference is set | |
data.calculationReference.referenceInstant | string (date-time) | when calculationReference is set | |
data.calculationReference.calendarDate | string (date) | when calculationReference is set | |
data.calculationReference.timeZone | string | when calculationReference is set | |
data.calculationReference.authority | one of advisory | when calculationReference is set | |
data.result | object | no | The single result when the request addressed one rollover, reorganization, or screening target by path. It carries the readiness flags, the warnings, and the published output cells. |
data.result.ready | boolean | when result is set | True when the target computed from complete facts, false when a required fact was missing or a finding stopped the computation. The warnings say what to supply. |
data.result.provisional | boolean | when result is set | True when the result rests on an unanswered fact or an assumption the engine disclosed in warnings. Treat the figures as not final until the fact is answered. A handoff export refuses provisional results. |
data.result.warnings | array of any | when result is set | Findings the engine raised for this target. Each carries a code, a message written for the preparer, and usually a severity of error, warning, or info. A finding may also name the box or line it concerns. |
data.result.* | any | no | The target's output cells; see the computation reference. |
data.handoff | object | no | The handoff projection. Present only when the request sent the handoff selector. It carries the receiving product's import identifiers (cells, or forms for 'taxcycle'), or its import file (files), with the mapping table version and vendor edition they were mapped against. |
data.handoff.blocked | nullable object | when handoff is set | Null when the projection was built. Otherwise an object whose reason says why Filemark would refuse the equivalent handoff export or could not build the selected product's payload. It may also carry a code, an action, and the issues (readiness blockers) behind the refusal. The computation results are unaffected. |
data.handoff.warnings | array of any | when handoff is set | One entry per computed cell, and per continuation or attachment sheet, the selected product cannot carry, each with a code and a message that says what to transfer by hand. Branch on the code. The message is prose. |
data.handoff.* | any | no | Members not listed here. |
data.vendorInput | object | no | Mapping provenance for accepted vendor cells. Present only when vendorInput was submitted. Values are never echoed. |
data.vendorInput.vendor | one of taxcycle, taxprep | when vendorInput is set | The vendor vocabulary the submitted cells were resolved in, echoing the selector. |
data.vendorInput.mappingTableVersion | string | when vendorInput is set | Version of the Filemark mapping table the cells were resolved through. |
data.vendorInput.vendorEdition | nullable string | when vendorInput is set | The product release these cell identifiers are valid for. Null when the table does not record one for this vendor. |
data.vendorInput.fieldMapVersion | string | when vendorInput is set | Version of the vendor field map used to resolve the cells. Present only for vendors that resolve through a field map. |
data.vendorInput.applied | array of objects | when vendorInput is set | One entry per accepted vendor cell, in cell order, naming the canonical input it became. |
data.vendorInput.applied[].cell | string | when vendorInput is set | The vendor cell identifier as submitted. |
data.vendorInput.applied[].schedule | string | when vendorInput is set | The schedule the cell belongs to in the mapping table. |
data.vendorInput.applied[].filemarkConcept | string | when vendorInput is set | The Filemark concept the cell maps to. |
data.vendorInput.applied[].inputPath | string | when vendorInput is set | The dotted path inside inputs the submitted value was written to. |
data.vendorInput.applied[].cardinality | string, always "one_to_one" | when vendorInput is set | Always one_to_one. Each accepted vendor cell resolves to exactly one canonical input. |
data.readiness | object | no | Filing-readiness summary. Published only by responses computed from a saved filing. A stateless computation does not evaluate readiness and omits it. |
data.readiness.ready | boolean | when readiness is set | True when the saved filing has no open readiness blocker. |
data.readiness.* | any | no | Members not listed here. |
data.* | any | no | Members not listed here. |
computeVersion | string | yes | Identifier of the engine build that produced this response. Two responses with equal computeVersion and ratesVersion were computed on identical engine code and identical rate tables. |
engineSchemaVersion | string | yes | Semantic version of the response envelope's shape. The major part changes when a response field is removed or renamed. The minor part changes when a field is added that existing clients can ignore. |
ratesVersion | string | yes | Content hash of the rate tables. Two responses with equal computeVersion and ratesVersion were computed on identical engine code and identical rate tables. |
timestamp | string (date-time) | yes | When this computation ran. ISO-8601 in UTC. |
Errors
In addition to the shared error codes:
404: The requested organization-scoped resource was not found.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Archive a client
POST /api/v1/clients/{client_id}/archive
Moves the client and everything under it to the 30-day recoverable archive. Refused while any of its returns is filed or partner-signed.
Scope: clients:write.
Request
curl --request POST https://api.filemark.ca/api/v1/clients/$CLIENT_ID/archive \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
CLIENT_ID = "<client-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/clients/{CLIENT_ID}/archive",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const CLIENT_ID = "<client-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/clients/${CLIENT_ID}/archive`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
client_id | path | string (uuid) | yes | UUID of the client to archive. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the client. |
name | string | yes | Client display name. |
createdAt | string (date-time) | yes | ISO-8601 creation timestamp (UTC). |
updatedAt | string (date-time) | yes | ISO-8601 last-modified timestamp (UTC). |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or recoverable-archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
Errors
See the shared error codes.
Restore an archived client
POST /api/v1/clients/{client_id}/restore
Restores a client from the recoverable archive before its retention ends.
Scope: clients:write.
Request
curl --request POST https://api.filemark.ca/api/v1/clients/$CLIENT_ID/restore \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
CLIENT_ID = "<client-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/clients/{CLIENT_ID}/restore",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const CLIENT_ID = "<client-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/clients/${CLIENT_ID}/restore`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
client_id | path | string (uuid) | yes | UUID of the archived client. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the client. |
name | string | yes | Client display name. |
createdAt | string (date-time) | yes | ISO-8601 creation timestamp (UTC). |
updatedAt | string (date-time) | yes | ISO-8601 last-modified timestamp (UTC). |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or recoverable-archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
Errors
See the shared error codes.
Create an entity
POST /api/v1/entities
Creates a legal entity under one of your clients. The name must be unique within the client, archived entities included: a taken name is refused with 409 conflict naming the entity that holds it. Send legalName: without it the answer warns legal_name_missing and the engagement cannot be computed or filed until an entity update sets it. Send a UUID in Idempotency-Key.
Scope: entities:write.
Request
curl --request POST https://api.filemark.ca/api/v1/entities \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>"
}'import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
"https://api.filemark.ca/api/v1/entities",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>",
},
"ontarioCorporationNumber": "<string>",
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch("https://api.filemark.ca/api/v1/entities", {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
clientId | string (uuid) | yes | UUID of the client the entity belongs to. |
name | string, 1 to 300 characters | yes | Entity display name, unique within the client. |
legalName | nullable string, up to 300 characters | no | Registered legal name, as T2 box 002 prints it. Send it: the engagement cannot be computed, matched to uploaded documents or filed without it, and an omitted one is never guessed (the answer warns legal_name_missing until an entity update sets it). |
businessNumber | nullable string, up to 40 characters | no | The corporation's own CRA business number: the nine-digit BN, or the 15-character corporation income tax account (#########RC####). T2 box 001 is the RC account; from nine digits alone a return states RC0001, the program account the T2 guide (T4012, line 001) says to enter unless the CRA has advised another, and says it did. |
naicsCode | nullable string, matching ^\d{2,6}$ | no | NAICS industry code, 2 to 6 digits. An exported return states the six-digit code of the main revenue-generating activity. |
incorporationJurisdiction | nullable string, up to 2 characters | no | Corporate-law jurisdiction of incorporation or continuance: 'CA' for the federal CBCA, or a two-letter province or territory code. Null clears it. |
incorporationDate | nullable string (date) | no | YYYY-MM-DD the current jurisdiction took effect (original incorporation or the latest continuance). Not in the future. Null clears it. |
registeredOffice | nullable object | no | The registered office. A Canadian address names its province or territory by code. A return whose T2 jacket holds no head office (lines 011 to 018) states this one as its head office and says so. Null clears it. |
registeredOffice.addressLine | nullable string, up to 300 characters | when registeredOffice is set | Street address on one line. |
registeredOffice.city | nullable string, up to 100 characters | when registeredOffice is set | City or municipality. |
registeredOffice.provinceCode | nullable string, up to 2 characters | when registeredOffice is set | Two-letter province or territory code, such as ON. Required for a Canadian address. |
registeredOffice.postalCode | nullable string, up to 10 characters | when registeredOffice is set | Postal code, such as L4W 5S2. |
registeredOffice.countryCode | nullable string, up to 2 characters | when registeredOffice is set | Two-letter country code; CA or omitted for Canada. |
ontarioCorporationNumber | nullable string, matching ^\s*[0-9]{1,10}\s*$ | no | The Ontario Corporation Number on the Ontario Business Registry, digits only (at most 10), for a corporation incorporated, amalgamated or continued in Ontario or registered there. Null clears it. |
Response
201. The record was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "<string>",
"legalName": "<string>",
"corporationNumber": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"prescribedLsvccHistory": {
"wasPrescribed": false,
"firstPrescribedDate": "2025-12-31"
},
"dissolvedAt": "2025-12-31",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the entity. |
clientId | string (uuid) | yes | UUID of the entity's parent client. |
name | string | yes | Entity display name. |
legalName | nullable string | no | Registered legal name, when known. |
corporationNumber | nullable string | no | Corporate registry number, when known. |
businessNumber | nullable string | no | Canadian business number, when known. |
naicsCode | nullable string | no | NAICS industry code, when assigned. |
prescribedLsvccHistory | nullable object | no | Prescribed labour-sponsored venture capital corporation status (Reg. 6701), used by every return ending on or after the first prescribed date (ITA 131(11)). Null when the corporation never was one. |
prescribedLsvccHistory.wasPrescribed | boolean | when prescribedLsvccHistory is set | Ever prescribed, including a former registration (Reg. 6701). |
prescribedLsvccHistory.firstPrescribedDate | nullable string (date) | when prescribedLsvccHistory is set | First prescribed date; ITA 131(11) applies from it. |
dissolvedAt | nullable string (date) | no | Entity dissolution date, when applicable. |
incorporationJurisdiction | nullable string | no | Corporate-law jurisdiction the corporation is incorporated or continued under. Values: 'CA' for the federal CBCA, or a two-letter province/territory code. It changes only by continuance (ITA s.250(5.1)), never by fiscal period. It is not the provincial jurisdiction the corporation is taxed in, which is the per-tax-year permanent-establishment set (ITR Reg. 400(2)/402(3)) reported on Schedule 5. Null when not recorded, and also for a corporation incorporated outside Canada. |
incorporationDate | nullable string (date) | no | Date the current incorporationJurisdiction took effect. This is the original incorporation date, or the date of the most recent continuance into that jurisdiction. Null when not recorded. |
registeredOffice | nullable object | no | The corporation's registered office. A return whose T2 jacket holds no head office address (lines 011 to 018) states this one as its head office, and says so. Null when not recorded. |
registeredOffice.addressLine | nullable string | when registeredOffice is set | Street address on one line. |
registeredOffice.city | nullable string | when registeredOffice is set | City or municipality. |
registeredOffice.provinceCode | nullable string | when registeredOffice is set | Two-letter province or territory code, such as ON. |
registeredOffice.postalCode | nullable string | when registeredOffice is set | Postal code, such as L4W 5S2. |
registeredOffice.countryCode | nullable string | when registeredOffice is set | Two-letter country code: CA. |
ontarioCorporationNumber | nullable string | no | The Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or recoverable-archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Conditions of the entity the caller should act on. legal_name_missing: no legal name is recorded, so the engagement cannot be computed, matched to uploaded documents or filed until legalName is set. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
See the shared error codes.
Archive an entity
POST /api/v1/entities/{entity_id}/archive
Moves the entity and its engagements to the 30-day recoverable archive. Refused while any of its returns is filed or partner-signed.
Scope: entities:write.
Request
curl --request POST https://api.filemark.ca/api/v1/entities/$ENTITY_ID/archive \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENTITY_ID = "<entity-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/entities/{ENTITY_ID}/archive",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENTITY_ID = "<entity-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/entities/${ENTITY_ID}/archive`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | path | string (uuid) | yes | UUID of the entity to archive. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "<string>",
"legalName": "<string>",
"corporationNumber": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"prescribedLsvccHistory": {
"wasPrescribed": false,
"firstPrescribedDate": "2025-12-31"
},
"dissolvedAt": "2025-12-31",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the entity. |
clientId | string (uuid) | yes | UUID of the entity's parent client. |
name | string | yes | Entity display name. |
legalName | nullable string | no | Registered legal name, when known. |
corporationNumber | nullable string | no | Corporate registry number, when known. |
businessNumber | nullable string | no | Canadian business number, when known. |
naicsCode | nullable string | no | NAICS industry code, when assigned. |
prescribedLsvccHistory | nullable object | no | Prescribed labour-sponsored venture capital corporation status (Reg. 6701), used by every return ending on or after the first prescribed date (ITA 131(11)). Null when the corporation never was one. |
prescribedLsvccHistory.wasPrescribed | boolean | when prescribedLsvccHistory is set | Ever prescribed, including a former registration (Reg. 6701). |
prescribedLsvccHistory.firstPrescribedDate | nullable string (date) | when prescribedLsvccHistory is set | First prescribed date; ITA 131(11) applies from it. |
dissolvedAt | nullable string (date) | no | Entity dissolution date, when applicable. |
incorporationJurisdiction | nullable string | no | Corporate-law jurisdiction the corporation is incorporated or continued under. Values: 'CA' for the federal CBCA, or a two-letter province/territory code. It changes only by continuance (ITA s.250(5.1)), never by fiscal period. It is not the provincial jurisdiction the corporation is taxed in, which is the per-tax-year permanent-establishment set (ITR Reg. 400(2)/402(3)) reported on Schedule 5. Null when not recorded, and also for a corporation incorporated outside Canada. |
incorporationDate | nullable string (date) | no | Date the current incorporationJurisdiction took effect. This is the original incorporation date, or the date of the most recent continuance into that jurisdiction. Null when not recorded. |
registeredOffice | nullable object | no | The corporation's registered office. A return whose T2 jacket holds no head office address (lines 011 to 018) states this one as its head office, and says so. Null when not recorded. |
registeredOffice.addressLine | nullable string | when registeredOffice is set | Street address on one line. |
registeredOffice.city | nullable string | when registeredOffice is set | City or municipality. |
registeredOffice.provinceCode | nullable string | when registeredOffice is set | Two-letter province or territory code, such as ON. |
registeredOffice.postalCode | nullable string | when registeredOffice is set | Postal code, such as L4W 5S2. |
registeredOffice.countryCode | nullable string | when registeredOffice is set | Two-letter country code: CA. |
ontarioCorporationNumber | nullable string | no | The Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or recoverable-archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Conditions of the entity the caller should act on. legal_name_missing: no legal name is recorded, so the engagement cannot be computed, matched to uploaded documents or filed until legalName is set. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
See the shared error codes.
Restore an archived entity
POST /api/v1/entities/{entity_id}/restore
Restores an entity (and its archived client, if needed) before retention ends.
Scope: entities:write.
Request
curl --request POST https://api.filemark.ca/api/v1/entities/$ENTITY_ID/restore \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENTITY_ID = "<entity-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/entities/{ENTITY_ID}/restore",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENTITY_ID = "<entity-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/entities/${ENTITY_ID}/restore`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
entity_id | path | string (uuid) | yes | UUID of the archived entity. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "<string>",
"legalName": "<string>",
"corporationNumber": "<string>",
"businessNumber": "<string>",
"naicsCode": "<string>",
"prescribedLsvccHistory": {
"wasPrescribed": false,
"firstPrescribedDate": "2025-12-31"
},
"dissolvedAt": "2025-12-31",
"incorporationJurisdiction": "<string>",
"incorporationDate": "2025-12-31",
"registeredOffice": {
"addressLine": "<string>",
"city": "<string>",
"provinceCode": "<string>",
"postalCode": "<string>",
"countryCode": "<string>"
},
"ontarioCorporationNumber": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the entity. |
clientId | string (uuid) | yes | UUID of the entity's parent client. |
name | string | yes | Entity display name. |
legalName | nullable string | no | Registered legal name, when known. |
corporationNumber | nullable string | no | Corporate registry number, when known. |
businessNumber | nullable string | no | Canadian business number, when known. |
naicsCode | nullable string | no | NAICS industry code, when assigned. |
prescribedLsvccHistory | nullable object | no | Prescribed labour-sponsored venture capital corporation status (Reg. 6701), used by every return ending on or after the first prescribed date (ITA 131(11)). Null when the corporation never was one. |
prescribedLsvccHistory.wasPrescribed | boolean | when prescribedLsvccHistory is set | Ever prescribed, including a former registration (Reg. 6701). |
prescribedLsvccHistory.firstPrescribedDate | nullable string (date) | when prescribedLsvccHistory is set | First prescribed date; ITA 131(11) applies from it. |
dissolvedAt | nullable string (date) | no | Entity dissolution date, when applicable. |
incorporationJurisdiction | nullable string | no | Corporate-law jurisdiction the corporation is incorporated or continued under. Values: 'CA' for the federal CBCA, or a two-letter province/territory code. It changes only by continuance (ITA s.250(5.1)), never by fiscal period. It is not the provincial jurisdiction the corporation is taxed in, which is the per-tax-year permanent-establishment set (ITR Reg. 400(2)/402(3)) reported on Schedule 5. Null when not recorded, and also for a corporation incorporated outside Canada. |
incorporationDate | nullable string (date) | no | Date the current incorporationJurisdiction took effect. This is the original incorporation date, or the date of the most recent continuance into that jurisdiction. Null when not recorded. |
registeredOffice | nullable object | no | The corporation's registered office. A return whose T2 jacket holds no head office address (lines 011 to 018) states this one as its head office, and says so. Null when not recorded. |
registeredOffice.addressLine | nullable string | when registeredOffice is set | Street address on one line. |
registeredOffice.city | nullable string | when registeredOffice is set | City or municipality. |
registeredOffice.provinceCode | nullable string | when registeredOffice is set | Two-letter province or territory code, such as ON. |
registeredOffice.postalCode | nullable string | when registeredOffice is set | Postal code, such as L4W 5S2. |
registeredOffice.countryCode | nullable string | when registeredOffice is set | Two-letter country code: CA. |
ontarioCorporationNumber | nullable string | no | The Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or recoverable-archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Conditions of the entity the caller should act on. legal_name_missing: no legal name is recorded, so the engagement cannot be computed, matched to uploaded documents or filed until legalName is set. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
See the shared error codes.
Archive an engagement
POST /api/v1/engagements/{engagement_id}/archive
Moves the engagement to the 30-day recoverable archive. Refused while the return is filed or partner-signed, or while a later return links it as its prior year.
Scope: engagements:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/archive \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/archive",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/archive`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement to archive. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"recordLifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
client | object | yes | The client the engagement belongs to. |
client.id | string (uuid) | yes | UUID of the client or entity. |
client.name | string | yes | Display name of the client or entity. |
client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
entity | object | yes | The legal entity the engagement belongs to. |
entity.id | string (uuid) | yes | UUID of the client or entity. |
entity.name | string | yes | Display name of the client or entity. |
entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
recordLifecycle | object | yes | Live or recoverable-archived (distinct from the return's filing lifecycle). |
recordLifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
recordLifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
recordLifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Non-blocking conditions the write reported: an unsupported filing year, an unverified election, a failed prior-year roll-forward or permanent-establishment seed, or a missing period start (period_start_missing: send periodStart with an engagement update). |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
See the shared error codes.
Restore an archived engagement
POST /api/v1/engagements/{engagement_id}/restore
Restores an engagement (and its archived parents, if needed) before retention ends.
Scope: engagements:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/restore \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/restore",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/restore`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the archived engagement. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"recordLifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
client | object | yes | The client the engagement belongs to. |
client.id | string (uuid) | yes | UUID of the client or entity. |
client.name | string | yes | Display name of the client or entity. |
client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
entity | object | yes | The legal entity the engagement belongs to. |
entity.id | string (uuid) | yes | UUID of the client or entity. |
entity.name | string | yes | Display name of the client or entity. |
entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
recordLifecycle | object | yes | Live or recoverable-archived (distinct from the return's filing lifecycle). |
recordLifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
recordLifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
recordLifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Non-blocking conditions the write reported: an unsupported filing year, an unverified election, a failed prior-year roll-forward or permanent-establishment seed, or a missing period start (period_start_missing: send periodStart with an engagement update). |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
See the shared error codes.
Get mutation preconditions
GET /api/v1/engagements/{engagement_id}/mutation-preconditions
Reports whether an engagement currently accepts writes, why not when it does not (filed, partner-signed, archived), and the resourceVersion a write must present. Context only: it authorizes nothing and is not a lease; every write re-checks under its own lock.
Scope: clients:write or entities:write or engagements:write or tax-data:write.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/mutation-preconditions \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/mutation-preconditions",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/mutation-preconditions`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
Response
200. Lifecycle and version context for the engagement.
{
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"editable": false,
"lockReason": "<filed | signed | archived>",
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"isLocked": false,
"recordLifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"domains": {
"engagementRecord": false,
"taxData": false,
"documents": false,
"workpapers": false,
"review": false
},
"evaluatedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
engagementId | string (uuid) | yes | UUID of the engagement. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
editable | boolean | yes | True when the engagement accepts writes: not filed, not partner-signed, not archived. |
lockReason | nullable one of filed, signed, archived | no | Why writes are refused. Null when editable. |
filedAt | nullable string (date-time) | no | ISO-8601 in UTC. Null while unfiled. |
revisionNumber | integer, at least 1 | yes | Position in the amendment chain; 1 is the original. |
isLocked | boolean | yes | True while a practitioner holds the collaborative edit lock. |
recordLifecycle | object | yes | Live or recoverable-archived. |
recordLifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
recordLifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
recordLifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
domains | object | yes | Per-domain answer, all false when not editable. |
domains.engagementRecord | boolean | yes | Update, archive, or restore the engagement record. |
domains.taxData | boolean | yes | Saved trial-balance, mapping, classification, and adjustment actions. |
domains.documents | boolean | yes | Document staging, ingestion, replacement, and archive. |
domains.workpapers | boolean | yes | Workpaper create, update, archive, restore, links, and carry-forward. |
domains.review | boolean | yes | Account notes and question or correction marks. |
evaluatedAt | string (date-time) | yes | ISO-8601 in UTC. When this answer was computed; it is not a lease. |
Errors
In addition to the shared error codes:
404: The engagement does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
List account notes
GET /api/v1/engagements/{engagement_id}/account-notes
Returns the engagement's live account notes, newest first. Withdrawn notes are not listed. Filter to one account with accountId. The author is returned as a display name only.
Scope: review:read.
Request
curl --get https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/account-notes \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/account-notes",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/account-notes`);
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
accountId | query | nullable string, up to 512 characters | no | Return only the notes on this trial-balance account. |
limit | query | integer, 1 to 200, default 50 | no | Page size; at most 200. |
offset | query | integer, 0 to 100000, default 0 | no | Zero-based row offset. |
Response
200. One page of account notes.
{
"data": [
{
"id": "6f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"accountId": "<string>",
"text": "<string>",
"authorName": "<string>",
"lastEditedByName": "<string>",
"revision": 1,
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}
],
"pagination": {
"limit": 1,
"offset": 0,
"total": 0,
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The notes on this page, newest first. |
data[].id | string (uuid) | yes | UUID of the note. |
data[].engagementId | string (uuid) | yes | UUID of the engagement the note belongs to. |
data[].accountId | string, 1 to 512 characters | yes | Identifier of the account inside the engagement's saved trial balance. There is no accounts table, so an identifier that matches no saved account is accepted and simply renders nowhere. |
data[].text | string, 1 to 4000 characters | yes | The note text. |
data[].authorName | nullable string, up to 300 characters | no | Display name of whoever wrote the annotation: a firm member's current name, followed by ' (via API client)' when they wrote it through a delegated API client; the label of the machine API client that wrote it; or the name recorded when it was written, once the author has left the firm. Never an identifier. |
data[].lastEditedByName | nullable string, up to 300 characters | no | Display name of whoever last edited the text. Null until it is edited. |
data[].revision | integer, at least 1 | yes | Edit counter; 1 for a note that has never been edited. |
data[].createdAt | string (date-time) | yes | ISO-8601 in UTC. When the note was created. |
data[].updatedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the text was last edited. Null until it is edited. |
data[].lifecycle | object | yes | Live or withdrawn. |
data[].lifecycle.state | one of active, archived | yes | 'active' while the annotation stands, 'archived' once it was withdrawn. |
data[].lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the annotation was withdrawn. Null while active. |
data[].resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
pagination | object | yes | Offset pagination state for this page. |
pagination.limit | integer, 1 to 200 | yes | Page size applied. |
pagination.offset | integer, 0 to 100000 | yes | Zero-based row offset applied. |
pagination.total | integer, at least 0 | yes | Total matching resources. |
pagination.hasMore | boolean | yes | True when another page is available. |
Errors
In addition to the shared error codes:
404: The engagement does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Create an account note
POST /api/v1/engagements/{engagement_id}/account-notes
Attaches a note to one account of the engagement's saved trial balance. Refused while the return is filed or partner-signed. Send a UUID in Idempotency-Key; retrying with the same key returns the note it created.
Scope: review:annotate.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/account-notes \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"accountId": "<string>",
"text": "<string>"
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/account-notes",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"accountId": "<string>",
"text": "<string>",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/account-notes`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"accountId": "<string>",
"text": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
accountId | string, 1 to 512 characters | yes | Identifier of the account inside the engagement's saved trial balance. There is no accounts table, so an identifier that matches no saved account is accepted and simply renders nowhere. |
text | string, 1 to 4000 characters | yes | The note text. |
Response
201. The annotation was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"id": "6f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"accountId": "<string>",
"text": "<string>",
"authorName": "<string>",
"lastEditedByName": "<string>",
"revision": 1,
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the note. |
engagementId | string (uuid) | yes | UUID of the engagement the note belongs to. |
accountId | string, 1 to 512 characters | yes | Identifier of the account inside the engagement's saved trial balance. There is no accounts table, so an identifier that matches no saved account is accepted and simply renders nowhere. |
text | string, 1 to 4000 characters | yes | The note text. |
authorName | nullable string, up to 300 characters | no | Display name of whoever wrote the annotation: a firm member's current name, followed by ' (via API client)' when they wrote it through a delegated API client; the label of the machine API client that wrote it; or the name recorded when it was written, once the author has left the firm. Never an identifier. |
lastEditedByName | nullable string, up to 300 characters | no | Display name of whoever last edited the text. Null until it is edited. |
revision | integer, at least 1 | yes | Edit counter; 1 for a note that has never been edited. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the note was created. |
updatedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the text was last edited. Null until it is edited. |
lifecycle | object | yes | Live or withdrawn. |
lifecycle.state | one of active, archived | yes | 'active' while the annotation stands, 'archived' once it was withdrawn. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the annotation was withdrawn. Null while active. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
Errors
In addition to the shared error codes:
404: The annotation does not exist in the caller's organization or client scope.
Update an account note
PATCH /api/v1/account-notes/{note_id}
Replaces a live note's text and bumps its revision. The account anchor and the original author never change. Send the note's current resourceVersion in If-Match.
Scope: review:annotate.
Request
curl --request PATCH https://api.filemark.ca/api/v1/account-notes/$NOTE_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"text": "<string>"
}'import os
import requests
NOTE_ID = "<note-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.patch(
f"https://api.filemark.ca/api/v1/account-notes/{NOTE_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"text": "<string>",
},
)
response.raise_for_status()
print(response.json())const NOTE_ID = "<note-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/account-notes/${NOTE_ID}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"text": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
note_id | path | string (uuid) | yes | UUID of the note to edit. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The annotation's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
Only the text is editable; the account anchor and the author are not.
| Field | Type | Required | Description |
|---|---|---|---|
text | string, 1 to 4000 characters | yes | The replacement note text. |
Response
200. The annotation after the change, or the earlier result of the same Idempotency-Key.
{
"id": "6f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"accountId": "<string>",
"text": "<string>",
"authorName": "<string>",
"lastEditedByName": "<string>",
"revision": 1,
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the note. |
engagementId | string (uuid) | yes | UUID of the engagement the note belongs to. |
accountId | string, 1 to 512 characters | yes | Identifier of the account inside the engagement's saved trial balance. There is no accounts table, so an identifier that matches no saved account is accepted and simply renders nowhere. |
text | string, 1 to 4000 characters | yes | The note text. |
authorName | nullable string, up to 300 characters | no | Display name of whoever wrote the annotation: a firm member's current name, followed by ' (via API client)' when they wrote it through a delegated API client; the label of the machine API client that wrote it; or the name recorded when it was written, once the author has left the firm. Never an identifier. |
lastEditedByName | nullable string, up to 300 characters | no | Display name of whoever last edited the text. Null until it is edited. |
revision | integer, at least 1 | yes | Edit counter; 1 for a note that has never been edited. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the note was created. |
updatedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the text was last edited. Null until it is edited. |
lifecycle | object | yes | Live or withdrawn. |
lifecycle.state | one of active, archived | yes | 'active' while the annotation stands, 'archived' once it was withdrawn. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the annotation was withdrawn. Null while active. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
Errors
In addition to the shared error codes:
404: The annotation does not exist in the caller's organization or client scope.
Archive an account note
POST /api/v1/account-notes/{note_id}/archive
Withdraws a note through the product's soft delete; it stops appearing in Filemark and in the list, and the public API cannot restore it. A note already withdrawn answers with its current state.
Scope: review:annotate.
Request
curl --request POST https://api.filemark.ca/api/v1/account-notes/$NOTE_ID/archive \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
NOTE_ID = "<note-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/account-notes/{NOTE_ID}/archive",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const NOTE_ID = "<note-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/account-notes/${NOTE_ID}/archive`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
note_id | path | string (uuid) | yes | UUID of the note to withdraw. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The annotation's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The annotation after the change, or the earlier result of the same Idempotency-Key.
{
"id": "6f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"accountId": "<string>",
"text": "<string>",
"authorName": "<string>",
"lastEditedByName": "<string>",
"revision": 1,
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the note. |
engagementId | string (uuid) | yes | UUID of the engagement the note belongs to. |
accountId | string, 1 to 512 characters | yes | Identifier of the account inside the engagement's saved trial balance. There is no accounts table, so an identifier that matches no saved account is accepted and simply renders nowhere. |
text | string, 1 to 4000 characters | yes | The note text. |
authorName | nullable string, up to 300 characters | no | Display name of whoever wrote the annotation: a firm member's current name, followed by ' (via API client)' when they wrote it through a delegated API client; the label of the machine API client that wrote it; or the name recorded when it was written, once the author has left the firm. Never an identifier. |
lastEditedByName | nullable string, up to 300 characters | no | Display name of whoever last edited the text. Null until it is edited. |
revision | integer, at least 1 | yes | Edit counter; 1 for a note that has never been edited. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the note was created. |
updatedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the text was last edited. Null until it is edited. |
lifecycle | object | yes | Live or withdrawn. |
lifecycle.state | one of active, archived | yes | 'active' while the annotation stands, 'archived' once it was withdrawn. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the annotation was withdrawn. Null while active. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
Errors
In addition to the shared error codes:
404: The annotation does not exist in the caller's organization or client scope.
List review marks
GET /api/v1/engagements/{engagement_id}/review-marks
Returns the engagement's active question and correction marks, newest first. Sign-off marks (first review, partner review) are never listed here; read them through the engagement review summary.
Scope: review:read.
Request
curl --get https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/review-marks \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/review-marks",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/review-marks`);
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
fieldPath | query | nullable string, up to 512 characters | no | Return only the marks on this field path. |
limit | query | integer, 1 to 200, default 50 | no | Page size; at most 200. |
offset | query | integer, 0 to 100000, default 0 | no | Zero-based row offset. |
Response
200. One page of question and correction marks.
{
"data": [
{
"id": "7f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"fieldPath": "<string>",
"markType": "<question | correction>",
"notes": "<string>",
"authorName": "<string>",
"lastEditedByName": "<string>",
"revision": 1,
"markedAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}
],
"pagination": {
"limit": 1,
"offset": 0,
"total": 0,
"hasMore": false
},
"importedEvidence": [
{
"id": "<string>",
"sourceRevisionId": "<string>",
"fieldPath": "<string>",
"nativeFieldId": {
"<member>": "<any>"
},
"preparation": "<any>",
"sourceActor": "<string>",
"sourceMarkedAt": "<string>",
"sourceTime": {
"<member>": "<any>"
},
"sourceValue": {
"<member>": "<any>"
},
"valueWithheld": false,
"sourceDetails": {
"<member>": "<any>"
},
"evidenceKind": "<string>",
"isHistorical": true,
"countsForSignoff": false
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The marks on this page, newest first. |
data[].id | string (uuid) | yes | UUID of the mark. |
data[].engagementId | string (uuid) | yes | UUID of the engagement the mark belongs to. |
data[].fieldPath | string, 1 to 512 characters | yes | Dotted path of the reviewed field, exactly as the product records it (for example 'schedule1.line104' or 'accounts.abc123.balance'). |
data[].markType | one of question, correction | yes | 'question' asks the preparer something; 'correction' says the value is wrong. Sign-off marks are never exposed here. |
data[].notes | nullable string, up to 2000 characters | no | Free text explaining the mark. |
data[].authorName | nullable string, up to 300 characters | no | Display name of whoever wrote the annotation: a firm member's current name, followed by ' (via API client)' when they wrote it through a delegated API client; the label of the machine API client that wrote it; or the name recorded when it was written, once the author has left the firm. Never an identifier. |
data[].lastEditedByName | nullable string, up to 300 characters | no | Display name of whoever last edited the notes. Null until they are edited. |
data[].revision | integer, at least 1 | yes | Edit counter; 1 for a mark whose notes have never been edited. |
data[].markedAt | string (date-time) | yes | ISO-8601 in UTC. When the mark was placed. |
data[].updatedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the notes were last edited. Null until they are edited. |
data[].lifecycle | object | yes | Live or cleared. A mark is cleared in Filemark by a reviewer; the public API never clears one. |
data[].lifecycle.state | one of active, archived | yes | 'active' while the annotation stands, 'archived' once it was withdrawn. |
data[].lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the annotation was withdrawn. Null while active. |
data[].resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
pagination | object | yes | Offset pagination state for this page. |
pagination.limit | integer, 1 to 200 | yes | Page size applied. |
pagination.offset | integer, 0 to 100000 | yes | Zero-based row offset applied. |
pagination.total | integer, at least 0 | yes | Total matching resources. |
pagination.hasMore | boolean | yes | True when another page is available. |
importedEvidence | array of objects | no | Historical source evidence, excluded from active marks and sign-off counts. |
importedEvidence[].id | string | when importedEvidence is set | |
importedEvidence[].sourceRevisionId | string | when importedEvidence is set | |
importedEvidence[].fieldPath | nullable string | when importedEvidence is set | |
importedEvidence[].nativeFieldId | nullable object | when importedEvidence is set | |
importedEvidence[].nativeFieldId.* | any | no | Members not listed here. |
importedEvidence[].preparation | any | when importedEvidence is set | |
importedEvidence[].sourceActor | nullable string | when importedEvidence is set | |
importedEvidence[].sourceMarkedAt | nullable string | when importedEvidence is set | |
importedEvidence[].sourceTime | nullable object | when importedEvidence is set | |
importedEvidence[].sourceTime.* | any | no | Members not listed here. |
importedEvidence[].sourceValue | nullable object | when importedEvidence is set | |
importedEvidence[].sourceValue.* | any | no | Members not listed here. |
importedEvidence[].valueWithheld | boolean | when importedEvidence is set | |
importedEvidence[].sourceDetails | nullable object | when importedEvidence is set | |
importedEvidence[].sourceDetails.* | any | no | Members not listed here. |
importedEvidence[].evidenceKind | string | when importedEvidence is set | |
importedEvidence[].isHistorical | boolean, always true | when importedEvidence is set | |
importedEvidence[].countsForSignoff | boolean, always false | when importedEvidence is set |
Errors
In addition to the shared error codes:
404: The engagement does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Create a review mark
POST /api/v1/engagements/{engagement_id}/review-marks
Places a question or correction mark on one field. Sign-off marks are refused: first review and partner review carry reviewer authority and are placed in Filemark by a person. One active mark stands per field and type, so marking an already-marked field answers the existing mark. Refused while the return is filed or partner-signed.
Scope: review:annotate.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/review-marks \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"fieldPath": "<string>",
"markType": "<question | correction>",
"notes": "<string>"
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/review-marks",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"fieldPath": "<string>",
"markType": "<question | correction>",
"notes": "<string>",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/review-marks`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"fieldPath": "<string>",
"markType": "<question | correction>",
"notes": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
fieldPath | string, 1 to 512 characters | yes | Dotted path of the reviewed field, exactly as the product records it (for example 'schedule1.line104' or 'accounts.abc123.balance'). |
markType | one of question, correction | yes | 'question' or 'correction' only. A sign-off mark ('first_review', 'partner_review') is reviewer authority and is refused. |
notes | nullable string, up to 2000 characters | no | Free text explaining the mark. |
Response
201. The annotation was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"id": "7f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"fieldPath": "<string>",
"markType": "<question | correction>",
"notes": "<string>",
"authorName": "<string>",
"lastEditedByName": "<string>",
"revision": 1,
"markedAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the mark. |
engagementId | string (uuid) | yes | UUID of the engagement the mark belongs to. |
fieldPath | string, 1 to 512 characters | yes | Dotted path of the reviewed field, exactly as the product records it (for example 'schedule1.line104' or 'accounts.abc123.balance'). |
markType | one of question, correction | yes | 'question' asks the preparer something; 'correction' says the value is wrong. Sign-off marks are never exposed here. |
notes | nullable string, up to 2000 characters | no | Free text explaining the mark. |
authorName | nullable string, up to 300 characters | no | Display name of whoever wrote the annotation: a firm member's current name, followed by ' (via API client)' when they wrote it through a delegated API client; the label of the machine API client that wrote it; or the name recorded when it was written, once the author has left the firm. Never an identifier. |
lastEditedByName | nullable string, up to 300 characters | no | Display name of whoever last edited the notes. Null until they are edited. |
revision | integer, at least 1 | yes | Edit counter; 1 for a mark whose notes have never been edited. |
markedAt | string (date-time) | yes | ISO-8601 in UTC. When the mark was placed. |
updatedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the notes were last edited. Null until they are edited. |
lifecycle | object | yes | Live or cleared. A mark is cleared in Filemark by a reviewer; the public API never clears one. |
lifecycle.state | one of active, archived | yes | 'active' while the annotation stands, 'archived' once it was withdrawn. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the annotation was withdrawn. Null while active. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
Errors
In addition to the shared error codes:
404: The annotation does not exist in the caller's organization or client scope.
Update a review mark
PATCH /api/v1/review-marks/{mark_id}
Replaces an active mark's explanation and bumps its revision. The field path, the mark type and the pinned value never change, and this never clears the mark. Send the mark's current resourceVersion in If-Match.
Scope: review:annotate.
Request
curl --request PATCH https://api.filemark.ca/api/v1/review-marks/$MARK_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"notes": "<string>"
}'import os
import requests
MARK_ID = "<mark-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.patch(
f"https://api.filemark.ca/api/v1/review-marks/{MARK_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"notes": "<string>",
},
)
response.raise_for_status()
print(response.json())const MARK_ID = "<mark-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/review-marks/${MARK_ID}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"notes": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
mark_id | path | string (uuid) | yes | UUID of the mark to edit. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The annotation's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
Only the notes text is editable; the field, the type and the pin are not.
| Field | Type | Required | Description |
|---|---|---|---|
notes | nullable string, up to 2000 characters | no | The replacement explanation. Null clears the text without clearing the mark. |
Response
200. The annotation after the change, or the earlier result of the same Idempotency-Key.
{
"id": "7f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"fieldPath": "<string>",
"markType": "<question | correction>",
"notes": "<string>",
"authorName": "<string>",
"lastEditedByName": "<string>",
"revision": 1,
"markedAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the mark. |
engagementId | string (uuid) | yes | UUID of the engagement the mark belongs to. |
fieldPath | string, 1 to 512 characters | yes | Dotted path of the reviewed field, exactly as the product records it (for example 'schedule1.line104' or 'accounts.abc123.balance'). |
markType | one of question, correction | yes | 'question' asks the preparer something; 'correction' says the value is wrong. Sign-off marks are never exposed here. |
notes | nullable string, up to 2000 characters | no | Free text explaining the mark. |
authorName | nullable string, up to 300 characters | no | Display name of whoever wrote the annotation: a firm member's current name, followed by ' (via API client)' when they wrote it through a delegated API client; the label of the machine API client that wrote it; or the name recorded when it was written, once the author has left the firm. Never an identifier. |
lastEditedByName | nullable string, up to 300 characters | no | Display name of whoever last edited the notes. Null until they are edited. |
revision | integer, at least 1 | yes | Edit counter; 1 for a mark whose notes have never been edited. |
markedAt | string (date-time) | yes | ISO-8601 in UTC. When the mark was placed. |
updatedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the notes were last edited. Null until they are edited. |
lifecycle | object | yes | Live or cleared. A mark is cleared in Filemark by a reviewer; the public API never clears one. |
lifecycle.state | one of active, archived | yes | 'active' while the annotation stands, 'archived' once it was withdrawn. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the annotation was withdrawn. Null while active. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
Errors
In addition to the shared error codes:
404: The annotation does not exist in the caller's organization or client scope.
Apply a saved tax-data action
POST /api/v1/engagements/{engagement_id}/data-actions
Applies one allowlisted action to the engagement's saved trial-balance state: assign or clear an account's CRA GIFI code, set the classification answers the Filemark Classification card writes, apply a host model's answer to the account-classification request an MCP trial-balance landing deferred to it (applyClassificationAnswers: the submit_classifications input, applied exactly as Filemark's own model's answer and only to accounts still waiting for it), save a host model's GIFI code suggestions for a deferred GIFI request (applyGifiSuggestions: the submit_gifi_suggestions input, saved as the suggestions Filemark shows for accounts with no code or suggestion yet), create, replace, or remove one adjusting journal entry, or save the return's own answers no trial balance supplies (setReturnAnswers: the T2 head office, signing officer, signing date, contact, language, principal products and line 259, the Schedule 524 Ontario specialty type, the s.157(1.2)(d) quarterly instalment answers, Schedule 141, the Schedule 50 shareholders and the preceding year's association), each on its own leaf. A posting entry must balance to the cent and every amount line must resolve to one live account. Potential entries are saved without posting and may be unbalanced. Refused while the return is filed or partner-signed. Send the engagement's current resourceVersion in If-Match; the saved blobs are never returned here, so read them back through the trial-balance, account, and adjustment reads, and the return answers through an export.
Scope: tax-data:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/data-actions \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"action": "<assignGifi | clearGifi | setAccountClassification | applyClassificationAnswers | applyGifiSuggestions | upsertAdjustingEntry | removeAdjustingEntry | setReturnAnswers>",
"accountId": "<string>",
"gifiCode": "<string>",
"classification": {
"incomeType": "<active_business | property | rental | capital>",
"foreignSource": false,
"incidentToActiveBusiness": false,
"bookTreatment": "<accept_as_booked | reclassify | accrue_adjust | write_off | requires_review | unclassified>",
"taxTreatment": "<fully_deductible | non_deductible_full | partially_deductible | capital_cca | timing_difference | deduct_other_schedule | non_taxable | disclosure_only | informational | unclassified>",
"deductibilityPercentage": 0,
"adjustmentType": "<addition | deduction>",
"s1Line": "<string>",
"shareholderOrigin": "<borrowed_principal | unpaid_remuneration | unpaid_related_expenses | shareholder_advance>",
"salesTaxAttribution": {
"<member>": "<any>"
}
},
"classificationAnswers": {
"classifications": [
{
"account_code": "<string>",
"assumption": "<string>",
"treatment": "<no_adjustment | full_addition | partial_addition | conditional | timing_difference | capital | requires_review>",
"adjustment_type": "<addition | deduction | null>",
"deductibility_percentage": "<any>",
"deductibility_rule": "<any>",
"schedule1_relevant": false,
"conditional_on": "<any>",
"reasoning": "<any>",
"confidence": 0,
"follow_up_question": "<any>",
"template_id": "<any>",
"ita_references": [
"<string>"
],
"gifi_suggestions": [
"<string>"
]
}
]
},
"gifiSuggestionAnswers": {
"suggestions": [
{
"account_code": "<string>",
"gifi_suggestions": [
"<string>"
]
}
]
},
"entry": {
"id": "<string>",
"name": "<string>",
"description": "<string>",
"entryType": "<adjusting | reclassifying | tax_provision | potential>",
"entryDate": "<string>",
"lines": [
{
"id": "<string>",
"type": "<debit | credit>",
"accountId": "<string>",
"accountCode": "<string>",
"accountName": "<string>",
"amount": 0
}
]
},
"entryId": "<string>",
"returnAnswers": {
"t2Jacket": {
"identification": {
"businessNumber": "<string>"
},
"addresses": {
"headOffice": {
"street1": "<string>",
"street2": "<string>",
"city": "<string>",
"provinceOrState": "<string>",
"country": "<string>",
"postalOrZip": "<string>"
},
"headOfficeChanged": false
},
"certification": {
"signerLastName": "<string>",
"signerFirstName": "<string>",
"signerPosition": "<string>",
"signerTelephone": "<string>",
"signerTitle": "<string>",
"signerEmail": "<string>",
"signatureDate": "2025-12-31",
"contactSameAsSigner": false,
"otherContactName": "<string>",
"otherContactTelephone": "<string>",
"language": 0
},
"additionalInfo": {
"productsOrServices": [
{
"description": "<string>",
"percentOfTotalRevenue": 0
}
],
"section94_1OffshoreInvestmentFundPropertyApplies": false,
"ontarioSpecialtyType": "<01 | 02 | 03 | 04 | 06 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 99>",
"ontarioSpecialtyTypePreviousYear": "<01 | 02 | 03 | 04 | 06 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 99>",
"instalmentComplianceRemittedOnTime": false,
"instalmentComplianceFiledOnTime": false
},
"applicability": {
"specifiedForeignPropertyOver100K": false
}
},
"wasAssociatedInPrecedingYear": false,
"schedule141": {
"identifyPerson": "<Yes | No>",
"professionalDesignation": "<Yes | No>",
"accountantConnected": "<Yes | No>",
"involvementAuditorReport": false,
"involvementReviewEngagement": false,
"involvementCompilationEngagement": false,
"involvementAccountingServices": false,
"involvementBookkeepingServices": false,
"involvementOther": "<string>",
"accountantReservation": "<Yes | No>",
"notesPrepared": "<Yes | No>",
"subsequentEvents": "<Yes | No>",
"reevaluationOfAssets": "<Yes | No>",
"contingentLiabilities": "<Yes | No>",
"commitments": "<Yes | No>",
"jointVentureInvestments": "<Yes | No>",
"impairmentRecognized": "<Yes | No>",
"ppeNetIncome": 0,
"ppeOCI": 0,
"intangibleNetIncome": 0,
"intangibleOCI": 0,
"investmentPropertyNetIncome": 0,
"biologicalNetIncome": 0,
"financialInstrumentsNetIncome": 0,
"financialInstrumentsOCI": 0,
"otherImpairmentNetIncome": 0,
"otherImpairmentOCI": 0,
"derecognizeFinancialInstrument": "<Yes | No>",
"appliedHedgeAccounting": "<Yes | No>",
"discontinueHedgeAccounting": "<Yes | No>",
"openingEquityAdjustment": "<Yes | No>",
"preparerHasAccountingDesignation": false,
"preparerIsPart1Person": false,
"preparedTaxAndFI": false,
"clientProvidedFS": false,
"clientProvidedTB": false,
"clientProvidedGL": false,
"preparedOther": "<string>"
},
"schedule50": {
"rows": [
{
"name": "<string>",
"shareholderType": "<corporation | partnership | individual | trust>",
"businessNumber": "<string>",
"socialInsuranceNumber": "<string>",
"trustNumber": "<string>",
"percentCommonShares": 0,
"percentPreferredShares": 0
}
]
}
}
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/data-actions",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"action": "<assignGifi | clearGifi | setAccountClassification | applyClassificationAnswers | applyGifiSuggestions | upsertAdjustingEntry | removeAdjustingEntry | setReturnAnswers>",
"accountId": "<string>",
"gifiCode": "<string>",
"classification": {
"incomeType": "<active_business | property | rental | capital>",
"foreignSource": False,
"incidentToActiveBusiness": False,
"bookTreatment": "<accept_as_booked | reclassify | accrue_adjust | write_off | requires_review | unclassified>",
"taxTreatment": "<fully_deductible | non_deductible_full | partially_deductible | capital_cca | timing_difference | deduct_other_schedule | non_taxable | disclosure_only | informational | unclassified>",
"deductibilityPercentage": 0,
"adjustmentType": "<addition | deduction>",
"s1Line": "<string>",
"shareholderOrigin": "<borrowed_principal | unpaid_remuneration | unpaid_related_expenses | shareholder_advance>",
"salesTaxAttribution": {
"<member>": "<any>",
},
},
"classificationAnswers": {
"classifications": [
{
"account_code": "<string>",
"assumption": "<string>",
"treatment": "<no_adjustment | full_addition | partial_addition | conditional | timing_difference | capital | requires_review>",
"adjustment_type": "<addition | deduction | null>",
"deductibility_percentage": "<any>",
"deductibility_rule": "<any>",
"schedule1_relevant": False,
"conditional_on": "<any>",
"reasoning": "<any>",
"confidence": 0,
"follow_up_question": "<any>",
"template_id": "<any>",
"ita_references": [
"<string>",
],
"gifi_suggestions": [
"<string>",
],
},
],
},
"gifiSuggestionAnswers": {
"suggestions": [
{
"account_code": "<string>",
"gifi_suggestions": [
"<string>",
],
},
],
},
"entry": {
"id": "<string>",
"name": "<string>",
"description": "<string>",
"entryType": "<adjusting | reclassifying | tax_provision | potential>",
"entryDate": "<string>",
"lines": [
{
"id": "<string>",
"type": "<debit | credit>",
"accountId": "<string>",
"accountCode": "<string>",
"accountName": "<string>",
"amount": 0,
},
],
},
"entryId": "<string>",
"returnAnswers": {
"t2Jacket": {
"identification": {
"businessNumber": "<string>",
},
"addresses": {
"headOffice": {
"street1": "<string>",
"street2": "<string>",
"city": "<string>",
"provinceOrState": "<string>",
"country": "<string>",
"postalOrZip": "<string>",
},
"headOfficeChanged": False,
},
"certification": {
"signerLastName": "<string>",
"signerFirstName": "<string>",
"signerPosition": "<string>",
"signerTelephone": "<string>",
"signerTitle": "<string>",
"signerEmail": "<string>",
"signatureDate": "2025-12-31",
"contactSameAsSigner": False,
"otherContactName": "<string>",
"otherContactTelephone": "<string>",
"language": 0,
},
"additionalInfo": {
"productsOrServices": [
{
"description": "<string>",
"percentOfTotalRevenue": 0,
},
],
"section94_1OffshoreInvestmentFundPropertyApplies": False,
"ontarioSpecialtyType": "<01 | 02 | 03 | 04 | 06 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 99>",
"ontarioSpecialtyTypePreviousYear": "<01 | 02 | 03 | 04 | 06 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 99>",
"instalmentComplianceRemittedOnTime": False,
"instalmentComplianceFiledOnTime": False,
},
"applicability": {
"specifiedForeignPropertyOver100K": False,
},
},
"wasAssociatedInPrecedingYear": False,
"schedule141": {
"identifyPerson": "<Yes | No>",
"professionalDesignation": "<Yes | No>",
"accountantConnected": "<Yes | No>",
"involvementAuditorReport": False,
"involvementReviewEngagement": False,
"involvementCompilationEngagement": False,
"involvementAccountingServices": False,
"involvementBookkeepingServices": False,
"involvementOther": "<string>",
"accountantReservation": "<Yes | No>",
"notesPrepared": "<Yes | No>",
"subsequentEvents": "<Yes | No>",
"reevaluationOfAssets": "<Yes | No>",
"contingentLiabilities": "<Yes | No>",
"commitments": "<Yes | No>",
"jointVentureInvestments": "<Yes | No>",
"impairmentRecognized": "<Yes | No>",
"ppeNetIncome": 0,
"ppeOCI": 0,
"intangibleNetIncome": 0,
"intangibleOCI": 0,
"investmentPropertyNetIncome": 0,
"biologicalNetIncome": 0,
"financialInstrumentsNetIncome": 0,
"financialInstrumentsOCI": 0,
"otherImpairmentNetIncome": 0,
"otherImpairmentOCI": 0,
"derecognizeFinancialInstrument": "<Yes | No>",
"appliedHedgeAccounting": "<Yes | No>",
"discontinueHedgeAccounting": "<Yes | No>",
"openingEquityAdjustment": "<Yes | No>",
"preparerHasAccountingDesignation": False,
"preparerIsPart1Person": False,
"preparedTaxAndFI": False,
"clientProvidedFS": False,
"clientProvidedTB": False,
"clientProvidedGL": False,
"preparedOther": "<string>",
},
"schedule50": {
"rows": [
{
"name": "<string>",
"shareholderType": "<corporation | partnership | individual | trust>",
"businessNumber": "<string>",
"socialInsuranceNumber": "<string>",
"trustNumber": "<string>",
"percentCommonShares": 0,
"percentPreferredShares": 0,
},
],
},
},
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/data-actions`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"action": "<assignGifi | clearGifi | setAccountClassification | applyClassificationAnswers | applyGifiSuggestions | upsertAdjustingEntry | removeAdjustingEntry | setReturnAnswers>",
"accountId": "<string>",
"gifiCode": "<string>",
"classification": {
"incomeType": "<active_business | property | rental | capital>",
"foreignSource": false,
"incidentToActiveBusiness": false,
"bookTreatment": "<accept_as_booked | reclassify | accrue_adjust | write_off | requires_review | unclassified>",
"taxTreatment": "<fully_deductible | non_deductible_full | partially_deductible | capital_cca | timing_difference | deduct_other_schedule | non_taxable | disclosure_only | informational | unclassified>",
"deductibilityPercentage": 0,
"adjustmentType": "<addition | deduction>",
"s1Line": "<string>",
"shareholderOrigin": "<borrowed_principal | unpaid_remuneration | unpaid_related_expenses | shareholder_advance>",
"salesTaxAttribution": {
"<member>": "<any>"
}
},
"classificationAnswers": {
"classifications": [
{
"account_code": "<string>",
"assumption": "<string>",
"treatment": "<no_adjustment | full_addition | partial_addition | conditional | timing_difference | capital | requires_review>",
"adjustment_type": "<addition | deduction | null>",
"deductibility_percentage": "<any>",
"deductibility_rule": "<any>",
"schedule1_relevant": false,
"conditional_on": "<any>",
"reasoning": "<any>",
"confidence": 0,
"follow_up_question": "<any>",
"template_id": "<any>",
"ita_references": [
"<string>"
],
"gifi_suggestions": [
"<string>"
]
}
]
},
"gifiSuggestionAnswers": {
"suggestions": [
{
"account_code": "<string>",
"gifi_suggestions": [
"<string>"
]
}
]
},
"entry": {
"id": "<string>",
"name": "<string>",
"description": "<string>",
"entryType": "<adjusting | reclassifying | tax_provision | potential>",
"entryDate": "<string>",
"lines": [
{
"id": "<string>",
"type": "<debit | credit>",
"accountId": "<string>",
"accountCode": "<string>",
"accountName": "<string>",
"amount": 0
}
]
},
"entryId": "<string>",
"returnAnswers": {
"t2Jacket": {
"identification": {
"businessNumber": "<string>"
},
"addresses": {
"headOffice": {
"street1": "<string>",
"street2": "<string>",
"city": "<string>",
"provinceOrState": "<string>",
"country": "<string>",
"postalOrZip": "<string>"
},
"headOfficeChanged": false
},
"certification": {
"signerLastName": "<string>",
"signerFirstName": "<string>",
"signerPosition": "<string>",
"signerTelephone": "<string>",
"signerTitle": "<string>",
"signerEmail": "<string>",
"signatureDate": "2025-12-31",
"contactSameAsSigner": false,
"otherContactName": "<string>",
"otherContactTelephone": "<string>",
"language": 0
},
"additionalInfo": {
"productsOrServices": [
{
"description": "<string>",
"percentOfTotalRevenue": 0
}
],
"section94_1OffshoreInvestmentFundPropertyApplies": false,
"ontarioSpecialtyType": "<01 | 02 | 03 | 04 | 06 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 99>",
"ontarioSpecialtyTypePreviousYear": "<01 | 02 | 03 | 04 | 06 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 99>",
"instalmentComplianceRemittedOnTime": false,
"instalmentComplianceFiledOnTime": false
},
"applicability": {
"specifiedForeignPropertyOver100K": false
}
},
"wasAssociatedInPrecedingYear": false,
"schedule141": {
"identifyPerson": "<Yes | No>",
"professionalDesignation": "<Yes | No>",
"accountantConnected": "<Yes | No>",
"involvementAuditorReport": false,
"involvementReviewEngagement": false,
"involvementCompilationEngagement": false,
"involvementAccountingServices": false,
"involvementBookkeepingServices": false,
"involvementOther": "<string>",
"accountantReservation": "<Yes | No>",
"notesPrepared": "<Yes | No>",
"subsequentEvents": "<Yes | No>",
"reevaluationOfAssets": "<Yes | No>",
"contingentLiabilities": "<Yes | No>",
"commitments": "<Yes | No>",
"jointVentureInvestments": "<Yes | No>",
"impairmentRecognized": "<Yes | No>",
"ppeNetIncome": 0,
"ppeOCI": 0,
"intangibleNetIncome": 0,
"intangibleOCI": 0,
"investmentPropertyNetIncome": 0,
"biologicalNetIncome": 0,
"financialInstrumentsNetIncome": 0,
"financialInstrumentsOCI": 0,
"otherImpairmentNetIncome": 0,
"otherImpairmentOCI": 0,
"derecognizeFinancialInstrument": "<Yes | No>",
"appliedHedgeAccounting": "<Yes | No>",
"discontinueHedgeAccounting": "<Yes | No>",
"openingEquityAdjustment": "<Yes | No>",
"preparerHasAccountingDesignation": false,
"preparerIsPart1Person": false,
"preparedTaxAndFI": false,
"clientProvidedFS": false,
"clientProvidedTB": false,
"clientProvidedGL": false,
"preparedOther": "<string>"
},
"schedule50": {
"rows": [
{
"name": "<string>",
"shareholderType": "<corporation | partnership | individual | trust>",
"businessNumber": "<string>",
"socialInsuranceNumber": "<string>",
"trustNumber": "<string>",
"percentCommonShares": 0,
"percentPreferredShares": 0
}
]
}
}
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement to change. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The engagement's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
One allowlisted action against the engagement's saved tax data.
| Field | Type | Required | Description |
|---|---|---|---|
action | one of assignGifi, clearGifi, setAccountClassification, applyClassificationAnswers, applyGifiSuggestions, upsertAdjustingEntry, removeAdjustingEntry, setReturnAnswers | yes | The action to perform. |
accountId | nullable string, up to 128 characters | no | Identifier of the account in the engagement's saved trial balance, as the trial-balance and account reads return it. An identifier that matches no live account is refused. |
gifiCode | nullable string, up to 8 characters, matching ^[0-9]{4}$ | no | CRA GIFI code from the RC4088 master list. Auto-calculated subtotal codes are refused: they are form items, not account classifications. |
classification | nullable object | no | The classification answers to set (setAccountClassification). |
classification.incomeType | nullable one of active_business, property, rental, capital | when classification is set | Character of the income the account carries. Drives the Schedule 7 split and the small-business-deduction base. Null clears the answer. |
classification.foreignSource | nullable boolean | when classification is set | True when the income is foreign-source, false when it is Canadian. Null clears the answer. Setting it also records the income type as a manual answer, exactly as the Classification card does. |
classification.incidentToActiveBusiness | nullable boolean | when classification is set | For property or rental income: true when it is incident to or pertains to an active business (s.129(4)(b)(i)), which keeps it out of Schedule 7 investment income; false when it is not. Null clears the answer. |
classification.bookTreatment | nullable one of accept_as_booked, reclassify, accrue_adjust, write_off, requires_review, unclassified | when classification is set | The card's Book answer: whether the booked amount needs a journal entry. It has no Schedule 1 effect and cannot be cleared. |
classification.taxTreatment | nullable one of fully_deductible, non_deductible_full, partially_deductible, capital_cca, timing_difference, deduct_other_schedule, non_taxable, disclosure_only, informational, unclassified | when classification is set | The card's Tax answer: how the amount is treated for tax. fully_deductible also sets deductibilityPercentage to 100 and non_deductible_full sets it to 0, exactly as the card does. It cannot be cleared; send the treatment that applies. |
classification.deductibilityPercentage | nullable integer, 0 to 100 | when classification is set | Whole percentage of the amount that is deductible, 0 to 100. On a conditional or partial verdict it is the answer Schedule 1 posts from: the non-deductible share is added back. Null clears it. |
classification.adjustmentType | nullable one of addition, deduction | when classification is set | The card's Adjustment Type answer: which way the Schedule 1 adjustment goes. On an account with no Schedule 1 line it also sets the concept's own line, or the free-form grid when the concept has none. Null takes the account off Schedule 1, exactly as 'None' on the card does. |
classification.s1Line | nullable string, up to 16 characters, matching ^[0-9A-Za-z]{1,16}$ | when classification is set | Schedule 1 line the adjustment posts to. Null takes the account off Schedule 1 and clears its adjustment direction, exactly as answering 'No' on the card does. |
classification.shareholderOrigin | nullable one of borrowed_principal, unpaid_remuneration, unpaid_related_expenses, shareholder_advance | when classification is set | |
classification.salesTaxAttribution | nullable object | when classification is set | Opening and ledger-leg registration allocations, with debit-positive decimal amounts. Null reopens attribution. |
classification.salesTaxAttribution.* | any | no | Members not listed here. |
classificationAnswers | nullable object | no | A host model's submit_classifications input for a deferred account-classification request (applyClassificationAnswers). |
classificationAnswers.classifications | array of objects, 1 to 500 items | when classificationAnswers is set | One entry per account, keyed by the account_code the request carried, which is the account's identifier in the trial-balance and account reads. Only an account still waiting for this answer takes it; the applied summary lists the rest and why. |
classificationAnswers.classifications[].account_code | string | when classificationAnswers is set | |
classificationAnswers.classifications[].assumption | string | when classificationAnswers is set | |
classificationAnswers.classifications[].treatment | one of no_adjustment, full_addition, partial_addition, conditional, timing_difference, capital, requires_review | when classificationAnswers is set | |
classificationAnswers.classifications[].adjustment_type | one of addition, deduction, null | when classificationAnswers is set | |
classificationAnswers.classifications[].deductibility_percentage | number,null, 0 to 100 | when classificationAnswers is set | |
classificationAnswers.classifications[].deductibility_rule | string,null | when classificationAnswers is set | |
classificationAnswers.classifications[].schedule1_relevant | boolean | when classificationAnswers is set | |
classificationAnswers.classifications[].conditional_on | string,null | when classificationAnswers is set | |
classificationAnswers.classifications[].reasoning | string,null | when classificationAnswers is set | |
classificationAnswers.classifications[].confidence | number, 0 to 1 | when classificationAnswers is set | |
classificationAnswers.classifications[].follow_up_question | string,null | when classificationAnswers is set | |
classificationAnswers.classifications[].template_id | string,null | when classificationAnswers is set | |
classificationAnswers.classifications[].ita_references | array of strings | when classificationAnswers is set | |
classificationAnswers.classifications[].gifi_suggestions | array of strings, up to 5 items | when classificationAnswers is set | 0 to 5 CRA GIFI codes (strings) that this account's evidence supports, ranked from most likely to least likely; empty when nothing supports one. Must come from the master list in the system prompt; must NOT include any auto-calculated subtotal code (the system prompt lists them). |
gifiSuggestionAnswers | nullable object | no | A host model's submit_gifi_suggestions input for a deferred GIFI suggestion request (applyGifiSuggestions). |
gifiSuggestionAnswers.suggestions | array of objects, 1 to 500 items | when gifiSuggestionAnswers is set | One entry per account. The applied summary lists the entries not taken and why. |
gifiSuggestionAnswers.suggestions[].account_code | string, 1 to 128 characters | when gifiSuggestionAnswers is set | The account_code the request carried: the account's identifier in the trial-balance and account reads. |
gifiSuggestionAnswers.suggestions[].gifi_suggestions | array of strings, 1 to 5 items | when gifiSuggestionAnswers is set | GIFI codes from the RC4088 master list, most likely first. Subtotal codes are dropped. |
entry | nullable object | no | The adjusting journal entry to create or replace. |
entry.id | string, 1 to 128 characters | when entry is set | Identifier of the entry; reused to replace it. |
entry.name | nullable string, up to 300 characters | when entry is set | Short entry name. |
entry.description | nullable string, up to 2000 characters | when entry is set | What the entry adjusts and why. |
entry.entryType | one of adjusting, reclassifying, tax_provision, potential | when entry is set | Category of the entry. 'potential' is tracked but never booked; the other three post identically. |
entry.entryDate | nullable string, up to 32 characters | when entry is set | YYYY-MM-DD the entry is dated, when known. |
entry.lines | array of objects, 1 to 500 items | when entry is set | The entry's debit and credit lines. |
entry.lines[].id | string, 1 to 128 characters | when entry is set | Identifier of the line, unique in the entry. |
entry.lines[].type | one of debit, credit | when entry is set | Side of the entry this line posts to. |
entry.lines[].accountId | string, 1 to 128 characters | when entry is set | Identifier of the account in the engagement's saved trial balance, as the trial-balance and account reads return it. An identifier that matches no live account is refused. |
entry.lines[].accountCode | string, 1 to 128 characters | when entry is set | The account's code. It is the durable anchor the posting rule resolves through, so it must identify exactly one live account. |
entry.lines[].accountName | nullable string, up to 512 characters | when entry is set | The account's name, for display. |
entry.lines[].amount | number, at least 0 or string | when entry is set | Non-negative amount in dollars, at most two decimal places. Send it as a string to keep exact cents. |
entryId | nullable string, up to 128 characters | no | Identifier of the adjusting entry to remove. |
returnAnswers | nullable object | no | setReturnAnswers: the return's own answers that no trial balance or computation supplies, saved leaf by leaf. |
returnAnswers.t2Jacket | object | when returnAnswers is set | T2 jacket answers: identification (box 001), addresses (head office, lines 010 to 018), certification (signing officer, lines 950 to 956 with title and email; signing date 955; contact 957 to 959; language 990), additionalInfo (principal products 284 to 289, Schedule 524 Ontario specialty type, s.94.1, the s.157(1.2)(d) instalment answers) and applicability (line 259). Send it as an object; to clear one answer in it, send that answer as null. |
returnAnswers.t2Jacket.identification | object | when returnAnswers is set | T2 box 001. Send it as an object; to clear one answer in it, send that answer as null. |
returnAnswers.t2Jacket.identification.businessNumber | nullable string, up to 20 characters | when returnAnswers is set | T2 box 001: the corporation income tax account #########RC#### as the CRA issued it. Its first nine digits are the entity's business number (update_entity). Without it the return states RC0001 and says so. |
returnAnswers.t2Jacket.addresses | object | when returnAnswers is set | T2 lines 010 to 018. Send it as an object; to clear one answer in it, send that answer as null. |
returnAnswers.t2Jacket.addresses.headOffice | nullable object | when returnAnswers is set | Head office address, T2 lines 011 to 018. A taxcycle save file's mailing and books-and-records addresses follow it unless they differ. |
returnAnswers.t2Jacket.addresses.headOffice.street1 | nullable string, up to 300 characters | when returnAnswers is set | First street line. |
returnAnswers.t2Jacket.addresses.headOffice.street2 | nullable string, up to 300 characters | when returnAnswers is set | Second street line. |
returnAnswers.t2Jacket.addresses.headOffice.city | nullable string, up to 100 characters | when returnAnswers is set | City. |
returnAnswers.t2Jacket.addresses.headOffice.provinceOrState | nullable string, up to 2 characters | when returnAnswers is set | Two-letter province or territory code (ON), or a US state code. |
returnAnswers.t2Jacket.addresses.headOffice.country | nullable string, up to 100 characters | when returnAnswers is set | Country other than Canada; omit it for a Canadian address (T2 line 017). |
returnAnswers.t2Jacket.addresses.headOffice.postalOrZip | nullable string, up to 10 characters | when returnAnswers is set | Postal code (A1A 1A1) or ZIP code. |
returnAnswers.t2Jacket.addresses.headOfficeChanged | nullable boolean | when returnAnswers is set | T2 line 010: has the head office address changed since the CRA was last told? |
returnAnswers.t2Jacket.certification | object | when returnAnswers is set | T2 lines 950 to 959 and 990, and the T183 CORP email. Send it as an object; to clear one answer in it, send that answer as null. |
returnAnswers.t2Jacket.certification.signerLastName | nullable string, up to 100 characters | when returnAnswers is set | T2 line 950: the authorized signing officer's last name. |
returnAnswers.t2Jacket.certification.signerFirstName | nullable string, up to 100 characters | when returnAnswers is set | T2 line 951: first name. |
returnAnswers.t2Jacket.certification.signerPosition | nullable string, up to 100 characters | when returnAnswers is set | T2 line 954: position, office or rank. |
returnAnswers.t2Jacket.certification.signerTelephone | nullable string, up to 20 characters | when returnAnswers is set | T2 line 956: telephone number, ten digits; punctuation is dropped. |
returnAnswers.t2Jacket.certification.signerTitle | nullable string, up to 20 characters | when returnAnswers is set | Courtesy title for the client letter, such as Ms., Mr. or Dr. |
returnAnswers.t2Jacket.certification.signerEmail | nullable string, up to 254 characters | when returnAnswers is set | T183 CORP Part 1 email address. Supplying it registers the corporation to receive CRA email notifications: the CRA emails this address when new correspondence is in My Business Account. |
returnAnswers.t2Jacket.certification.signatureDate | nullable string (date) | when returnAnswers is set | T2 line 955: the date the officer signs, YYYY-MM-DD, on or after the tax year-end. |
returnAnswers.t2Jacket.certification.contactSameAsSigner | nullable boolean | when returnAnswers is set | T2 line 957: is the contact person the authorized signing officer? |
returnAnswers.t2Jacket.certification.otherContactName | nullable string, up to 200 characters | when returnAnswers is set | T2 line 958: the other contact's full name (when line 957 is No). |
returnAnswers.t2Jacket.certification.otherContactTelephone | nullable string, up to 20 characters | when returnAnswers is set | T2 line 959: the other contact's telephone number. |
returnAnswers.t2Jacket.certification.language | nullable one of 1, 2 | when returnAnswers is set | T2 line 990, language of correspondence: 1 English, 2 French. |
returnAnswers.t2Jacket.additionalInfo | object | when returnAnswers is set | T2 lines 284 to 289, Schedule 524 line 100 and the s.157(1.2)(d) answers. Send it as an object; to clear one answer in it, send that answer as null. |
returnAnswers.t2Jacket.additionalInfo.productsOrServices | nullable array of objects, up to 3 items | when returnAnswers is set | T2 lines 284 to 289: up to three principal products or services, with their approximate percentage of total revenue. Replaces the list the return held. |
returnAnswers.t2Jacket.additionalInfo.productsOrServices[].description | string, 1 to 300 characters | when returnAnswers is set | The product mined, manufactured, sold or constructed, or the service provided. |
returnAnswers.t2Jacket.additionalInfo.productsOrServices[].percentOfTotalRevenue | number, 0 to 100 or string | when returnAnswers is set | Its approximate share of total revenue, in percent (100 for all of it). |
returnAnswers.t2Jacket.additionalInfo.section94_1OffshoreInvestmentFundPropertyApplies | nullable boolean | when returnAnswers is set | Whether the corporation held an interest in offshore investment fund property to which ITA s.94.1 applies; asked beside a Yes on line 259. |
returnAnswers.t2Jacket.additionalInfo.ontarioSpecialtyType | nullable one of 01, 02, 03, 04, 06, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 99 | when returnAnswers is set | Schedule 524 line 100, the Ontario specialty type, by its CRA code: 01 family farm; 02 family fishing; 03 mortgage investment; 04 credit union; 06 bank; 08 financial institution prescribed by regulation only; 09 registered securities dealer; 10 farm feeder finance co-operative; 11 insurance; 12 mutual insurance; 13 specialty mutual insurance; 14 mutual fund; 15 bare trustee; 16 professional corporation (incorporated professional only); 17 limited liability; 18 generator of electrical energy for sale, or producer of steam for use in the generation of electrical energy for sale; 19 hydro successor, municipal electrical utility, or subsidiary of either; 20 producer and seller of steam for uses other than for the generation of electricity; 21 mining; 22 non-resident; 99 other (none of these applies). |
returnAnswers.t2Jacket.additionalInfo.ontarioSpecialtyTypePreviousYear | nullable one of 01, 02, 03, 04, 06, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 99 | when returnAnswers is set | The Ontario specialty type for the preceding tax year, by the same code: Schedule 524 is filed for a year the type changes. |
returnAnswers.t2Jacket.additionalInfo.instalmentComplianceRemittedOnTime | nullable boolean | when returnAnswers is set | ITA s.157(1.2)(d)(i): through the 12 months before its last instalment fell due, did the corporation remit on time every amount due under s.153(1), Part IX of the Excise Tax Act, the EI Act s.82(1) and the CPP s.21(1)? |
returnAnswers.t2Jacket.additionalInfo.instalmentComplianceFiledOnTime | nullable boolean | when returnAnswers is set | ITA s.157(1.2)(d)(ii): through the same 12 months, did it file on time every return due under the Income Tax Act and Part IX of the Excise Tax Act? |
returnAnswers.t2Jacket.applicability | object | when returnAnswers is set | T2 line 259. Send it as an object; to clear one answer in it, send that answer as null. |
returnAnswers.t2Jacket.applicability.specifiedForeignPropertyOver100K | nullable boolean | when returnAnswers is set | T2 line 259: did the corporation own or hold specified foreign property costing more than $100,000 at any time in the year (Form T1135)? |
returnAnswers.wasAssociatedInPrecedingYear | nullable boolean | when returnAnswers is set | Was the corporation associated with any corporation in its preceding tax year? |
returnAnswers.schedule141 | object | when returnAnswers is set | Schedule 141, GIFI additional information, answer by answer. Send it as an object; to clear one answer in it, send that answer as null. |
returnAnswers.schedule141.identifyPerson | nullable one of Yes, No | when returnAnswers is set | Line 111: can you identify the person primarily involved with the financial information (more than 50% of the work)? |
returnAnswers.schedule141.professionalDesignation | nullable one of Yes, No | when returnAnswers is set | Line 095: does that person have a professional designation in accounting? |
returnAnswers.schedule141.accountantConnected | nullable one of Yes, No | when returnAnswers is set | Line 097: is that person connected with the corporation (over-10% shareholder, director, officer, employee, or not at arm's length)? |
returnAnswers.schedule141.involvementAuditorReport | nullable boolean | when returnAnswers is set | Line 300: completed an auditor's report. |
returnAnswers.schedule141.involvementReviewEngagement | nullable boolean | when returnAnswers is set | Line 301: completed a review engagement report. |
returnAnswers.schedule141.involvementCompilationEngagement | nullable boolean | when returnAnswers is set | Line 302: conducted a compilation engagement. |
returnAnswers.schedule141.involvementAccountingServices | nullable boolean | when returnAnswers is set | Line 303: provided accounting services. |
returnAnswers.schedule141.involvementBookkeepingServices | nullable boolean | when returnAnswers is set | Line 304: provided bookkeeping services. |
returnAnswers.schedule141.involvementOther | nullable string, up to 2000 characters | when returnAnswers is set | Line 305: other involvement, described. Null or empty text clears it. |
returnAnswers.schedule141.accountantReservation | nullable one of Yes, No | when returnAnswers is set | Line 099 (after 300 or 301): has the person expressed a reservation? |
returnAnswers.schedule141.notesPrepared | nullable one of Yes, No | when returnAnswers is set | Line 101: were notes to the financial statements prepared? |
returnAnswers.schedule141.subsequentEvents | nullable one of Yes, No | when returnAnswers is set | Line 104: any subsequent events? |
returnAnswers.schedule141.reevaluationOfAssets | nullable one of Yes, No | when returnAnswers is set | Line 105: did the corporation re-evaluate its assets in the year? |
returnAnswers.schedule141.contingentLiabilities | nullable one of Yes, No | when returnAnswers is set | Line 106: any contingent liabilities in the year? |
returnAnswers.schedule141.commitments | nullable one of Yes, No | when returnAnswers is set | Line 107: any commitments in the year? |
returnAnswers.schedule141.jointVentureInvestments | nullable one of Yes, No | when returnAnswers is set | Line 108: investments in joint ventures or partnerships? |
returnAnswers.schedule141.impairmentRecognized | nullable one of Yes, No | when returnAnswers is set | Line 200: an impairment loss, its reversal, or a change in fair value recognized in net income or OCI in the year? |
returnAnswers.schedule141.ppeNetIncome | nullable number or string | when returnAnswers is set | Line 210: property, plant and equipment, in net income: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.ppeOCI | nullable number or string | when returnAnswers is set | Line 211: property, plant and equipment, in OCI: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.intangibleNetIncome | nullable number or string | when returnAnswers is set | Line 215: intangible assets, in net income: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.intangibleOCI | nullable number or string | when returnAnswers is set | Line 216: intangible assets, in OCI: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.investmentPropertyNetIncome | nullable number or string | when returnAnswers is set | Line 220: investment property, in net income: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.biologicalNetIncome | nullable number or string | when returnAnswers is set | Line 225: biological assets, in net income: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.financialInstrumentsNetIncome | nullable number or string | when returnAnswers is set | Line 230: financial instruments, in net income: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.financialInstrumentsOCI | nullable number or string | when returnAnswers is set | Line 231: financial instruments, in OCI: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.otherImpairmentNetIncome | nullable number or string | when returnAnswers is set | Line 235: other, in net income: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.otherImpairmentOCI | nullable number or string | when returnAnswers is set | Line 236: other, in OCI: the increase (negative for a decrease) recognized in the year, in dollars, answered when line 200 is Yes. Send it as a string to keep exact cents. |
returnAnswers.schedule141.derecognizeFinancialInstrument | nullable one of Yes, No | when returnAnswers is set | Line 250: derecognized a financial instrument (other than trade receivables)? |
returnAnswers.schedule141.appliedHedgeAccounting | nullable one of Yes, No | when returnAnswers is set | Line 255: applied hedge accounting? |
returnAnswers.schedule141.discontinueHedgeAccounting | nullable one of Yes, No | when returnAnswers is set | Line 260: discontinued hedge accounting? |
returnAnswers.schedule141.openingEquityAdjustment | nullable one of Yes, No | when returnAnswers is set | Line 265: an amount included in opening retained earnings or equity to correct an error, change an accounting policy or adopt a new standard? |
returnAnswers.schedule141.preparerHasAccountingDesignation | nullable boolean | when returnAnswers is set | Part 5: does the person who prepared the T2 return have a professional designation in accounting? With preparerIsPart1Person it settles whether Part 5 (lines 310 to 314) applies. |
returnAnswers.schedule141.preparerIsPart1Person | nullable boolean | when returnAnswers is set | Part 5: is the person who prepared the T2 return the person identified in Part 1? Part 5 applies to a preparer with a designation who is not that person. |
returnAnswers.schedule141.preparedTaxAndFI | nullable boolean | when returnAnswers is set | Line 310: the T2 preparer prepared the return and its financial information. |
returnAnswers.schedule141.clientProvidedFS | nullable boolean | when returnAnswers is set | Line 311: the client provided the financial statements. |
returnAnswers.schedule141.clientProvidedTB | nullable boolean | when returnAnswers is set | Line 312: the client provided a trial balance. |
returnAnswers.schedule141.clientProvidedGL | nullable boolean | when returnAnswers is set | Line 313: the client provided a general ledger. |
returnAnswers.schedule141.preparedOther | nullable string, up to 2000 characters | when returnAnswers is set | Line 314: other, described. Null or empty text clears it. |
returnAnswers.schedule50 | object | when returnAnswers is set | Schedule 50, the shareholders holding 10% or more. Send it as an object whose rows replace the saved rows. |
returnAnswers.schedule50.rows | array of objects, up to 10 items | when returnAnswers is set | Every shareholder holding 10% or more of the common or preferred shares. Replaces the rows the return held. |
returnAnswers.schedule50.rows[].name | string, 1 to 300 characters | when returnAnswers is set | Line 100: the shareholder's name. |
returnAnswers.schedule50.rows[].shareholderType | one of corporation, partnership, individual, trust | when returnAnswers is set | Which identifier the row carries: corporation or partnership (line 200), individual (line 300), trust (line 350). |
returnAnswers.schedule50.rows[].businessNumber | nullable string, up to 20 characters | when returnAnswers is set | Line 200: business number, or NR for a corporation without one. |
returnAnswers.schedule50.rows[].socialInsuranceNumber | nullable string, up to 11 characters | when returnAnswers is set | Line 300: social insurance number of an individual. |
returnAnswers.schedule50.rows[].trustNumber | nullable string, up to 12 characters | when returnAnswers is set | Line 350: trust account number (T followed by eight digits). |
returnAnswers.schedule50.rows[].percentCommonShares | nullable number, 0 to 100 or string | when returnAnswers is set | Line 400: percentage of common shares. |
returnAnswers.schedule50.rows[].percentPreferredShares | nullable number, 0 to 100 or string | when returnAnswers is set | Line 500: percentage of preferred shares. |
Response
200. The engagement after the action, or the earlier result of the same Idempotency-Key.
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"recordLifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
],
"action": "<assignGifi | clearGifi | setAccountClassification | applyClassificationAnswers | applyGifiSuggestions | upsertAdjustingEntry | removeAdjustingEntry | setReturnAnswers>",
"accountId": "<string>",
"applied": {
"changed": false,
"gifiCode": "<string>",
"previousGifiCode": "<string>",
"fields": [
"<string>"
],
"entryId": "<string>",
"created": false,
"posts": false,
"lineCount": 0,
"totalDebits": "<string>",
"totalCredits": "<string>",
"remainingEntryCount": 0,
"answeredCount": 0,
"unchangedCount": 0,
"skipped": [
{
"answerIndex": 0,
"accountId": "<string>",
"reason": "<not_awaiting_answer | answered_by_practitioner>"
}
],
"unmatchedAnswerIndexes": [
0
],
"remainingDeferredCount": 0
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
client | object | yes | The client the engagement belongs to. |
client.id | string (uuid) | yes | UUID of the client or entity. |
client.name | string | yes | Display name of the client or entity. |
client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
entity | object | yes | The legal entity the engagement belongs to. |
entity.id | string (uuid) | yes | UUID of the client or entity. |
entity.name | string | yes | Display name of the client or entity. |
entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
recordLifecycle | object | yes | Live or recoverable-archived (distinct from the return's filing lifecycle). |
recordLifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
recordLifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
recordLifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Non-blocking conditions the write reported: an unsupported filing year, an unverified election, a failed prior-year roll-forward or permanent-establishment seed, or a missing period start (period_start_missing: send periodStart with an engagement update). |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
action | one of assignGifi, clearGifi, setAccountClassification, applyClassificationAnswers, applyGifiSuggestions, upsertAdjustingEntry, removeAdjustingEntry, setReturnAnswers | yes | The action that ran. |
accountId | nullable string | no | The account the action targeted, when it targeted one. |
applied | object | yes | Bounded summary of the change. |
applied.changed | boolean | yes | False when the engagement already held the requested state and nothing was written. |
applied.gifiCode | nullable string | no | The GIFI code now assigned to the account, if any. |
applied.previousGifiCode | nullable string | no | The GIFI code the account carried before, if any. |
applied.fields | array of strings | no | Names of the classification answers this action set or cleared; for setReturnAnswers, the dotted path of each return answer it saved (t2Jacket.certification.signerLastName, schedule50.rows). |
applied.entryId | nullable string | no | Identifier of the adjusting entry that was written or removed. |
applied.created | nullable boolean | no | True when the entry was new, false when an existing entry was replaced. |
applied.posts | nullable boolean | no | True when the entry books to the trial balance. False for a 'potential' entry, which is tracked and never booked. |
applied.lineCount | nullable integer, at least 0 | no | Number of lines on the written entry. |
applied.totalDebits | nullable string | no | Canonical decimal total of the entry's debit lines. |
applied.totalCredits | nullable string | no | Canonical decimal total of the entry's credit lines. |
applied.remainingEntryCount | nullable integer, at least 0 | no | Adjusting entries left on the engagement after a removal. |
applied.answeredCount | nullable integer, at least 0 | no | Accounts the answers were saved for (applyClassificationAnswers, applyGifiSuggestions). |
applied.unchangedCount | nullable integer, at least 0 | no | Answers the account already held, so nothing was written for them. |
applied.skipped | nullable array of objects, up to 500 items | no | Answers that were not applied, each with the reason. |
applied.skipped[].answerIndex | integer, 0 to 499 | when skipped is set | Position of the answer in the submitted list (classificationAnswers.classifications or gifiSuggestionAnswers.suggestions), counting from 0. |
applied.skipped[].accountId | string, up to 128 characters | when skipped is set | The saved account the answer named. An answer naming no live account is not listed here: see unmatchedAnswerIndexes. |
applied.skipped[].reason | one of not_awaiting_answer, answered_by_practitioner | when skipped is set | not_awaiting_answer: the account is not waiting for this answer: already classified by a rule, the prior year or an earlier model answer, or, for GIFI codes, already carrying a code or a suggestion. answered_by_practitioner: a practitioner has classified or reviewed the account in Filemark, and their answer stands. For a classification answer, an income type, source, book treatment or deductible percentage answered alone does not skip the account: the answer is applied around it. |
applied.unmatchedAnswerIndexes | nullable array of integers, up to 500 items | no | Positions, counting from 0, of the submitted answers whose account_code names no live account of the saved trial balance (applyClassificationAnswers, applyGifiSuggestions). The code itself is never sent back. |
applied.remainingDeferredCount | nullable integer, at least 0 | no | Accounts still waiting for a host model's classification answer after this action. |
Errors
In addition to the shared error codes:
404: The engagement or the account does not exist in the caller's scope.
Apply a pending trial-balance import
POST /api/v1/engagements/{engagement_id}/trial-balance/apply
Apply a scoped preview with the same conflict, restore, missing and duplicate decisions as the hub. Re-reads and re-matches the source; If-Match guards the engagement version.
Scope: tax-data:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/trial-balance/apply \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"previewId": "<string>",
"decisions": {
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"missing": {
"<key>": "<keep | delete>"
},
"duplicates": {
"<key>": "<combine | first | skip>"
},
"coverage": {
"<key>": false
},
"accountResolutions": {
"<key>": {
"accountName": "<string>",
"accountCode": "<string>",
"gifiCode": "<string>",
"manual": false
}
}
},
"replaceMissing": false
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/trial-balance/apply",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"previewId": "<string>",
"decisions": {
"conflicts": {
"<key>": "<rename | new_account>",
},
"restores": {
"<key>": "<restore | skip>",
},
"missing": {
"<key>": "<keep | delete>",
},
"duplicates": {
"<key>": "<combine | first | skip>",
},
"coverage": {
"<key>": False,
},
"accountResolutions": {
"<key>": {
"accountName": "<string>",
"accountCode": "<string>",
"gifiCode": "<string>",
"manual": False,
},
},
},
"replaceMissing": False,
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/trial-balance/apply`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"previewId": "<string>",
"decisions": {
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"missing": {
"<key>": "<keep | delete>"
},
"duplicates": {
"<key>": "<combine | first | skip>"
},
"coverage": {
"<key>": false
},
"accountResolutions": {
"<key>": {
"accountName": "<string>",
"accountCode": "<string>",
"gifiCode": "<string>",
"manual": false
}
}
},
"replaceMissing": false
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The engagement's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
previewId | string, 1 to 36 characters | yes | The previewId a landing or a document ingestion's import_preview outcome returned. |
decisions | object | no | |
decisions.conflicts | map of one of rename, new_account | when decisions is set | |
decisions.conflicts.{key} | one of rename, new_account | per key | |
decisions.restores | map of one of restore, skip | when decisions is set | |
decisions.restores.{key} | one of restore, skip | per key | |
decisions.missing | map of one of keep, delete | when decisions is set | |
decisions.missing.{key} | one of keep, delete | per key | |
decisions.duplicates | map of one of combine, first, skip | when decisions is set | |
decisions.duplicates.{key} | one of combine, first, skip | per key | |
decisions.coverage | map of boolean | when decisions is set | |
decisions.coverage.{key} | boolean | per key | |
decisions.accountResolutions | map of object | when decisions is set | |
decisions.accountResolutions.{key} | object | per key | |
decisions.accountResolutions.{key}.accountName | string, 1 to 300 characters | per key | |
decisions.accountResolutions.{key}.accountCode | string, up to 64 characters | per key | |
decisions.accountResolutions.{key}.gifiCode | string, matching ^\d{4}$ | per key | |
decisions.accountResolutions.{key}.manual | boolean | per key | |
replaceMissing | boolean | no | Delete all missing ordinary accounts; explicit per-account decisions win. Accounts added by hand are kept. |
Response
200. The engagement after the landing, or the earlier result of the same Idempotency-Key.
{
"id": "7c9e1d02-4b6a-4f13-9a80-1d5e2f6b8c04",
"client": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"entity": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"name": "Acme Holdings Inc.",
"legalName": "<string>"
},
"yearEnd": "2025-12-31",
"periodStart": "2025-12-31",
"status": "<in_progress | review | complete>",
"source": "<prepared | imported>",
"engagementType": "<compilation | review | audit | tax_only>",
"priorYearEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"isLocked": false,
"lifecycle": {
"filedAt": "2026-07-14T12:00:00Z",
"revisionNumber": 1,
"parentEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"amendedByEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"documentsPage": "<string>",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"recordLifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
],
"importStatus": "<applied | needs_answers | blocked>",
"applied": {
"accountCount": 0,
"classifiedCount": 0,
"unclassifiedCount": 0,
"gifiSeededCount": 0,
"totalDebits": "<string>",
"totalCredits": "<string>",
"balanced": false,
"warnings": [
"<string>"
]
},
"submitted": {
"totalDebits": "<string>",
"totalCredits": "<string>",
"balanced": false,
"warnings": [
"<string>"
]
},
"previewId": "<string>",
"questions": {
"mode": "<books | rows>",
"confirmationCounts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
},
"conflicts": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"existingName": "<string>",
"incomingName": "<string>",
"incomingBalance": "<string>",
"incomingCode": "<string>",
"crossKey": false
}
],
"restores": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"deletedAt": "<string>",
"incomingBalance": "<string>"
}
],
"missing": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"balance": "<string>",
"refs": {
"ajeLines": 0,
"workpapers": 0,
"groups": 0
}
}
],
"duplicates": [
{
"code": "<string>",
"combinedBalance": "<string>",
"rows": [
{
"name": "<string>",
"balance": "<string>"
}
],
"rowsCount": 0,
"rowsTruncated": false,
"plans": {
"combine": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"first": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"skip": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
]
}
}
],
"lowMatch": {
"matchedRows": 0,
"rows": 0
},
"blocked": {
"reason": "<string>",
"message": "<string>"
},
"coverage": {
"fiscalPeriod": {
"start": "<string>",
"end": "<string>"
},
"files": [
{
"glId": "<string>",
"filename": "<string>",
"accounts": [
"<string>"
],
"reportRange": {
"start": "<string>",
"end": "<string>"
},
"observedRange": {
"start": "<string>",
"end": "<string>"
}
}
],
"missingRanges": [
{
"start": "<string>",
"end": "<string>"
}
],
"requirements": [
{
"id": "<string>",
"message": "<string>"
}
]
},
"priorYearMismatch": false,
"missingCount": 0,
"missingTruncated": false,
"conflictsCount": 0,
"conflictsTruncated": false,
"restoresCount": 0,
"restoresTruncated": false,
"duplicatesCount": 0,
"duplicatesTruncated": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the engagement, the same identifier the tax-year resources use. |
client | object | yes | The client the engagement belongs to. |
client.id | string (uuid) | yes | UUID of the client or entity. |
client.name | string | yes | Display name of the client or entity. |
client.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
entity | object | yes | The legal entity the engagement belongs to. |
entity.id | string (uuid) | yes | UUID of the client or entity. |
entity.name | string | yes | Display name of the client or entity. |
entity.legalName | nullable string | no | Registered legal name of the entity, when one is recorded. Always null on the client party. |
yearEnd | string (date) | yes | Fiscal year-end date, YYYY-MM-DD. |
periodStart | nullable string (date) | no | First day of the fiscal period, YYYY-MM-DD. Null when no start has been recorded, for example on a reference year an import created without one. |
status | one of in_progress, review, complete | yes | Workflow status the practitioner has set: in_progress while the file is being prepared, review while it sits with a reviewer, complete once preparation is finished. |
source | one of prepared, imported | yes | Values: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference. |
engagementType | nullable one of compilation, review, audit, tax_only | no | Service the firm is performing: compilation, review, audit, or tax_only for a return with no attached financial-statement engagement. Null when none has been recorded. |
priorYearEngagementId | nullable string (uuid) | no | UUID of the prior-year engagement used for comparatives. Null when no prior year is linked, or when the linked record does not belong to the same client and entity with an earlier year end. |
isLocked | boolean | yes | True when the engagement has been locked and no longer accepts edits. |
lifecycle | object | yes | Filing time, revision number, and the links to the neighbouring revisions of this return. |
lifecycle.filedAt | nullable string (date-time) | no | ISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed. |
lifecycle.revisionNumber | integer, at least 1 | yes | Position of this revision in its amendment chain. 1 is the original return and each amendment adds one. |
lifecycle.parentEngagementId | nullable string (uuid) | no | UUID of the revision this one amends. Null on the original return. |
lifecycle.amendedByEngagementId | nullable string (uuid) | no | UUID of the revision that amends this one. Null when this is the newest revision in the chain. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. When the engagement was last modified. |
documentsPage | nullable string | no | Filemark page where a signed-in user opens this engagement's Documents inbox: the place to upload a trial balance, a saved tax-software return or source documents, which no tool or API operation carries. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
recordLifecycle | object | yes | Live or recoverable-archived (distinct from the return's filing lifecycle). |
recordLifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
recordLifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
recordLifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
warnings | array of objects | no | Non-blocking conditions the write reported: an unsupported filing year, an unverified election, a failed prior-year roll-forward or permanent-establishment seed, or a missing period start (period_start_missing: send periodStart with an engagement update). |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
importStatus | one of applied, needs_answers, blocked | no | |
applied | nullable object | no | What this call imported: the engagement's books after the import. Null when nothing was imported (importStatus needs_answers or blocked). |
applied.accountCount | integer, at least 0 | when applied is set | Live accounts on the engagement after the import. |
applied.classifiedCount | integer, at least 0 | when applied is set | Live accounts with a classification other than unclassified. |
applied.unclassifiedCount | integer, at least 0 | when applied is set | Live accounts still awaiting classification or a successful model answer. Call classify awaiting accounts for one bounded model round. Through MCP these include the accounts whose model classification was deferred to you: answer the hostModelRequests on this result and save each classification answer with update_engagement_data, action applyClassificationAnswers. |
applied.gifiSeededCount | integer, at least 0 | when applied is set | Live accounts with a GIFI assignment, including validated source codes and chart defaults. |
applied.totalDebits | nullable string | when applied is set | Canonical decimal debit total of the submitted source rows. |
applied.totalCredits | nullable string | when applied is set | Canonical decimal credit total of the submitted source rows. |
applied.balanced | boolean | when applied is set | True when both exact source amounts and the saved cent amounts balance within a dollar. rows mode can import an unbalanced subset. |
applied.warnings | array of strings, up to 50 items | when applied is set | Parser warnings, for the practitioner. |
submitted | nullable object | no | The submitted rows' totals and parser warnings when nothing was imported (importStatus needs_answers or blocked); null once an import applies, whose summary carries them. |
submitted.totalDebits | nullable string | when submitted is set | Canonical decimal debit total of the submitted rows. |
submitted.totalCredits | nullable string | when submitted is set | Canonical decimal credit total of the submitted rows. |
submitted.balanced | boolean | when submitted is set | True when the submitted debits and credits balance within a dollar. |
submitted.warnings | array of strings, up to 50 items | when submitted is set | Parser warnings, for the practitioner. |
previewId | nullable string | no | Principal- and engagement-scoped preview for the apply capability; expires after six hours. |
questions | nullable object | no | |
questions.mode | one of books, rows | when questions is set | |
questions.confirmationCounts | object | when questions is set | |
questions.confirmationCounts.balancesUpdated | integer, at least 0 | when questions is set | |
questions.confirmationCounts.renamed | integer, at least 0 | when questions is set | |
questions.confirmationCounts.added | integer, at least 0 | when questions is set | |
questions.confirmationCounts.restored | integer, at least 0 | when questions is set | |
questions.confirmationCounts.deleted | integer, at least 0 | when questions is set | |
questions.confirmationCounts.unchanged | integer, at least 0 | when questions is set | |
questions.confirmationCounts.skipped | integer, at least 0 | when questions is set | |
questions.conflicts | array of objects, up to 5000 items | when questions is set | |
questions.conflicts[].plans | nullable map of object | when questions is set | |
questions.conflicts[].plans.{key} | object | per key | |
questions.conflicts[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.renamed | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.added | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.restored | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.deleted | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.unchanged | integer, at least 0 | per key | |
questions.conflicts[].plans.{key}.skipped | integer, at least 0 | per key | |
questions.conflicts[].accountId | string | when questions is set | |
questions.conflicts[].code | string | when questions is set | |
questions.conflicts[].existingName | string | when questions is set | |
questions.conflicts[].incomingName | string | when questions is set | |
questions.conflicts[].incomingBalance | string | when questions is set | |
questions.conflicts[].incomingCode | nullable string | when questions is set | |
questions.conflicts[].crossKey | boolean | when questions is set | |
questions.restores | array of objects, up to 5000 items | when questions is set | |
questions.restores[].plans | nullable map of object | when questions is set | |
questions.restores[].plans.{key} | object | per key | |
questions.restores[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
questions.restores[].plans.{key}.renamed | integer, at least 0 | per key | |
questions.restores[].plans.{key}.added | integer, at least 0 | per key | |
questions.restores[].plans.{key}.restored | integer, at least 0 | per key | |
questions.restores[].plans.{key}.deleted | integer, at least 0 | per key | |
questions.restores[].plans.{key}.unchanged | integer, at least 0 | per key | |
questions.restores[].plans.{key}.skipped | integer, at least 0 | per key | |
questions.restores[].accountId | string | when questions is set | |
questions.restores[].code | string | when questions is set | |
questions.restores[].name | string | when questions is set | |
questions.restores[].deletedAt | nullable string | when questions is set | |
questions.restores[].incomingBalance | string | when questions is set | |
questions.missing | array of objects, up to 5000 items | when questions is set | |
questions.missing[].plans | map of object | when questions is set | |
questions.missing[].plans.{key} | object | per key | |
questions.missing[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
questions.missing[].plans.{key}.renamed | integer, at least 0 | per key | |
questions.missing[].plans.{key}.added | integer, at least 0 | per key | |
questions.missing[].plans.{key}.restored | integer, at least 0 | per key | |
questions.missing[].plans.{key}.deleted | integer, at least 0 | per key | |
questions.missing[].plans.{key}.unchanged | integer, at least 0 | per key | |
questions.missing[].plans.{key}.skipped | integer, at least 0 | per key | |
questions.missing[].accountId | string | when questions is set | |
questions.missing[].code | string | when questions is set | |
questions.missing[].name | string | when questions is set | |
questions.missing[].balance | string | when questions is set | |
questions.missing[].refs | object | when questions is set | |
questions.missing[].refs.ajeLines | integer | when questions is set | |
questions.missing[].refs.workpapers | integer | when questions is set | |
questions.missing[].refs.groups | integer | when questions is set | |
questions.duplicates | array of objects, up to 5000 items | when questions is set | |
questions.duplicates[].code | string | when questions is set | |
questions.duplicates[].combinedBalance | string | when questions is set | |
questions.duplicates[].rows | array of objects, up to 5000 items | when questions is set | |
questions.duplicates[].rows[].name | string | when questions is set | |
questions.duplicates[].rows[].balance | string | when questions is set | |
questions.duplicates[].rowsCount | integer, at least 0 | when questions is set | Member rows in this source group, listed or not. |
questions.duplicates[].rowsTruncated | boolean | when questions is set | Only the first 5,000 member rows are listed. Review the source for the rest; combine explicitly accepts the sum of every member, including unlisted rows. |
questions.duplicates[].plans | object | when questions is set | Apply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner. |
questions.duplicates[].plans.combine | array of objects, 1 to 2 items | when questions is set | |
questions.duplicates[].plans.combine[].conflicts | map of one of rename, new_account | when questions is set | |
questions.duplicates[].plans.combine[].conflicts.{key} | one of rename, new_account | per key | |
questions.duplicates[].plans.combine[].restores | map of one of restore, skip | when questions is set | |
questions.duplicates[].plans.combine[].restores.{key} | one of restore, skip | per key | |
questions.duplicates[].plans.combine[].counts | object | when questions is set | |
questions.duplicates[].plans.combine[].counts.balancesUpdated | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.renamed | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.added | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.restored | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.deleted | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.unchanged | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.combine[].counts.skipped | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first | array of objects, 1 to 2 items | when questions is set | |
questions.duplicates[].plans.first[].conflicts | map of one of rename, new_account | when questions is set | |
questions.duplicates[].plans.first[].conflicts.{key} | one of rename, new_account | per key | |
questions.duplicates[].plans.first[].restores | map of one of restore, skip | when questions is set | |
questions.duplicates[].plans.first[].restores.{key} | one of restore, skip | per key | |
questions.duplicates[].plans.first[].counts | object | when questions is set | |
questions.duplicates[].plans.first[].counts.balancesUpdated | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.renamed | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.added | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.restored | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.deleted | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.unchanged | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.first[].counts.skipped | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip | array of objects, 1 to 1 items | when questions is set | |
questions.duplicates[].plans.skip[].conflicts | map of one of rename, new_account | when questions is set | |
questions.duplicates[].plans.skip[].conflicts.{key} | one of rename, new_account | per key | |
questions.duplicates[].plans.skip[].restores | map of one of restore, skip | when questions is set | |
questions.duplicates[].plans.skip[].restores.{key} | one of restore, skip | per key | |
questions.duplicates[].plans.skip[].counts | object | when questions is set | |
questions.duplicates[].plans.skip[].counts.balancesUpdated | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.renamed | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.added | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.restored | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.deleted | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.unchanged | integer, at least 0 | when questions is set | |
questions.duplicates[].plans.skip[].counts.skipped | integer, at least 0 | when questions is set | |
questions.lowMatch | nullable object | when questions is set | |
questions.lowMatch.matchedRows | integer | when questions is set | |
questions.lowMatch.rows | integer | when questions is set | |
questions.blocked | nullable object | when questions is set | |
questions.blocked.reason | string | when questions is set | |
questions.blocked.message | string | when questions is set | |
questions.coverage | nullable object | when questions is set | |
questions.coverage.fiscalPeriod | object | when questions is set | |
questions.coverage.fiscalPeriod.start | nullable string | when questions is set | |
questions.coverage.fiscalPeriod.end | nullable string | when questions is set | |
questions.coverage.files | array of objects | when questions is set | |
questions.coverage.files[].glId | string | when questions is set | |
questions.coverage.files[].filename | string | when questions is set | |
questions.coverage.files[].accounts | array of strings | when questions is set | |
questions.coverage.files[].reportRange | nullable object | when questions is set | |
questions.coverage.files[].reportRange.start | nullable string | when questions is set | |
questions.coverage.files[].reportRange.end | nullable string | when questions is set | |
questions.coverage.files[].observedRange | nullable object | when questions is set | |
questions.coverage.files[].observedRange.start | nullable string | when questions is set | |
questions.coverage.files[].observedRange.end | nullable string | when questions is set | |
questions.coverage.missingRanges | array of objects | when questions is set | |
questions.coverage.missingRanges[].start | nullable string | when questions is set | |
questions.coverage.missingRanges[].end | nullable string | when questions is set | |
questions.coverage.requirements | array of objects | when questions is set | |
questions.coverage.requirements[].id | string | when questions is set | |
questions.coverage.requirements[].message | string | when questions is set | |
questions.priorYearMismatch | boolean | when questions is set | |
questions.missingCount | integer, at least 0 | when questions is set | |
questions.missingTruncated | boolean | when questions is set | Only the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account. |
questions.conflictsCount | integer, at least 0 | when questions is set | Name conflicts the import found, listed or not. |
questions.conflictsTruncated | boolean | when questions is set | Only the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest. |
questions.restoresCount | integer, at least 0 | when questions is set | Rows matching a deleted account, listed or not. |
questions.restoresTruncated | boolean | when questions is set | Only the first 5,000 rows matching a deleted account are listed; omitted answers skip the row. |
questions.duplicatesCount | integer, at least 0 | when questions is set | Duplicated account codes in the source, listed or not. |
questions.duplicatesTruncated | boolean | when questions is set | Only the first 5,000 duplicated codes are listed. Every duplicated code needs an answer: import this trial balance in Filemark to answer the rest. |
Errors
In addition to the shared error codes:
404: The engagement does not exist in the caller's scope.
Classify awaiting accounts
POST /api/v1/engagements/{engagement_id}/accounts/classify-awaiting
Run one bounded classification/GIFI suggestion round under the firm's AI policy and billing gate. Returns counts and any policy denial; repeat with a fresh key and version while accounts remain.
Scope: tax-data:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/accounts/classify-awaiting \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"limit": 1
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/accounts/classify-awaiting",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"limit": 1,
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/accounts/classify-awaiting`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"limit": 1
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The engagement's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
limit | nullable integer, 1 to 200 | no | Maximum accounts for this call, further bounded by the service's single parallel round. |
Response
202. Processing is running. Repeat this request's method, URL and payload with the returned pollHeaders to read this attempt. A deliberate mutation retry omits X-Filemark-Job and reuses its original Idempotency-Key; the domain ledger owns recovery and replay. The final response retains the operation's normal contract.
Also 200: The engagement after the landing, or the earlier result of the same Idempotency-Key.
{
"kind": "ai-job/v1",
"jobId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"statusUrl": "<string>",
"pollMethod": "POST",
"pollHeaders": {
"Idempotency-Key": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"X-Filemark-Job": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
kind | string, always "ai-job/v1" | yes | |
jobId | string (uuid) | yes | |
statusUrl | string | yes | |
pollMethod | string, always "POST" | yes | |
pollHeaders | object | yes | |
pollHeaders.Idempotency-Key | string (uuid) | yes | |
pollHeaders.X-Filemark-Job | string (uuid) | yes |
Errors
In addition to the shared error codes:
404: The engagement does not exist in the caller's scope.
List an engagement's portal contacts
GET /api/v1/engagements/{engagement_id}/portal/contacts
Lists the client contacts a portal invitation for this engagement can be addressed to, ordered by name. Use a contact's id when sending, or supply a new name and email to create one.
Scope: portal:read.
Request
curl --get https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/portal/contacts \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/portal/contacts",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/portal/contacts`);
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
limit | query | integer, 1 to 200, default 50 | no | Page size (1-200). |
cursor | query | nullable string, up to 512 characters | no | Opaque nextCursor from the previous page. Omit for the first page. |
Response
200. One page of client contacts.
{
"data": [
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"clientId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"fullName": "<string>",
"email": "<string>",
"role": "<string>",
"isPrimary": false,
"createdAt": "2026-07-14T12:00:00Z"
}
],
"pagination": {
"limit": 0,
"nextCursor": "<string>",
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The contacts on this page. |
data[].id | string (uuid) | yes | Stable UUID of the contact. |
data[].clientId | string (uuid) | yes | UUID of the client the contact belongs to. |
data[].fullName | string | yes | The contact's name, as the invitation addresses it. |
data[].email | string | yes | The address an invitation or reminder is mailed to. |
data[].role | nullable string | no | Free-text role at the client, when recorded. |
data[].isPrimary | boolean | yes | True for the client's primary contact. |
data[].createdAt | string (date-time) | yes | ISO-8601 in UTC. When the contact was recorded. |
pagination | object | yes | Keyset pagination state for this page. |
pagination.limit | integer | yes | Page size applied (1-200). |
pagination.nextCursor | nullable string | no | Opaque cursor for the next page. Null on the final page. |
pagination.hasMore | boolean | yes | True when another page is available. |
Errors
In addition to the shared error codes:
409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
List an engagement's portal requests
GET /api/v1/engagements/{engagement_id}/portal/requests
Lists every question the engagement has asked its client, oldest first, with the client's answer, the state of the review, and how much of the delivered evidence has been read.
Scope: portal:read.
Request
curl --get https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/portal/requests \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--data-urlencode "limit=50"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/portal/requests",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
params={
"limit": "50",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const url = new URL(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/portal/requests`);
url.searchParams.set("limit", "50");
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
limit | query | integer, 1 to 200, default 50 | no | Page size (1-200). |
cursor | query | nullable string, up to 512 characters | no | Opaque nextCursor from the previous page. Omit for the first page. |
Response
200. One page of portal requests.
{
"data": [
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"accountId": "<string>",
"accountCode": "<string>",
"accountName": "<string>",
"questionText": "<string>",
"questionCategory": "<string>",
"responseType": "<string>",
"sourceConcept": "<string>",
"status": "<draft | sent | responded | accepted | applied | rejected>",
"response": {
"text": "<string>",
"confirmed": false,
"dateStart": "<string>",
"dateEnd": "<string>",
"amount": "<string>",
"choice": "<string>",
"respondedAt": "2026-07-14T12:00:00Z"
},
"evidence": {
"uploadCount": 0,
"unreadCount": 0,
"commentCount": 0
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}
],
"pagination": {
"limit": 0,
"nextCursor": "<string>",
"hasMore": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | The requests on this page. |
data[].id | string (uuid) | yes | Stable UUID of the portal request. |
data[].engagementId | string (uuid) | yes | UUID of the engagement the request belongs to. |
data[].sessionId | nullable string (uuid) | no | UUID of the invitation that carried it. Null for a draft. |
data[].accountId | nullable string | no | Identifier of the trial-balance account the question is about. |
data[].accountCode | nullable string | no | Account code, when known. |
data[].accountName | nullable string | no | Account name, when known. |
data[].questionText | string | yes | The question the client was asked. |
data[].questionCategory | nullable string | no | What the question is about; 'account' for an account follow-up. |
data[].responseType | nullable string | no | How the client answers: confirm, text, date_range, amount, choice, or document. |
data[].sourceConcept | nullable string | no | Filemark concept that generated the question, when it came from one. |
data[].status | one of draft, sent, responded, accepted, applied, rejected | yes | 'draft' before it is sent, 'sent' while it awaits the client, 'responded' once answered, 'accepted' or 'rejected' after a practitioner reviewed it, 'applied' once its evidence reached a workpaper. Accepting and rejecting are reserved to a practitioner in Filemark and no public capability performs them. |
data[].response | object | yes | The client's answer, all-null while unanswered. |
data[].response.text | nullable string | no | Free-text answer. |
data[].response.confirmed | nullable boolean | no | Answer to a confirm question. |
data[].response.dateStart | nullable string | no | YYYY-MM-DD start of a date-range answer. |
data[].response.dateEnd | nullable string | no | YYYY-MM-DD end of a date-range answer. |
data[].response.amount | nullable string | no | Canonical decimal string of an amount answer. Never a float. |
data[].response.choice | nullable string | no | Selected option of a choice question. |
data[].response.respondedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the client answered. |
data[].evidence | object | yes | Attachment and comment counts. |
data[].evidence.uploadCount | integer, at least 0 | yes | Documents the client attached to this request. |
data[].evidence.unreadCount | integer, at least 0 | yes | Attached documents that were meant to be read into workpaper rows and have not been: the extraction failed, was refused, needs column mapping, or has not run. Applying over a non-zero count builds the workpaper total from part of the evidence and must be acknowledged. |
data[].evidence.commentCount | integer, at least 0 | yes | Comments on the request, both sides. |
data[].createdAt | string (date-time) | yes | ISO-8601 in UTC. |
data[].updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
data[].resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
pagination | object | yes | Keyset pagination state for this page. |
pagination.limit | integer | yes | Page size applied (1-200). |
pagination.nextCursor | nullable string | no | Opaque cursor for the next page. Null on the final page. |
pagination.hasMore | boolean | yes | True when another page is available. |
Errors
In addition to the shared error codes:
409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Get a portal request
GET /api/v1/portal-requests/{request_id}
Reads one portal request: the question, the client's answer, the evidence counts, and the resourceVersion an action must present.
Scope: portal:read.
Request
curl https://api.filemark.ca/api/v1/portal-requests/$REQUEST_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
REQUEST_ID = "<request-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/portal-requests/{REQUEST_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const REQUEST_ID = "<request-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/portal-requests/${REQUEST_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
request_id | path | string (uuid) | yes | UUID of the portal request. |
Response
200. The portal request.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"accountId": "<string>",
"accountCode": "<string>",
"accountName": "<string>",
"questionText": "<string>",
"questionCategory": "<string>",
"responseType": "<string>",
"sourceConcept": "<string>",
"status": "<draft | sent | responded | accepted | applied | rejected>",
"response": {
"text": "<string>",
"confirmed": false,
"dateStart": "<string>",
"dateEnd": "<string>",
"amount": "<string>",
"choice": "<string>",
"respondedAt": "2026-07-14T12:00:00Z"
},
"evidence": {
"uploadCount": 0,
"unreadCount": 0,
"commentCount": 0
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the portal request. |
engagementId | string (uuid) | yes | UUID of the engagement the request belongs to. |
sessionId | nullable string (uuid) | no | UUID of the invitation that carried it. Null for a draft. |
accountId | nullable string | no | Identifier of the trial-balance account the question is about. |
accountCode | nullable string | no | Account code, when known. |
accountName | nullable string | no | Account name, when known. |
questionText | string | yes | The question the client was asked. |
questionCategory | nullable string | no | What the question is about; 'account' for an account follow-up. |
responseType | nullable string | no | How the client answers: confirm, text, date_range, amount, choice, or document. |
sourceConcept | nullable string | no | Filemark concept that generated the question, when it came from one. |
status | one of draft, sent, responded, accepted, applied, rejected | yes | 'draft' before it is sent, 'sent' while it awaits the client, 'responded' once answered, 'accepted' or 'rejected' after a practitioner reviewed it, 'applied' once its evidence reached a workpaper. Accepting and rejecting are reserved to a practitioner in Filemark and no public capability performs them. |
response | object | yes | The client's answer, all-null while unanswered. |
response.text | nullable string | no | Free-text answer. |
response.confirmed | nullable boolean | no | Answer to a confirm question. |
response.dateStart | nullable string | no | YYYY-MM-DD start of a date-range answer. |
response.dateEnd | nullable string | no | YYYY-MM-DD end of a date-range answer. |
response.amount | nullable string | no | Canonical decimal string of an amount answer. Never a float. |
response.choice | nullable string | no | Selected option of a choice question. |
response.respondedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the client answered. |
evidence | object | yes | Attachment and comment counts. |
evidence.uploadCount | integer, at least 0 | yes | Documents the client attached to this request. |
evidence.unreadCount | integer, at least 0 | yes | Attached documents that were meant to be read into workpaper rows and have not been: the extraction failed, was refused, needs column mapping, or has not run. Applying over a non-zero count builds the workpaper total from part of the evidence and must be acknowledged. |
evidence.commentCount | integer, at least 0 | yes | Comments on the request, both sides. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
Errors
In addition to the shared error codes:
409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Preview a portal action
POST /api/v1/portal-requests/{request_id}/actions/preview
Reports what one portal action would do against this request, and what would refuse it. It performs nothing. For the actions that reach the client; comment, send, remind, cancel; it also mints the single-use confirmationToken that operation must present, bound to this API client, this target, and this exact action; change the action and preview again.
Scope: portal:write.
Request
curl --request POST https://api.filemark.ca/api/v1/portal-requests/$REQUEST_ID/actions/preview \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"kind": "<applyToWorkpaper | comment | send | remind | cancel>",
"applyToWorkpaper": {
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"acknowledgeUnreadUploads": false
},
"comment": {
"body": "<string>"
},
"send": {
"requestIds": [
"<string>"
],
"contactId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"contactName": "<string>",
"contactEmail": "<string>",
"expiresDays": 1,
"message": "<string>"
},
"remind": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"cancel": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}
}'import os
import requests
REQUEST_ID = "<request-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/portal-requests/{REQUEST_ID}/actions/preview",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"kind": "<applyToWorkpaper | comment | send | remind | cancel>",
"applyToWorkpaper": {
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"acknowledgeUnreadUploads": False,
},
"comment": {
"body": "<string>",
},
"send": {
"requestIds": [
"<string>",
],
"contactId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"contactName": "<string>",
"contactEmail": "<string>",
"expiresDays": 1,
"message": "<string>",
},
"remind": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
},
"cancel": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
},
},
)
response.raise_for_status()
print(response.json())const REQUEST_ID = "<request-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/portal-requests/${REQUEST_ID}/actions/preview`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"kind": "<applyToWorkpaper | comment | send | remind | cancel>",
"applyToWorkpaper": {
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"acknowledgeUnreadUploads": false
},
"comment": {
"body": "<string>"
},
"send": {
"requestIds": [
"<string>"
],
"contactId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"contactName": "<string>",
"contactEmail": "<string>",
"expiresDays": 1,
"message": "<string>"
},
"remind": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"cancel": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
request_id | path | string (uuid) | yes | UUID of the portal request. |
Request body
What to evaluate. Supply exactly the payload the chosen kind names.
| Field | Type | Required | Description |
|---|---|---|---|
kind | one of applyToWorkpaper, comment, send, remind, cancel | yes | 'applyToWorkpaper' writes read evidence into a workpaper; 'comment' posts a practitioner comment the client sees; 'send' mails a new invitation; 'remind' re-sends an outstanding invitation; 'cancel' revokes an invitation's link. The last three and 'comment' reach the client, so their preview mints the confirmationToken their operation spends. |
applyToWorkpaper | nullable object | no | |
applyToWorkpaper.workpaperId | string (uuid) | when applyToWorkpaper is set | UUID of the workpaper in the request's engagement. |
applyToWorkpaper.acknowledgeUnreadUploads | boolean | when applyToWorkpaper is set | Set true to apply while some delivered documents are still unread. The refusal names every unread document; acknowledging it builds the workpaper total from the part of the evidence that parsed, on your own judgement, and the acknowledgement is audited. |
comment | nullable object | no | |
comment.body | string, 1 to 4000 characters | when comment is set | The comment text. |
send | nullable object | no | |
send.requestIds | array of strings, 1 to 200 items | when send is set | Trial-balance account identifiers whose follow-up questions the invitation carries. An account with no follow-up question is skipped. |
send.contactId | nullable string (uuid) | when send is set | UUID of an existing contact of the engagement's client. Use this or the name and email pair. |
send.contactName | nullable string, up to 200 characters | when send is set | Name of a new contact to create and address. |
send.contactEmail | nullable string, up to 320 characters | when send is set | Email of a new contact to create and address. |
send.expiresDays | nullable integer, 1 to 365 | when send is set | Days the client's link stays valid. Defaults to the firm's portal setting, else 30. |
send.message | nullable string, up to 2000 characters | when send is set | Optional note included in the invitation email. |
remind | nullable object | no | |
remind.sessionId | string (uuid) | when remind is set | UUID of the portal session (invitation). |
cancel | nullable object | no | |
cancel.sessionId | string (uuid) | when cancel is set | UUID of the portal session (invitation). |
Response
200. What the action would do, plus the token the Class 3 operations spend.
{
"kind": "<applyToWorkpaper | comment | send | remind | cancel>",
"requestId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"requestResourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"summary": "<string>",
"effects": [
"<string>"
],
"blockers": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
],
"confirmationRequired": false,
"confirmationToken": "<string>",
"confirmationExpiresAt": "2026-07-14T12:00:00Z",
"confirmationTarget": {
"capabilityId": "<string>",
"resourceType": "<portal_request | engagement>",
"resourceId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"actionSha256": "<string>",
"evaluatedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
kind | one of applyToWorkpaper, comment, send, remind, cancel | yes | The evaluated action. |
requestId | string (uuid) | yes | UUID of the portal request the preview was taken on. |
engagementId | string (uuid) | yes | UUID of that request's engagement. |
requestResourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
summary | string | yes | One sentence naming what would happen. |
effects | array of strings | yes | Each concrete change the action would make, in the order it would make them. |
blockers | array of objects | no | Conditions that would refuse the action as requested. Empty when it would proceed; an unread-evidence blocker clears by acknowledging it. |
blockers[].code | string | when blockers is set | Stable warning identifier. |
blockers[].message | string | when blockers is set | Readable explanation of the warning. |
confirmationRequired | boolean | yes | True for the actions that reach the client and must present a token. |
confirmationToken | nullable string | no | Single-use secret bound to this principal, target and exact action. Present it once, before it expires; a changed action needs a new preview. |
confirmationExpiresAt | nullable string (date-time) | no | ISO-8601 in UTC. After this the token is dead. |
confirmationTarget | nullable object | no | Where to spend the token. Null when none was minted. |
confirmationTarget.capabilityId | string | when confirmationTarget is set | The public capability that spends the token. |
confirmationTarget.resourceType | one of portal_request, engagement | when confirmationTarget is set | What the token is bound to. |
confirmationTarget.resourceId | string (uuid) | when confirmationTarget is set | UUID of the bound resource; POST the effect against it. |
actionSha256 | string, matching ^[0-9a-f]{64}$ | yes | Canonical hash of the action the preview evaluated, 64 lowercase hex characters. |
evaluatedAt | string (date-time) | yes | ISO-8601 in UTC. Point-in-time evaluation; the operation re-checks under its own lock. |
Errors
In addition to the shared error codes:
409: The portal state does not allow the action being previewed.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Apply a portal action
POST /api/v1/portal-requests/{request_id}/actions
Performs the reversible portal action: writes the read evidence a client delivered on this request into one of the engagement's workpapers and advances the request to applied. The workpaper keeps its version history, so the change is reversible in Filemark. Send the request's current resourceVersion in If-Match and a UUID in Idempotency-Key. Refused while any delivered document that was meant to be read is unread, unless acknowledgeUnreadUploads says otherwise.
Scope: portal:write.
Request
curl --request POST https://api.filemark.ca/api/v1/portal-requests/$REQUEST_ID/actions \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"kind": "applyToWorkpaper",
"applyToWorkpaper": {
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"acknowledgeUnreadUploads": false
}
}'import os
import requests
REQUEST_ID = "<request-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/portal-requests/{REQUEST_ID}/actions",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"kind": "applyToWorkpaper",
"applyToWorkpaper": {
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"acknowledgeUnreadUploads": False,
},
},
)
response.raise_for_status()
print(response.json())const REQUEST_ID = "<request-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/portal-requests/${REQUEST_ID}/actions`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"kind": "applyToWorkpaper",
"applyToWorkpaper": {
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"acknowledgeUnreadUploads": false
}
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
request_id | path | string (uuid) | yes | UUID of the portal request. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The portal request's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
The reversible portal action. Class 3 kinds have their own operations.
| Field | Type | Required | Description |
|---|---|---|---|
kind | string, always "applyToWorkpaper" | yes | Only 'applyToWorkpaper' is reversible; comment, send, remind and cancel reach the client. |
applyToWorkpaper | object | yes | Which workpaper to write, and whether unread evidence is acknowledged. |
applyToWorkpaper.workpaperId | string (uuid) | yes | UUID of the workpaper in the request's engagement. |
applyToWorkpaper.acknowledgeUnreadUploads | boolean | no | Set true to apply while some delivered documents are still unread. The refusal names every unread document; acknowledging it builds the workpaper total from the part of the evidence that parsed, on your own judgement, and the acknowledgement is audited. |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"kind": "applyToWorkpaper",
"requestId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"rowsAdded": 0,
"request": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"accountId": "<string>",
"accountCode": "<string>",
"accountName": "<string>",
"questionText": "<string>",
"questionCategory": "<string>",
"responseType": "<string>",
"sourceConcept": "<string>",
"status": "<draft | sent | responded | accepted | applied | rejected>",
"response": {
"text": "<string>",
"confirmed": false,
"dateStart": "<string>",
"dateEnd": "<string>",
"amount": "<string>",
"choice": "<string>",
"respondedAt": "2026-07-14T12:00:00Z"
},
"evidence": {
"uploadCount": 0,
"unreadCount": 0,
"commentCount": 0
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"incompleteEvidence": "<string>",
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
kind | string, always "applyToWorkpaper" | yes | The action performed. |
requestId | string (uuid) | yes | UUID of the portal request. |
engagementId | string (uuid) | yes | UUID of the engagement. |
workpaperId | string (uuid) | yes | UUID of the workpaper the rows were written to. |
rowsAdded | integer, at least 0 | yes | Rows the apply added to the workpaper. |
request | object | yes | The portal request after the change. |
request.id | string (uuid) | yes | Stable UUID of the portal request. |
request.engagementId | string (uuid) | yes | UUID of the engagement the request belongs to. |
request.sessionId | nullable string (uuid) | no | UUID of the invitation that carried it. Null for a draft. |
request.accountId | nullable string | no | Identifier of the trial-balance account the question is about. |
request.accountCode | nullable string | no | Account code, when known. |
request.accountName | nullable string | no | Account name, when known. |
request.questionText | string | yes | The question the client was asked. |
request.questionCategory | nullable string | no | What the question is about; 'account' for an account follow-up. |
request.responseType | nullable string | no | How the client answers: confirm, text, date_range, amount, choice, or document. |
request.sourceConcept | nullable string | no | Filemark concept that generated the question, when it came from one. |
request.status | one of draft, sent, responded, accepted, applied, rejected | yes | 'draft' before it is sent, 'sent' while it awaits the client, 'responded' once answered, 'accepted' or 'rejected' after a practitioner reviewed it, 'applied' once its evidence reached a workpaper. Accepting and rejecting are reserved to a practitioner in Filemark and no public capability performs them. |
request.response | object | yes | The client's answer, all-null while unanswered. |
request.response.text | nullable string | no | Free-text answer. |
request.response.confirmed | nullable boolean | no | Answer to a confirm question. |
request.response.dateStart | nullable string | no | YYYY-MM-DD start of a date-range answer. |
request.response.dateEnd | nullable string | no | YYYY-MM-DD end of a date-range answer. |
request.response.amount | nullable string | no | Canonical decimal string of an amount answer. Never a float. |
request.response.choice | nullable string | no | Selected option of a choice question. |
request.response.respondedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the client answered. |
request.evidence | object | yes | Attachment and comment counts. |
request.evidence.uploadCount | integer, at least 0 | yes | Documents the client attached to this request. |
request.evidence.unreadCount | integer, at least 0 | yes | Attached documents that were meant to be read into workpaper rows and have not been: the extraction failed, was refused, needs column mapping, or has not run. Applying over a non-zero count builds the workpaper total from part of the evidence and must be acknowledged. |
request.evidence.commentCount | integer, at least 0 | yes | Comments on the request, both sides. |
request.createdAt | string (date-time) | yes | ISO-8601 in UTC. |
request.updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
request.resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
incompleteEvidence | nullable string | no | Null on a complete evidence set. Otherwise the verbatim disclosure of what was not read, so a partial total is never an unqualified success. |
warnings | array of objects | no | Non-blocking conditions: a failed lifecycle advance, extractor warnings on the evidence. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
See the shared error codes.
Comment on a portal request
POST /api/v1/portal-requests/{request_id}/comments
Posts a practitioner comment on a portal request. The client sees it in the portal immediately and it cannot be unsaid, so it is an external effect: preview the same comment first and present the confirmationToken that preview minted. The comment is attributed to your API client by the display name Filemark resolves for it, and that name is what the client reads.
Scopes: portal:write + portal:send.
Request
curl --request POST https://api.filemark.ca/api/v1/portal-requests/$REQUEST_ID/comments \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"body": "<string>",
"confirmationToken": "<string>"
}'import os
import requests
REQUEST_ID = "<request-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/portal-requests/{REQUEST_ID}/comments",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"body": "<string>",
"confirmationToken": "<string>",
},
)
response.raise_for_status()
print(response.json())const REQUEST_ID = "<request-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/portal-requests/${REQUEST_ID}/comments`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"body": "<string>",
"confirmationToken": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
request_id | path | string (uuid) | yes | UUID of the portal request. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
Post a practitioner comment. The client sees it in the portal at once.
| Field | Type | Required | Description |
|---|---|---|---|
body | string, 1 to 4000 characters | yes | The comment text. |
confirmationToken | string, 1 to 128 characters | yes | The confirmationToken a fresh preview of the same comment minted. |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"requestId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"authorType": "<practitioner | client>",
"authorName": "<string>",
"body": "<string>",
"createdAt": "2026-07-14T12:00:00Z",
"requestResourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | Stable UUID of the comment. |
requestId | string (uuid) | yes | UUID of the portal request it belongs to. |
authorType | one of practitioner, client | yes | Which side of the conversation wrote it. |
authorName | string | yes | Display name the portal shows next to the comment. |
body | string | yes | The comment text. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
requestResourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
Errors
See the shared error codes.
Send, remind, or cancel a portal invitation
POST /api/v1/engagements/{engagement_id}/portal/deliveries
Performs one client-facing invitation delivery. 'send' mails a new invitation and revokes the engagement's live one; 'remind' re-issues an outstanding invitation's link and mails the reminder, within the reminder allowance the firm's portal settings set; 'cancel' revokes an invitation so its link stops working, and never revokes consent or a signoff. Each mails or cuts a client's access, so present the confirmationToken a fresh preview of the same delivery minted. The response never carries the invitation link or its token: the client receives it by mail and nowhere else.
Scope: portal:send.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/portal/deliveries \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"kind": "<send | remind | cancel>",
"send": {
"requestIds": [
"<string>"
],
"contactId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"contactName": "<string>",
"contactEmail": "<string>",
"expiresDays": 1,
"message": "<string>"
},
"remind": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"cancel": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"confirmationToken": "<string>"
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/portal/deliveries",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"kind": "<send | remind | cancel>",
"send": {
"requestIds": [
"<string>",
],
"contactId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"contactName": "<string>",
"contactEmail": "<string>",
"expiresDays": 1,
"message": "<string>",
},
"remind": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
},
"cancel": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
},
"confirmationToken": "<string>",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/portal/deliveries`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"kind": "<send | remind | cancel>",
"send": {
"requestIds": [
"<string>"
],
"contactId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"contactName": "<string>",
"contactEmail": "<string>",
"expiresDays": 1,
"message": "<string>"
},
"remind": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"cancel": {
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
},
"confirmationToken": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
Send, re-send or revoke a client invitation. Every kind mails or cuts a link.
| Field | Type | Required | Description |
|---|---|---|---|
kind | one of send, remind, cancel | yes | 'send' mails a new invitation and supersedes the engagement's live one; 'remind' re-issues an outstanding invitation's link and mails the reminder; 'cancel' revokes an invitation so its link stops working. Cancellation never revokes consent or a signoff. |
send | nullable object | no | |
send.requestIds | array of strings, 1 to 200 items | when send is set | Trial-balance account identifiers whose follow-up questions the invitation carries. An account with no follow-up question is skipped. |
send.contactId | nullable string (uuid) | when send is set | UUID of an existing contact of the engagement's client. Use this or the name and email pair. |
send.contactName | nullable string, up to 200 characters | when send is set | Name of a new contact to create and address. |
send.contactEmail | nullable string, up to 320 characters | when send is set | Email of a new contact to create and address. |
send.expiresDays | nullable integer, 1 to 365 | when send is set | Days the client's link stays valid. Defaults to the firm's portal setting, else 30. |
send.message | nullable string, up to 2000 characters | when send is set | Optional note included in the invitation email. |
remind | nullable object | no | |
remind.sessionId | string (uuid) | when remind is set | UUID of the portal session (invitation). |
cancel | nullable object | no | |
cancel.sessionId | string (uuid) | when cancel is set | UUID of the portal session (invitation). |
confirmationToken | string, 1 to 128 characters | yes | The confirmationToken a fresh preview of the same delivery minted. |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"kind": "<send | remind | cancel>",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"sessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"sessionStatus": "<active | completed | revoked>",
"requestCount": 0,
"mailDispatched": false,
"deliveryState": "smtp_accepted",
"supersededSessionId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"deliveryResourceVersion": "<string>",
"evaluatedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
kind | one of send, remind, cancel | yes | The delivery performed. |
engagementId | string (uuid) | yes | UUID of the engagement. |
sessionId | string (uuid) | yes | UUID of the invitation created, reminded, or revoked. |
sessionStatus | one of active, completed, revoked | yes | The invitation's state after the delivery. |
requestCount | integer, at least 0 | yes | Questions the invitation carries, or still outstanding on a reminder. |
mailDispatched | boolean | yes | True only when the mail transport accepted the message. False for a cancellation, and for a send or reminder the transport did not accept. |
deliveryState | string | yes | Transport state of the message: 'smtp_accepted', 'not_queued', a failure state, or 'not_applicable'. |
supersededSessionId | nullable string (uuid) | no | The live invitation this send revoked, when there was one. |
deliveryResourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the engagement's portal-invitation state after the delivery. A preview binds the version it saw and the delivery is refused when the invitations moved since. |
evaluatedAt | string (date-time) | yes | ISO-8601 in UTC. When the delivery ran. |
Errors
See the shared error codes.
List integration connections
GET /api/v1/integrations
Lists every connector your organization can see and whether it is connected. Enrolling a connector, accepting its disclosure, entering a key and revoking a connection are practitioner actions in Filemark; this API only uses a connection a human enrolled, and never returns a credential, a token or a provider account id.
Scope: integrations:read.
Request
curl https://api.filemark.ca/api/v1/integrations \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
"https://api.filemark.ca/api/v1/integrations",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch("https://api.filemark.ca/api/v1/integrations", {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Response
200. Every visible connector with its enrollment state.
{
"data": [
{
"connectorId": "qbo",
"displayName": "<string>",
"capabilities": [
"read_chart_of_accounts",
"read_gl_detail",
"read_trial_balance"
],
"enrollmentStatus": "<active | disabled | not_enrolled | coming_soon>",
"lastSyncedAt": "2026-07-14T12:00:00Z"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
data | array of objects | yes | Every connector visible to your organization, ordered by name. |
data[].connectorId | string | yes | Stable connector identifier. |
data[].displayName | string | yes | Connector name as Filemark shows it. |
data[].capabilities | array of strings | yes | What the connector can read, sorted. Only read_trial_balance, read_gl_detail and read_chart_of_accounts are syncable through this API. |
data[].enrollmentStatus | one of active, disabled, not_enrolled, coming_soon | yes | 'active' when a practitioner enrolled the connector and it is usable, 'disabled' when the enrollment is switched off, 'not_enrolled' when nobody has connected it, 'coming_soon' when the connector cannot be connected yet. Enrolling, consenting, entering a key and revoking are practitioner actions in Filemark; this API only uses a connection a human enrolled. |
data[].lastSyncedAt | nullable string (date-time) | no | ISO-8601 in UTC of the last applied sync, or null. |
Errors
In addition to the shared error codes:
404: The resource does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
List integration sources
GET /api/v1/integrations/{connector_id}/sources
Lists the companies the enrolled connection can read from. Pass one of these sourceId values when previewing a sync.
Scope: integrations:read.
Request
curl https://api.filemark.ca/api/v1/integrations/$CONNECTOR_ID/sources \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
CONNECTOR_ID = "<connector-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/integrations/{CONNECTOR_ID}/sources",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const CONNECTOR_ID = "<connector-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/integrations/${CONNECTOR_ID}/sources`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
connector_id | path | string, 1 to 64 characters, matching ^[a-z][a-z0-9_-]*$ | yes | Identifier of the connector. |
Response
202. Processing is running. Repeat this request's method, URL and payload with the returned pollHeaders to read this attempt. A deliberate mutation retry omits X-Filemark-Job and reuses its original Idempotency-Key; the domain ledger owns recovery and replay. The final response retains the operation's normal contract.
Also 200: The companies available on this connection.
{
"kind": "ai-job/v1",
"jobId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"statusUrl": "<string>",
"pollMethod": "GET",
"pollHeaders": {
"Idempotency-Key": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"X-Filemark-Job": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
kind | string, always "ai-job/v1" | yes | |
jobId | string (uuid) | yes | |
statusUrl | string | yes | |
pollMethod | string, always "GET" | yes | |
pollHeaders | object | yes | |
pollHeaders.Idempotency-Key | string (uuid) | yes | |
pollHeaders.X-Filemark-Job | string (uuid) | yes |
Errors
In addition to the shared error codes:
404: The resource does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Preview an integration sync
POST /api/v1/engagements/{engagement_id}/integration-syncs/preview
Reads the connection and reports what a sync WOULD change in this engagement, as counts per capability. It writes nothing into the engagement. The confirmationToken it returns is single use and authorizes applying exactly this preview: narrow the capabilities here, because the apply lands what the preview reported.
Scope: integrations:read.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/integration-syncs/preview \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"connectorId": "qbo",
"sourceId": "<string>",
"sourceName": "<string>",
"capabilities": [
"<read_trial_balance | read_gl_detail | read_chart_of_accounts>"
]
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/integration-syncs/preview",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"connectorId": "qbo",
"sourceId": "<string>",
"sourceName": "<string>",
"capabilities": [
"<read_trial_balance | read_gl_detail | read_chart_of_accounts>",
],
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/integration-syncs/preview`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"connectorId": "qbo",
"sourceId": "<string>",
"sourceName": "<string>",
"capabilities": [
"<read_trial_balance | read_gl_detail | read_chart_of_accounts>"
]
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the sync would land in. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
connectorId | string, 1 to 64 characters, matching ^[a-z][a-z0-9_-]*$ | yes | Identifier of the connector, as listed by the connections read. |
sourceId | string, 1 to 256 characters | yes | The company to read, from the sources list. |
sourceName | nullable string, up to 256 characters | no | Label for the landed data, when you want one other than the source id. Defaults to the source id. |
capabilities | nullable array of strings, up to 3 items | no | Narrow the sync to these capabilities. Defaults to every syncable capability the connector offers. The apply lands exactly what the preview reported, so narrow here rather than at apply. |
Response
202. Processing is running. Repeat this request's method, URL and payload with the returned pollHeaders to read this attempt. A deliberate mutation retry omits X-Filemark-Job and reuses its original Idempotency-Key; the domain ledger owns recovery and replay. The final response retains the operation's normal contract.
Also 200: The changeset summary and the token that applies it.
{
"kind": "ai-job/v1",
"jobId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"statusUrl": "<string>",
"pollMethod": "POST",
"pollHeaders": {
"Idempotency-Key": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"X-Filemark-Job": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
kind | string, always "ai-job/v1" | yes | |
jobId | string (uuid) | yes | |
statusUrl | string | yes | |
pollMethod | string, always "POST" | yes | |
pollHeaders | object | yes | |
pollHeaders.Idempotency-Key | string (uuid) | yes | |
pollHeaders.X-Filemark-Job | string (uuid) | yes |
Errors
In addition to the shared error codes:
404: The resource does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Apply an integration sync
POST /api/v1/engagements/{engagement_id}/integration-syncs
Lands the previewed sync into the engagement. It spends the confirmationToken from that preview, applies the payload the preview fetched (never a fresh read), and is refused when the engagement has changed before landing started. Refused while the return is filed or partner-signed. If landing started but completion needs a retry, the sync reads as applied_pending. Retry the same request and Idempotency-Key to resume unfinished capabilities and import postconditions. Completed capabilities keep their receipts. A completed request replays its record (200 instead of 201).
Scope: integrations:sync.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/integration-syncs \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"previewId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"confirmationToken": "<string>",
"decisions": {
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"missing": {
"<key>": "<keep | delete>"
},
"duplicates": {
"<key>": "<combine | first | skip>"
},
"coverage": {
"<key>": false
},
"accountResolutions": {
"<key>": {
"accountName": "<string>",
"accountCode": "<string>",
"gifiCode": "<string>",
"manual": false
}
}
},
"replaceMissing": false
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/integration-syncs",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"previewId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"confirmationToken": "<string>",
"decisions": {
"conflicts": {
"<key>": "<rename | new_account>",
},
"restores": {
"<key>": "<restore | skip>",
},
"missing": {
"<key>": "<keep | delete>",
},
"duplicates": {
"<key>": "<combine | first | skip>",
},
"coverage": {
"<key>": False,
},
"accountResolutions": {
"<key>": {
"accountName": "<string>",
"accountCode": "<string>",
"gifiCode": "<string>",
"manual": False,
},
},
},
"replaceMissing": False,
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/integration-syncs`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"previewId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"confirmationToken": "<string>",
"decisions": {
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"missing": {
"<key>": "<keep | delete>"
},
"duplicates": {
"<key>": "<combine | first | skip>"
},
"coverage": {
"<key>": false
},
"accountResolutions": {
"<key>": {
"accountName": "<string>",
"accountCode": "<string>",
"gifiCode": "<string>",
"manual": false
}
}
},
"replaceMissing": false
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the sync lands in. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
previewId | string (uuid) | yes | The previewId the preview returned. |
confirmationToken | string, 4 to 128 characters | yes | The confirmationToken from that same preview. Single use. |
decisions | object | no | Answers to the trial-balance questions the preview returned. |
decisions.conflicts | map of one of rename, new_account | when decisions is set | |
decisions.conflicts.{key} | one of rename, new_account | per key | |
decisions.restores | map of one of restore, skip | when decisions is set | |
decisions.restores.{key} | one of restore, skip | per key | |
decisions.missing | map of one of keep, delete | when decisions is set | |
decisions.missing.{key} | one of keep, delete | per key | |
decisions.duplicates | map of one of combine, first, skip | when decisions is set | |
decisions.duplicates.{key} | one of combine, first, skip | per key | |
decisions.coverage | map of boolean | when decisions is set | |
decisions.coverage.{key} | boolean | per key | |
decisions.accountResolutions | map of object | when decisions is set | |
decisions.accountResolutions.{key} | object | per key | |
decisions.accountResolutions.{key}.accountName | string, 1 to 300 characters | per key | |
decisions.accountResolutions.{key}.accountCode | string, up to 64 characters | per key | |
decisions.accountResolutions.{key}.gifiCode | string, matching ^\d{4}$ | per key | |
decisions.accountResolutions.{key}.manual | boolean | per key | |
replaceMissing | boolean | no | Delete all missing ordinary accounts; explicit per-account decisions win. Accounts added by hand are kept. |
Response
201. The sync was applied.
Also 200: The sync record, or the earlier result of the same Idempotency-Key.
Also 202: Processing is running. Repeat this request's method, URL and payload with the returned pollHeaders to read this attempt. A deliberate mutation retry omits X-Filemark-Job and reuses its original Idempotency-Key; the domain ledger owns recovery and replay. The final response retains the operation's normal contract.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"connectorId": "<string>",
"sourceId": "<string>",
"sourceName": "<string>",
"status": "<previewed | applied | applied_pending | failed>",
"capabilities": [
"<string>"
],
"applied": [
{
"capability": "<string>",
"added": 0,
"removed": 0,
"modified": 0
}
],
"lastSyncedAtStamped": false,
"error": {
"code": "source_unavailable",
"message": "<string>"
},
"createdAt": "2026-07-14T12:00:00Z",
"appliedAt": "2026-07-14T12:00:00Z",
"expiresAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the sync record; the same value as its previewId. |
engagementId | string (uuid) | yes | UUID of the engagement the sync landed in. |
connectorId | string | yes | The connector that was read. |
sourceId | string | yes | The company that was read. |
sourceName | string | yes | Label the landed data carries in Filemark. |
status | one of previewed, applied, applied_pending, failed | yes | 'previewed' while the preview has not been applied, 'applied' when the sync finished, 'applied_pending' when landing started and remaining steps need a retry of the same request, 'failed' when the sync was refused or stopped before landing started. |
capabilities | array of strings | yes | The previewed capabilities, in canonical order. |
applied | array of objects | no | What each capability landed. An applied_pending sync includes completed capabilities and any committed trial-balance merge. |
applied[].capability | string | when applied is set | The connector capability. |
applied[].added | integer, at least 0 | when applied is set | Records added. |
applied[].removed | integer, at least 0 | when applied is set | Records removed. |
applied[].modified | integer, at least 0 | when applied is set | Records changed. |
lastSyncedAtStamped | nullable boolean | no | Whether the connection's last-sync timestamp was updated. False means the data landed but the stamp did not. Null unless applied. |
error | nullable object | no | Why the sync failed or its remaining steps need a retry. Null otherwise. |
error.code | string | when error is set | Stable failure identifier. |
error.message | string | when error is set | Readable explanation, never provider text. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the preview was taken. |
appliedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the sync settled. Null while previewed or applied_pending. |
expiresAt | string (date-time) | yes | ISO-8601 in UTC. When an unapplied preview stops being applicable. |
Errors
In addition to the shared error codes:
404: The resource does not exist in the caller's organization or client scope.
Get an integration sync
GET /api/v1/integration-syncs/{sync_id}
Reads one sync record. 'applied_pending' means landing started and remaining steps need a retry of the same apply request. 'applied' means completion, 'failed' means the sync was refused or stopped before landing started, and 'previewed' means the preview has not been applied and may still expire.
Scope: integrations:read.
Request
curl https://api.filemark.ca/api/v1/integration-syncs/$SYNC_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
SYNC_ID = "<sync-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/integration-syncs/{SYNC_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const SYNC_ID = "<sync-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/integration-syncs/${SYNC_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
sync_id | path | string (uuid) | yes | UUID of the sync record (the previewId it was created with). |
Response
200. The sync record.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"connectorId": "<string>",
"sourceId": "<string>",
"sourceName": "<string>",
"status": "<previewed | applied | applied_pending | failed>",
"capabilities": [
"<string>"
],
"applied": [
{
"capability": "<string>",
"added": 0,
"removed": 0,
"modified": 0
}
],
"lastSyncedAtStamped": false,
"error": {
"code": "source_unavailable",
"message": "<string>"
},
"createdAt": "2026-07-14T12:00:00Z",
"appliedAt": "2026-07-14T12:00:00Z",
"expiresAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the sync record; the same value as its previewId. |
engagementId | string (uuid) | yes | UUID of the engagement the sync landed in. |
connectorId | string | yes | The connector that was read. |
sourceId | string | yes | The company that was read. |
sourceName | string | yes | Label the landed data carries in Filemark. |
status | one of previewed, applied, applied_pending, failed | yes | 'previewed' while the preview has not been applied, 'applied' when the sync finished, 'applied_pending' when landing started and remaining steps need a retry of the same request, 'failed' when the sync was refused or stopped before landing started. |
capabilities | array of strings | yes | The previewed capabilities, in canonical order. |
applied | array of objects | no | What each capability landed. An applied_pending sync includes completed capabilities and any committed trial-balance merge. |
applied[].capability | string | when applied is set | The connector capability. |
applied[].added | integer, at least 0 | when applied is set | Records added. |
applied[].removed | integer, at least 0 | when applied is set | Records removed. |
applied[].modified | integer, at least 0 | when applied is set | Records changed. |
lastSyncedAtStamped | nullable boolean | no | Whether the connection's last-sync timestamp was updated. False means the data landed but the stamp did not. Null unless applied. |
error | nullable object | no | Why the sync failed or its remaining steps need a retry. Null otherwise. |
error.code | string | when error is set | Stable failure identifier. |
error.message | string | when error is set | Readable explanation, never provider text. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the preview was taken. |
appliedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the sync settled. Null while previewed or applied_pending. |
expiresAt | string (date-time) | yes | ISO-8601 in UTC. When an unapplied preview stops being applicable. |
Errors
In addition to the shared error codes:
404: The resource does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Deliver an export artifact
POST /api/v1/export-jobs/{export_job_id}/deliveries
Issues a short-lived download URL for a succeeded export job's handoff package, together with the package's SHA-256 and byte length so you can verify what you fetched. The job must have succeeded and must not have passed its retention. Every delivery is audited against the engagement, attributed to your API client. The URL is not renewable: retry with the same Idempotency-Key to get the same URL while it lives, and use a new key once it has expired.
Scope: exports:deliver.
Request
curl --request POST https://api.filemark.ca/api/v1/export-jobs/$EXPORT_JOB_ID/deliveries \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{}'import os
import requests
EXPORT_JOB_ID = "<export-job-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/export-jobs/{EXPORT_JOB_ID}/deliveries",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={},
)
response.raise_for_status()
print(response.json())const EXPORT_JOB_ID = "<export-job-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/export-jobs/${EXPORT_JOB_ID}/deliveries`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
export_job_id | path | string (uuid) | yes | UUID of the export job to deliver. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this delivery. Retrying with the same key inside the download URL's lifetime answers the same URL and writes no second audit row; once it has expired, a new download needs a new key. |
Request body
No body fields: the job names the artifact and the scope names you.
Response
201. A download was issued.
Also 200: The earlier delivery of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"exportJobId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"downloadUrl": "<string>",
"expiresAt": "2026-07-14T12:00:00Z",
"fileName": "<string>",
"mediaType": "application/zip",
"byteLength": 0,
"sha256": "<string>",
"deliveredAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of this delivery. |
exportJobId | string (uuid) | yes | UUID of the export job that was delivered. |
engagementId | string (uuid) | yes | UUID of the engagement the package belongs to. |
downloadUrl | string | yes | Short-lived HTTPS URL for the package. It expires at expiresAt and is not renewable: request a delivery under a NEW Idempotency-Key to get another one. |
expiresAt | string (date-time) | yes | ISO-8601 in UTC. When downloadUrl stops working. |
fileName | string | yes | Suggested file name for the package. |
mediaType | string | yes | Media type of the package. |
byteLength | integer, at least 0 | yes | Exact size of the package in bytes. |
sha256 | string, matching ^[0-9a-f]{64}$ | yes | SHA-256 of the package bytes. Verify what you download against it. |
deliveredAt | string (date-time) | yes | ISO-8601 in UTC. When the delivery was issued. |
Errors
In addition to the shared error codes:
404: The export job does not exist in the caller's organization or client scope.
Get a document
GET /api/v1/engagements/{engagement_id}/documents/{document_id}
Returns one document's metadata: the same projection the document list publishes, plus the resourceVersion a change must present, whether the document is live or archived, and which document operations it currently admits. File contents are never returned; use Create a document delivery for a download URL.
Scope: documents:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/documents/$DOCUMENT_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
DOCUMENT_ID = "<document-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/documents/{DOCUMENT_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const DOCUMENT_ID = "<document-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/documents/${DOCUMENT_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404. |
document_id | path | string, 1 to 256 characters, matching ^(gl|fs|wp|sd|basis|pu|ev|upload):[A-Za-z0-9_.:%@-]{1,200}$ | yes | Identifier of the document within this engagement, as the document list returns it (for example 'sd:9f2c…' or 'basis:currentTb'). A staged upload is addressed as 'upload:{uploadId}'. Not a UUID, and not unique across engagements. |
Response
200. The document with its resourceVersion.
{
"id": "<string>",
"fileName": "General Ledger 2025.xlsx",
"kind": "<gl | transactions | fs | balance-sheet | income-statement | retained-earnings-statement | cash-flow-statement | workpaper | supporting | tb-current | tb-prior | tb-classification | gifi-export | prior-return | portal-upload | evidence | inbox>",
"source": "<onboarding | manual | portal | connector | api | unknown>",
"uploadedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"capabilities": {
"replace": false,
"archive": false,
"deliver": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string, 1 to 256 characters | yes | Identifier of the document within this engagement, as the document list returns it (for example 'sd:9f2c…' or 'basis:currentTb'). A staged upload is addressed as 'upload:{uploadId}'. Not a UUID, and not unique across engagements. |
fileName | string, 1 to 512 characters | yes | Name to show for the document. When the stored record carries no file name, a generic label for its kind is returned. |
kind | one of gl, transactions, fs, balance-sheet, income-statement, retained-earnings-statement, cash-flow-statement, workpaper, supporting, tb-current, tb-prior, tb-classification, gifi-export, prior-return, portal-upload, evidence, inbox | yes | What the document is to the engagement. The values the document list publishes, plus 'inbox' for a staged upload that has been verified but not yet ingested. |
source | one of onboarding, manual, portal, connector, api, unknown | yes | How the document entered the engagement. onboarding is the onboarding pipeline, manual is a practitioner upload in the workspace, portal is a client upload through the client portal, connector is an accounting-system connection, and unknown means the record does not say. |
uploadedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the document was added. Null when no time was recorded for it, or when the stored value could not be read as a timestamp. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live document, 'archived' once it was removed. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
capabilities | object | yes | Which document operations this document currently admits. |
capabilities.replace | boolean | yes | True when documents.replace can swap this document's object for a completed staged upload. Only supporting documents admit it. |
capabilities.archive | boolean | yes | True when documents.archive can remove this document. Supporting documents and staged uploads admit it; basis files an engagement was built from never do. |
capabilities.deliver | boolean | yes | True when documents.delivery.create can mint a download URL: the document has a stored object the caller may read. |
Errors
In addition to the shared error codes:
409: The stored record cannot be represented.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Replace a document's file
POST /api/v1/engagements/{engagement_id}/documents/{document_id}/replace
Swaps a supporting document's stored file for a completed staged upload, keeping the document's identity and its links. The previous file is kept under the archive prefix until retention. Only supporting documents can be replaced.
Scope: documents:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/documents/$DOCUMENT_ID/replace \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
DOCUMENT_ID = "<document-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/documents/{DOCUMENT_ID}/replace",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const DOCUMENT_ID = "<document-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/documents/${DOCUMENT_ID}/replace`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404. |
document_id | path | string, 1 to 256 characters, matching ^(gl|fs|wp|sd|basis|pu|ev|upload):[A-Za-z0-9_.:%@-]{1,200}$ | yes | Identifier of the document within this engagement, as the document list returns it (for example 'sd:9f2c…' or 'basis:currentTb'). A staged upload is addressed as 'upload:{uploadId}'. Not a UUID, and not unique across engagements. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
uploadId | string (uuid) | yes | UUID of a completed staged upload whose object replaces this document's. The document's identity, links and metadata are kept. |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "<string>",
"fileName": "General Ledger 2025.xlsx",
"kind": "<gl | transactions | fs | balance-sheet | income-statement | retained-earnings-statement | cash-flow-statement | workpaper | supporting | tb-current | tb-prior | tb-classification | gifi-export | prior-return | portal-upload | evidence | inbox>",
"source": "<onboarding | manual | portal | connector | api | unknown>",
"uploadedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"capabilities": {
"replace": false,
"archive": false,
"deliver": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string, 1 to 256 characters | yes | Identifier of the document within this engagement, as the document list returns it (for example 'sd:9f2c…' or 'basis:currentTb'). A staged upload is addressed as 'upload:{uploadId}'. Not a UUID, and not unique across engagements. |
fileName | string, 1 to 512 characters | yes | Name to show for the document. When the stored record carries no file name, a generic label for its kind is returned. |
kind | one of gl, transactions, fs, balance-sheet, income-statement, retained-earnings-statement, cash-flow-statement, workpaper, supporting, tb-current, tb-prior, tb-classification, gifi-export, prior-return, portal-upload, evidence, inbox | yes | What the document is to the engagement. The values the document list publishes, plus 'inbox' for a staged upload that has been verified but not yet ingested. |
source | one of onboarding, manual, portal, connector, api, unknown | yes | How the document entered the engagement. onboarding is the onboarding pipeline, manual is a practitioner upload in the workspace, portal is a client upload through the client portal, connector is an accounting-system connection, and unknown means the record does not say. |
uploadedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the document was added. Null when no time was recorded for it, or when the stored value could not be read as a timestamp. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live document, 'archived' once it was removed. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
capabilities | object | yes | Which document operations this document currently admits. |
capabilities.replace | boolean | yes | True when documents.replace can swap this document's object for a completed staged upload. Only supporting documents admit it. |
capabilities.archive | boolean | yes | True when documents.archive can remove this document. Supporting documents and staged uploads admit it; basis files an engagement was built from never do. |
capabilities.deliver | boolean | yes | True when documents.delivery.create can mint a download URL: the document has a stored object the caller may read. |
Errors
See the shared error codes.
Archive a document
POST /api/v1/engagements/{engagement_id}/documents/{document_id}/archive
Removes a supporting document from the engagement, or abandons a staged upload and deletes its inbox object, through the product's own removal path. A document with no removal path in Filemark; a financial-statement source, a trial-balance basis file, a client portal upload; is refused.
Scope: documents:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/documents/$DOCUMENT_ID/archive \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
DOCUMENT_ID = "<document-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/documents/{DOCUMENT_ID}/archive",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const DOCUMENT_ID = "<document-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/documents/${DOCUMENT_ID}/archive`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404. |
document_id | path | string, 1 to 256 characters, matching ^(gl|fs|wp|sd|basis|pu|ev|upload):[A-Za-z0-9_.:%@-]{1,200}$ | yes | Identifier of the document within this engagement, as the document list returns it (for example 'sd:9f2c…' or 'basis:currentTb'). A staged upload is addressed as 'upload:{uploadId}'. Not a UUID, and not unique across engagements. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "<string>",
"fileName": "General Ledger 2025.xlsx",
"kind": "<gl | transactions | fs | balance-sheet | income-statement | retained-earnings-statement | cash-flow-statement | workpaper | supporting | tb-current | tb-prior | tb-classification | gifi-export | prior-return | portal-upload | evidence | inbox>",
"source": "<onboarding | manual | portal | connector | api | unknown>",
"uploadedAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z"
},
"capabilities": {
"replace": false,
"archive": false,
"deliver": false
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string, 1 to 256 characters | yes | Identifier of the document within this engagement, as the document list returns it (for example 'sd:9f2c…' or 'basis:currentTb'). A staged upload is addressed as 'upload:{uploadId}'. Not a UUID, and not unique across engagements. |
fileName | string, 1 to 512 characters | yes | Name to show for the document. When the stored record carries no file name, a generic label for its kind is returned. |
kind | one of gl, transactions, fs, balance-sheet, income-statement, retained-earnings-statement, cash-flow-statement, workpaper, supporting, tb-current, tb-prior, tb-classification, gifi-export, prior-return, portal-upload, evidence, inbox | yes | What the document is to the engagement. The values the document list publishes, plus 'inbox' for a staged upload that has been verified but not yet ingested. |
source | one of onboarding, manual, portal, connector, api, unknown | yes | How the document entered the engagement. onboarding is the onboarding pipeline, manual is a practitioner upload in the workspace, portal is a client upload through the client portal, connector is an accounting-system connection, and unknown means the record does not say. |
uploadedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the document was added. Null when no time was recorded for it, or when the stored value could not be read as a timestamp. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
lifecycle | object | yes | Live or archived. |
lifecycle.state | one of active, archived | yes | 'active' for a live document, 'archived' once it was removed. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
capabilities | object | yes | Which document operations this document currently admits. |
capabilities.replace | boolean | yes | True when documents.replace can swap this document's object for a completed staged upload. Only supporting documents admit it. |
capabilities.archive | boolean | yes | True when documents.archive can remove this document. Supporting documents and staged uploads admit it; basis files an engagement was built from never do. |
capabilities.deliver | boolean | yes | True when documents.delivery.create can mint a download URL: the document has a stored object the caller may read. |
Errors
See the shared error codes.
Create a document delivery
POST /api/v1/engagements/{engagement_id}/documents/{document_id}/deliveries
Mints a short-lived download URL for one document you may read. The URL is the only place a storage reference appears in this API, every delivery is audited, and a file whose malware verdict is not clean is refused. Requires both documents:read and documents:write.
Scopes: documents:read + documents:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/documents/$DOCUMENT_ID/deliveries \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
DOCUMENT_ID = "<document-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/documents/{DOCUMENT_ID}/deliveries",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const DOCUMENT_ID = "<document-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/documents/${DOCUMENT_ID}/deliveries`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404. |
document_id | path | string, 1 to 256 characters, matching ^(gl|fs|wp|sd|basis|pu|ev|upload):[A-Za-z0-9_.:%@-]{1,200}$ | yes | Identifier of the document within this engagement, as the document list returns it (for example 'sd:9f2c…' or 'basis:currentTb'). A staged upload is addressed as 'upload:{uploadId}'. Not a UUID, and not unique across engagements. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Response
201. The record was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"documentId": "<string>",
"fileName": "<string>",
"url": "<string>",
"expiresAt": "2026-07-14T12:00:00Z",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
documentId | string, 1 to 256 characters | yes | Identifier of the document within this engagement, as the document list returns it (for example 'sd:9f2c…' or 'basis:currentTb'). A staged upload is addressed as 'upload:{uploadId}'. Not a UUID, and not unique across engagements. |
fileName | string, 1 to 512 characters | yes | Name to save the file under. |
url | string, 1 to 4096 characters | yes | Presigned download URL. Short-lived and single-purpose: it is the only place a storage reference appears anywhere in this API. |
expiresAt | string (date-time) | yes | ISO-8601 in UTC. When the URL stops being accepted. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
Errors
See the shared error codes.
Start a document upload
POST /api/v1/engagements/{engagement_id}/document-uploads
Stages one upload for an engagement and answers a presigned URL to send the file to. Declare the file's name, exact byte length and SHA-256: completing the upload verifies the stored object against them and refuses anything else. The bytes never travel through this API. Send a UUID in Idempotency-Key.
Scope: documents:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/document-uploads \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"fileName": "Schedule 5 detail.xlsx",
"byteLength": 1,
"sha256": "<string>",
"mediaType": "<string>"
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/document-uploads",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"fileName": "Schedule 5 detail.xlsx",
"byteLength": 1,
"sha256": "<string>",
"mediaType": "<string>",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/document-uploads`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"fileName": "Schedule 5 detail.xlsx",
"byteLength": 1,
"sha256": "<string>",
"mediaType": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
fileName | string, 1 to 512 characters, matching ^[^/\\]+$ | yes | File name including its extension. The extension must be one the Documents inbox accepts. No path separators. |
byteLength | integer, 1 to 52428800 | yes | Exact size of the file in bytes. Maximum 50 MB. |
sha256 | string, matching ^[0-9a-fA-F]{64}$ | yes | Lowercase hex SHA-256 of the file's bytes. |
mediaType | nullable string, up to 255 characters | no | Media type to store the object under. Defaults to the type Filemark derives from the extension; a value that disagrees with the extension is refused. |
Response
201. The record was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"upload": {
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"state": "<staged | completed | consumed | abandoned>",
"fileName": "<string>",
"byteLength": 1,
"sha256": "<string>",
"mediaType": "<string>",
"docTypeId": "<string>",
"classification": {
"docTypeId": "<string>",
"label": "<string>",
"confidence": 0,
"status": "<classified | failed>",
"tierUsed": "<filename | structure | llm | user | fallback>",
"alternatives": [
{
"docTypeId": "<string>",
"confidence": 0
}
],
"warnings": [
"<string>"
]
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"completedAt": "2026-07-14T12:00:00Z",
"expiresAt": "2026-07-14T12:00:00Z"
},
"uploadInstructions": {
"url": "<string>",
"method": "PUT",
"headers": {
"<key>": "<string>"
},
"expiresAt": "2026-07-14T12:00:00Z"
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
upload | object | yes | The staged upload. |
upload.id | string (uuid) | yes | UUID of the staged upload. |
upload.engagementId | string (uuid) | yes | UUID of the engagement it stages a file for. |
upload.state | one of staged, completed, consumed, abandoned | yes | 'staged' while the presigned URL is outstanding, 'completed' once the object was verified and scanned clean (it is an inbox document from then on), 'consumed' once an ingestion took it, 'abandoned' once it was archived or expired uncompleted. |
upload.fileName | string, 1 to 512 characters | yes | The file name you declared. |
upload.byteLength | integer, at least 1 | yes | The byte length you declared. |
upload.sha256 | string, matching ^[0-9a-f]{64}$ | yes | The SHA-256 you declared; completion refuses anything else. |
upload.mediaType | string, 1 to 255 characters | yes | The media type you declared. |
upload.docTypeId | nullable string, up to 32 characters | no | The confirmed or suggested document type. Null until classified. |
upload.classification | nullable object | no | The classifier's answer. Null until classified. |
upload.classification.docTypeId | nullable string, up to 32 characters | when classification is set | The suggested document type, or null when the classifier could not place the file. Confirm or override it when you start an ingestion. |
upload.classification.label | nullable string, up to 200 characters | when classification is set | Readable name of the suggested document type. Null when unknown. |
upload.classification.confidence | nullable number, 0 to 1 | when classification is set | 0 to 1. Null when the classification failed or is unknown. |
upload.classification.status | one of classified, failed | when classification is set | 'classified' when the classifier produced a decision (which may still be an unknown type), 'failed' when it could not run. |
upload.classification.tierUsed | one of filename, structure, llm, user, fallback | when classification is set | How the decision was reached: filename or structure is deterministic, llm is the governed AI tier, user is a confirmed override, fallback means no tier placed the file. |
upload.classification.alternatives | array of objects | when classification is set | Other document types the classifier considered, most likely first. |
upload.classification.alternatives[].docTypeId | string, up to 32 characters | when classification is set | A document type also considered. |
upload.classification.alternatives[].confidence | number, 0 to 1 | when classification is set | 0 to 1 for this alternative. |
upload.classification.warnings | array of strings | when classification is set | Non-blocking notes about the classification. |
upload.resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
upload.createdAt | string (date-time) | yes | ISO-8601 in UTC. When staging started. |
upload.updatedAt | string (date-time) | yes | ISO-8601 in UTC. Last change to the row. |
upload.completedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the object was verified. |
upload.expiresAt | string (date-time) | yes | ISO-8601 in UTC. After this, a staged upload can no longer be completed and its object is swept. |
uploadInstructions | object | yes | Where and how to send the file. Returned by start only. |
uploadInstructions.url | string, 1 to 4096 characters | yes | Presigned S3 URL. Send the file to it and nothing else; the signature pins the object, its media type and its byte length. |
uploadInstructions.method | string, always "PUT" | yes | HTTP method to use for the upload. |
uploadInstructions.headers | map of string | yes | Headers the upload must carry verbatim. Sending different values makes S3 refuse the request. |
uploadInstructions.headers.{key} | string | per key | Headers the upload must carry verbatim. Sending different values makes S3 refuse the request. |
uploadInstructions.expiresAt | string (date-time) | yes | ISO-8601 in UTC. When the URL stops being accepted. |
Errors
See the shared error codes.
Complete a document upload
POST /api/v1/document-uploads/{upload_id}/complete
Verifies the object you sent against the declaration made when the upload was staged; its byte length and its SHA-256; then runs the same malware gate a practitioner upload runs. Only then is the file an inbox document. Send the upload's current resourceVersion in If-Match.
Scope: documents:write.
Request
curl --request POST https://api.filemark.ca/api/v1/document-uploads/$UPLOAD_ID/complete \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
UPLOAD_ID = "<upload-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/document-uploads/{UPLOAD_ID}/complete",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const UPLOAD_ID = "<upload-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/document-uploads/${UPLOAD_ID}/complete`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
upload_id | path | string (uuid) | yes | UUID of the staged upload. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"state": "<staged | completed | consumed | abandoned>",
"fileName": "<string>",
"byteLength": 1,
"sha256": "<string>",
"mediaType": "<string>",
"docTypeId": "<string>",
"classification": {
"docTypeId": "<string>",
"label": "<string>",
"confidence": 0,
"status": "<classified | failed>",
"tierUsed": "<filename | structure | llm | user | fallback>",
"alternatives": [
{
"docTypeId": "<string>",
"confidence": 0
}
],
"warnings": [
"<string>"
]
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"completedAt": "2026-07-14T12:00:00Z",
"expiresAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the staged upload. |
engagementId | string (uuid) | yes | UUID of the engagement it stages a file for. |
state | one of staged, completed, consumed, abandoned | yes | 'staged' while the presigned URL is outstanding, 'completed' once the object was verified and scanned clean (it is an inbox document from then on), 'consumed' once an ingestion took it, 'abandoned' once it was archived or expired uncompleted. |
fileName | string, 1 to 512 characters | yes | The file name you declared. |
byteLength | integer, at least 1 | yes | The byte length you declared. |
sha256 | string, matching ^[0-9a-f]{64}$ | yes | The SHA-256 you declared; completion refuses anything else. |
mediaType | string, 1 to 255 characters | yes | The media type you declared. |
docTypeId | nullable string, up to 32 characters | no | The confirmed or suggested document type. Null until classified. |
classification | nullable object | no | The classifier's answer. Null until classified. |
classification.docTypeId | nullable string, up to 32 characters | when classification is set | The suggested document type, or null when the classifier could not place the file. Confirm or override it when you start an ingestion. |
classification.label | nullable string, up to 200 characters | when classification is set | Readable name of the suggested document type. Null when unknown. |
classification.confidence | nullable number, 0 to 1 | when classification is set | 0 to 1. Null when the classification failed or is unknown. |
classification.status | one of classified, failed | when classification is set | 'classified' when the classifier produced a decision (which may still be an unknown type), 'failed' when it could not run. |
classification.tierUsed | one of filename, structure, llm, user, fallback | when classification is set | How the decision was reached: filename or structure is deterministic, llm is the governed AI tier, user is a confirmed override, fallback means no tier placed the file. |
classification.alternatives | array of objects | when classification is set | Other document types the classifier considered, most likely first. |
classification.alternatives[].docTypeId | string, up to 32 characters | when classification is set | A document type also considered. |
classification.alternatives[].confidence | number, 0 to 1 | when classification is set | 0 to 1 for this alternative. |
classification.warnings | array of strings | when classification is set | Non-blocking notes about the classification. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When staging started. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. Last change to the row. |
completedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the object was verified. |
expiresAt | string (date-time) | yes | ISO-8601 in UTC. After this, a staged upload can no longer be completed and its object is swept. |
Errors
See the shared error codes.
Classify a staged document
POST /api/v1/document-uploads/{upload_id}/classify
Runs Filemark's document classifier over a completed staged upload and records the suggested document type on it. The suggestion is a suggestion: confirm or override it when you start an ingestion. Send the upload's current resourceVersion in If-Match.
Scope: documents:write.
Request
curl --request POST https://api.filemark.ca/api/v1/document-uploads/$UPLOAD_ID/classify \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
UPLOAD_ID = "<upload-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/document-uploads/{UPLOAD_ID}/classify",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const UPLOAD_ID = "<upload-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/document-uploads/${UPLOAD_ID}/classify`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
upload_id | path | string (uuid) | yes | UUID of the staged upload. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
202. Processing is running. Repeat this request's method, URL and payload with the returned pollHeaders to read this attempt. A deliberate mutation retry omits X-Filemark-Job and reuses its original Idempotency-Key; the domain ledger owns recovery and replay. The final response retains the operation's normal contract.
Also 200: The record after the change, or the earlier result of the same Idempotency-Key.
{
"kind": "ai-job/v1",
"jobId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"statusUrl": "<string>",
"pollMethod": "POST",
"pollHeaders": {
"Idempotency-Key": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"X-Filemark-Job": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
kind | string, always "ai-job/v1" | yes | |
jobId | string (uuid) | yes | |
statusUrl | string | yes | |
pollMethod | string, always "POST" | yes | |
pollHeaders | object | yes | |
pollHeaders.Idempotency-Key | string (uuid) | yes | |
pollHeaders.X-Filemark-Job | string (uuid) | yes |
Errors
See the shared error codes.
Start a document ingestion
POST /api/v1/engagements/{engagement_id}/document-ingestions
Queues a background job that ingests completed staged uploads into the engagement, each as the document type you confirm. Poll Get a document ingestion for per-file outcomes. Send a UUID in Idempotency-Key; retrying with the same key returns the job it queued. For trial_balance_columns_unrecognized, use the outcome's bounded mappingPreview to construct columnMapping. Start a new ingestion for the same uploadId with that mapping and a new Idempotency-Key.
Scope: documents:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/document-ingestions \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"files": [
{
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"docTypeId": "s5",
"sheetName": "<string>",
"columnMapping": {
"headerRow": 0,
"columnMap": {
"<key>": 0
}
}
}
]
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/document-ingestions",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"files": [
{
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"docTypeId": "s5",
"sheetName": "<string>",
"columnMapping": {
"headerRow": 0,
"columnMap": {
"<key>": 0,
},
},
},
],
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/document-ingestions`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"files": [
{
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"docTypeId": "s5",
"sheetName": "<string>",
"columnMapping": {
"headerRow": 0,
"columnMap": {
"<key>": 0
}
}
}
]
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
files | array of objects, 1 to 50 items | yes | The completed staged uploads to ingest, with their confirmed types. |
files[].uploadId | string (uuid) | yes | UUID of a completed staged upload to ingest. |
files[].docTypeId | string, 1 to 32 characters, matching ^[A-Za-z0-9_.-]+$ | yes | The document type to ingest the file as. Confirm the suggestion documents.classify returned, or send your own. |
files[].sheetName | nullable string, up to 31 characters | no | Worksheet to read, for a workbook that needs one named. |
files[].columnMapping | nullable object | no | Account-import column mapping. Columns are one-based; headerRow is one-based, or zero for no header. When an outcome has code trial_balance_columns_unrecognized, use mappingPreview to map the columns and start a new ingestion for the same uploadId with a new Idempotency-Key. |
files[].columnMapping.headerRow | integer, 0 to 10000 | when columnMapping is set | |
files[].columnMapping.columnMap | map of integer | when columnMapping is set | |
files[].columnMapping.columnMap.{key} | integer | per key |
Response
202. The ingestion was queued.
Also 200: The job the same Idempotency-Key already queued.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"status": "<queued | running | succeeded | failed | cancelled>",
"attemptCount": 0,
"requestedUploadIds": [
"3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
],
"outcomes": [
{
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"status": "<ingested | merged | duplicate | unsupported | import_preview | prior_year_input | error>",
"message": "<string>",
"artifact": {
"kind": "<string>",
"ref": "<string>"
},
"previewId": "<string>",
"targetEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"questions": {
"mode": "<books | rows>",
"confirmationCounts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
},
"conflicts": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"existingName": "<string>",
"incomingName": "<string>",
"incomingBalance": "<string>",
"incomingCode": "<string>",
"crossKey": false
}
],
"restores": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"deletedAt": "<string>",
"incomingBalance": "<string>"
}
],
"missing": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"balance": "<string>",
"refs": {
"ajeLines": 0,
"workpapers": 0,
"groups": 0
}
}
],
"duplicates": [
{
"code": "<string>",
"combinedBalance": "<string>",
"rows": [
{
"name": "<string>",
"balance": "<string>"
}
],
"rowsCount": 0,
"rowsTruncated": false,
"plans": {
"combine": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"first": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"skip": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
]
}
}
],
"lowMatch": {
"matchedRows": 0,
"rows": 0
},
"blocked": {
"reason": "<string>",
"message": "<string>"
},
"coverage": {
"fiscalPeriod": {
"start": "<string>",
"end": "<string>"
},
"files": [
{
"glId": "<string>",
"filename": "<string>",
"accounts": [
"<string>"
],
"reportRange": {
"start": "<string>",
"end": "<string>"
},
"observedRange": {
"start": "<string>",
"end": "<string>"
}
}
],
"missingRanges": [
{
"start": "<string>",
"end": "<string>"
}
],
"requirements": [
{
"id": "<string>",
"message": "<string>"
}
]
},
"priorYearMismatch": false,
"missingCount": 0,
"missingTruncated": false,
"conflictsCount": 0,
"conflictsTruncated": false,
"restoresCount": 0,
"restoresTruncated": false,
"duplicatesCount": 0,
"duplicatesTruncated": false
},
"code": "<string>",
"mappingPreview": {
"sheetName": "<string>",
"headerRow": 0,
"columnMap": {
"<key>": 1
},
"rows": [
[
"<string>"
]
]
}
}
],
"error": {
"code": "<string>",
"message": "<string>"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"startedAt": "2026-07-14T12:00:00Z",
"finishedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the ingestion job. |
engagementId | string (uuid) | yes | UUID of the engagement being ingested into. |
status | one of queued, running, succeeded, failed, cancelled | yes | 'queued' before the worker takes it, 'running' while it works, 'succeeded' when every file reached an outcome, 'failed' when the job itself could not finish, 'cancelled' when it was cancelled before it started. |
attemptCount | integer, at least 0 | yes | How many times the worker has run it. |
requestedUploadIds | array of strings | yes | The staged uploads the job was asked to ingest, in order. |
outcomes | array of objects | no | One entry per file the job finished. Empty while queued. |
outcomes[].uploadId | string (uuid) | when outcomes is set | UUID of the staged upload this outcome is for. |
outcomes[].status | one of ingested, merged, duplicate, unsupported, import_preview, prior_year_input, error | when outcomes is set | 'ingested' added it, 'merged' folded it into an existing artifact, 'duplicate' found every row already present, 'unsupported' means the type cannot be ingested through the API, 'import_preview' staged the file as an account import that nothing has applied yet, 'prior_year_input' means the file needs prior-year setup before it can be used, 'error' means the file failed. A trial balance aimed at an onboarding basis stages an import ('import_preview'). |
outcomes[].message | string, up to 1000 characters | when outcomes is set | Readable explanation of the outcome. |
outcomes[].artifact | nullable object | when outcomes is set | What the file became. Null when nothing was created. |
outcomes[].artifact.kind | string, up to 64 characters | when outcomes is set | The artifact family: workpaper, supporting-doc, gl-file, coa, prior-return. |
outcomes[].artifact.ref | nullable string, up to 256 characters | when outcomes is set | Identifier of the artifact within the engagement. Null when it has none. |
outcomes[].previewId | nullable string | when outcomes is set | For 'import_preview': send it, with answers to the questions, to engagements.trial-balance.apply at targetEngagementId. Read that engagement's resourceVersion for If-Match first. Scoped to the caller and that engagement; expires after six hours. |
outcomes[].targetEngagementId | nullable string (uuid) | when outcomes is set | For 'import_preview': the engagement that owns previewId. A prior-year trial balance targets the linked prior year. Read this engagement's resourceVersion and apply the preview there. |
outcomes[].questions | nullable object | when outcomes is set | For 'import_preview': what the import asks before it merges. |
outcomes[].questions.mode | one of books, rows | when outcomes is set | |
outcomes[].questions.confirmationCounts | object | when outcomes is set | |
outcomes[].questions.confirmationCounts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.conflicts | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.conflicts[].plans | nullable map of object | when outcomes is set | |
outcomes[].questions.conflicts[].plans.{key} | object | per key | |
outcomes[].questions.conflicts[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].accountId | string | when outcomes is set | |
outcomes[].questions.conflicts[].code | string | when outcomes is set | |
outcomes[].questions.conflicts[].existingName | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingName | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingBalance | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingCode | nullable string | when outcomes is set | |
outcomes[].questions.conflicts[].crossKey | boolean | when outcomes is set | |
outcomes[].questions.restores | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.restores[].plans | nullable map of object | when outcomes is set | |
outcomes[].questions.restores[].plans.{key} | object | per key | |
outcomes[].questions.restores[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.restores[].accountId | string | when outcomes is set | |
outcomes[].questions.restores[].code | string | when outcomes is set | |
outcomes[].questions.restores[].name | string | when outcomes is set | |
outcomes[].questions.restores[].deletedAt | nullable string | when outcomes is set | |
outcomes[].questions.restores[].incomingBalance | string | when outcomes is set | |
outcomes[].questions.missing | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.missing[].plans | map of object | when outcomes is set | |
outcomes[].questions.missing[].plans.{key} | object | per key | |
outcomes[].questions.missing[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.missing[].accountId | string | when outcomes is set | |
outcomes[].questions.missing[].code | string | when outcomes is set | |
outcomes[].questions.missing[].name | string | when outcomes is set | |
outcomes[].questions.missing[].balance | string | when outcomes is set | |
outcomes[].questions.missing[].refs | object | when outcomes is set | |
outcomes[].questions.missing[].refs.ajeLines | integer | when outcomes is set | |
outcomes[].questions.missing[].refs.workpapers | integer | when outcomes is set | |
outcomes[].questions.missing[].refs.groups | integer | when outcomes is set | |
outcomes[].questions.duplicates | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.duplicates[].code | string | when outcomes is set | |
outcomes[].questions.duplicates[].combinedBalance | string | when outcomes is set | |
outcomes[].questions.duplicates[].rows | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.duplicates[].rows[].name | string | when outcomes is set | |
outcomes[].questions.duplicates[].rows[].balance | string | when outcomes is set | |
outcomes[].questions.duplicates[].rowsCount | integer, at least 0 | when outcomes is set | Member rows in this source group, listed or not. |
outcomes[].questions.duplicates[].rowsTruncated | boolean | when outcomes is set | Only the first 5,000 member rows are listed. Review the source for the rest; combine explicitly accepts the sum of every member, including unlisted rows. |
outcomes[].questions.duplicates[].plans | object | when outcomes is set | Apply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner. |
outcomes[].questions.duplicates[].plans.combine | array of objects, 1 to 2 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.combine[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.combine[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first | array of objects, 1 to 2 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.first[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.first[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip | array of objects, 1 to 1 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.skip[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.skip[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.lowMatch | nullable object | when outcomes is set | |
outcomes[].questions.lowMatch.matchedRows | integer | when outcomes is set | |
outcomes[].questions.lowMatch.rows | integer | when outcomes is set | |
outcomes[].questions.blocked | nullable object | when outcomes is set | |
outcomes[].questions.blocked.reason | string | when outcomes is set | |
outcomes[].questions.blocked.message | string | when outcomes is set | |
outcomes[].questions.coverage | nullable object | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod | object | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files | array of objects | when outcomes is set | |
outcomes[].questions.coverage.files[].glId | string | when outcomes is set | |
outcomes[].questions.coverage.files[].filename | string | when outcomes is set | |
outcomes[].questions.coverage.files[].accounts | array of strings | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange | nullable object | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange | nullable object | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.missingRanges | array of objects | when outcomes is set | |
outcomes[].questions.coverage.missingRanges[].start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.missingRanges[].end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.requirements | array of objects | when outcomes is set | |
outcomes[].questions.coverage.requirements[].id | string | when outcomes is set | |
outcomes[].questions.coverage.requirements[].message | string | when outcomes is set | |
outcomes[].questions.priorYearMismatch | boolean | when outcomes is set | |
outcomes[].questions.missingCount | integer, at least 0 | when outcomes is set | |
outcomes[].questions.missingTruncated | boolean | when outcomes is set | Only the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account. |
outcomes[].questions.conflictsCount | integer, at least 0 | when outcomes is set | Name conflicts the import found, listed or not. |
outcomes[].questions.conflictsTruncated | boolean | when outcomes is set | Only the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest. |
outcomes[].questions.restoresCount | integer, at least 0 | when outcomes is set | Rows matching a deleted account, listed or not. |
outcomes[].questions.restoresTruncated | boolean | when outcomes is set | Only the first 5,000 rows matching a deleted account are listed; omitted answers skip the row. |
outcomes[].questions.duplicatesCount | integer, at least 0 | when outcomes is set | Duplicated account codes in the source, listed or not. |
outcomes[].questions.duplicatesTruncated | boolean | when outcomes is set | Only the first 5,000 duplicated codes are listed. Every duplicated code needs an answer: import this trial balance in Filemark to answer the rest. |
outcomes[].code | nullable string, up to 64 characters | when outcomes is set | Stable per-file refusal code when column mapping is needed. |
outcomes[].mappingPreview | nullable object | when outcomes is set | For trial_balance_columns_unrecognized: inspect these rows and submit columnMapping in a new ingestion. |
outcomes[].mappingPreview.sheetName | string, up to 31 characters | when outcomes is set | Worksheet the parser read. |
outcomes[].mappingPreview.headerRow | integer, 0 to 10000 | when outcomes is set | Suggested one-based header row; zero means none detected. |
outcomes[].mappingPreview.columnMap | map of integer, 1 to 16384 | when outcomes is set | Detected roles and their one-based column numbers. May be empty. |
outcomes[].mappingPreview.columnMap.{key} | integer, 1 to 16384 | per key | Detected roles and their one-based column numbers. May be empty. |
outcomes[].mappingPreview.rows | array of arrays, up to 15 items | when outcomes is set | First 15 rows, up to 128 columns each. Cells are text or null, truncated to 256 characters. |
error | nullable object | no | Why the job failed. Null unless status is failed. |
error.code | string, up to 64 characters | when error is set | Stable failure identifier. |
error.message | string, up to 1000 characters | when error is set | Readable explanation. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the job was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. Last change to the job. |
startedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the worker claimed it. |
finishedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it reached a terminal status. |
Errors
See the shared error codes.
Get a document ingestion
GET /api/v1/document-ingestions/{ingestion_id}
Returns one ingestion job: its status, how many attempts it has had, and one sanitized outcome per requested file. A running job whose worker lease lapsed reads as failed (interrupted).
Scope: documents:write.
Request
curl https://api.filemark.ca/api/v1/document-ingestions/$INGESTION_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
INGESTION_ID = "<ingestion-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/document-ingestions/{INGESTION_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const INGESTION_ID = "<ingestion-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/document-ingestions/${INGESTION_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
ingestion_id | path | string (uuid) | yes | UUID of the ingestion job. |
Response
200. The ingestion job.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"status": "<queued | running | succeeded | failed | cancelled>",
"attemptCount": 0,
"requestedUploadIds": [
"3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
],
"outcomes": [
{
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"status": "<ingested | merged | duplicate | unsupported | import_preview | prior_year_input | error>",
"message": "<string>",
"artifact": {
"kind": "<string>",
"ref": "<string>"
},
"previewId": "<string>",
"targetEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"questions": {
"mode": "<books | rows>",
"confirmationCounts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
},
"conflicts": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"existingName": "<string>",
"incomingName": "<string>",
"incomingBalance": "<string>",
"incomingCode": "<string>",
"crossKey": false
}
],
"restores": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"deletedAt": "<string>",
"incomingBalance": "<string>"
}
],
"missing": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"balance": "<string>",
"refs": {
"ajeLines": 0,
"workpapers": 0,
"groups": 0
}
}
],
"duplicates": [
{
"code": "<string>",
"combinedBalance": "<string>",
"rows": [
{
"name": "<string>",
"balance": "<string>"
}
],
"rowsCount": 0,
"rowsTruncated": false,
"plans": {
"combine": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"first": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"skip": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
]
}
}
],
"lowMatch": {
"matchedRows": 0,
"rows": 0
},
"blocked": {
"reason": "<string>",
"message": "<string>"
},
"coverage": {
"fiscalPeriod": {
"start": "<string>",
"end": "<string>"
},
"files": [
{
"glId": "<string>",
"filename": "<string>",
"accounts": [
"<string>"
],
"reportRange": {
"start": "<string>",
"end": "<string>"
},
"observedRange": {
"start": "<string>",
"end": "<string>"
}
}
],
"missingRanges": [
{
"start": "<string>",
"end": "<string>"
}
],
"requirements": [
{
"id": "<string>",
"message": "<string>"
}
]
},
"priorYearMismatch": false,
"missingCount": 0,
"missingTruncated": false,
"conflictsCount": 0,
"conflictsTruncated": false,
"restoresCount": 0,
"restoresTruncated": false,
"duplicatesCount": 0,
"duplicatesTruncated": false
},
"code": "<string>",
"mappingPreview": {
"sheetName": "<string>",
"headerRow": 0,
"columnMap": {
"<key>": 1
},
"rows": [
[
"<string>"
]
]
}
}
],
"error": {
"code": "<string>",
"message": "<string>"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"startedAt": "2026-07-14T12:00:00Z",
"finishedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the ingestion job. |
engagementId | string (uuid) | yes | UUID of the engagement being ingested into. |
status | one of queued, running, succeeded, failed, cancelled | yes | 'queued' before the worker takes it, 'running' while it works, 'succeeded' when every file reached an outcome, 'failed' when the job itself could not finish, 'cancelled' when it was cancelled before it started. |
attemptCount | integer, at least 0 | yes | How many times the worker has run it. |
requestedUploadIds | array of strings | yes | The staged uploads the job was asked to ingest, in order. |
outcomes | array of objects | no | One entry per file the job finished. Empty while queued. |
outcomes[].uploadId | string (uuid) | when outcomes is set | UUID of the staged upload this outcome is for. |
outcomes[].status | one of ingested, merged, duplicate, unsupported, import_preview, prior_year_input, error | when outcomes is set | 'ingested' added it, 'merged' folded it into an existing artifact, 'duplicate' found every row already present, 'unsupported' means the type cannot be ingested through the API, 'import_preview' staged the file as an account import that nothing has applied yet, 'prior_year_input' means the file needs prior-year setup before it can be used, 'error' means the file failed. A trial balance aimed at an onboarding basis stages an import ('import_preview'). |
outcomes[].message | string, up to 1000 characters | when outcomes is set | Readable explanation of the outcome. |
outcomes[].artifact | nullable object | when outcomes is set | What the file became. Null when nothing was created. |
outcomes[].artifact.kind | string, up to 64 characters | when outcomes is set | The artifact family: workpaper, supporting-doc, gl-file, coa, prior-return. |
outcomes[].artifact.ref | nullable string, up to 256 characters | when outcomes is set | Identifier of the artifact within the engagement. Null when it has none. |
outcomes[].previewId | nullable string | when outcomes is set | For 'import_preview': send it, with answers to the questions, to engagements.trial-balance.apply at targetEngagementId. Read that engagement's resourceVersion for If-Match first. Scoped to the caller and that engagement; expires after six hours. |
outcomes[].targetEngagementId | nullable string (uuid) | when outcomes is set | For 'import_preview': the engagement that owns previewId. A prior-year trial balance targets the linked prior year. Read this engagement's resourceVersion and apply the preview there. |
outcomes[].questions | nullable object | when outcomes is set | For 'import_preview': what the import asks before it merges. |
outcomes[].questions.mode | one of books, rows | when outcomes is set | |
outcomes[].questions.confirmationCounts | object | when outcomes is set | |
outcomes[].questions.confirmationCounts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.conflicts | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.conflicts[].plans | nullable map of object | when outcomes is set | |
outcomes[].questions.conflicts[].plans.{key} | object | per key | |
outcomes[].questions.conflicts[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].accountId | string | when outcomes is set | |
outcomes[].questions.conflicts[].code | string | when outcomes is set | |
outcomes[].questions.conflicts[].existingName | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingName | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingBalance | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingCode | nullable string | when outcomes is set | |
outcomes[].questions.conflicts[].crossKey | boolean | when outcomes is set | |
outcomes[].questions.restores | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.restores[].plans | nullable map of object | when outcomes is set | |
outcomes[].questions.restores[].plans.{key} | object | per key | |
outcomes[].questions.restores[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.restores[].accountId | string | when outcomes is set | |
outcomes[].questions.restores[].code | string | when outcomes is set | |
outcomes[].questions.restores[].name | string | when outcomes is set | |
outcomes[].questions.restores[].deletedAt | nullable string | when outcomes is set | |
outcomes[].questions.restores[].incomingBalance | string | when outcomes is set | |
outcomes[].questions.missing | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.missing[].plans | map of object | when outcomes is set | |
outcomes[].questions.missing[].plans.{key} | object | per key | |
outcomes[].questions.missing[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.missing[].accountId | string | when outcomes is set | |
outcomes[].questions.missing[].code | string | when outcomes is set | |
outcomes[].questions.missing[].name | string | when outcomes is set | |
outcomes[].questions.missing[].balance | string | when outcomes is set | |
outcomes[].questions.missing[].refs | object | when outcomes is set | |
outcomes[].questions.missing[].refs.ajeLines | integer | when outcomes is set | |
outcomes[].questions.missing[].refs.workpapers | integer | when outcomes is set | |
outcomes[].questions.missing[].refs.groups | integer | when outcomes is set | |
outcomes[].questions.duplicates | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.duplicates[].code | string | when outcomes is set | |
outcomes[].questions.duplicates[].combinedBalance | string | when outcomes is set | |
outcomes[].questions.duplicates[].rows | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.duplicates[].rows[].name | string | when outcomes is set | |
outcomes[].questions.duplicates[].rows[].balance | string | when outcomes is set | |
outcomes[].questions.duplicates[].rowsCount | integer, at least 0 | when outcomes is set | Member rows in this source group, listed or not. |
outcomes[].questions.duplicates[].rowsTruncated | boolean | when outcomes is set | Only the first 5,000 member rows are listed. Review the source for the rest; combine explicitly accepts the sum of every member, including unlisted rows. |
outcomes[].questions.duplicates[].plans | object | when outcomes is set | Apply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner. |
outcomes[].questions.duplicates[].plans.combine | array of objects, 1 to 2 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.combine[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.combine[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first | array of objects, 1 to 2 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.first[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.first[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip | array of objects, 1 to 1 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.skip[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.skip[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.lowMatch | nullable object | when outcomes is set | |
outcomes[].questions.lowMatch.matchedRows | integer | when outcomes is set | |
outcomes[].questions.lowMatch.rows | integer | when outcomes is set | |
outcomes[].questions.blocked | nullable object | when outcomes is set | |
outcomes[].questions.blocked.reason | string | when outcomes is set | |
outcomes[].questions.blocked.message | string | when outcomes is set | |
outcomes[].questions.coverage | nullable object | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod | object | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files | array of objects | when outcomes is set | |
outcomes[].questions.coverage.files[].glId | string | when outcomes is set | |
outcomes[].questions.coverage.files[].filename | string | when outcomes is set | |
outcomes[].questions.coverage.files[].accounts | array of strings | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange | nullable object | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange | nullable object | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.missingRanges | array of objects | when outcomes is set | |
outcomes[].questions.coverage.missingRanges[].start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.missingRanges[].end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.requirements | array of objects | when outcomes is set | |
outcomes[].questions.coverage.requirements[].id | string | when outcomes is set | |
outcomes[].questions.coverage.requirements[].message | string | when outcomes is set | |
outcomes[].questions.priorYearMismatch | boolean | when outcomes is set | |
outcomes[].questions.missingCount | integer, at least 0 | when outcomes is set | |
outcomes[].questions.missingTruncated | boolean | when outcomes is set | Only the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account. |
outcomes[].questions.conflictsCount | integer, at least 0 | when outcomes is set | Name conflicts the import found, listed or not. |
outcomes[].questions.conflictsTruncated | boolean | when outcomes is set | Only the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest. |
outcomes[].questions.restoresCount | integer, at least 0 | when outcomes is set | Rows matching a deleted account, listed or not. |
outcomes[].questions.restoresTruncated | boolean | when outcomes is set | Only the first 5,000 rows matching a deleted account are listed; omitted answers skip the row. |
outcomes[].questions.duplicatesCount | integer, at least 0 | when outcomes is set | Duplicated account codes in the source, listed or not. |
outcomes[].questions.duplicatesTruncated | boolean | when outcomes is set | Only the first 5,000 duplicated codes are listed. Every duplicated code needs an answer: import this trial balance in Filemark to answer the rest. |
outcomes[].code | nullable string, up to 64 characters | when outcomes is set | Stable per-file refusal code when column mapping is needed. |
outcomes[].mappingPreview | nullable object | when outcomes is set | For trial_balance_columns_unrecognized: inspect these rows and submit columnMapping in a new ingestion. |
outcomes[].mappingPreview.sheetName | string, up to 31 characters | when outcomes is set | Worksheet the parser read. |
outcomes[].mappingPreview.headerRow | integer, 0 to 10000 | when outcomes is set | Suggested one-based header row; zero means none detected. |
outcomes[].mappingPreview.columnMap | map of integer, 1 to 16384 | when outcomes is set | Detected roles and their one-based column numbers. May be empty. |
outcomes[].mappingPreview.columnMap.{key} | integer, 1 to 16384 | per key | Detected roles and their one-based column numbers. May be empty. |
outcomes[].mappingPreview.rows | array of arrays, up to 15 items | when outcomes is set | First 15 rows, up to 128 columns each. Cells are text or null, truncated to 256 characters. |
error | nullable object | no | Why the job failed. Null unless status is failed. |
error.code | string, up to 64 characters | when error is set | Stable failure identifier. |
error.message | string, up to 1000 characters | when error is set | Readable explanation. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the job was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. Last change to the job. |
startedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the worker claimed it. |
finishedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it reached a terminal status. |
Errors
In addition to the shared error codes:
409: The stored record cannot be represented.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Retry a document ingestion
POST /api/v1/document-ingestions/{ingestion_id}/retry
Re-queues the files a finished ingestion did not ingest: the ones that errored, and any that never reached an outcome. Refused while the job is queued or running, and refused when every file already succeeded. Send the job's current resourceVersion in If-Match.
Scope: documents:write.
Request
curl --request POST https://api.filemark.ca/api/v1/document-ingestions/$INGESTION_ID/retry \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
INGESTION_ID = "<ingestion-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/document-ingestions/{INGESTION_ID}/retry",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const INGESTION_ID = "<ingestion-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/document-ingestions/${INGESTION_ID}/retry`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
ingestion_id | path | string (uuid) | yes | UUID of the ingestion job. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"status": "<queued | running | succeeded | failed | cancelled>",
"attemptCount": 0,
"requestedUploadIds": [
"3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
],
"outcomes": [
{
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"status": "<ingested | merged | duplicate | unsupported | import_preview | prior_year_input | error>",
"message": "<string>",
"artifact": {
"kind": "<string>",
"ref": "<string>"
},
"previewId": "<string>",
"targetEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"questions": {
"mode": "<books | rows>",
"confirmationCounts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
},
"conflicts": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"existingName": "<string>",
"incomingName": "<string>",
"incomingBalance": "<string>",
"incomingCode": "<string>",
"crossKey": false
}
],
"restores": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"deletedAt": "<string>",
"incomingBalance": "<string>"
}
],
"missing": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"balance": "<string>",
"refs": {
"ajeLines": 0,
"workpapers": 0,
"groups": 0
}
}
],
"duplicates": [
{
"code": "<string>",
"combinedBalance": "<string>",
"rows": [
{
"name": "<string>",
"balance": "<string>"
}
],
"rowsCount": 0,
"rowsTruncated": false,
"plans": {
"combine": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"first": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"skip": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
]
}
}
],
"lowMatch": {
"matchedRows": 0,
"rows": 0
},
"blocked": {
"reason": "<string>",
"message": "<string>"
},
"coverage": {
"fiscalPeriod": {
"start": "<string>",
"end": "<string>"
},
"files": [
{
"glId": "<string>",
"filename": "<string>",
"accounts": [
"<string>"
],
"reportRange": {
"start": "<string>",
"end": "<string>"
},
"observedRange": {
"start": "<string>",
"end": "<string>"
}
}
],
"missingRanges": [
{
"start": "<string>",
"end": "<string>"
}
],
"requirements": [
{
"id": "<string>",
"message": "<string>"
}
]
},
"priorYearMismatch": false,
"missingCount": 0,
"missingTruncated": false,
"conflictsCount": 0,
"conflictsTruncated": false,
"restoresCount": 0,
"restoresTruncated": false,
"duplicatesCount": 0,
"duplicatesTruncated": false
},
"code": "<string>",
"mappingPreview": {
"sheetName": "<string>",
"headerRow": 0,
"columnMap": {
"<key>": 1
},
"rows": [
[
"<string>"
]
]
}
}
],
"error": {
"code": "<string>",
"message": "<string>"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"startedAt": "2026-07-14T12:00:00Z",
"finishedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the ingestion job. |
engagementId | string (uuid) | yes | UUID of the engagement being ingested into. |
status | one of queued, running, succeeded, failed, cancelled | yes | 'queued' before the worker takes it, 'running' while it works, 'succeeded' when every file reached an outcome, 'failed' when the job itself could not finish, 'cancelled' when it was cancelled before it started. |
attemptCount | integer, at least 0 | yes | How many times the worker has run it. |
requestedUploadIds | array of strings | yes | The staged uploads the job was asked to ingest, in order. |
outcomes | array of objects | no | One entry per file the job finished. Empty while queued. |
outcomes[].uploadId | string (uuid) | when outcomes is set | UUID of the staged upload this outcome is for. |
outcomes[].status | one of ingested, merged, duplicate, unsupported, import_preview, prior_year_input, error | when outcomes is set | 'ingested' added it, 'merged' folded it into an existing artifact, 'duplicate' found every row already present, 'unsupported' means the type cannot be ingested through the API, 'import_preview' staged the file as an account import that nothing has applied yet, 'prior_year_input' means the file needs prior-year setup before it can be used, 'error' means the file failed. A trial balance aimed at an onboarding basis stages an import ('import_preview'). |
outcomes[].message | string, up to 1000 characters | when outcomes is set | Readable explanation of the outcome. |
outcomes[].artifact | nullable object | when outcomes is set | What the file became. Null when nothing was created. |
outcomes[].artifact.kind | string, up to 64 characters | when outcomes is set | The artifact family: workpaper, supporting-doc, gl-file, coa, prior-return. |
outcomes[].artifact.ref | nullable string, up to 256 characters | when outcomes is set | Identifier of the artifact within the engagement. Null when it has none. |
outcomes[].previewId | nullable string | when outcomes is set | For 'import_preview': send it, with answers to the questions, to engagements.trial-balance.apply at targetEngagementId. Read that engagement's resourceVersion for If-Match first. Scoped to the caller and that engagement; expires after six hours. |
outcomes[].targetEngagementId | nullable string (uuid) | when outcomes is set | For 'import_preview': the engagement that owns previewId. A prior-year trial balance targets the linked prior year. Read this engagement's resourceVersion and apply the preview there. |
outcomes[].questions | nullable object | when outcomes is set | For 'import_preview': what the import asks before it merges. |
outcomes[].questions.mode | one of books, rows | when outcomes is set | |
outcomes[].questions.confirmationCounts | object | when outcomes is set | |
outcomes[].questions.confirmationCounts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.conflicts | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.conflicts[].plans | nullable map of object | when outcomes is set | |
outcomes[].questions.conflicts[].plans.{key} | object | per key | |
outcomes[].questions.conflicts[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].accountId | string | when outcomes is set | |
outcomes[].questions.conflicts[].code | string | when outcomes is set | |
outcomes[].questions.conflicts[].existingName | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingName | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingBalance | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingCode | nullable string | when outcomes is set | |
outcomes[].questions.conflicts[].crossKey | boolean | when outcomes is set | |
outcomes[].questions.restores | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.restores[].plans | nullable map of object | when outcomes is set | |
outcomes[].questions.restores[].plans.{key} | object | per key | |
outcomes[].questions.restores[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.restores[].accountId | string | when outcomes is set | |
outcomes[].questions.restores[].code | string | when outcomes is set | |
outcomes[].questions.restores[].name | string | when outcomes is set | |
outcomes[].questions.restores[].deletedAt | nullable string | when outcomes is set | |
outcomes[].questions.restores[].incomingBalance | string | when outcomes is set | |
outcomes[].questions.missing | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.missing[].plans | map of object | when outcomes is set | |
outcomes[].questions.missing[].plans.{key} | object | per key | |
outcomes[].questions.missing[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.missing[].accountId | string | when outcomes is set | |
outcomes[].questions.missing[].code | string | when outcomes is set | |
outcomes[].questions.missing[].name | string | when outcomes is set | |
outcomes[].questions.missing[].balance | string | when outcomes is set | |
outcomes[].questions.missing[].refs | object | when outcomes is set | |
outcomes[].questions.missing[].refs.ajeLines | integer | when outcomes is set | |
outcomes[].questions.missing[].refs.workpapers | integer | when outcomes is set | |
outcomes[].questions.missing[].refs.groups | integer | when outcomes is set | |
outcomes[].questions.duplicates | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.duplicates[].code | string | when outcomes is set | |
outcomes[].questions.duplicates[].combinedBalance | string | when outcomes is set | |
outcomes[].questions.duplicates[].rows | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.duplicates[].rows[].name | string | when outcomes is set | |
outcomes[].questions.duplicates[].rows[].balance | string | when outcomes is set | |
outcomes[].questions.duplicates[].rowsCount | integer, at least 0 | when outcomes is set | Member rows in this source group, listed or not. |
outcomes[].questions.duplicates[].rowsTruncated | boolean | when outcomes is set | Only the first 5,000 member rows are listed. Review the source for the rest; combine explicitly accepts the sum of every member, including unlisted rows. |
outcomes[].questions.duplicates[].plans | object | when outcomes is set | Apply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner. |
outcomes[].questions.duplicates[].plans.combine | array of objects, 1 to 2 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.combine[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.combine[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first | array of objects, 1 to 2 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.first[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.first[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip | array of objects, 1 to 1 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.skip[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.skip[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.lowMatch | nullable object | when outcomes is set | |
outcomes[].questions.lowMatch.matchedRows | integer | when outcomes is set | |
outcomes[].questions.lowMatch.rows | integer | when outcomes is set | |
outcomes[].questions.blocked | nullable object | when outcomes is set | |
outcomes[].questions.blocked.reason | string | when outcomes is set | |
outcomes[].questions.blocked.message | string | when outcomes is set | |
outcomes[].questions.coverage | nullable object | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod | object | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files | array of objects | when outcomes is set | |
outcomes[].questions.coverage.files[].glId | string | when outcomes is set | |
outcomes[].questions.coverage.files[].filename | string | when outcomes is set | |
outcomes[].questions.coverage.files[].accounts | array of strings | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange | nullable object | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange | nullable object | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.missingRanges | array of objects | when outcomes is set | |
outcomes[].questions.coverage.missingRanges[].start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.missingRanges[].end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.requirements | array of objects | when outcomes is set | |
outcomes[].questions.coverage.requirements[].id | string | when outcomes is set | |
outcomes[].questions.coverage.requirements[].message | string | when outcomes is set | |
outcomes[].questions.priorYearMismatch | boolean | when outcomes is set | |
outcomes[].questions.missingCount | integer, at least 0 | when outcomes is set | |
outcomes[].questions.missingTruncated | boolean | when outcomes is set | Only the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account. |
outcomes[].questions.conflictsCount | integer, at least 0 | when outcomes is set | Name conflicts the import found, listed or not. |
outcomes[].questions.conflictsTruncated | boolean | when outcomes is set | Only the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest. |
outcomes[].questions.restoresCount | integer, at least 0 | when outcomes is set | Rows matching a deleted account, listed or not. |
outcomes[].questions.restoresTruncated | boolean | when outcomes is set | Only the first 5,000 rows matching a deleted account are listed; omitted answers skip the row. |
outcomes[].questions.duplicatesCount | integer, at least 0 | when outcomes is set | Duplicated account codes in the source, listed or not. |
outcomes[].questions.duplicatesTruncated | boolean | when outcomes is set | Only the first 5,000 duplicated codes are listed. Every duplicated code needs an answer: import this trial balance in Filemark to answer the rest. |
outcomes[].code | nullable string, up to 64 characters | when outcomes is set | Stable per-file refusal code when column mapping is needed. |
outcomes[].mappingPreview | nullable object | when outcomes is set | For trial_balance_columns_unrecognized: inspect these rows and submit columnMapping in a new ingestion. |
outcomes[].mappingPreview.sheetName | string, up to 31 characters | when outcomes is set | Worksheet the parser read. |
outcomes[].mappingPreview.headerRow | integer, 0 to 10000 | when outcomes is set | Suggested one-based header row; zero means none detected. |
outcomes[].mappingPreview.columnMap | map of integer, 1 to 16384 | when outcomes is set | Detected roles and their one-based column numbers. May be empty. |
outcomes[].mappingPreview.columnMap.{key} | integer, 1 to 16384 | per key | Detected roles and their one-based column numbers. May be empty. |
outcomes[].mappingPreview.rows | array of arrays, up to 15 items | when outcomes is set | First 15 rows, up to 128 columns each. Cells are text or null, truncated to 256 characters. |
error | nullable object | no | Why the job failed. Null unless status is failed. |
error.code | string, up to 64 characters | when error is set | Stable failure identifier. |
error.message | string, up to 1000 characters | when error is set | Readable explanation. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the job was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. Last change to the job. |
startedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the worker claimed it. |
finishedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it reached a terminal status. |
Errors
See the shared error codes.
Cancel a document ingestion
POST /api/v1/document-ingestions/{ingestion_id}/cancel
Cancels an ingestion that has not started. A running job is not interrupted: it finishes the file it is on and then stops, so a cancel is refused once the worker has claimed it. Send the job's current resourceVersion in If-Match.
Scope: documents:write.
Request
curl --request POST https://api.filemark.ca/api/v1/document-ingestions/$INGESTION_ID/cancel \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
INGESTION_ID = "<ingestion-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/document-ingestions/{INGESTION_ID}/cancel",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const INGESTION_ID = "<ingestion-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/document-ingestions/${INGESTION_ID}/cancel`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
ingestion_id | path | string (uuid) | yes | UUID of the ingestion job. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The record's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The record after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"status": "<queued | running | succeeded | failed | cancelled>",
"attemptCount": 0,
"requestedUploadIds": [
"3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
],
"outcomes": [
{
"uploadId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"status": "<ingested | merged | duplicate | unsupported | import_preview | prior_year_input | error>",
"message": "<string>",
"artifact": {
"kind": "<string>",
"ref": "<string>"
},
"previewId": "<string>",
"targetEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"questions": {
"mode": "<books | rows>",
"confirmationCounts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
},
"conflicts": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"existingName": "<string>",
"incomingName": "<string>",
"incomingBalance": "<string>",
"incomingCode": "<string>",
"crossKey": false
}
],
"restores": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"deletedAt": "<string>",
"incomingBalance": "<string>"
}
],
"missing": [
{
"plans": {
"<key>": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
},
"accountId": "<string>",
"code": "<string>",
"name": "<string>",
"balance": "<string>",
"refs": {
"ajeLines": 0,
"workpapers": 0,
"groups": 0
}
}
],
"duplicates": [
{
"code": "<string>",
"combinedBalance": "<string>",
"rows": [
{
"name": "<string>",
"balance": "<string>"
}
],
"rowsCount": 0,
"rowsTruncated": false,
"plans": {
"combine": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"first": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
],
"skip": [
{
"conflicts": {
"<key>": "<rename | new_account>"
},
"restores": {
"<key>": "<restore | skip>"
},
"counts": {
"balancesUpdated": 0,
"renamed": 0,
"added": 0,
"restored": 0,
"deleted": 0,
"unchanged": 0,
"skipped": 0
}
}
]
}
}
],
"lowMatch": {
"matchedRows": 0,
"rows": 0
},
"blocked": {
"reason": "<string>",
"message": "<string>"
},
"coverage": {
"fiscalPeriod": {
"start": "<string>",
"end": "<string>"
},
"files": [
{
"glId": "<string>",
"filename": "<string>",
"accounts": [
"<string>"
],
"reportRange": {
"start": "<string>",
"end": "<string>"
},
"observedRange": {
"start": "<string>",
"end": "<string>"
}
}
],
"missingRanges": [
{
"start": "<string>",
"end": "<string>"
}
],
"requirements": [
{
"id": "<string>",
"message": "<string>"
}
]
},
"priorYearMismatch": false,
"missingCount": 0,
"missingTruncated": false,
"conflictsCount": 0,
"conflictsTruncated": false,
"restoresCount": 0,
"restoresTruncated": false,
"duplicatesCount": 0,
"duplicatesTruncated": false
},
"code": "<string>",
"mappingPreview": {
"sheetName": "<string>",
"headerRow": 0,
"columnMap": {
"<key>": 1
},
"rows": [
[
"<string>"
]
]
}
}
],
"error": {
"code": "<string>",
"message": "<string>"
},
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"startedAt": "2026-07-14T12:00:00Z",
"finishedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the ingestion job. |
engagementId | string (uuid) | yes | UUID of the engagement being ingested into. |
status | one of queued, running, succeeded, failed, cancelled | yes | 'queued' before the worker takes it, 'running' while it works, 'succeeded' when every file reached an outcome, 'failed' when the job itself could not finish, 'cancelled' when it was cancelled before it started. |
attemptCount | integer, at least 0 | yes | How many times the worker has run it. |
requestedUploadIds | array of strings | yes | The staged uploads the job was asked to ingest, in order. |
outcomes | array of objects | no | One entry per file the job finished. Empty while queued. |
outcomes[].uploadId | string (uuid) | when outcomes is set | UUID of the staged upload this outcome is for. |
outcomes[].status | one of ingested, merged, duplicate, unsupported, import_preview, prior_year_input, error | when outcomes is set | 'ingested' added it, 'merged' folded it into an existing artifact, 'duplicate' found every row already present, 'unsupported' means the type cannot be ingested through the API, 'import_preview' staged the file as an account import that nothing has applied yet, 'prior_year_input' means the file needs prior-year setup before it can be used, 'error' means the file failed. A trial balance aimed at an onboarding basis stages an import ('import_preview'). |
outcomes[].message | string, up to 1000 characters | when outcomes is set | Readable explanation of the outcome. |
outcomes[].artifact | nullable object | when outcomes is set | What the file became. Null when nothing was created. |
outcomes[].artifact.kind | string, up to 64 characters | when outcomes is set | The artifact family: workpaper, supporting-doc, gl-file, coa, prior-return. |
outcomes[].artifact.ref | nullable string, up to 256 characters | when outcomes is set | Identifier of the artifact within the engagement. Null when it has none. |
outcomes[].previewId | nullable string | when outcomes is set | For 'import_preview': send it, with answers to the questions, to engagements.trial-balance.apply at targetEngagementId. Read that engagement's resourceVersion for If-Match first. Scoped to the caller and that engagement; expires after six hours. |
outcomes[].targetEngagementId | nullable string (uuid) | when outcomes is set | For 'import_preview': the engagement that owns previewId. A prior-year trial balance targets the linked prior year. Read this engagement's resourceVersion and apply the preview there. |
outcomes[].questions | nullable object | when outcomes is set | For 'import_preview': what the import asks before it merges. |
outcomes[].questions.mode | one of books, rows | when outcomes is set | |
outcomes[].questions.confirmationCounts | object | when outcomes is set | |
outcomes[].questions.confirmationCounts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.confirmationCounts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.conflicts | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.conflicts[].plans | nullable map of object | when outcomes is set | |
outcomes[].questions.conflicts[].plans.{key} | object | per key | |
outcomes[].questions.conflicts[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.conflicts[].accountId | string | when outcomes is set | |
outcomes[].questions.conflicts[].code | string | when outcomes is set | |
outcomes[].questions.conflicts[].existingName | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingName | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingBalance | string | when outcomes is set | |
outcomes[].questions.conflicts[].incomingCode | nullable string | when outcomes is set | |
outcomes[].questions.conflicts[].crossKey | boolean | when outcomes is set | |
outcomes[].questions.restores | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.restores[].plans | nullable map of object | when outcomes is set | |
outcomes[].questions.restores[].plans.{key} | object | per key | |
outcomes[].questions.restores[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.restores[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.restores[].accountId | string | when outcomes is set | |
outcomes[].questions.restores[].code | string | when outcomes is set | |
outcomes[].questions.restores[].name | string | when outcomes is set | |
outcomes[].questions.restores[].deletedAt | nullable string | when outcomes is set | |
outcomes[].questions.restores[].incomingBalance | string | when outcomes is set | |
outcomes[].questions.missing | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.missing[].plans | map of object | when outcomes is set | |
outcomes[].questions.missing[].plans.{key} | object | per key | |
outcomes[].questions.missing[].plans.{key}.balancesUpdated | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.renamed | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.added | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.restored | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.deleted | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.unchanged | integer, at least 0 | per key | |
outcomes[].questions.missing[].plans.{key}.skipped | integer, at least 0 | per key | |
outcomes[].questions.missing[].accountId | string | when outcomes is set | |
outcomes[].questions.missing[].code | string | when outcomes is set | |
outcomes[].questions.missing[].name | string | when outcomes is set | |
outcomes[].questions.missing[].balance | string | when outcomes is set | |
outcomes[].questions.missing[].refs | object | when outcomes is set | |
outcomes[].questions.missing[].refs.ajeLines | integer | when outcomes is set | |
outcomes[].questions.missing[].refs.workpapers | integer | when outcomes is set | |
outcomes[].questions.missing[].refs.groups | integer | when outcomes is set | |
outcomes[].questions.duplicates | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.duplicates[].code | string | when outcomes is set | |
outcomes[].questions.duplicates[].combinedBalance | string | when outcomes is set | |
outcomes[].questions.duplicates[].rows | array of objects, up to 5000 items | when outcomes is set | |
outcomes[].questions.duplicates[].rows[].name | string | when outcomes is set | |
outcomes[].questions.duplicates[].rows[].balance | string | when outcomes is set | |
outcomes[].questions.duplicates[].rowsCount | integer, at least 0 | when outcomes is set | Member rows in this source group, listed or not. |
outcomes[].questions.duplicates[].rowsTruncated | boolean | when outcomes is set | Only the first 5,000 member rows are listed. Review the source for the rest; combine explicitly accepts the sum of every member, including unlisted rows. |
outcomes[].questions.duplicates[].plans | object | when outcomes is set | Apply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner. |
outcomes[].questions.duplicates[].plans.combine | array of objects, 1 to 2 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.combine[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.combine[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.combine[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first | array of objects, 1 to 2 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.first[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.first[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.first[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip | array of objects, 1 to 1 items | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].conflicts | map of one of rename, new_account | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].conflicts.{key} | one of rename, new_account | per key | |
outcomes[].questions.duplicates[].plans.skip[].restores | map of one of restore, skip | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].restores.{key} | one of restore, skip | per key | |
outcomes[].questions.duplicates[].plans.skip[].counts | object | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.balancesUpdated | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.renamed | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.added | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.restored | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.deleted | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.unchanged | integer, at least 0 | when outcomes is set | |
outcomes[].questions.duplicates[].plans.skip[].counts.skipped | integer, at least 0 | when outcomes is set | |
outcomes[].questions.lowMatch | nullable object | when outcomes is set | |
outcomes[].questions.lowMatch.matchedRows | integer | when outcomes is set | |
outcomes[].questions.lowMatch.rows | integer | when outcomes is set | |
outcomes[].questions.blocked | nullable object | when outcomes is set | |
outcomes[].questions.blocked.reason | string | when outcomes is set | |
outcomes[].questions.blocked.message | string | when outcomes is set | |
outcomes[].questions.coverage | nullable object | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod | object | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.fiscalPeriod.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files | array of objects | when outcomes is set | |
outcomes[].questions.coverage.files[].glId | string | when outcomes is set | |
outcomes[].questions.coverage.files[].filename | string | when outcomes is set | |
outcomes[].questions.coverage.files[].accounts | array of strings | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange | nullable object | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].reportRange.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange | nullable object | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange.start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.files[].observedRange.end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.missingRanges | array of objects | when outcomes is set | |
outcomes[].questions.coverage.missingRanges[].start | nullable string | when outcomes is set | |
outcomes[].questions.coverage.missingRanges[].end | nullable string | when outcomes is set | |
outcomes[].questions.coverage.requirements | array of objects | when outcomes is set | |
outcomes[].questions.coverage.requirements[].id | string | when outcomes is set | |
outcomes[].questions.coverage.requirements[].message | string | when outcomes is set | |
outcomes[].questions.priorYearMismatch | boolean | when outcomes is set | |
outcomes[].questions.missingCount | integer, at least 0 | when outcomes is set | |
outcomes[].questions.missingTruncated | boolean | when outcomes is set | Only the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account. |
outcomes[].questions.conflictsCount | integer, at least 0 | when outcomes is set | Name conflicts the import found, listed or not. |
outcomes[].questions.conflictsTruncated | boolean | when outcomes is set | Only the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest. |
outcomes[].questions.restoresCount | integer, at least 0 | when outcomes is set | Rows matching a deleted account, listed or not. |
outcomes[].questions.restoresTruncated | boolean | when outcomes is set | Only the first 5,000 rows matching a deleted account are listed; omitted answers skip the row. |
outcomes[].questions.duplicatesCount | integer, at least 0 | when outcomes is set | Duplicated account codes in the source, listed or not. |
outcomes[].questions.duplicatesTruncated | boolean | when outcomes is set | Only the first 5,000 duplicated codes are listed. Every duplicated code needs an answer: import this trial balance in Filemark to answer the rest. |
outcomes[].code | nullable string, up to 64 characters | when outcomes is set | Stable per-file refusal code when column mapping is needed. |
outcomes[].mappingPreview | nullable object | when outcomes is set | For trial_balance_columns_unrecognized: inspect these rows and submit columnMapping in a new ingestion. |
outcomes[].mappingPreview.sheetName | string, up to 31 characters | when outcomes is set | Worksheet the parser read. |
outcomes[].mappingPreview.headerRow | integer, 0 to 10000 | when outcomes is set | Suggested one-based header row; zero means none detected. |
outcomes[].mappingPreview.columnMap | map of integer, 1 to 16384 | when outcomes is set | Detected roles and their one-based column numbers. May be empty. |
outcomes[].mappingPreview.columnMap.{key} | integer, 1 to 16384 | per key | Detected roles and their one-based column numbers. May be empty. |
outcomes[].mappingPreview.rows | array of arrays, up to 15 items | when outcomes is set | First 15 rows, up to 128 columns each. Cells are text or null, truncated to 256 characters. |
error | nullable object | no | Why the job failed. Null unless status is failed. |
error.code | string, up to 64 characters | when error is set | Stable failure identifier. |
error.message | string, up to 1000 characters | when error is set | Readable explanation. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. When the job was created. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. Last change to the job. |
startedAt | nullable string (date-time) | no | ISO-8601 in UTC. When the worker claimed it. |
finishedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it reached a terminal status. |
Errors
See the shared error codes.
Get a workpaper
GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}
Returns one workpaper: its published metadata, its resourceVersion, its linked trial-balance account ids, and one page of its body: the typed row grid projected through the concept template's declared columns, or the narrative blocks. rowCount and narrativeBlockCount are the stored totals. Pass nextCursor as cursor for the next page until it is null; every page comes from the same resourceVersion, and if the workpaper changes between pages the next page returns 409 and reading restarts at the first page.
Scope: workpapers:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
cursor | query | nullable string, 1 to 4096 characters | no | The nextCursor value from the previous page. Omit it to start at the first page. A cursor stops working once the workpaper changes. |
Response
200. The workpaper with its resourceVersion.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"templateId": "<string>",
"name": "<string>",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"origin": "<string>",
"status": "<draft | complete>",
"category": "<string>",
"version": 1,
"isReviewed": false,
"reviewedAt": "2026-07-14T12:00:00Z",
"parseStatus": "<string>",
"detectedType": "<string>",
"sourceFileName": "<string>",
"assumption": "<string>",
"notes": "<string>",
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"linkedAccountIds": [
"<string>"
],
"columns": [
{
"key": "description",
"label": "<string>",
"type": "<text | number | date | select | cohorts>",
"options": [
"<string>"
],
"sectionId": "<string>",
"entryFields": [
{
"key": "acquisitionDate",
"label": "<string>",
"type": "<text | number | date | select>",
"options": [
"<string>"
]
}
]
}
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
},
"untypedCells": [
"<string>"
]
}
],
"rowCount": 0,
"rowsTruncated": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>",
"needsReview": false
}
],
"narrativeBlockCount": 0,
"narrativeTruncated": false,
"nextCursor": "<string>",
"completenessStatus": "<incomplete | complete>",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the workpaper. |
engagementId | string (uuid) | yes | UUID of the engagement it belongs to. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
templateId | nullable string | no | Concept template the workpaper was built from. |
name | string | yes | Practitioner-facing display name. |
customName | nullable string | no | The one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name). |
kind | one of row_grid, narrative | yes | Body shape. |
origin | string | yes | How the workpaper came to exist: blank, uploaded, generated, carryforward. |
status | one of draft, complete | yes | Preparer status. |
category | nullable string | no | Practitioner grouping label. |
version | integer, at least 1 | yes | Monotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number. |
isReviewed | boolean | yes | True once a practitioner marked it reviewed in Filemark. The API never sets it. |
reviewedAt | nullable string (date-time) | no | ISO-8601 in UTC. Null while unreviewed. |
parseStatus | nullable string | no | Outcome of parsing the uploaded source file, when there is one. |
detectedType | nullable string | no | Layout the parser detected in the source file. |
sourceFileName | nullable string, up to 512 characters | no | File name of the uploaded source, when there is one. |
assumption | nullable string | no | The preparer's stated assumption for the working paper. |
notes | nullable string | no | Preparer notes. |
tieOut | object | yes | Agreement with the linked trial-balance accounts. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
linkedAccountIds | array of strings | yes | Saved trial-balance account ids the workpaper is linked to. |
columns | array of objects | yes | Declared grid columns. Empty for a narrative workpaper. |
columns[].key | string | yes | Row key this column occupies. |
columns[].label | string | yes | Practitioner-facing column heading. |
columns[].type | one of text, number, date, select, cohorts | yes | Declared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields. |
columns[].options | nullable array of strings | no | Closed option list for a 'select' column. Null for every other type. |
columns[].sectionId | nullable string | no | Template section this column belongs to. Null for the main grid. |
columns[].entryFields | nullable array of objects | no | The declared fields of each entry of a 'cohorts' column. Null for every other type. |
columns[].entryFields[].key | string | when entryFields is set | Entry key this field occupies. |
columns[].entryFields[].label | string | when entryFields is set | Practitioner-facing field heading. |
columns[].entryFields[].type | one of text, number, date, select | when entryFields is set | Declared value type. A written entry value must match it. |
columns[].entryFields[].options | nullable array of strings | when entryFields is set | Closed option list for a 'select' field. Null for every other type. |
rows | array of objects | yes | One page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper. |
rows[].rowKey | string | yes | Stable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one. |
rows[].sectionId | nullable string | no | Template section the row sits in. Null for the main grid. |
rows[].cells | object | no | Cell values keyed by column key. Only keys the template declares appear here and only they are accepted on a write. A 'cohorts' cell is a list of entries, each carrying only its column's declared entryFields. |
rows[].cells.* | any | no | Members not listed here. |
rows[].untypedCells | array of strings | no | Declared column keys whose stored value does not fit the column's declared type, so it is not in cells. Correct the cell in Filemark or write it again through an update. Empty when every declared cell is published. |
rowCount | integer, at least 0 | yes | Total rows stored, across every page. |
rowsTruncated | boolean | yes | True when more rows follow this page; nextCursor reads them. |
narrative | array of objects | yes | One page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper. |
narrative[].id | string | yes | Stable block identifier. |
narrative[].kind | one of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divider | yes | Block kind. |
narrative[].content | nullable string | no | Plain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally. |
narrative[].level | nullable integer, 1 to 3 | no | Heading level, 1 to 3. Null for other kinds. |
narrative[].variant | nullable one of note, warning, risk, conclusion | no | Callout variant. Null for other kinds. |
narrative[].needsReview | boolean | yes | True while a carried-forward block still awaits practitioner review. |
narrativeBlockCount | integer, at least 0 | yes | Total narrative blocks stored, across every page. |
narrativeTruncated | boolean | yes | True when more narrative blocks follow this page; nextCursor reads them. |
nextCursor | nullable string, up to 4096 characters | no | Continues the body: the next page of rows for a row grid, of blocks for a narrative. Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
completenessStatus | nullable one of incomplete, complete | no | Narrative completeness checklist state. Null for a row-grid workpaper. |
lifecycle | object | yes | Live or archived (recoverable in Filemark). |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
Errors
In addition to the shared error codes:
400: The cursor is malformed, or was issued for another workpaper or list.404: The workpaper does not exist in the caller's organization or client scope.409: The workpaper changed since the cursor's page was read (codeconflict). Read it again from the first page.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Update a workpaper
PATCH /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}
Changes the fields you send: status, name, category, assumption, notes, grid rows, or the narrative body. Rows merge by rowKey unless replaceRows is true. Review state, signoff and carry-forward provenance are never writable. Send the workpaper's current resourceVersion in If-Match.
Scope: workpapers:write.
Request
curl --request PATCH https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"status": "<draft | complete>",
"customName": "<string>",
"category": "<string>",
"assumption": "<string>",
"notes": "<string>",
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
}
}
],
"replaceRows": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>"
}
]
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.patch(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"status": "<draft | complete>",
"customName": "<string>",
"category": "<string>",
"assumption": "<string>",
"notes": "<string>",
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>",
},
},
],
"replaceRows": False,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>",
},
],
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"status": "<draft | complete>",
"customName": "<string>",
"category": "<string>",
"assumption": "<string>",
"notes": "<string>",
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
}
}
],
"replaceRows": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>"
}
]
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The workpaper's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
Send only the fields to change. Review, signoff and provenance are never writable.
| Field | Type | Required | Description |
|---|---|---|---|
status | nullable one of draft, complete | no | Preparer status. |
customName | nullable string, up to 200 characters | no | |
category | nullable string, up to 100 characters | no | |
assumption | nullable string, up to 4000 characters | no | |
notes | nullable string, up to 8000 characters | no | |
rows | nullable array of objects | no | Grid rows. A row with a rowKey replaces that row; a row without one is appended with a server-assigned key. Cells are validated against the template's declared column types; an undeclared column key is refused. There is no row count limit beyond the request body size, so a whole grid of any stored size can be sent. |
rows[].rowKey | nullable string, up to 200 characters | when rows is set | Stable key of the row to replace. Omit to append a new row. |
rows[].sectionId | nullable string, up to 100 characters | when rows is set | Template section the row belongs to. |
rows[].cells | object | when rows is set | Cell values keyed by the template's declared column keys. |
rows[].cells.* | any | no | Members not listed here. |
replaceRows | boolean | no | True replaces the whole grid with rows; false (the default) merges rows by rowKey and leaves untouched rows alone. A JSON boolean only: the string "false" is refused, never read as a replace. |
narrative | nullable array of objects | no | Replacement narrative body, in document order: every block of the memo, read in full through nextCursor first. Validated by the product's narrative validator; a malformed citation token is refused. There is no block count limit beyond the request body size, so any stored memo can be replaced whole. Send the resourceVersion of the pages you read in If-Match; a body read from an older version is refused. |
narrative[].id | nullable string, up to 200 characters | when narrative is set | Block identifier. Omit for a new block. |
narrative[].kind | one of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divider | when narrative is set | Block kind. |
narrative[].content | nullable string, up to 100000 characters | when narrative is set | Plain-text content in the token grammar. |
narrative[].level | nullable integer, 1 to 3 | when narrative is set | Heading level. |
narrative[].variant | nullable one of note, warning, risk, conclusion | when narrative is set | Callout variant. |
Response
200. The workpaper after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"templateId": "<string>",
"name": "<string>",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"origin": "<string>",
"status": "<draft | complete>",
"category": "<string>",
"version": 1,
"isReviewed": false,
"reviewedAt": "2026-07-14T12:00:00Z",
"parseStatus": "<string>",
"detectedType": "<string>",
"sourceFileName": "<string>",
"assumption": "<string>",
"notes": "<string>",
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"linkedAccountIds": [
"<string>"
],
"columns": [
{
"key": "description",
"label": "<string>",
"type": "<text | number | date | select | cohorts>",
"options": [
"<string>"
],
"sectionId": "<string>",
"entryFields": [
{
"key": "acquisitionDate",
"label": "<string>",
"type": "<text | number | date | select>",
"options": [
"<string>"
]
}
]
}
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
},
"untypedCells": [
"<string>"
]
}
],
"rowCount": 0,
"rowsTruncated": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>",
"needsReview": false
}
],
"narrativeBlockCount": 0,
"narrativeTruncated": false,
"nextCursor": "<string>",
"completenessStatus": "<incomplete | complete>",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the workpaper. |
engagementId | string (uuid) | yes | UUID of the engagement it belongs to. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
templateId | nullable string | no | Concept template the workpaper was built from. |
name | string | yes | Practitioner-facing display name. |
customName | nullable string | no | The one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name). |
kind | one of row_grid, narrative | yes | Body shape. |
origin | string | yes | How the workpaper came to exist: blank, uploaded, generated, carryforward. |
status | one of draft, complete | yes | Preparer status. |
category | nullable string | no | Practitioner grouping label. |
version | integer, at least 1 | yes | Monotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number. |
isReviewed | boolean | yes | True once a practitioner marked it reviewed in Filemark. The API never sets it. |
reviewedAt | nullable string (date-time) | no | ISO-8601 in UTC. Null while unreviewed. |
parseStatus | nullable string | no | Outcome of parsing the uploaded source file, when there is one. |
detectedType | nullable string | no | Layout the parser detected in the source file. |
sourceFileName | nullable string, up to 512 characters | no | File name of the uploaded source, when there is one. |
assumption | nullable string | no | The preparer's stated assumption for the working paper. |
notes | nullable string | no | Preparer notes. |
tieOut | object | yes | Agreement with the linked trial-balance accounts. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
linkedAccountIds | array of strings | yes | Saved trial-balance account ids the workpaper is linked to. |
columns | array of objects | yes | Declared grid columns. Empty for a narrative workpaper. |
columns[].key | string | yes | Row key this column occupies. |
columns[].label | string | yes | Practitioner-facing column heading. |
columns[].type | one of text, number, date, select, cohorts | yes | Declared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields. |
columns[].options | nullable array of strings | no | Closed option list for a 'select' column. Null for every other type. |
columns[].sectionId | nullable string | no | Template section this column belongs to. Null for the main grid. |
columns[].entryFields | nullable array of objects | no | The declared fields of each entry of a 'cohorts' column. Null for every other type. |
columns[].entryFields[].key | string | when entryFields is set | Entry key this field occupies. |
columns[].entryFields[].label | string | when entryFields is set | Practitioner-facing field heading. |
columns[].entryFields[].type | one of text, number, date, select | when entryFields is set | Declared value type. A written entry value must match it. |
columns[].entryFields[].options | nullable array of strings | when entryFields is set | Closed option list for a 'select' field. Null for every other type. |
rows | array of objects | yes | One page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper. |
rows[].rowKey | string | yes | Stable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one. |
rows[].sectionId | nullable string | no | Template section the row sits in. Null for the main grid. |
rows[].cells | object | no | Cell values keyed by column key. Only keys the template declares appear here and only they are accepted on a write. A 'cohorts' cell is a list of entries, each carrying only its column's declared entryFields. |
rows[].cells.* | any | no | Members not listed here. |
rows[].untypedCells | array of strings | no | Declared column keys whose stored value does not fit the column's declared type, so it is not in cells. Correct the cell in Filemark or write it again through an update. Empty when every declared cell is published. |
rowCount | integer, at least 0 | yes | Total rows stored, across every page. |
rowsTruncated | boolean | yes | True when more rows follow this page; nextCursor reads them. |
narrative | array of objects | yes | One page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper. |
narrative[].id | string | yes | Stable block identifier. |
narrative[].kind | one of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divider | yes | Block kind. |
narrative[].content | nullable string | no | Plain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally. |
narrative[].level | nullable integer, 1 to 3 | no | Heading level, 1 to 3. Null for other kinds. |
narrative[].variant | nullable one of note, warning, risk, conclusion | no | Callout variant. Null for other kinds. |
narrative[].needsReview | boolean | yes | True while a carried-forward block still awaits practitioner review. |
narrativeBlockCount | integer, at least 0 | yes | Total narrative blocks stored, across every page. |
narrativeTruncated | boolean | yes | True when more narrative blocks follow this page; nextCursor reads them. |
nextCursor | nullable string, up to 4096 characters | no | Continues the body: the next page of rows for a row grid, of blocks for a narrative. Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
completenessStatus | nullable one of incomplete, complete | no | Narrative completeness checklist state. Null for a row-grid workpaper. |
lifecycle | object | yes | Live or archived (recoverable in Filemark). |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
warnings | array of objects | no | Non-blocking conditions: an unknown template, a dropped cell, a partial seed. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.
Archive a workpaper
POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/archive
Soft-deletes the workpaper. It stays restorable while the return is editable. Refused while the return is filed or partner-signed.
Scope: workpapers:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/archive \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/archive",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/archive`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The workpaper's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The workpaper after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"templateId": "<string>",
"name": "<string>",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"origin": "<string>",
"status": "<draft | complete>",
"category": "<string>",
"version": 1,
"isReviewed": false,
"reviewedAt": "2026-07-14T12:00:00Z",
"parseStatus": "<string>",
"detectedType": "<string>",
"sourceFileName": "<string>",
"assumption": "<string>",
"notes": "<string>",
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"linkedAccountIds": [
"<string>"
],
"columns": [
{
"key": "description",
"label": "<string>",
"type": "<text | number | date | select | cohorts>",
"options": [
"<string>"
],
"sectionId": "<string>",
"entryFields": [
{
"key": "acquisitionDate",
"label": "<string>",
"type": "<text | number | date | select>",
"options": [
"<string>"
]
}
]
}
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
},
"untypedCells": [
"<string>"
]
}
],
"rowCount": 0,
"rowsTruncated": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>",
"needsReview": false
}
],
"narrativeBlockCount": 0,
"narrativeTruncated": false,
"nextCursor": "<string>",
"completenessStatus": "<incomplete | complete>",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the workpaper. |
engagementId | string (uuid) | yes | UUID of the engagement it belongs to. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
templateId | nullable string | no | Concept template the workpaper was built from. |
name | string | yes | Practitioner-facing display name. |
customName | nullable string | no | The one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name). |
kind | one of row_grid, narrative | yes | Body shape. |
origin | string | yes | How the workpaper came to exist: blank, uploaded, generated, carryforward. |
status | one of draft, complete | yes | Preparer status. |
category | nullable string | no | Practitioner grouping label. |
version | integer, at least 1 | yes | Monotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number. |
isReviewed | boolean | yes | True once a practitioner marked it reviewed in Filemark. The API never sets it. |
reviewedAt | nullable string (date-time) | no | ISO-8601 in UTC. Null while unreviewed. |
parseStatus | nullable string | no | Outcome of parsing the uploaded source file, when there is one. |
detectedType | nullable string | no | Layout the parser detected in the source file. |
sourceFileName | nullable string, up to 512 characters | no | File name of the uploaded source, when there is one. |
assumption | nullable string | no | The preparer's stated assumption for the working paper. |
notes | nullable string | no | Preparer notes. |
tieOut | object | yes | Agreement with the linked trial-balance accounts. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
linkedAccountIds | array of strings | yes | Saved trial-balance account ids the workpaper is linked to. |
columns | array of objects | yes | Declared grid columns. Empty for a narrative workpaper. |
columns[].key | string | yes | Row key this column occupies. |
columns[].label | string | yes | Practitioner-facing column heading. |
columns[].type | one of text, number, date, select, cohorts | yes | Declared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields. |
columns[].options | nullable array of strings | no | Closed option list for a 'select' column. Null for every other type. |
columns[].sectionId | nullable string | no | Template section this column belongs to. Null for the main grid. |
columns[].entryFields | nullable array of objects | no | The declared fields of each entry of a 'cohorts' column. Null for every other type. |
columns[].entryFields[].key | string | when entryFields is set | Entry key this field occupies. |
columns[].entryFields[].label | string | when entryFields is set | Practitioner-facing field heading. |
columns[].entryFields[].type | one of text, number, date, select | when entryFields is set | Declared value type. A written entry value must match it. |
columns[].entryFields[].options | nullable array of strings | when entryFields is set | Closed option list for a 'select' field. Null for every other type. |
rows | array of objects | yes | One page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper. |
rows[].rowKey | string | yes | Stable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one. |
rows[].sectionId | nullable string | no | Template section the row sits in. Null for the main grid. |
rows[].cells | object | no | Cell values keyed by column key. Only keys the template declares appear here and only they are accepted on a write. A 'cohorts' cell is a list of entries, each carrying only its column's declared entryFields. |
rows[].cells.* | any | no | Members not listed here. |
rows[].untypedCells | array of strings | no | Declared column keys whose stored value does not fit the column's declared type, so it is not in cells. Correct the cell in Filemark or write it again through an update. Empty when every declared cell is published. |
rowCount | integer, at least 0 | yes | Total rows stored, across every page. |
rowsTruncated | boolean | yes | True when more rows follow this page; nextCursor reads them. |
narrative | array of objects | yes | One page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper. |
narrative[].id | string | yes | Stable block identifier. |
narrative[].kind | one of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divider | yes | Block kind. |
narrative[].content | nullable string | no | Plain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally. |
narrative[].level | nullable integer, 1 to 3 | no | Heading level, 1 to 3. Null for other kinds. |
narrative[].variant | nullable one of note, warning, risk, conclusion | no | Callout variant. Null for other kinds. |
narrative[].needsReview | boolean | yes | True while a carried-forward block still awaits practitioner review. |
narrativeBlockCount | integer, at least 0 | yes | Total narrative blocks stored, across every page. |
narrativeTruncated | boolean | yes | True when more narrative blocks follow this page; nextCursor reads them. |
nextCursor | nullable string, up to 4096 characters | no | Continues the body: the next page of rows for a row grid, of blocks for a narrative. Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
completenessStatus | nullable one of incomplete, complete | no | Narrative completeness checklist state. Null for a row-grid workpaper. |
lifecycle | object | yes | Live or archived (recoverable in Filemark). |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
warnings | array of objects | no | Non-blocking conditions: an unknown template, a dropped cell, a partial seed. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.
Restore an archived workpaper
POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/restore
Restores an archived workpaper. Refused when a live workpaper already holds a template that permits one instance for the tax year.
Scope: workpapers:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/restore \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/restore",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/restore`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The workpaper's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Response
200. The workpaper after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"templateId": "<string>",
"name": "<string>",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"origin": "<string>",
"status": "<draft | complete>",
"category": "<string>",
"version": 1,
"isReviewed": false,
"reviewedAt": "2026-07-14T12:00:00Z",
"parseStatus": "<string>",
"detectedType": "<string>",
"sourceFileName": "<string>",
"assumption": "<string>",
"notes": "<string>",
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"linkedAccountIds": [
"<string>"
],
"columns": [
{
"key": "description",
"label": "<string>",
"type": "<text | number | date | select | cohorts>",
"options": [
"<string>"
],
"sectionId": "<string>",
"entryFields": [
{
"key": "acquisitionDate",
"label": "<string>",
"type": "<text | number | date | select>",
"options": [
"<string>"
]
}
]
}
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
},
"untypedCells": [
"<string>"
]
}
],
"rowCount": 0,
"rowsTruncated": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>",
"needsReview": false
}
],
"narrativeBlockCount": 0,
"narrativeTruncated": false,
"nextCursor": "<string>",
"completenessStatus": "<incomplete | complete>",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the workpaper. |
engagementId | string (uuid) | yes | UUID of the engagement it belongs to. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
templateId | nullable string | no | Concept template the workpaper was built from. |
name | string | yes | Practitioner-facing display name. |
customName | nullable string | no | The one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name). |
kind | one of row_grid, narrative | yes | Body shape. |
origin | string | yes | How the workpaper came to exist: blank, uploaded, generated, carryforward. |
status | one of draft, complete | yes | Preparer status. |
category | nullable string | no | Practitioner grouping label. |
version | integer, at least 1 | yes | Monotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number. |
isReviewed | boolean | yes | True once a practitioner marked it reviewed in Filemark. The API never sets it. |
reviewedAt | nullable string (date-time) | no | ISO-8601 in UTC. Null while unreviewed. |
parseStatus | nullable string | no | Outcome of parsing the uploaded source file, when there is one. |
detectedType | nullable string | no | Layout the parser detected in the source file. |
sourceFileName | nullable string, up to 512 characters | no | File name of the uploaded source, when there is one. |
assumption | nullable string | no | The preparer's stated assumption for the working paper. |
notes | nullable string | no | Preparer notes. |
tieOut | object | yes | Agreement with the linked trial-balance accounts. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
linkedAccountIds | array of strings | yes | Saved trial-balance account ids the workpaper is linked to. |
columns | array of objects | yes | Declared grid columns. Empty for a narrative workpaper. |
columns[].key | string | yes | Row key this column occupies. |
columns[].label | string | yes | Practitioner-facing column heading. |
columns[].type | one of text, number, date, select, cohorts | yes | Declared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields. |
columns[].options | nullable array of strings | no | Closed option list for a 'select' column. Null for every other type. |
columns[].sectionId | nullable string | no | Template section this column belongs to. Null for the main grid. |
columns[].entryFields | nullable array of objects | no | The declared fields of each entry of a 'cohorts' column. Null for every other type. |
columns[].entryFields[].key | string | when entryFields is set | Entry key this field occupies. |
columns[].entryFields[].label | string | when entryFields is set | Practitioner-facing field heading. |
columns[].entryFields[].type | one of text, number, date, select | when entryFields is set | Declared value type. A written entry value must match it. |
columns[].entryFields[].options | nullable array of strings | when entryFields is set | Closed option list for a 'select' field. Null for every other type. |
rows | array of objects | yes | One page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper. |
rows[].rowKey | string | yes | Stable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one. |
rows[].sectionId | nullable string | no | Template section the row sits in. Null for the main grid. |
rows[].cells | object | no | Cell values keyed by column key. Only keys the template declares appear here and only they are accepted on a write. A 'cohorts' cell is a list of entries, each carrying only its column's declared entryFields. |
rows[].cells.* | any | no | Members not listed here. |
rows[].untypedCells | array of strings | no | Declared column keys whose stored value does not fit the column's declared type, so it is not in cells. Correct the cell in Filemark or write it again through an update. Empty when every declared cell is published. |
rowCount | integer, at least 0 | yes | Total rows stored, across every page. |
rowsTruncated | boolean | yes | True when more rows follow this page; nextCursor reads them. |
narrative | array of objects | yes | One page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper. |
narrative[].id | string | yes | Stable block identifier. |
narrative[].kind | one of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divider | yes | Block kind. |
narrative[].content | nullable string | no | Plain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally. |
narrative[].level | nullable integer, 1 to 3 | no | Heading level, 1 to 3. Null for other kinds. |
narrative[].variant | nullable one of note, warning, risk, conclusion | no | Callout variant. Null for other kinds. |
narrative[].needsReview | boolean | yes | True while a carried-forward block still awaits practitioner review. |
narrativeBlockCount | integer, at least 0 | yes | Total narrative blocks stored, across every page. |
narrativeTruncated | boolean | yes | True when more narrative blocks follow this page; nextCursor reads them. |
nextCursor | nullable string, up to 4096 characters | no | Continues the body: the next page of rows for a row grid, of blocks for a narrative. Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
completenessStatus | nullable one of incomplete, complete | no | Narrative completeness checklist state. Null for a row-grid workpaper. |
lifecycle | object | yes | Live or archived (recoverable in Filemark). |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
warnings | array of objects | no | Non-blocking conditions: an unknown template, a dropped cell, a partial seed. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.
List workpaper evidence
GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/evidence
Returns the evidence behind the workpaper: the parsed source file it was built from and every attached engagement document. Metadata only; file name, label, digest, parse outcome; never bytes, storage keys, or URLs. total counts every item; pass nextCursor as cursor for the next page until hasMore is false. A cursor stops working once the workpaper changes, and the next page then returns 409.
Scope: workpapers:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/evidence \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/evidence",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/evidence`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
cursor | query | nullable string, 1 to 4096 characters | no | The nextCursor value from the previous page. Omit it to start at the first page. A cursor stops working once the workpaper changes. |
Response
200. One page of evidence metadata for the workpaper.
{
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"evidence": [
{
"documentId": "<string>",
"kind": "<source_file | supporting>",
"fileName": "<string>",
"label": "<string>",
"mime": "<string>",
"contentSha256": "<string>",
"parseStatus": "<string>",
"detectedType": "<string>",
"uploadedAt": "2026-07-14T12:00:00Z"
}
],
"total": 0,
"hasMore": false,
"nextCursor": "<string>"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
workpaperId | string (uuid) | yes | UUID of the workpaper. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
evidence | array of objects | yes | One page of the evidence behind the workpaper, at most 200 items: the parsed source file first, then each attachment in the order it was attached. |
evidence[].documentId | string | yes | Public document id. 'source' for the workpaper's own uploaded file, otherwise the id listEngagementDocuments publishes for the attachment. |
evidence[].kind | one of source_file, supporting | yes | 'source_file' is the parsed original; 'supporting' is an attachment. |
evidence[].fileName | nullable string, up to 512 characters | no | |
evidence[].label | nullable string, up to 512 characters | no | Practitioner label. |
evidence[].mime | nullable string, up to 200 characters | no | |
evidence[].contentSha256 | nullable string, up to 64 characters | no | SHA-256 digest of the stored file, when the store recorded one. |
evidence[].parseStatus | nullable string, up to 100 characters | no | |
evidence[].detectedType | nullable string, up to 100 characters | no | |
evidence[].uploadedAt | nullable string (date-time) | no | ISO-8601 in UTC. |
total | integer, at least 0 | yes | Total evidence items, across every page. |
hasMore | boolean | no | True when more evidence follows this page; nextCursor is set whenever it is true. |
nextCursor | nullable string, up to 4096 characters | no | Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
Errors
In addition to the shared error codes:
400: The cursor is malformed, or was issued for another workpaper or list.404: The workpaper does not exist in the caller's organization or client scope.409: The workpaper changed since the cursor's page was read (codeconflict). Read it again from the first page.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Attach or detach workpaper evidence
POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/evidence
Attaches one of the engagement's documents to the workpaper as evidence, or detaches it. The document must already belong to the engagement; uploads happen through the document staging surface, never here. Send the workpaper's current resourceVersion in If-Match.
Scope: workpapers:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/evidence \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"action": "<attach | detach>",
"documentId": "<string>"
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/evidence",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"action": "<attach | detach>",
"documentId": "<string>",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/evidence`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"action": "<attach | detach>",
"documentId": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The workpaper's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
action | one of attach, detach | yes | 'attach' links a document to the workpaper; 'detach' unlinks it. |
documentId | string, 1 to 200 characters | yes | The engagement document to link or unlink. It must already be one of the engagement's supporting documents (listEngagementDocuments publishes them with kind 'supporting'); this is where a completed upload lands. The workpaper's own parsed source file is not detachable. |
Response
200. The workpaper after the change, or the earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"templateId": "<string>",
"name": "<string>",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"origin": "<string>",
"status": "<draft | complete>",
"category": "<string>",
"version": 1,
"isReviewed": false,
"reviewedAt": "2026-07-14T12:00:00Z",
"parseStatus": "<string>",
"detectedType": "<string>",
"sourceFileName": "<string>",
"assumption": "<string>",
"notes": "<string>",
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"linkedAccountIds": [
"<string>"
],
"columns": [
{
"key": "description",
"label": "<string>",
"type": "<text | number | date | select | cohorts>",
"options": [
"<string>"
],
"sectionId": "<string>",
"entryFields": [
{
"key": "acquisitionDate",
"label": "<string>",
"type": "<text | number | date | select>",
"options": [
"<string>"
]
}
]
}
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
},
"untypedCells": [
"<string>"
]
}
],
"rowCount": 0,
"rowsTruncated": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>",
"needsReview": false
}
],
"narrativeBlockCount": 0,
"narrativeTruncated": false,
"nextCursor": "<string>",
"completenessStatus": "<incomplete | complete>",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the workpaper. |
engagementId | string (uuid) | yes | UUID of the engagement it belongs to. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
templateId | nullable string | no | Concept template the workpaper was built from. |
name | string | yes | Practitioner-facing display name. |
customName | nullable string | no | The one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name). |
kind | one of row_grid, narrative | yes | Body shape. |
origin | string | yes | How the workpaper came to exist: blank, uploaded, generated, carryforward. |
status | one of draft, complete | yes | Preparer status. |
category | nullable string | no | Practitioner grouping label. |
version | integer, at least 1 | yes | Monotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number. |
isReviewed | boolean | yes | True once a practitioner marked it reviewed in Filemark. The API never sets it. |
reviewedAt | nullable string (date-time) | no | ISO-8601 in UTC. Null while unreviewed. |
parseStatus | nullable string | no | Outcome of parsing the uploaded source file, when there is one. |
detectedType | nullable string | no | Layout the parser detected in the source file. |
sourceFileName | nullable string, up to 512 characters | no | File name of the uploaded source, when there is one. |
assumption | nullable string | no | The preparer's stated assumption for the working paper. |
notes | nullable string | no | Preparer notes. |
tieOut | object | yes | Agreement with the linked trial-balance accounts. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
linkedAccountIds | array of strings | yes | Saved trial-balance account ids the workpaper is linked to. |
columns | array of objects | yes | Declared grid columns. Empty for a narrative workpaper. |
columns[].key | string | yes | Row key this column occupies. |
columns[].label | string | yes | Practitioner-facing column heading. |
columns[].type | one of text, number, date, select, cohorts | yes | Declared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields. |
columns[].options | nullable array of strings | no | Closed option list for a 'select' column. Null for every other type. |
columns[].sectionId | nullable string | no | Template section this column belongs to. Null for the main grid. |
columns[].entryFields | nullable array of objects | no | The declared fields of each entry of a 'cohorts' column. Null for every other type. |
columns[].entryFields[].key | string | when entryFields is set | Entry key this field occupies. |
columns[].entryFields[].label | string | when entryFields is set | Practitioner-facing field heading. |
columns[].entryFields[].type | one of text, number, date, select | when entryFields is set | Declared value type. A written entry value must match it. |
columns[].entryFields[].options | nullable array of strings | when entryFields is set | Closed option list for a 'select' field. Null for every other type. |
rows | array of objects | yes | One page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper. |
rows[].rowKey | string | yes | Stable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one. |
rows[].sectionId | nullable string | no | Template section the row sits in. Null for the main grid. |
rows[].cells | object | no | Cell values keyed by column key. Only keys the template declares appear here and only they are accepted on a write. A 'cohorts' cell is a list of entries, each carrying only its column's declared entryFields. |
rows[].cells.* | any | no | Members not listed here. |
rows[].untypedCells | array of strings | no | Declared column keys whose stored value does not fit the column's declared type, so it is not in cells. Correct the cell in Filemark or write it again through an update. Empty when every declared cell is published. |
rowCount | integer, at least 0 | yes | Total rows stored, across every page. |
rowsTruncated | boolean | yes | True when more rows follow this page; nextCursor reads them. |
narrative | array of objects | yes | One page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper. |
narrative[].id | string | yes | Stable block identifier. |
narrative[].kind | one of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divider | yes | Block kind. |
narrative[].content | nullable string | no | Plain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally. |
narrative[].level | nullable integer, 1 to 3 | no | Heading level, 1 to 3. Null for other kinds. |
narrative[].variant | nullable one of note, warning, risk, conclusion | no | Callout variant. Null for other kinds. |
narrative[].needsReview | boolean | yes | True while a carried-forward block still awaits practitioner review. |
narrativeBlockCount | integer, at least 0 | yes | Total narrative blocks stored, across every page. |
narrativeTruncated | boolean | yes | True when more narrative blocks follow this page; nextCursor reads them. |
nextCursor | nullable string, up to 4096 characters | no | Continues the body: the next page of rows for a row grid, of blocks for a narrative. Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
completenessStatus | nullable one of incomplete, complete | no | Narrative completeness checklist state. Null for a row-grid workpaper. |
lifecycle | object | yes | Live or archived (recoverable in Filemark). |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
warnings | array of objects | no | Non-blocking conditions: an unknown template, a dropped cell, a partial seed. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.
List workpaper account links
GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/links
Returns the trial-balance accounts the workpaper is linked to, with the balances the tie-out compares against.
Scope: workpapers:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/links \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/links",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/links`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
Response
200. The workpaper's linked accounts.
{
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"accounts": [
{
"accountId": "<string>",
"accountCode": "<string>",
"accountName": "<string>",
"balance": 0,
"accountType": "<string>"
}
],
"total": 0
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
workpaperId | string (uuid) | yes | UUID of the workpaper. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
accounts | array of objects | yes | Linked accounts with their saved trial-balance balances. |
accounts[].accountId | string | yes | Saved trial-balance account id. |
accounts[].accountCode | nullable string | no | Account code, when known. |
accounts[].accountName | nullable string | no | Account name, when known. |
accounts[].balance | nullable number | no | Current-year balance from the saved trial balance. |
accounts[].accountType | nullable string | no | Classified account type. |
total | integer, at least 0 | yes | Total linked accounts. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Set workpaper account links
POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/links
Sets, adds, or removes the trial-balance accounts the workpaper covers. Every account must belong to this engagement's saved trial balance. Changing the link set moves the expected tie-out total, so the stored tie-out returns to 'pending' until it is verified again. Send the workpaper's current resourceVersion in If-Match.
Scope: workpapers:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/links \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"action": "<set | add | remove>",
"accountIds": [
"<string>"
]
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/links",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"action": "<set | add | remove>",
"accountIds": [
"<string>",
],
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/links`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"action": "<set | add | remove>",
"accountIds": [
"<string>"
]
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
If-Match | header | nullable string, up to 258 characters | no | The workpaper's current resourceVersion, quoted as an ETag. Required: a missing value is refused with precondition_required (428) and a stale one with resource_version_conflict (412). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
action | one of set, add, remove | yes | 'set' replaces the link set; 'add' and 'remove' change it. |
accountIds | array of strings, 1 to 500 items | yes | Saved trial-balance account ids. Every id must belong to this engagement's saved trial balance; an unknown id is refused. |
Response
200. The workpaper after the change, or the earlier result of the same Idempotency-Key.
{
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"accounts": [
{
"accountId": "<string>",
"accountCode": "<string>",
"accountName": "<string>",
"balance": 0,
"accountType": "<string>"
}
],
"total": 0
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
workpaperId | string (uuid) | yes | UUID of the workpaper. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
accounts | array of objects | yes | Linked accounts with their saved trial-balance balances. |
accounts[].accountId | string | yes | Saved trial-balance account id. |
accounts[].accountCode | nullable string | no | Account code, when known. |
accounts[].accountName | nullable string | no | Account name, when known. |
accounts[].balance | nullable number | no | Current-year balance from the saved trial balance. |
accounts[].accountType | nullable string | no | Classified account type. |
total | integer, at least 0 | yes | Total linked accounts. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.
Validate a workpaper
POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/validate
Runs Filemark's own workpaper validation over the stored workpaper and reports every finding: the write floor, the registry-typed columns, the narrative block and completeness rules, and the tie-out against the linked trial-balance accounts. Nothing is written back.
Scope: workpapers:read.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/validate \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/validate",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/validate`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
Response
200. The findings and the tie-out as it would stand.
{
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"valid": false,
"findings": [
{
"code": "row_cell_type",
"severity": "<error | warning>",
"message": "<string>",
"path": "<string>"
}
],
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"validation": {
"status": "<matched | difference | incomplete>",
"tbBalance": 0,
"workpaperValue": 0,
"difference": 0,
"tolerance": 0,
"linkedAccounts": [
{
"accountCode": "<string>",
"accountName": "<string>",
"balance": 0
}
],
"errors": {
"<key>": "<string>"
}
},
"evaluatedAt": "2026-07-14T12:00:00Z"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
workpaperId | string (uuid) | yes | UUID of the workpaper. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
valid | boolean | yes | True when no error-severity finding was raised. |
findings | array of objects | yes | Every finding, errors first. |
findings[].code | string | yes | Stable finding identifier. |
findings[].severity | one of error, warning | yes | 'error' blocks a save in Filemark; 'warning' is advisory. |
findings[].message | string | yes | What is wrong and what to do about it. |
findings[].path | nullable string | no | Where the finding is, e.g. rows[3].openingBalance. |
tieOut | object | yes | Tie-out as it would stand; nothing is written back. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
validation | nullable object | no | Complete comparison receipt. Null when no tie-out comparison could be run. |
validation.status | one of matched, difference, incomplete | when validation is set | Current comparison verdict; incomplete asserts no agreement. |
validation.tbBalance | nullable number | when validation is set | Absolute net balance of the linked accounts. |
validation.workpaperValue | nullable number | when validation is set | Absolute workpaper amount compared. |
validation.difference | nullable number | when validation is set | Comparison difference projected to cents. |
validation.tolerance | number | when validation is set | Strict comparison tolerance, in dollars. |
validation.linkedAccounts | array of objects | when validation is set | Every linked account operand used by this evaluation. |
validation.linkedAccounts[].accountCode | nullable string | when validation is set | Linked account code. |
validation.linkedAccounts[].accountName | nullable string | when validation is set | Linked account name. |
validation.linkedAccounts[].balance | nullable number | when validation is set | Balance used in the comparison. |
validation.errors | nullable map of string | when validation is set | Amount fields the validator could not represent, with corrective actions. |
validation.errors.{key} | string | per key | Amount fields the validator could not represent, with corrective actions. |
evaluatedAt | string (date-time) | yes | ISO-8601 in UTC. When this answer was computed; it is not a lease. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Get workpaper tie-out
GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/tie-out
Returns the workpaper's stored tie-out mode and status beside the linked account balances the comparison uses. A 'pending' status means the agreement has not been verified; 'incomplete' means an operand could not be read. Neither is a match.
Scope: workpapers:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/tie-out \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/tie-out",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/tie-out`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
Response
200. Tie-out state and the linked-account comparison.
{
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"accounts": [
{
"accountId": "<string>",
"accountCode": "<string>",
"accountName": "<string>",
"balance": 0,
"accountType": "<string>"
}
],
"trialBalanceTotal": 0
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
workpaperId | string (uuid) | yes | UUID of the workpaper. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
tieOut | object | yes | Stored tie-out state. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
accounts | array of objects | yes | Linked accounts with the balances the comparison uses. |
accounts[].accountId | string | yes | Saved trial-balance account id. |
accounts[].accountCode | nullable string | no | Account code, when known. |
accounts[].accountName | nullable string | no | Account name, when known. |
accounts[].balance | nullable number | no | Current-year balance from the saved trial balance. |
accounts[].accountType | nullable string | no | Classified account type. |
trialBalanceTotal | nullable number | no | Net total of the linked balances, absolute. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.409: The operation conflicts with the resource's current version or lifecycle state.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
List workpaper versions
GET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/versions
Returns the workpaper's recorded change history, most recent first: what each change did, which fields it touched, and when. Practitioner identities are never published. total counts every recorded change; pass nextCursor as cursor for older changes until hasMore is false. A cursor stops working once the workpaper changes, and the next page then returns 409.
Scope: workpapers:read.
Request
curl https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/versions \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN"import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.get(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/versions",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/versions`, {
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
cursor | query | nullable string, 1 to 4096 characters | no | The nextCursor value from the previous page. Omit it to start at the first page. A cursor stops working once the workpaper changes. |
Response
200. One page of the workpaper's change history.
{
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"versions": [
{
"version": 1,
"operation": "<created | updated | soft_deleted | restored>",
"changedFields": [
"<string>"
],
"changeSetId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"editedAt": "2026-07-14T12:00:00Z"
}
],
"total": 0,
"hasMore": false,
"nextCursor": "<string>"
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
workpaperId | string (uuid) | yes | UUID of the workpaper. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
versions | array of objects | yes | One page of the recorded history, most recent change first, at most 200. |
versions[].version | integer, at least 1 | yes | Workpaper version this change produced. |
versions[].operation | one of created, updated, soft_deleted, restored | yes | What the change did. |
versions[].changedFields | array of strings | yes | Top-level workpaper fields the change touched. Values are never published. |
versions[].changeSetId | string (uuid) | yes | Groups changes committed together. |
versions[].editedAt | string (date-time) | yes | ISO-8601 in UTC. |
total | integer, at least 0 | yes | Number of recorded changes to the workpaper, across every page. |
hasMore | boolean | no | True when older changes follow this page; nextCursor is set whenever it is true. |
nextCursor | nullable string, up to 4096 characters | no | Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
Errors
In addition to the shared error codes:
400: The cursor is malformed, or was issued for another workpaper or list.404: The workpaper does not exist in the caller's organization or client scope.409: The workpaper changed since the cursor's page was read (codeconflict). Read it again from the first page.422: One or more request parameters are invalid. The coderesult_not_representablemeans the selected strict payload contract admits the request but cannot represent its computed answer, or, on a saved-engagement scenario, that a computed result falls outside its target's published output contract.
Generate a workpaper from the general ledger
POST /api/v1/engagements/{engagement_id}/workpapers/generate
Builds a workpaper deterministically from the engagement's own saved general ledger for a template that has a builder. You supply the template and the accounts it covers; the server loads the accounts, the ledger and the fiscal period. A ledger without the detail the builder needs is refused, never answered with an empty workpaper.
Scope: workpapers:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/generate \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"templateId": "fixed-assets",
"linkedAccountIds": [
"<string>"
],
"customName": "<string>"
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/generate",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"templateId": "fixed-assets",
"linkedAccountIds": [
"<string>",
],
"customName": "<string>",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/generate`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"templateId": "fixed-assets",
"linkedAccountIds": [
"<string>"
],
"customName": "<string>"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string, 1 to 100 characters | yes | Template with a general-ledger builder. The server loads the engagement's own saved accounts and enabled GL; no ledger data is sent. |
linkedAccountIds | array of strings, 1 to 500 items | yes | Saved trial-balance account ids the generated workpaper covers. |
customName | nullable string, up to 200 characters | no |
Response
201. The workpaper was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"templateId": "<string>",
"name": "<string>",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"origin": "<string>",
"status": "<draft | complete>",
"category": "<string>",
"version": 1,
"isReviewed": false,
"reviewedAt": "2026-07-14T12:00:00Z",
"parseStatus": "<string>",
"detectedType": "<string>",
"sourceFileName": "<string>",
"assumption": "<string>",
"notes": "<string>",
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"linkedAccountIds": [
"<string>"
],
"columns": [
{
"key": "description",
"label": "<string>",
"type": "<text | number | date | select | cohorts>",
"options": [
"<string>"
],
"sectionId": "<string>",
"entryFields": [
{
"key": "acquisitionDate",
"label": "<string>",
"type": "<text | number | date | select>",
"options": [
"<string>"
]
}
]
}
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
},
"untypedCells": [
"<string>"
]
}
],
"rowCount": 0,
"rowsTruncated": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>",
"needsReview": false
}
],
"narrativeBlockCount": 0,
"narrativeTruncated": false,
"nextCursor": "<string>",
"completenessStatus": "<incomplete | complete>",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the workpaper. |
engagementId | string (uuid) | yes | UUID of the engagement it belongs to. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
templateId | nullable string | no | Concept template the workpaper was built from. |
name | string | yes | Practitioner-facing display name. |
customName | nullable string | no | The one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name). |
kind | one of row_grid, narrative | yes | Body shape. |
origin | string | yes | How the workpaper came to exist: blank, uploaded, generated, carryforward. |
status | one of draft, complete | yes | Preparer status. |
category | nullable string | no | Practitioner grouping label. |
version | integer, at least 1 | yes | Monotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number. |
isReviewed | boolean | yes | True once a practitioner marked it reviewed in Filemark. The API never sets it. |
reviewedAt | nullable string (date-time) | no | ISO-8601 in UTC. Null while unreviewed. |
parseStatus | nullable string | no | Outcome of parsing the uploaded source file, when there is one. |
detectedType | nullable string | no | Layout the parser detected in the source file. |
sourceFileName | nullable string, up to 512 characters | no | File name of the uploaded source, when there is one. |
assumption | nullable string | no | The preparer's stated assumption for the working paper. |
notes | nullable string | no | Preparer notes. |
tieOut | object | yes | Agreement with the linked trial-balance accounts. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
linkedAccountIds | array of strings | yes | Saved trial-balance account ids the workpaper is linked to. |
columns | array of objects | yes | Declared grid columns. Empty for a narrative workpaper. |
columns[].key | string | yes | Row key this column occupies. |
columns[].label | string | yes | Practitioner-facing column heading. |
columns[].type | one of text, number, date, select, cohorts | yes | Declared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields. |
columns[].options | nullable array of strings | no | Closed option list for a 'select' column. Null for every other type. |
columns[].sectionId | nullable string | no | Template section this column belongs to. Null for the main grid. |
columns[].entryFields | nullable array of objects | no | The declared fields of each entry of a 'cohorts' column. Null for every other type. |
columns[].entryFields[].key | string | when entryFields is set | Entry key this field occupies. |
columns[].entryFields[].label | string | when entryFields is set | Practitioner-facing field heading. |
columns[].entryFields[].type | one of text, number, date, select | when entryFields is set | Declared value type. A written entry value must match it. |
columns[].entryFields[].options | nullable array of strings | when entryFields is set | Closed option list for a 'select' field. Null for every other type. |
rows | array of objects | yes | One page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper. |
rows[].rowKey | string | yes | Stable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one. |
rows[].sectionId | nullable string | no | Template section the row sits in. Null for the main grid. |
rows[].cells | object | no | Cell values keyed by column key. Only keys the template declares appear here and only they are accepted on a write. A 'cohorts' cell is a list of entries, each carrying only its column's declared entryFields. |
rows[].cells.* | any | no | Members not listed here. |
rows[].untypedCells | array of strings | no | Declared column keys whose stored value does not fit the column's declared type, so it is not in cells. Correct the cell in Filemark or write it again through an update. Empty when every declared cell is published. |
rowCount | integer, at least 0 | yes | Total rows stored, across every page. |
rowsTruncated | boolean | yes | True when more rows follow this page; nextCursor reads them. |
narrative | array of objects | yes | One page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper. |
narrative[].id | string | yes | Stable block identifier. |
narrative[].kind | one of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divider | yes | Block kind. |
narrative[].content | nullable string | no | Plain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally. |
narrative[].level | nullable integer, 1 to 3 | no | Heading level, 1 to 3. Null for other kinds. |
narrative[].variant | nullable one of note, warning, risk, conclusion | no | Callout variant. Null for other kinds. |
narrative[].needsReview | boolean | yes | True while a carried-forward block still awaits practitioner review. |
narrativeBlockCount | integer, at least 0 | yes | Total narrative blocks stored, across every page. |
narrativeTruncated | boolean | yes | True when more narrative blocks follow this page; nextCursor reads them. |
nextCursor | nullable string, up to 4096 characters | no | Continues the body: the next page of rows for a row grid, of blocks for a narrative. Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
completenessStatus | nullable one of incomplete, complete | no | Narrative completeness checklist state. Null for a row-grid workpaper. |
lifecycle | object | yes | Live or archived (recoverable in Filemark). |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
warnings | array of objects | no | Non-blocking conditions: an unknown template, a dropped cell, a partial seed. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.
Carry a workpaper forward from the prior year
POST /api/v1/engagements/{engagement_id}/workpapers/carry-forward
Carries one narrative workpaper forward from an earlier engagement of the same firm: every block is re-templated for the new year and flagged for practitioner review, and the completeness checklist resets. A row grid's prior-year balances arrive through the engagement's closing balances instead, so a row-grid source is refused here.
Scope: workpapers:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/carry-forward \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"priorEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"priorWorkpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/carry-forward",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"priorEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"priorWorkpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/carry-forward`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"priorEngagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"priorWorkpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f"
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
priorEngagementId | string (uuid) | yes | UUID of the prior-year engagement holding the workpaper to carry. |
priorWorkpaperId | string (uuid) | yes | UUID of the prior-year workpaper to carry into this engagement. |
Response
201. The workpaper was created.
Also 200: The earlier result of the same Idempotency-Key.
{
"id": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"engagementId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"resourceVersion": "eyJ0IjoiY2xpZW50IiwidiI6WyIyMDI2LTAxLTAxVDAwOjAwOjAwKzAwOjAwIl19",
"templateId": "<string>",
"name": "<string>",
"customName": "<string>",
"kind": "<row_grid | narrative>",
"origin": "<string>",
"status": "<draft | complete>",
"category": "<string>",
"version": 1,
"isReviewed": false,
"reviewedAt": "2026-07-14T12:00:00Z",
"parseStatus": "<string>",
"detectedType": "<string>",
"sourceFileName": "<string>",
"assumption": "<string>",
"notes": "<string>",
"tieOut": {
"mode": "<sum | cell>",
"status": "<pending | matched | difference | incomplete>",
"expected": 0,
"actual": 0,
"difference": 0
},
"linkedAccountIds": [
"<string>"
],
"columns": [
{
"key": "description",
"label": "<string>",
"type": "<text | number | date | select | cohorts>",
"options": [
"<string>"
],
"sectionId": "<string>",
"entryFields": [
{
"key": "acquisitionDate",
"label": "<string>",
"type": "<text | number | date | select>",
"options": [
"<string>"
]
}
]
}
],
"rows": [
{
"rowKey": "<string>",
"sectionId": "<string>",
"cells": {
"<member>": "<any>"
},
"untypedCells": [
"<string>"
]
}
],
"rowCount": 0,
"rowsTruncated": false,
"narrative": [
{
"id": "<string>",
"kind": "<heading | paragraph | bulletList | numberedList | callout | table | exhibit | divider>",
"content": "<string>",
"level": 1,
"variant": "<note | warning | risk | conclusion>",
"needsReview": false
}
],
"narrativeBlockCount": 0,
"narrativeTruncated": false,
"nextCursor": "<string>",
"completenessStatus": "<incomplete | complete>",
"lifecycle": {
"state": "<active | archived>",
"archivedAt": "2026-07-14T12:00:00Z",
"restorableUntil": "2026-07-14T12:00:00Z"
},
"createdAt": "2026-07-14T12:00:00Z",
"updatedAt": "2026-07-14T12:00:00Z",
"warnings": [
{
"code": "unsupported_filing_year",
"message": "<string>"
}
]
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
id | string (uuid) | yes | UUID of the workpaper. |
engagementId | string (uuid) | yes | UUID of the engagement it belongs to. |
resourceVersion | string, 1 to 256 characters | yes | Opaque strong version of the record. Send it back as If-Match (REST) or expectedResourceVersion (MCP) on the next update, archive or restore; a stale value is refused with resource_version_conflict. |
templateId | nullable string | no | Concept template the workpaper was built from. |
name | string | yes | Practitioner-facing display name. |
customName | nullable string | no | The one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name). |
kind | one of row_grid, narrative | yes | Body shape. |
origin | string | yes | How the workpaper came to exist: blank, uploaded, generated, carryforward. |
status | one of draft, complete | yes | Preparer status. |
category | nullable string | no | Practitioner grouping label. |
version | integer, at least 1 | yes | Monotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number. |
isReviewed | boolean | yes | True once a practitioner marked it reviewed in Filemark. The API never sets it. |
reviewedAt | nullable string (date-time) | no | ISO-8601 in UTC. Null while unreviewed. |
parseStatus | nullable string | no | Outcome of parsing the uploaded source file, when there is one. |
detectedType | nullable string | no | Layout the parser detected in the source file. |
sourceFileName | nullable string, up to 512 characters | no | File name of the uploaded source, when there is one. |
assumption | nullable string | no | The preparer's stated assumption for the working paper. |
notes | nullable string | no | Preparer notes. |
tieOut | object | yes | Agreement with the linked trial-balance accounts. |
tieOut.mode | nullable one of sum, cell | no | 'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out. |
tieOut.status | nullable one of pending, matched, difference, incomplete | no | Filemark's own tie-out vocabulary. 'matched' is agreement within the cent tolerance; 'difference' is a real discrepancy; 'pending' means it has not been verified yet; 'incomplete' means an operand could not be read, which is never a silent zero. Only 'matched' is agreement. Null for a narrative workpaper, which does not tie out. |
tieOut.expected | nullable number | no | Trial-balance total the workpaper must agree with. |
tieOut.actual | nullable number | no | Amount the workpaper asserts. |
tieOut.difference | nullable number | no | expected minus actual, to the cent. |
linkedAccountIds | array of strings | yes | Saved trial-balance account ids the workpaper is linked to. |
columns | array of objects | yes | Declared grid columns. Empty for a narrative workpaper. |
columns[].key | string | yes | Row key this column occupies. |
columns[].label | string | yes | Practitioner-facing column heading. |
columns[].type | one of text, number, date, select, cohorts | yes | Declared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields. |
columns[].options | nullable array of strings | no | Closed option list for a 'select' column. Null for every other type. |
columns[].sectionId | nullable string | no | Template section this column belongs to. Null for the main grid. |
columns[].entryFields | nullable array of objects | no | The declared fields of each entry of a 'cohorts' column. Null for every other type. |
columns[].entryFields[].key | string | when entryFields is set | Entry key this field occupies. |
columns[].entryFields[].label | string | when entryFields is set | Practitioner-facing field heading. |
columns[].entryFields[].type | one of text, number, date, select | when entryFields is set | Declared value type. A written entry value must match it. |
columns[].entryFields[].options | nullable array of strings | when entryFields is set | Closed option list for a 'select' field. Null for every other type. |
rows | array of objects | yes | One page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper. |
rows[].rowKey | string | yes | Stable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one. |
rows[].sectionId | nullable string | no | Template section the row sits in. Null for the main grid. |
rows[].cells | object | no | Cell values keyed by column key. Only keys the template declares appear here and only they are accepted on a write. A 'cohorts' cell is a list of entries, each carrying only its column's declared entryFields. |
rows[].cells.* | any | no | Members not listed here. |
rows[].untypedCells | array of strings | no | Declared column keys whose stored value does not fit the column's declared type, so it is not in cells. Correct the cell in Filemark or write it again through an update. Empty when every declared cell is published. |
rowCount | integer, at least 0 | yes | Total rows stored, across every page. |
rowsTruncated | boolean | yes | True when more rows follow this page; nextCursor reads them. |
narrative | array of objects | yes | One page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper. |
narrative[].id | string | yes | Stable block identifier. |
narrative[].kind | one of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divider | yes | Block kind. |
narrative[].content | nullable string | no | Plain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally. |
narrative[].level | nullable integer, 1 to 3 | no | Heading level, 1 to 3. Null for other kinds. |
narrative[].variant | nullable one of note, warning, risk, conclusion | no | Callout variant. Null for other kinds. |
narrative[].needsReview | boolean | yes | True while a carried-forward block still awaits practitioner review. |
narrativeBlockCount | integer, at least 0 | yes | Total narrative blocks stored, across every page. |
narrativeTruncated | boolean | yes | True when more narrative blocks follow this page; nextCursor reads them. |
nextCursor | nullable string, up to 4096 characters | no | Continues the body: the next page of rows for a row grid, of blocks for a narrative. Pass as cursor to read the next page. Null on the last page. It is bound to this resourceVersion: once the workpaper changes it is refused with 409 and you start again from the first page. |
completenessStatus | nullable one of incomplete, complete | no | Narrative completeness checklist state. Null for a row-grid workpaper. |
lifecycle | object | yes | Live or archived (recoverable in Filemark). |
lifecycle.state | one of active, archived | yes | 'active' for a live record, 'archived' while it can still be restored. |
lifecycle.archivedAt | nullable string (date-time) | no | ISO-8601 in UTC. When it was archived. Null while active. |
lifecycle.restorableUntil | nullable string (date-time) | no | ISO-8601 in UTC. The moment the archived record is purged for good. Null while active. |
createdAt | string (date-time) | yes | ISO-8601 in UTC. |
updatedAt | string (date-time) | yes | ISO-8601 in UTC. |
warnings | array of objects | no | Non-blocking conditions: an unknown template, a dropped cell, a partial seed. |
warnings[].code | string | when warnings is set | Stable warning identifier. |
warnings[].message | string | when warnings is set | Readable explanation of the warning. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.
Request a governed narrative draft
POST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/governed-drafts
Asks Filemark's governed drafting boundary for a proposed narrative for this workpaper. The firm's AI policy, disclosure and client consent all apply, and the call is recorded with its policy evidence and cost. The answer is a PROPOSAL: accepting a draft into a workpaper is a practitioner action in Filemark and this API never accepts one. A machine client carries no practitioner identity, so the policy's actor gate refuses it; a delegated token drafts as its practitioner.
Scope: workpapers:write.
Request
curl --request POST https://api.filemark.ca/api/v1/engagements/$ENGAGEMENT_ID/workpapers/$WORKPAPER_ID/governed-drafts \
--header "Authorization: Bearer $FILEMARK_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"templateId": "<string>",
"facts": {
"<member>": "<any>"
}
}'import os
import requests
ENGAGEMENT_ID = "<engagement-id>"
WORKPAPER_ID = "<workpaper-id>"
FILEMARK_ACCESS_TOKEN = os.environ["FILEMARK_ACCESS_TOKEN"]
response = requests.post(
f"https://api.filemark.ca/api/v1/engagements/{ENGAGEMENT_ID}/workpapers/{WORKPAPER_ID}/governed-drafts",
headers={
"Authorization": f"Bearer {FILEMARK_ACCESS_TOKEN}",
},
json={
"templateId": "<string>",
"facts": {
"<member>": "<any>",
},
},
)
response.raise_for_status()
print(response.json())const ENGAGEMENT_ID = "<engagement-id>";
const WORKPAPER_ID = "<workpaper-id>";
const FILEMARK_ACCESS_TOKEN = process.env.FILEMARK_ACCESS_TOKEN;
const response = await fetch(`https://api.filemark.ca/api/v1/engagements/${ENGAGEMENT_ID}/workpapers/${WORKPAPER_ID}/governed-drafts`, {
method: "POST",
headers: {
Authorization: `Bearer ${FILEMARK_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"templateId": "<string>",
"facts": {
"<member>": "<any>"
}
}),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
engagement_id | path | string (uuid) | yes | UUID of the engagement the workpaper belongs to. |
workpaper_id | path | string (uuid) | yes | UUID of the workpaper. |
Idempotency-Key | header | string, 36 to 36 characters | yes | UUID that identifies this request. Send the same value when retrying; the same key with the same request replays the original result, the same key with a different request is refused (idempotency_key_reused). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
templateId | string, 1 to 100 characters | yes | Draft template the governed narrative proposal is built from. |
facts | object | no | Caller-supplied facts the draft may use. Everything else comes from the engagement's own stored workpaper. |
facts.* | any | no | Members not listed here. |
Response
201. The workpaper was created.
Also 200: The earlier result of the same Idempotency-Key.
Also 202: Processing is running. Repeat this request's method, URL and payload with the returned pollHeaders to read this attempt. A deliberate mutation retry omits X-Filemark-Job and reuses its original Idempotency-Key; the domain ledger owns recovery and replay. The final response retains the operation's normal contract.
{
"workpaperId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"draftEventId": "3f1a5b2c-9d4e-4f8a-b1c2-0a1b2c3d4e5f",
"templateId": "<string>",
"model": "<string>",
"proposedText": "<string>",
"evidenceLinks": [
{
"<member>": "<any>"
}
],
"redactions": [
{
"<member>": "<any>"
}
],
"warnings": [
{
"<member>": "<any>"
}
],
"accepted": false
}Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
workpaperId | string (uuid) | yes | UUID of the workpaper the draft is for. |
draftEventId | string (uuid) | yes | Identifier of the recorded draft event; the practitioner accepts it in Filemark. |
templateId | string | yes | Draft template used. |
model | string | yes | Model that produced the proposal. |
proposedText | string | yes | The proposed narrative text. Nothing was written. |
evidenceLinks | array of objects | no | Where each claim in the proposal comes from. |
evidenceLinks[].* | any | no | Members not listed here. |
redactions | array of objects | no | What the governance boundary removed before the call. |
redactions[].* | any | no | Members not listed here. |
warnings | array of objects | no | Non-blocking conditions the drafter reported. |
warnings[].* | any | no | Members not listed here. |
accepted | boolean, always false | yes | Always false. Accepting a draft into a workpaper is a practitioner action in Filemark; the API never accepts one. |
Errors
In addition to the shared error codes:
404: The workpaper does not exist in the caller's organization or client scope.