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.

ResourceOperationMethod and pathScope
ClientsList clientsGET /api/v1/clientsclients:read
RecordsCreate a clientPOST /api/v1/clientsclients:write
ClientsGet a clientGET /api/v1/clients/{client_id}clients:read
RecordsUpdate a clientPATCH /api/v1/clients/{client_id}clients:write
ClientsList a client's entitiesGET /api/v1/clients/{client_id}/entitiesentities:read
EntitiesGet an entityGET /api/v1/entities/{entity_id}entities:read
RecordsUpdate an entityPATCH /api/v1/entities/{entity_id}entities:write
EntitiesList an entity's tax yearsGET /api/v1/entities/{entity_id}/tax-yearstax-years:read
Tax yearsGet a tax yearGET /api/v1/tax-years/{tax_year_id}tax-years:read
SearchSearch engagement recordsGET /api/v1/searchengagements:read
EngagementsList engagementsGET /api/v1/engagementsengagements:read
RecordsCreate an engagementPOST /api/v1/engagementsengagements:write
EngagementsGet an engagementGET /api/v1/engagements/{engagement_id}engagements:read
RecordsUpdate an engagementPATCH /api/v1/engagements/{engagement_id}engagements:write
EngagementsGet engagement contextGET /api/v1/engagements/{engagement_id}/contextengagements:read
EngagementsGet engagement historyGET /api/v1/engagements/{engagement_id}/historyengagements:read
EngagementsList engagement documentsGET /api/v1/engagements/{engagement_id}/documentsdocuments:read
EngagementsList engagement workpapersGET /api/v1/engagements/{engagement_id}/workpapersworkpapers:read
WorkpapersCreate a workpaperPOST /api/v1/engagements/{engagement_id}/workpapersworkpapers:write
EngagementsGet engagement review summaryGET /api/v1/engagements/{engagement_id}/review-summaryreview:read
Engagement formsGet an engagement form catalogGET /api/v1/engagements/{engagement_id}/formsengagements:read
Saved tax dataGet a saved trial balanceGET /api/v1/engagements/{engagement_id}/trial-balancetax-data:read
Trial balance writesLand a trial balance on an engagementPOST /api/v1/engagements/{engagement_id}/trial-balancetax-data:write
Saved accountsGet a saved engagement accountGET /api/v1/engagements/{engagement_id}/accounts/{account_id}tax-data:read
Saved accountsList saved account adjustmentsGET /api/v1/engagements/{engagement_id}/accounts/{account_id}/adjustmentstax-data:read
Saved-state computationsCompute an engagement's saved state with cells replacedPOST /api/v1/engagements/{engagement_id}/computations/scenariotax-data:read + tax:compute
ComputationsList computation targetsGET /api/v1/computationstax:compute
ComputationsGet a computation target contractGET /api/v1/computations/targets/{target_id}tax:compute
ComputationsCompute tax schedulesPOST /api/v1/computations/batchtax:compute
ComputationsCompute a rollover or reorganizationPOST /api/v1/computations/rollovers/{target}tax:compute
RecordsArchive a clientPOST /api/v1/clients/{client_id}/archiveclients:write
RecordsRestore an archived clientPOST /api/v1/clients/{client_id}/restoreclients:write
RecordsCreate an entityPOST /api/v1/entitiesentities:write
RecordsArchive an entityPOST /api/v1/entities/{entity_id}/archiveentities:write
RecordsRestore an archived entityPOST /api/v1/entities/{entity_id}/restoreentities:write
RecordsArchive an engagementPOST /api/v1/engagements/{engagement_id}/archiveengagements:write
RecordsRestore an archived engagementPOST /api/v1/engagements/{engagement_id}/restoreengagements:write
RecordsGet mutation preconditionsGET /api/v1/engagements/{engagement_id}/mutation-preconditionsclients:write or entities:write or engagements:write or tax-data:write
ReviewList account notesGET /api/v1/engagements/{engagement_id}/account-notesreview:read
ReviewCreate an account notePOST /api/v1/engagements/{engagement_id}/account-notesreview:annotate
ReviewUpdate an account notePATCH /api/v1/account-notes/{note_id}review:annotate
ReviewArchive an account notePOST /api/v1/account-notes/{note_id}/archivereview:annotate
ReviewList review marksGET /api/v1/engagements/{engagement_id}/review-marksreview:read
ReviewCreate a review markPOST /api/v1/engagements/{engagement_id}/review-marksreview:annotate
ReviewUpdate a review markPATCH /api/v1/review-marks/{mark_id}review:annotate
Tax data writesApply a saved tax-data actionPOST /api/v1/engagements/{engagement_id}/data-actionstax-data:write
Trial balance writesApply a pending trial-balance importPOST /api/v1/engagements/{engagement_id}/trial-balance/applytax-data:write
Trial balance writesClassify awaiting accountsPOST /api/v1/engagements/{engagement_id}/accounts/classify-awaitingtax-data:write
PortalList an engagement's portal contactsGET /api/v1/engagements/{engagement_id}/portal/contactsportal:read
PortalList an engagement's portal requestsGET /api/v1/engagements/{engagement_id}/portal/requestsportal:read
PortalGet a portal requestGET /api/v1/portal-requests/{request_id}portal:read
PortalPreview a portal actionPOST /api/v1/portal-requests/{request_id}/actions/previewportal:write
PortalApply a portal actionPOST /api/v1/portal-requests/{request_id}/actionsportal:write
PortalComment on a portal requestPOST /api/v1/portal-requests/{request_id}/commentsportal:write + portal:send
PortalSend, remind, or cancel a portal invitationPOST /api/v1/engagements/{engagement_id}/portal/deliveriesportal:send
IntegrationsList integration connectionsGET /api/v1/integrationsintegrations:read
IntegrationsList integration sourcesGET /api/v1/integrations/{connector_id}/sourcesintegrations:read
IntegrationsPreview an integration syncPOST /api/v1/engagements/{engagement_id}/integration-syncs/previewintegrations:read
IntegrationsApply an integration syncPOST /api/v1/engagements/{engagement_id}/integration-syncsintegrations:sync
IntegrationsGet an integration syncGET /api/v1/integration-syncs/{sync_id}integrations:read
Export deliveriesDeliver an export artifactPOST /api/v1/export-jobs/{export_job_id}/deliveriesexports:deliver
DocumentsGet a documentGET /api/v1/engagements/{engagement_id}/documents/{document_id}documents:read
DocumentsReplace a document's filePOST /api/v1/engagements/{engagement_id}/documents/{document_id}/replacedocuments:write
DocumentsArchive a documentPOST /api/v1/engagements/{engagement_id}/documents/{document_id}/archivedocuments:write
DocumentsCreate a document deliveryPOST /api/v1/engagements/{engagement_id}/documents/{document_id}/deliveriesdocuments:read + documents:write
DocumentsStart a document uploadPOST /api/v1/engagements/{engagement_id}/document-uploadsdocuments:write
DocumentsComplete a document uploadPOST /api/v1/document-uploads/{upload_id}/completedocuments:write
DocumentsClassify a staged documentPOST /api/v1/document-uploads/{upload_id}/classifydocuments:write
DocumentsStart a document ingestionPOST /api/v1/engagements/{engagement_id}/document-ingestionsdocuments:write
DocumentsGet a document ingestionGET /api/v1/document-ingestions/{ingestion_id}documents:write
DocumentsRetry a document ingestionPOST /api/v1/document-ingestions/{ingestion_id}/retrydocuments:write
DocumentsCancel a document ingestionPOST /api/v1/document-ingestions/{ingestion_id}/canceldocuments:write
WorkpapersGet a workpaperGET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}workpapers:read
WorkpapersUpdate a workpaperPATCH /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}workpapers:write
WorkpapersArchive a workpaperPOST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/archiveworkpapers:write
WorkpapersRestore an archived workpaperPOST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/restoreworkpapers:write
WorkpapersList workpaper evidenceGET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/evidenceworkpapers:read
WorkpapersAttach or detach workpaper evidencePOST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/evidenceworkpapers:write
WorkpapersList workpaper account linksGET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/linksworkpapers:read
WorkpapersSet workpaper account linksPOST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/linksworkpapers:write
WorkpapersValidate a workpaperPOST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/validateworkpapers:read
WorkpapersGet workpaper tie-outGET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/tie-outworkpapers:read
WorkpapersList workpaper versionsGET /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/versionsworkpapers:read
WorkpapersGenerate a workpaper from the general ledgerPOST /api/v1/engagements/{engagement_id}/workpapers/generateworkpapers:write
WorkpapersCarry a workpaper forward from the prior yearPOST /api/v1/engagements/{engagement_id}/workpapers/carry-forwardworkpapers:write
WorkpapersRequest a governed narrative draftPOST /api/v1/engagements/{engagement_id}/workpapers/{workpaper_id}/governed-draftsworkpapers: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

ParameterInTypeRequiredDescription
limitqueryinteger, 1 to 200, default 50noPage size (1-200).
offsetqueryinteger, 0 to 100000, default 0noZero-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

FieldTypeAlways presentDescription
dataarray of objectsyesThe clients on this page, ordered by name.
data[].idstring (uuid)yesStable UUID of the client.
data[].namestringyesClient display name.
data[].createdAtstring (date-time)yesISO-8601 creation timestamp (UTC).
data[].updatedAtstring (date-time)yesISO-8601 last-modified timestamp (UTC).
paginationobjectyesOffset pagination state for this page.
pagination.limitintegeryesPage size applied (1-200).
pagination.offsetintegeryesZero-based row offset (max 100,000).
pagination.totalintegeryesTotal matching resources in the organization.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
namestring, 1 to 300 charactersyesClient 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the client.
namestringyesClient display name.
createdAtstring (date-time)yesISO-8601 creation timestamp (UTC).
updatedAtstring (date-time)yesISO-8601 last-modified timestamp (UTC).
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or recoverable-archived.
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-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

ParameterInTypeRequiredDescription
client_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the client.
namestringyesClient display name.
createdAtstring (date-time)yesISO-8601 creation timestamp (UTC).
updatedAtstring (date-time)yesISO-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 code result_not_representable means 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

ParameterInTypeRequiredDescription
client_idpathstring (uuid)yesUUID of the client to rename.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeRequiredDescription
namestring, 1 to 300 charactersyesNew 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the client.
namestringyesClient display name.
createdAtstring (date-time)yesISO-8601 creation timestamp (UTC).
updatedAtstring (date-time)yesISO-8601 last-modified timestamp (UTC).
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or recoverable-archived.
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-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

ParameterInTypeRequiredDescription
client_idpathstring (uuid)yesUUID of the client whose entities to list.
limitqueryinteger, 1 to 200, default 50noPage size (1-200).
cursorquerynullable string, 1 to 4096 charactersnoOpaque 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

FieldTypeAlways presentDescription
dataarray of objectsyesThe client's entities on this page, ordered by name.
data[].idstring (uuid)yesStable UUID of the entity.
data[].clientIdstring (uuid)yesUUID of the entity's parent client.
data[].namestringyesEntity display name.
data[].legalNamenullable stringnoRegistered legal name, when known.
data[].corporationNumbernullable stringnoCorporate registry number, when known.
data[].businessNumbernullable stringnoCanadian business number, when known.
data[].naicsCodenullable stringnoNAICS industry code, when assigned.
data[].prescribedLsvccHistorynullable objectnoPrescribed 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.wasPrescribedbooleanwhen prescribedLsvccHistory is setEver prescribed, including a former registration (Reg. 6701).
data[].prescribedLsvccHistory.firstPrescribedDatenullable string (date)when prescribedLsvccHistory is setFirst prescribed date; ITA 131(11) applies from it.
data[].dissolvedAtnullable string (date)noEntity dissolution date, when applicable.
data[].incorporationJurisdictionnullable stringnoCorporate-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[].incorporationDatenullable string (date)noDate 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[].registeredOfficenullable objectnoThe 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.addressLinenullable stringwhen registeredOffice is setStreet address on one line.
data[].registeredOffice.citynullable stringwhen registeredOffice is setCity or municipality.
data[].registeredOffice.provinceCodenullable stringwhen registeredOffice is setTwo-letter province or territory code, such as ON.
data[].registeredOffice.postalCodenullable stringwhen registeredOffice is setPostal code, such as L4W 5S2.
data[].registeredOffice.countryCodenullable stringwhen registeredOffice is setTwo-letter country code: CA.
data[].ontarioCorporationNumbernullable stringnoThe Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded.
paginationobjectyesCursor pagination state for this page.
pagination.limitintegeryesPage size applied (1-200).
pagination.nextCursornullable stringnoOpaque cursor for the next page. Null on the final page.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
entity_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the entity.
clientIdstring (uuid)yesUUID of the entity's parent client.
namestringyesEntity display name.
legalNamenullable stringnoRegistered legal name, when known.
corporationNumbernullable stringnoCorporate registry number, when known.
businessNumbernullable stringnoCanadian business number, when known.
naicsCodenullable stringnoNAICS industry code, when assigned.
prescribedLsvccHistorynullable objectnoPrescribed 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.wasPrescribedbooleanwhen prescribedLsvccHistory is setEver prescribed, including a former registration (Reg. 6701).
prescribedLsvccHistory.firstPrescribedDatenullable string (date)when prescribedLsvccHistory is setFirst prescribed date; ITA 131(11) applies from it.
dissolvedAtnullable string (date)noEntity dissolution date, when applicable.
incorporationJurisdictionnullable stringnoCorporate-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.
incorporationDatenullable string (date)noDate 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.
registeredOfficenullable objectnoThe 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.addressLinenullable stringwhen registeredOffice is setStreet address on one line.
registeredOffice.citynullable stringwhen registeredOffice is setCity or municipality.
registeredOffice.provinceCodenullable stringwhen registeredOffice is setTwo-letter province or territory code, such as ON.
registeredOffice.postalCodenullable stringwhen registeredOffice is setPostal code, such as L4W 5S2.
registeredOffice.countryCodenullable stringwhen registeredOffice is setTwo-letter country code: CA.
ontarioCorporationNumbernullable stringnoThe 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
entity_idpathstring (uuid)yesUUID of the entity to change.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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.

FieldTypeRequiredDescription
namenullable string, 1 to 300 charactersno
legalNamenullable string, up to 300 charactersno
businessNumbernullable string, up to 40 charactersnoThe 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.
naicsCodenullable string, matching ^\d{2,6}$noNAICS industry code, 2 to 6 digits. An exported return states the six-digit code of the main revenue-generating activity.
incorporationJurisdictionnullable string, up to 2 charactersnoCorporate-law jurisdiction of incorporation or continuance: 'CA' for the federal CBCA, or a two-letter province or territory code. Null clears it.
incorporationDatenullable string (date)noYYYY-MM-DD the current jurisdiction took effect (original incorporation or the latest continuance). Not in the future. Null clears it.
registeredOfficenullable objectnoThe 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.addressLinenullable string, up to 300 characterswhen registeredOffice is setStreet address on one line.
registeredOffice.citynullable string, up to 100 characterswhen registeredOffice is setCity or municipality.
registeredOffice.provinceCodenullable string, up to 2 characterswhen registeredOffice is setTwo-letter province or territory code, such as ON. Required for a Canadian address.
registeredOffice.postalCodenullable string, up to 10 characterswhen registeredOffice is setPostal code, such as L4W 5S2.
registeredOffice.countryCodenullable string, up to 2 characterswhen registeredOffice is setTwo-letter country code; CA or omitted for Canada.
ontarioCorporationNumbernullable string, matching ^\s*[0-9]{1,10}\s*$noThe 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.
prescribedLsvccHistorynullable objectnoWhether 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.wasPrescribedbooleanwhen prescribedLsvccHistory is setEver prescribed, including a former registration (Reg. 6701).
prescribedLsvccHistory.firstPrescribedDatenullable string (date)when prescribedLsvccHistory is setFirst 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the entity.
clientIdstring (uuid)yesUUID of the entity's parent client.
namestringyesEntity display name.
legalNamenullable stringnoRegistered legal name, when known.
corporationNumbernullable stringnoCorporate registry number, when known.
businessNumbernullable stringnoCanadian business number, when known.
naicsCodenullable stringnoNAICS industry code, when assigned.
prescribedLsvccHistorynullable objectnoPrescribed 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.wasPrescribedbooleanwhen prescribedLsvccHistory is setEver prescribed, including a former registration (Reg. 6701).
prescribedLsvccHistory.firstPrescribedDatenullable string (date)when prescribedLsvccHistory is setFirst prescribed date; ITA 131(11) applies from it.
dissolvedAtnullable string (date)noEntity dissolution date, when applicable.
incorporationJurisdictionnullable stringnoCorporate-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.
incorporationDatenullable string (date)noDate 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.
registeredOfficenullable objectnoThe 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.addressLinenullable stringwhen registeredOffice is setStreet address on one line.
registeredOffice.citynullable stringwhen registeredOffice is setCity or municipality.
registeredOffice.provinceCodenullable stringwhen registeredOffice is setTwo-letter province or territory code, such as ON.
registeredOffice.postalCodenullable stringwhen registeredOffice is setPostal code, such as L4W 5S2.
registeredOffice.countryCodenullable stringwhen registeredOffice is setTwo-letter country code: CA.
ontarioCorporationNumbernullable stringnoThe Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or recoverable-archived.
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoConditions 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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
entity_idpathstring (uuid)yesUUID of the entity whose tax years to list.
limitqueryinteger, 1 to 200, default 50noPage size (1-200).
cursorquerynullable string, 1 to 4096 charactersnoOpaque 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

FieldTypeAlways presentDescription
dataarray of objectsyesThe entity's tax years on this page, newest year end first.
data[].idstring (uuid)yesStable UUID of the tax-year engagement.
data[].entityIdstring (uuid)yesUUID of the parent entity.
data[].clientIdstring (uuid)yesUUID of the parent client.
data[].yearEndstring (date)yesFiscal year-end date.
data[].periodStartnullable string (date)noFiscal period start date. Null when no start has been recorded, for example on a reference year an import created without one.
data[].statusone of in_progress, review, completeyesCurrent engagement workflow status.
data[].sourceone of prepared, importedyesPrepared binder or imported prior-year reference.
data[].engagementTypenullable one of compilation, review, audit, tax_onlynoEngagement service type, when assigned.
data[].priorYearTaxYearIdnullable string (uuid)noLinked prior tax year used for comparatives, when present.
data[].createdAtstring (date-time)yesISO-8601 creation timestamp (UTC).
data[].lastModifiedAtstring (date-time)yesISO-8601 last-modified timestamp (UTC).
paginationobjectyesCursor pagination state for this page.
pagination.limitintegeryesPage size applied (1-200).
pagination.nextCursornullable stringnoOpaque cursor for the next page. Null on the final page.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
tax_year_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the tax-year engagement.
entityIdstring (uuid)yesUUID of the parent entity.
clientIdstring (uuid)yesUUID of the parent client.
yearEndstring (date)yesFiscal year-end date.
periodStartnullable string (date)noFiscal period start date. Null when no start has been recorded, for example on a reference year an import created without one.
statusone of in_progress, review, completeyesCurrent engagement workflow status.
sourceone of prepared, importedyesPrepared binder or imported prior-year reference.
engagementTypenullable one of compilation, review, audit, tax_onlynoEngagement service type, when assigned.
priorYearTaxYearIdnullable string (uuid)noLinked prior tax year used for comparatives, when present.
createdAtstring (date-time)yesISO-8601 creation timestamp (UTC).
lastModifiedAtstring (date-time)yesISO-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 code result_not_representable means 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

ParameterInTypeRequiredDescription
qquerystring, 2 to 80 charactersyesAt most six whitespace-separated terms. Percent and underscore characters match as text, not as wildcards.
year_endquerynullable string (date)noKeep only matches whose fiscal year end is exactly this date, YYYY-MM-DD.
statusquerynullable one of in_progress, review, completenoKeep only matches in this workflow status: in_progress, review, or complete.
limitqueryinteger, 1 to 25, default 10noMaximum 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

FieldTypeAlways presentDescription
querystring, 2 to 80 charactersyesThe search text as it was interpreted, with runs of whitespace collapsed to single spaces.
resolutionone of no_match, unique, ambiguousyesunique 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.
dataarray of objects, up to 25 itemsyesMatching engagements ordered by relevance, using the latest revision in each amendment chain.
data[].idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
data[].clientobjectyesThe client the engagement belongs to.
data[].client.idstring (uuid)yesUUID of the client or entity.
data[].client.namestringyesDisplay name of the client or entity.
data[].client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
data[].entityobjectyesThe legal entity the engagement belongs to.
data[].entity.idstring (uuid)yesUUID of the client or entity.
data[].entity.namestringyesDisplay name of the client or entity.
data[].entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
data[].yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
data[].periodStartnullable string (date)noFirst 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[].statusone of in_progress, review, completeyesWorkflow 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[].sourceone of prepared, importedyesValues: 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[].engagementTypenullable one of compilation, review, audit, tax_onlynoService 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[].priorYearEngagementIdnullable string (uuid)noUUID 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[].isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
data[].lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
data[].lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
data[].lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
data[].lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
data[].lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
data[].createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
data[].updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
data[].documentsPagenullable stringnoFilemark 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.
paginationobjectyesThe result limit that was applied and whether further matches were left out.
pagination.limitinteger, 1 to 25yesResult limit applied.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
client_idquerynullable string (uuid)noReturn only engagements belonging to this client.
entity_idquerynullable string (uuid)noReturn only engagements belonging to this legal entity.
year_endquerynullable string (date)noReturn only engagements whose fiscal year end is exactly this date, YYYY-MM-DD.
statusquerynullable one of in_progress, review, completenoReturn only engagements in this workflow status: in_progress, review, or complete.
limitqueryinteger, 1 to 200, default 50noPage size (1-200).
cursorquerynullable string, 1 to 4096 charactersnoOpaque 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

FieldTypeAlways presentDescription
dataarray of objectsyesThe engagements on this page, newest year end first.
data[].idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
data[].clientobjectyesThe client the engagement belongs to.
data[].client.idstring (uuid)yesUUID of the client or entity.
data[].client.namestringyesDisplay name of the client or entity.
data[].client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
data[].entityobjectyesThe legal entity the engagement belongs to.
data[].entity.idstring (uuid)yesUUID of the client or entity.
data[].entity.namestringyesDisplay name of the client or entity.
data[].entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
data[].yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
data[].periodStartnullable string (date)noFirst 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[].statusone of in_progress, review, completeyesWorkflow 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[].sourceone of prepared, importedyesValues: 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[].engagementTypenullable one of compilation, review, audit, tax_onlynoService 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[].priorYearEngagementIdnullable string (uuid)noUUID 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[].isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
data[].lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
data[].lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
data[].lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
data[].lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
data[].lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
data[].createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
data[].updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
data[].documentsPagenullable stringnoFilemark 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.
paginationobjectyesCursor pagination state for this page.
pagination.limitintegeryesPage size applied (1-200).
pagination.nextCursornullable stringnoOpaque cursor for the next page. Null on the final page.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
entityIdstring (uuid)yesUUID of the legal entity the engagement is for.
yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD. Unique per entity for an original return.
periodStartnullable string (date)noFirst 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.
engagementTypenullable one of compilation, review, audit, tax_onlynoService the firm performs for the year, when known.
priorYearEngagementIdnullable string (uuid)noUUID 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_bElectednullable booleannoTrue 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_cElectednullable booleannoTrue when the ITA s.249(3.1)(c) seven-day alignment election applies.
permanentEstablishmentJurisdictionsnullable array of strings, up to 13 itemsnoTwo-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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
clientobjectyesThe client the engagement belongs to.
client.idstring (uuid)yesUUID of the client or entity.
client.namestringyesDisplay name of the client or entity.
client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
entityobjectyesThe legal entity the engagement belongs to.
entity.idstring (uuid)yesUUID of the client or entity.
entity.namestringyesDisplay name of the client or entity.
entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
periodStartnullable string (date)noFirst 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.
statusone of in_progress, review, completeyesWorkflow 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.
sourceone of prepared, importedyesValues: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference.
engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.
priorYearEngagementIdnullable string (uuid)noUUID 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.
isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
documentsPagenullable stringnoFilemark 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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
recordLifecycleobjectyesLive or recoverable-archived (distinct from the return's filing lifecycle).
recordLifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
recordLifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
recordLifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoNon-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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
clientobjectyesThe client the engagement belongs to.
client.idstring (uuid)yesUUID of the client or entity.
client.namestringyesDisplay name of the client or entity.
client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
entityobjectyesThe legal entity the engagement belongs to.
entity.idstring (uuid)yesUUID of the client or entity.
entity.namestringyesDisplay name of the client or entity.
entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
periodStartnullable string (date)noFirst 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.
statusone of in_progress, review, completeyesWorkflow 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.
sourceone of prepared, importedyesValues: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference.
engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.
priorYearEngagementIdnullable string (uuid)noUUID 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.
isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
documentsPagenullable stringnoFilemark 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement to change.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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.

FieldTypeRequiredDescription
statusnullable one of in_progress, review, completenoWorkflow status.
engagementTypenullable one of compilation, review, audit, tax_onlynoService the firm performs for the year.
periodStartnullable string (date)noFirst day of the fiscal period, YYYY-MM-DD.
yearEndnullable string (date)noFiscal 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
clientobjectyesThe client the engagement belongs to.
client.idstring (uuid)yesUUID of the client or entity.
client.namestringyesDisplay name of the client or entity.
client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
entityobjectyesThe legal entity the engagement belongs to.
entity.idstring (uuid)yesUUID of the client or entity.
entity.namestringyesDisplay name of the client or entity.
entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
periodStartnullable string (date)noFirst 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.
statusone of in_progress, review, completeyesWorkflow 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.
sourceone of prepared, importedyesValues: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference.
engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.
priorYearEngagementIdnullable string (uuid)noUUID 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.
isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
documentsPagenullable stringnoFilemark 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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
recordLifecycleobjectyesLive or recoverable-archived (distinct from the return's filing lifecycle).
recordLifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
recordLifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
recordLifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoNon-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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
requestedEngagementobjectyesThe revision named by the engagement ID in the request.
requestedEngagement.idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
requestedEngagement.clientobjectyesThe client the engagement belongs to.
requestedEngagement.client.idstring (uuid)yesUUID of the client or entity.
requestedEngagement.client.namestringyesDisplay name of the client or entity.
requestedEngagement.client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
requestedEngagement.entityobjectyesThe legal entity the engagement belongs to.
requestedEngagement.entity.idstring (uuid)yesUUID of the client or entity.
requestedEngagement.entity.namestringyesDisplay name of the client or entity.
requestedEngagement.entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
requestedEngagement.yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
requestedEngagement.periodStartnullable string (date)noFirst 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.statusone of in_progress, review, completeyesWorkflow 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.sourceone of prepared, importedyesValues: 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.engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.priorYearEngagementIdnullable string (uuid)noUUID 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.isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
requestedEngagement.lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
requestedEngagement.lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
requestedEngagement.lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
requestedEngagement.lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
requestedEngagement.lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
requestedEngagement.createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
requestedEngagement.updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
requestedEngagement.documentsPagenullable stringnoFilemark 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.
currentEngagementobjectyesThe newest revision of the same amendment chain. Identical to requestedEngagement when the requested revision is already the newest.
currentEngagement.idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
currentEngagement.clientobjectyesThe client the engagement belongs to.
currentEngagement.client.idstring (uuid)yesUUID of the client or entity.
currentEngagement.client.namestringyesDisplay name of the client or entity.
currentEngagement.client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
currentEngagement.entityobjectyesThe legal entity the engagement belongs to.
currentEngagement.entity.idstring (uuid)yesUUID of the client or entity.
currentEngagement.entity.namestringyesDisplay name of the client or entity.
currentEngagement.entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
currentEngagement.yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
currentEngagement.periodStartnullable string (date)noFirst 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.statusone of in_progress, review, completeyesWorkflow 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.sourceone of prepared, importedyesValues: 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.engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.priorYearEngagementIdnullable string (uuid)noUUID 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.isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
currentEngagement.lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
currentEngagement.lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
currentEngagement.lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
currentEngagement.lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
currentEngagement.lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
currentEngagement.createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
currentEngagement.updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
currentEngagement.documentsPagenullable stringnoFilemark 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.
revisionobjectyesWhere the requested revision sits in the chain and how to reach its neighbours.
revision.rootEngagementIdstring (uuid)yesUUID of the first revision in the chain, the original return.
revision.previousEngagementIdnullable string (uuid)noUUID of the revision the requested one amends. Null when the requested revision is the original return.
revision.nextEngagementIdnullable string (uuid)noUUID of the revision that amends the requested one. Null when the requested revision is the newest.
revision.currentEngagementIdstring (uuid)yesUUID of the newest revision in the chain.
revision.isCurrentbooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
engagementIdstring (uuid)yesUUID of the engagement that was requested.
currentEngagementIdstring (uuid)yesUUID of the newest revision in the chain. It is the last entry in revisions.
revisionsarray of objects, 1 to 100 itemsyesRevision metadata ordered from the original return to the latest, up to 100 entries. Longer chains return a conflict.
revisions[].idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
revisions[].clientobjectyesThe client the engagement belongs to.
revisions[].client.idstring (uuid)yesUUID of the client or entity.
revisions[].client.namestringyesDisplay name of the client or entity.
revisions[].client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
revisions[].entityobjectyesThe legal entity the engagement belongs to.
revisions[].entity.idstring (uuid)yesUUID of the client or entity.
revisions[].entity.namestringyesDisplay name of the client or entity.
revisions[].entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
revisions[].yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
revisions[].periodStartnullable string (date)noFirst 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[].statusone of in_progress, review, completeyesWorkflow 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[].sourceone of prepared, importedyesValues: 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[].engagementTypenullable one of compilation, review, audit, tax_onlynoService 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[].priorYearEngagementIdnullable string (uuid)noUUID 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[].isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
revisions[].lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
revisions[].lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
revisions[].lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
revisions[].lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
revisions[].lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
revisions[].createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
revisions[].updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
revisions[].documentsPagenullable stringnoFilemark 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement whose documents to list.
limitqueryinteger, 1 to 200, default 50noPage size (1-200).
offsetqueryinteger, 0 to 100000, default 0noZero-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

FieldTypeAlways presentDescription
dataarray of objectsyesThe documents on this page, ordered by kind, then file name, then identifier.
data[].idstring, 1 to 256 charactersyesIdentifier 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[].fileNamestring, 1 to 512 charactersyesName to show for the document. When the stored record carries no file name, a generic label for its kind is returned.
data[].kindone 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, evidenceyesWhat 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[].sourceone of onboarding, manual, portal, connector, api, unknownyesHow 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[].uploadedAtnullable string (date-time)noISO-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.
paginationobjectyesOffset pagination state for this page.
pagination.limitinteger, 1 to 200yesPage size applied.
pagination.offsetinteger, 0 to 100000yesZero-based row offset applied.
pagination.totalinteger, at least 0yesTotal matching resources.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement whose workpapers to list.
limitqueryinteger, 1 to 200, default 50noPage size (1-200).
offsetqueryinteger, 0 to 100000, default 0noZero-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

FieldTypeAlways presentDescription
dataarray of objectsyesThe workpapers on this page, oldest first. Deleted workpapers are not included.
data[].idstring (uuid)yesUUID of the workpaper.
data[].templateIdnullable stringnoIdentifier of the workpaper template the workpaper was built from. Null for a workpaper created without one.
data[].kindstringyesrow_grid for a workpaper of tabular rows, narrative for a written memo.
data[].originstringyesHow 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[].statusstringyesdraft while the workpaper is still being worked on, complete once the preparer has finished it.
data[].parseStatusnullable stringnoFor 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[].tieOutModenullable stringnoHow 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[].tieOutStatusnullable stringnoTie-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[].detectedTypenullable stringnoWorkpaper type detected during upload processing. Null if no type was detected or the workpaper was created another way.
data[].categorynullable stringnoFree-text grouping label a practitioner set on the workpaper. There is no fixed list of values. Null when none is set.
data[].versioninteger, at least 1yesContent version of the workpaper, starting at 1 and increasing each time its saved content is replaced.
data[].isReviewedbooleanyesTrue when the workpaper has been marked reviewed, which is when reviewedAt is set.
data[].reviewedAtnullable string (date-time)noISO-8601 in UTC. When the workpaper was marked reviewed. Null while it has not been.
data[].sourceFileNamenullable string, up to 512 charactersnoName of the file the workpaper was uploaded from. Null when it was not created from an upload.
data[].linkedAccountCountinteger, at least 0yesNumber of trial-balance accounts linked to the workpaper.
data[].createdAtstring (date-time)yesISO-8601 in UTC. When the workpaper was created.
data[].updatedAtstring (date-time)yesISO-8601 in UTC. When the workpaper was last modified.
paginationobjectyesOffset pagination state for this page.
pagination.limitinteger, 1 to 200yesPage size applied.
pagination.offsetinteger, 0 to 100000yesZero-based row offset applied.
pagination.totalinteger, at least 0yesTotal matching resources.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
templateIdstring, 1 to 100 charactersyesConcept 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.
customNamenullable string, up to 200 charactersnoThe one practitioner-set name. Required in practice for a template that permits several instances, so each instance is identifiable.
kindone of row_grid, narrativenoBody shape to create.
categorynullable string, up to 100 charactersno
assumptionnullable string, up to 4000 charactersno
notesnullable string, up to 8000 charactersno
linkedAccountIdsnullable array of strings, up to 500 itemsnoSaved trial-balance account ids to link. Every id must belong to this engagement's saved trial balance.
rowsnullable array of objectsnoGrid 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[].rowKeynullable string, up to 200 characterswhen rows is setStable key of the row to replace. Omit to append a new row.
rows[].sectionIdnullable string, up to 100 characterswhen rows is setTemplate section the row belongs to.
rows[].cellsobjectwhen rows is setCell values keyed by the template's declared column keys.
rows[].cells.*anynoMembers 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the workpaper.
engagementIdstring (uuid)yesUUID of the engagement it belongs to.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
templateIdnullable stringnoConcept template the workpaper was built from.
namestringyesPractitioner-facing display name.
customNamenullable stringnoThe one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name).
kindone of row_grid, narrativeyesBody shape.
originstringyesHow the workpaper came to exist: blank, uploaded, generated, carryforward.
statusone of draft, completeyesPreparer status.
categorynullable stringnoPractitioner grouping label.
versioninteger, at least 1yesMonotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number.
isReviewedbooleanyesTrue once a practitioner marked it reviewed in Filemark. The API never sets it.
reviewedAtnullable string (date-time)noISO-8601 in UTC. Null while unreviewed.
parseStatusnullable stringnoOutcome of parsing the uploaded source file, when there is one.
detectedTypenullable stringnoLayout the parser detected in the source file.
sourceFileNamenullable string, up to 512 charactersnoFile name of the uploaded source, when there is one.
assumptionnullable stringnoThe preparer's stated assumption for the working paper.
notesnullable stringnoPreparer notes.
tieOutobjectyesAgreement with the linked trial-balance accounts.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
linkedAccountIdsarray of stringsyesSaved trial-balance account ids the workpaper is linked to.
columnsarray of objectsyesDeclared grid columns. Empty for a narrative workpaper.
columns[].keystringyesRow key this column occupies.
columns[].labelstringyesPractitioner-facing column heading.
columns[].typeone of text, number, date, select, cohortsyesDeclared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields.
columns[].optionsnullable array of stringsnoClosed option list for a 'select' column. Null for every other type.
columns[].sectionIdnullable stringnoTemplate section this column belongs to. Null for the main grid.
columns[].entryFieldsnullable array of objectsnoThe declared fields of each entry of a 'cohorts' column. Null for every other type.
columns[].entryFields[].keystringwhen entryFields is setEntry key this field occupies.
columns[].entryFields[].labelstringwhen entryFields is setPractitioner-facing field heading.
columns[].entryFields[].typeone of text, number, date, selectwhen entryFields is setDeclared value type. A written entry value must match it.
columns[].entryFields[].optionsnullable array of stringswhen entryFields is setClosed option list for a 'select' field. Null for every other type.
rowsarray of objectsyesOne page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper.
rows[].rowKeystringyesStable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one.
rows[].sectionIdnullable stringnoTemplate section the row sits in. Null for the main grid.
rows[].cellsobjectnoCell 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.*anynoMembers not listed here.
rows[].untypedCellsarray of stringsnoDeclared 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.
rowCountinteger, at least 0yesTotal rows stored, across every page.
rowsTruncatedbooleanyesTrue when more rows follow this page; nextCursor reads them.
narrativearray of objectsyesOne page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper.
narrative[].idstringyesStable block identifier.
narrative[].kindone of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divideryesBlock kind.
narrative[].contentnullable stringnoPlain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally.
narrative[].levelnullable integer, 1 to 3noHeading level, 1 to 3. Null for other kinds.
narrative[].variantnullable one of note, warning, risk, conclusionnoCallout variant. Null for other kinds.
narrative[].needsReviewbooleanyesTrue while a carried-forward block still awaits practitioner review.
narrativeBlockCountinteger, at least 0yesTotal narrative blocks stored, across every page.
narrativeTruncatedbooleanyesTrue when more narrative blocks follow this page; nextCursor reads them.
nextCursornullable string, up to 4096 charactersnoContinues 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.
completenessStatusnullable one of incomplete, completenoNarrative completeness checklist state. Null for a row-grid workpaper.
lifecycleobjectyesLive or archived (recoverable in Filemark).
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
createdAtstring (date-time)yesISO-8601 in UTC.
updatedAtstring (date-time)yesISO-8601 in UTC.
warningsarray of objectsnoNon-blocking conditions: an unknown template, a dropped cell, a partial seed.
warnings[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
importedReviewEvidencearray of objectsnoSource marks with their original attribution; never current approval or sign-off.
importedReviewEvidence[].idstringwhen importedReviewEvidence is set
importedReviewEvidence[].sourceRevisionIdstringwhen importedReviewEvidence is set
importedReviewEvidence[].fieldPathnullable stringwhen importedReviewEvidence is set
importedReviewEvidence[].nativeFieldIdnullable objectwhen importedReviewEvidence is set
importedReviewEvidence[].nativeFieldId.*anynoMembers not listed here.
importedReviewEvidence[].preparationanywhen importedReviewEvidence is set
importedReviewEvidence[].sourceActornullable stringwhen importedReviewEvidence is set
importedReviewEvidence[].sourceMarkedAtnullable stringwhen importedReviewEvidence is set
importedReviewEvidence[].sourceTimenullable objectwhen importedReviewEvidence is set
importedReviewEvidence[].sourceTime.*anynoMembers not listed here.
importedReviewEvidence[].sourceValuenullable objectwhen importedReviewEvidence is set
importedReviewEvidence[].sourceValue.*anynoMembers not listed here.
importedReviewEvidence[].valueWithheldbooleanwhen importedReviewEvidence is set
importedReviewEvidence[].sourceDetailsnullable objectwhen importedReviewEvidence is set
importedReviewEvidence[].sourceDetails.*anynoMembers not listed here.
importedReviewEvidence[].evidenceKindstringwhen importedReviewEvidence is set
importedReviewEvidence[].isHistoricalboolean, always truewhen importedReviewEvidence is set
importedReviewEvidence[].countsForSignoffboolean, always falsewhen importedReviewEvidence is set
engagementIdstring (uuid)yesUUID of the engagement these indicators belong to.
engagementStatusone of in_progress, review, completeyesWorkflow status the practitioner has set on the engagement: in_progress, review, or complete.
lifecycleStatusone of open, signed, filedyesDerived 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.
signoffsobjectyesWhich of the three sign-offs are recorded.
signoffs.preparerSignedbooleanyesTrue when a preparer sign-off is recorded.
signoffs.reviewerSignedbooleanyesTrue when a reviewer sign-off is recorded.
signoffs.reviewerStatusone of pending, signed, skippedyespending when no reviewer decision has been recorded, signed when a reviewer signed off, skipped when the reviewer step was waived for this engagement.
signoffs.partnerSignedbooleanyesTrue when a partner sign-off is recorded.
workpapersobjectyesWorkpaper counts by review state and tie-out result.
workpapers.totalinteger, at least 0yesWorkpapers on the engagement. Deleted workpapers are not counted.
workpapers.reviewedinteger, at least 0yesHow many of those workpapers are marked reviewed.
workpapers.tieOutMatchedinteger, at least 0yesWorkpapers whose tie-out difference is within tolerance.
workpapers.tieOutDifferenceinteger, at least 0yesWorkpapers whose tie-out difference is outside tolerance.
workpapers.tieOutIncompleteinteger, at least 0yesWorkpapers awaiting inputs for a tie-out.
workpapers.tieOutPendinginteger, at least 0yesWorkpapers awaiting a tie-out computation. Narrative workpapers are excluded from all four tie-out counts.
accountReviewStatusesobjectyesTrial-balance accounts counted by the review status set on them.
accountReviewStatuses.followUpinteger, at least 0yesAccounts flagged for a follow-up question to the client.
accountReviewStatuses.analyzedinteger, at least 0yesAccounts the preparer has marked as analyzed.
accountReviewStatuses.firstReviewinteger, at least 0yesAccounts marked as having passed first review.
accountReviewStatuses.secondReviewinteger, at least 0yesAccounts marked as having passed second review. Accounts still at the default status are counted in none of these four.
activeReviewMarksobjectyesUncleared review marks on the return's fields, by type.
activeReviewMarks.totalinteger, at least 0yesReview marks that nobody has cleared yet.
activeReviewMarks.questionsinteger, at least 0yesActive marks raising a question about a field.
activeReviewMarks.correctionsinteger, at least 0yesActive marks asking for a field to be corrected.
activeReviewMarks.firstReviewinteger, at least 0yesActive first-review marks on a field.
activeReviewMarks.partnerReviewinteger, at least 0yesActive partner-review marks on a field.
acknowledgedWarningCountinteger, at least 0yesComputed warnings a practitioner has acknowledged and not since reinstated.
readinessobjectyesStates that this response carries no filing-readiness verdict.
readiness.evaluatedboolean, always falsenoAlways false. This read reports stored review indicators and does not evaluate whether the return can be filed.
readiness.readynullnoAlways 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
contextobjectyesThe engagement and revision the catalog was built for.
context.engagementIdstring (uuid)yesUUID of the engagement the catalog was built for.
context.yearEndstring (date)yesThe engagement's fiscal year-end date, YYYY-MM-DD.
context.taxYearinteger, 1900 to 9999yesCalendar year of the year-end date, the year matched against each form's supported tax-year window.
context.revisionNumberinteger, at least 1yesRevision of the engagement the catalog was built from. 1 is the original return.
context.sourceone of prepared, importedyesValues: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file.
context.filedAtnullable string (date-time)yesISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
context.referenceOnlybooleanyesTrue when the engagement is an imported prior-year reference, which is the same as source being imported.
dataarray of objects, up to 200 itemsyesThe supported forms, with the T2 return first, then schedules and other forms in numeric order.
data[].targetIdstring, matching ^(?:S\d+[A-Z]?|T\d+[A-Z]?)$yesIdentifier 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[].displayNamestring, 1 to 256 charactersyesName to show for the form. It is derived from the target identifier. It is not the CRA's full form title.
data[].formIdstring, matching ^[A-Z0-9_-]{1,64}$yesThe CRA form number for this target.
data[].taxYearWindowobjectyesThe range of tax years this form is supported for.
data[].taxYearWindow.frominteger, 1900 to 9999yesFirst tax year the form is supported for.
data[].taxYearWindow.throughnullable integer, 1900 to 9999yesLast tax year the form is supported for. Null when the form has no declared end year and remains supported for later years.
data[].availabilitystring, always "supported"yesAlways supported. The form supports the engagement's tax year.
data[].applicabilityBasisstring, always "tax_year_window"yesAlways tax_year_window. Selection is based on the engagement's tax year.
data[].filingRequirementstring, always "not_evaluated"yesAlways not_evaluated. This response does not decide whether the corporation has to file the form.
evaluationobjectyesFlags recording what this response did not do. A listed form is not necessarily populated, computed, or required.
evaluation.formRevisionResolvedboolean, always falseyesAlways false. No year-specific revision of any form was selected.
evaluation.savedStateEvaluatedboolean, always falseyesAlways false. Saved form data is not evaluated by the catalog lookup.
evaluation.computationEvaluatedboolean, always falseyesAlways false. No amounts were computed for any form.
evaluation.readinessEvaluatedboolean, always falseyesAlways 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement whose trial balance you want. An engagement is one corporation's taxation year.
limitqueryinteger, 1 to 200, default 50noHow many active accounts to return, from 1 to 200. Defaults to 50.
cursorquerynullable string, 1 to 4096 charactersnoThe 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

FieldTypeAlways presentDescription
contextobjectyesThe engagement, reporting currency and sign convention these balances belong to.
context.engagementIdstring (uuid)yesUUID of the engagement these balances belong to. An engagement is one corporation's taxation year.
context.yearEndstring (date)yesThe engagement's taxation year end, YYYY-MM-DD.
context.revisionNumberinteger, at least 1yesFiling revision counter for this year end. The original engagement is 1. Each amendment opened from it adds one.
context.statusone of in_progress, review, completeyesPreparation 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.sourceone of prepared, importedyesHow 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.filedAtnullable string (date-time)yesWhen 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.referenceOnlybooleanyesTrue when source is imported, meaning the engagement is a reference-only record whose balances are comparatives.
context.reportingCurrencystring, matching ^[A-Z]{3}$yesISO 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.currencyBasisone of canadian_currency, functional_currency, reversionaryyesWhat 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.balanceConventionstring, always "debits_positive_credits_negative"yesAlways debits_positive_credits_negative. Every balance in this response is signed with debits positive and credits negative.
sourceRevisionobjectyesVersion counters and a fingerprint for the saved state this page was read from.
sourceRevision.classifiedReportVersioninteger, at least 0yesSaved trial-balance version, incremented whenever accounts or balances are saved. Zero before the first save.
sourceRevision.gifiAssignmentsVersioninteger, at least 0yesWrite counter for the saved GIFI code assignments, on the same convention as classifiedReportVersion.
sourceRevision.lastModifiedAtstring (date-time)yesWhen any part of the engagement was last saved, ISO-8601 in UTC.
sourceRevision.snapshotDigeststring, matching ^sha256:[0-9a-f]{64}$yesFingerprint 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.
availabilityone of available, empty, not_importedyesWhether 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.
projectionobjectyesWhat this response does and does not include.
projection.savedBookBalancesIncludedboolean, always trueyesAlways true. The balances are the saved book balances as entered or imported.
projection.gifiAssignmentsIncludedboolean, always trueyesAlways true. Each account carries the GIFI code saved for it on this engagement.
projection.archivedAccountsIncludedboolean, always falseyesAlways false. Deleted accounts are excluded from data and counted in archivedAccountCount.
projection.postedBookAdjustmentsIncludedboolean, always falseyesAlways false. Posted adjusting journal entries are not applied to these balances.
projection.taxAdjustmentsIncludedboolean, always falseyesAlways false. Workpaper tax adjustments are not applied to these balances.
projection.classificationIncludedboolean, always falseyesAlways false. Read a single account to get its tax classification.
projection.computationEvaluatedboolean, always falseyesAlways false. No schedule or return computation is run to produce this response.
projection.lineageIncludedboolean, always falseyesAlways false. No per-amount lineage or derivation trail is returned.
projection.readinessEvaluatedboolean, always falseyesAlways false. Filing readiness is not evaluated by this operation.
totalStoredAccountCountinteger, at least 0yesNumber of accounts held on the engagement, counting archived ones.
archivedAccountCountinteger, at least 0yesNumber of deleted accounts, excluded from data.
dataarray of objects, up to 200 itemsyesOne page of active accounts, in the order the engagement stores them. Empty when availability is empty or not_imported.
data[].idstring, 1 to 128 charactersyesIdentifier 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[].accountCodenullable string, up to 128 charactersyesAccount number as it appears in the client's chart of accounts. Null for a trial balance whose accounts are identified by name only.
data[].accountNamestring, up to 512 charactersyesAccount name from the client's chart of accounts. Escape this untrusted text before rendering. Empty when the source file has no name.
data[].currentYearBalancedecimal string, 1 to 96 charactersyesClosing balance for the taxation year, before adjusting entries and tax adjustments. Exact decimal string in the reporting currency, debits positive and credits negative.
data[].priorYearBalancenullable decimal string, 1 to 96 charactersyesThe 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[].gifiCodenullable string, matching ^[0-9]{4}$yesThe four-digit GIFI code assigned to this account on this engagement. Null when no code has been assigned yet.
data[].changeStatusone of new, removed, significant_increase, significant_decrease, minor_change, unchanged, unknownyesHow 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.
paginationobjectyesPage size, total active account count, and the next cursor.
pagination.limitinteger, 1 to 200yesPage size applied to this request, at most 200. It can be smaller than the limit you asked for.
pagination.totalinteger, at least 0yesNumber of active accounts in the whole trial balance, across all pages. Archived accounts are excluded.
pagination.nextCursornullable stringyesOpaque 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.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement whose account list the rows merge into.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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.

FieldTypeRequiredDescription
modeone of books, rowsnobooks asks about absent accounts; rows imports only the supplied rows.
sourceobjectyesWhere the rows came from.
source.systemone of quickbooks, xero, sage, otheryesThe accounting system the rows were read from.
source.labelnullable string, up to 200 charactersnoFree text naming the company file or report, for the practitioner.
accountsarray of objects, 1 to 20000 itemsyesEvery account with a balance, in the order the system reports them.
accounts[].accountCodestring, up to 128 charactersnoThe account's code in the client's chart. Optional when the system has no codes; then the name identifies the account.
accounts[].accountNamestring, 1 to 512 charactersyesThe account's name.
accounts[].debitnullable number, at least 0 or stringnoAmount in dollars. Send a decimal string to preserve the exact source amount. Combined account amounts are rounded half-up to cents on import.
accounts[].creditnullable number, at least 0 or stringnoAmount in dollars. Send a decimal string to preserve the exact source amount. Combined account amounts are rounded half-up to cents on import.
accounts[].balancenullable number or stringnoAmount 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[].priorYearBalancenullable number or stringnoAmount 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[].gifiCodenullable string, 1 to 16 charactersnoCaller-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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
clientobjectyesThe client the engagement belongs to.
client.idstring (uuid)yesUUID of the client or entity.
client.namestringyesDisplay name of the client or entity.
client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
entityobjectyesThe legal entity the engagement belongs to.
entity.idstring (uuid)yesUUID of the client or entity.
entity.namestringyesDisplay name of the client or entity.
entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
periodStartnullable string (date)noFirst 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.
statusone of in_progress, review, completeyesWorkflow 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.
sourceone of prepared, importedyesValues: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference.
engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.
priorYearEngagementIdnullable string (uuid)noUUID 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.
isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
documentsPagenullable stringnoFilemark 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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
recordLifecycleobjectyesLive or recoverable-archived (distinct from the return's filing lifecycle).
recordLifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
recordLifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
recordLifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoNon-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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable explanation of the warning.
importStatusone of applied, needs_answers, blockedno
appliednullable objectnoWhat this call imported: the engagement's books after the import. Null when nothing was imported (importStatus needs_answers or blocked).
applied.accountCountinteger, at least 0when applied is setLive accounts on the engagement after the import.
applied.classifiedCountinteger, at least 0when applied is setLive accounts with a classification other than unclassified.
applied.unclassifiedCountinteger, at least 0when applied is setLive 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.gifiSeededCountinteger, at least 0when applied is setLive accounts with a GIFI assignment, including validated source codes and chart defaults.
applied.totalDebitsnullable stringwhen applied is setCanonical decimal debit total of the submitted source rows.
applied.totalCreditsnullable stringwhen applied is setCanonical decimal credit total of the submitted source rows.
applied.balancedbooleanwhen applied is setTrue when both exact source amounts and the saved cent amounts balance within a dollar. rows mode can import an unbalanced subset.
applied.warningsarray of strings, up to 50 itemswhen applied is setParser warnings, for the practitioner.
submittednullable objectnoThe 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.totalDebitsnullable stringwhen submitted is setCanonical decimal debit total of the submitted rows.
submitted.totalCreditsnullable stringwhen submitted is setCanonical decimal credit total of the submitted rows.
submitted.balancedbooleanwhen submitted is setTrue when the submitted debits and credits balance within a dollar.
submitted.warningsarray of strings, up to 50 itemswhen submitted is setParser warnings, for the practitioner.
previewIdnullable stringnoPrincipal- and engagement-scoped preview for the apply capability; expires after six hours.
questionsnullable objectno
questions.modeone of books, rowswhen questions is set
questions.confirmationCountsobjectwhen questions is set
questions.confirmationCounts.balancesUpdatedinteger, at least 0when questions is set
questions.confirmationCounts.renamedinteger, at least 0when questions is set
questions.confirmationCounts.addedinteger, at least 0when questions is set
questions.confirmationCounts.restoredinteger, at least 0when questions is set
questions.confirmationCounts.deletedinteger, at least 0when questions is set
questions.confirmationCounts.unchangedinteger, at least 0when questions is set
questions.confirmationCounts.skippedinteger, at least 0when questions is set
questions.conflictsarray of objects, up to 5000 itemswhen questions is set
questions.conflicts[].plansnullable map of objectwhen questions is set
questions.conflicts[].plans.{key}objectper key
questions.conflicts[].plans.{key}.balancesUpdatedinteger, at least 0per key
questions.conflicts[].plans.{key}.renamedinteger, at least 0per key
questions.conflicts[].plans.{key}.addedinteger, at least 0per key
questions.conflicts[].plans.{key}.restoredinteger, at least 0per key
questions.conflicts[].plans.{key}.deletedinteger, at least 0per key
questions.conflicts[].plans.{key}.unchangedinteger, at least 0per key
questions.conflicts[].plans.{key}.skippedinteger, at least 0per key
questions.conflicts[].accountIdstringwhen questions is set
questions.conflicts[].codestringwhen questions is set
questions.conflicts[].existingNamestringwhen questions is set
questions.conflicts[].incomingNamestringwhen questions is set
questions.conflicts[].incomingBalancestringwhen questions is set
questions.conflicts[].incomingCodenullable stringwhen questions is set
questions.conflicts[].crossKeybooleanwhen questions is set
questions.restoresarray of objects, up to 5000 itemswhen questions is set
questions.restores[].plansnullable map of objectwhen questions is set
questions.restores[].plans.{key}objectper key
questions.restores[].plans.{key}.balancesUpdatedinteger, at least 0per key
questions.restores[].plans.{key}.renamedinteger, at least 0per key
questions.restores[].plans.{key}.addedinteger, at least 0per key
questions.restores[].plans.{key}.restoredinteger, at least 0per key
questions.restores[].plans.{key}.deletedinteger, at least 0per key
questions.restores[].plans.{key}.unchangedinteger, at least 0per key
questions.restores[].plans.{key}.skippedinteger, at least 0per key
questions.restores[].accountIdstringwhen questions is set
questions.restores[].codestringwhen questions is set
questions.restores[].namestringwhen questions is set
questions.restores[].deletedAtnullable stringwhen questions is set
questions.restores[].incomingBalancestringwhen questions is set
questions.missingarray of objects, up to 5000 itemswhen questions is set
questions.missing[].plansmap of objectwhen questions is set
questions.missing[].plans.{key}objectper key
questions.missing[].plans.{key}.balancesUpdatedinteger, at least 0per key
questions.missing[].plans.{key}.renamedinteger, at least 0per key
questions.missing[].plans.{key}.addedinteger, at least 0per key
questions.missing[].plans.{key}.restoredinteger, at least 0per key
questions.missing[].plans.{key}.deletedinteger, at least 0per key
questions.missing[].plans.{key}.unchangedinteger, at least 0per key
questions.missing[].plans.{key}.skippedinteger, at least 0per key
questions.missing[].accountIdstringwhen questions is set
questions.missing[].codestringwhen questions is set
questions.missing[].namestringwhen questions is set
questions.missing[].balancestringwhen questions is set
questions.missing[].refsobjectwhen questions is set
questions.missing[].refs.ajeLinesintegerwhen questions is set
questions.missing[].refs.workpapersintegerwhen questions is set
questions.missing[].refs.groupsintegerwhen questions is set
questions.duplicatesarray of objects, up to 5000 itemswhen questions is set
questions.duplicates[].codestringwhen questions is set
questions.duplicates[].combinedBalancestringwhen questions is set
questions.duplicates[].rowsarray of objects, up to 5000 itemswhen questions is set
questions.duplicates[].rows[].namestringwhen questions is set
questions.duplicates[].rows[].balancestringwhen questions is set
questions.duplicates[].rowsCountinteger, at least 0when questions is setMember rows in this source group, listed or not.
questions.duplicates[].rowsTruncatedbooleanwhen questions is setOnly 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[].plansobjectwhen questions is setApply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner.
questions.duplicates[].plans.combinearray of objects, 1 to 2 itemswhen questions is set
questions.duplicates[].plans.combine[].conflictsmap of one of rename, new_accountwhen questions is set
questions.duplicates[].plans.combine[].conflicts.{key}one of rename, new_accountper key
questions.duplicates[].plans.combine[].restoresmap of one of restore, skipwhen questions is set
questions.duplicates[].plans.combine[].restores.{key}one of restore, skipper key
questions.duplicates[].plans.combine[].countsobjectwhen questions is set
questions.duplicates[].plans.combine[].counts.balancesUpdatedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.renamedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.addedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.restoredinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.deletedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.unchangedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.skippedinteger, at least 0when questions is set
questions.duplicates[].plans.firstarray of objects, 1 to 2 itemswhen questions is set
questions.duplicates[].plans.first[].conflictsmap of one of rename, new_accountwhen questions is set
questions.duplicates[].plans.first[].conflicts.{key}one of rename, new_accountper key
questions.duplicates[].plans.first[].restoresmap of one of restore, skipwhen questions is set
questions.duplicates[].plans.first[].restores.{key}one of restore, skipper key
questions.duplicates[].plans.first[].countsobjectwhen questions is set
questions.duplicates[].plans.first[].counts.balancesUpdatedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.renamedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.addedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.restoredinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.deletedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.unchangedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.skippedinteger, at least 0when questions is set
questions.duplicates[].plans.skiparray of objects, 1 to 1 itemswhen questions is set
questions.duplicates[].plans.skip[].conflictsmap of one of rename, new_accountwhen questions is set
questions.duplicates[].plans.skip[].conflicts.{key}one of rename, new_accountper key
questions.duplicates[].plans.skip[].restoresmap of one of restore, skipwhen questions is set
questions.duplicates[].plans.skip[].restores.{key}one of restore, skipper key
questions.duplicates[].plans.skip[].countsobjectwhen questions is set
questions.duplicates[].plans.skip[].counts.balancesUpdatedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.renamedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.addedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.restoredinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.deletedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.unchangedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.skippedinteger, at least 0when questions is set
questions.lowMatchnullable objectwhen questions is set
questions.lowMatch.matchedRowsintegerwhen questions is set
questions.lowMatch.rowsintegerwhen questions is set
questions.blockednullable objectwhen questions is set
questions.blocked.reasonstringwhen questions is set
questions.blocked.messagestringwhen questions is set
questions.coveragenullable objectwhen questions is set
questions.coverage.fiscalPeriodobjectwhen questions is set
questions.coverage.fiscalPeriod.startnullable stringwhen questions is set
questions.coverage.fiscalPeriod.endnullable stringwhen questions is set
questions.coverage.filesarray of objectswhen questions is set
questions.coverage.files[].glIdstringwhen questions is set
questions.coverage.files[].filenamestringwhen questions is set
questions.coverage.files[].accountsarray of stringswhen questions is set
questions.coverage.files[].reportRangenullable objectwhen questions is set
questions.coverage.files[].reportRange.startnullable stringwhen questions is set
questions.coverage.files[].reportRange.endnullable stringwhen questions is set
questions.coverage.files[].observedRangenullable objectwhen questions is set
questions.coverage.files[].observedRange.startnullable stringwhen questions is set
questions.coverage.files[].observedRange.endnullable stringwhen questions is set
questions.coverage.missingRangesarray of objectswhen questions is set
questions.coverage.missingRanges[].startnullable stringwhen questions is set
questions.coverage.missingRanges[].endnullable stringwhen questions is set
questions.coverage.requirementsarray of objectswhen questions is set
questions.coverage.requirements[].idstringwhen questions is set
questions.coverage.requirements[].messagestringwhen questions is set
questions.priorYearMismatchbooleanwhen questions is set
questions.missingCountinteger, at least 0when questions is set
questions.missingTruncatedbooleanwhen questions is setOnly the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account.
questions.conflictsCountinteger, at least 0when questions is setName conflicts the import found, listed or not.
questions.conflictsTruncatedbooleanwhen questions is setOnly the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest.
questions.restoresCountinteger, at least 0when questions is setRows matching a deleted account, listed or not.
questions.restoresTruncatedbooleanwhen questions is setOnly the first 5,000 rows matching a deleted account are listed; omitted answers skip the row.
questions.duplicatesCountinteger, at least 0when questions is setDuplicated account codes in the source, listed or not.
questions.duplicatesTruncatedbooleanwhen questions is setOnly 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the account belongs to. An engagement is one corporation's taxation year.
account_idpathstring, 1 to 128 charactersyesIdentifier 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

FieldTypeAlways presentDescription
contextobjectyesThe engagement, reporting currency and sign convention this account belongs to.
context.engagementIdstring (uuid)yesUUID of the engagement this account belongs to. An engagement is one corporation's taxation year.
context.yearEndstring (date)yesThe engagement's taxation year end, YYYY-MM-DD.
context.revisionNumberinteger, at least 1yesFiling revision counter for this year end. The original engagement is 1. Each amendment opened from it adds one.
context.statusone of in_progress, review, completeyesPreparation 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.sourceone of prepared, importedyesHow 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.filedAtnullable string (date-time)yesWhen 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.referenceOnlybooleanyesTrue when source is imported, meaning the engagement is a reference-only record whose balances are comparatives.
context.reportingCurrencystring, matching ^[A-Z]{3}$yesISO 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.currencyBasisone of canadian_currency, functional_currency, reversionaryyesWhat 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.balanceConventionstring, always "debits_positive_credits_negative"yesAlways debits_positive_credits_negative. Every balance in this response is signed with debits positive and credits negative.
sourceRevisionobjectyesVersion counters and fingerprints for the saved state this response was read from.
sourceRevision.classifiedReportVersioninteger, at least 0yesSaved trial-balance version, including account classifications, incremented on every save. Zero before the first save.
sourceRevision.gifiAssignmentsVersioninteger, at least 0yesWrite counter for the saved GIFI code assignments, on the same convention as classifiedReportVersion.
sourceRevision.gifiCarriedForwardVersioninteger, at least 0yesWrite counter for the record of which GIFI assignments were carried forward from the prior year, on the same convention as classifiedReportVersion.
sourceRevision.gifiAutoAppliedAccountIdsVersioninteger, at least 0yesWrite counter for the record of which GIFI assignments were applied in bulk from suggestions, on the same convention as classifiedReportVersion.
sourceRevision.ajeEntriesVersioninteger, at least 0yesWrite counter for the saved adjusting journal entries, on the same convention as classifiedReportVersion.
sourceRevision.lastModifiedAtstring (date-time)yesWhen any part of the engagement was last saved, ISO-8601 in UTC.
sourceRevision.workpaperRevisionDigeststring, matching ^sha256:[0-9a-f]{64}$yesFingerprint 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.snapshotDigeststring, matching ^sha256:[0-9a-f]{64}$yesFingerprint 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.
accountobjectyesThe account's code, name and balances before any adjustment.
account.idstring, 1 to 128 charactersyesIdentifier 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.accountCodenullable string, up to 128 charactersyesAccount number as it appears in the client's chart of accounts. Null for a trial balance whose accounts are identified by name only.
account.accountNamestring, up to 512 charactersyesAccount name as it appears in the client's chart of accounts. May be an empty string when the source file carried no name.
account.currentYearBalancedecimal string, 1 to 96 charactersyesClosing balance for the taxation year, before adjusting entries and tax adjustments. Exact decimal string in the reporting currency, debits positive and credits negative.
account.priorYearBalancenullable decimal string, 1 to 96 charactersyesThe 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.changeStatusone of new, removed, significant_increase, significant_decrease, minor_change, unchanged, unknownyesHow 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.
classificationobjectyesThe saved tax treatment of the account.
classification.availabilityone of classified, not_classifiedyesclassified 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.userStatusnullable one of pending, modified, acceptedyesWhere 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.ruleIdnullable string, up to 256 charactersyesIdentifier of the classification rule that matched the account, if one did. Null for a classification that did not come from a rule.
classification.sourcenullable one of rule, prior_year, llm, llm_failed, deferred_to_host, manualyesWhere 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.confidencenullable decimal string, 1 to 96 charactersyesThe 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.assumptionnullable string, up to 4000 charactersyesThe 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.bookTreatmentnullable one of accept_as_booked, reclassify, accrue_adjust, write_off, requires_review, unclassifiedyesWhat 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.taxTreatmentnullable one of fully_deductible, non_deductible_full, partially_deductible, capital_cca, timing_difference, deduct_other_schedule, non_taxable, disclosure_only, informational, unclassifiedyesHow 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.bookTreatmentSourcenullable one of derived, manualyesderived 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.taxTreatmentSourcenullable one of derived, manualyesderived 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.s1Linenullable string, up to 32 charactersyesSchedule 1 line for the account adjustment. Null when no adjustment is routed to Schedule 1 or a preparer must resolve an ambiguous mapping.
classification.feedsSchedulenullable string, up to 32 charactersyesThe 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.taxAssumptionNotenullable string, up to 4000 charactersyesExplanation of a conditional or unresolved routing decision. Null when no note applies.
classification.derivationRuleIdnullable string, up to 256 charactersyesIdentifier of the branch that produced bookTreatment, taxTreatment, s1Line and feedsSchedule. Null when the account is not classified.
classification.adjustmentTypenullable one of addition, deductionyesWhich 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.deductibilityRulenullable string, up to 2000 charactersyesPlain-language statement of the rule limiting the deduction, including its statutory basis. Null when no rule is recorded.
classification.deductibilityPercentagenullable decimal string, 1 to 96 charactersyesShare 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.incomeTypenullable one of active_business, property, rental, capitalyesThe 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.incomeTypeSourcenullable one of derived, manualyesderived when incomeType was computed from the classification, manual when a preparer set it. Null when incomeType is null.
classification.shareholderOriginnullable one of borrowed_principal, unpaid_remuneration, unpaid_related_expenses, shareholder_advanceno
classification.shareholderOriginSourcenullable string, always "manual"no
classification.salesTaxAttributionnullable objectno
classification.salesTaxAttribution.*anynoMembers not listed here.
classification.salesTaxAttributionSourcenullable string, always "manual"no
classification.foreignSourcenullable booleanyesTrue 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.incomeSourceBasisnullable string, always "rule_default_canadian_not_incident"yesHow 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.subtypenullable string, up to 64 charactersyesA finer tag within the income type, such as dividend, used to route the amount more precisely. Null when the classification sets none.
classification.conditionalOnnullable string, up to 2000 charactersyesThe factual condition that must hold for this treatment to apply. Null when the treatment is unconditional.
classification.templateIdnullable string, up to 256 charactersyesIdentifier of the workpaper template this account calls for. Null when the account needs no workpaper.
classification.itaReferencesarray of strings, up to 32 itemsyesIncome Tax Act provisions the classification relies on, as written citations. Empty when none are recorded.
gifiMappingobjectyesThe GIFI code saved for this account and the chart-of-accounts record behind it.
gifiMapping.assignedGifiCodenullable string, matching ^[0-9]{4}$yesThe four-digit GIFI code assigned to the account for this year. Null when no code has been assigned yet.
gifiMapping.carriedForwardbooleanyesTrue when the assignment was copied forward from the prior year.
gifiMapping.machineAppliedbooleanyesTrue when the assignment was applied in bulk from a suggestion. These assignments count as unconfirmed and are not used to train later suggestions.
gifiMapping.relationToCurrentDefaultone of unassigned, no_default, matches_default, overrides_defaultyesHow 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.chartAccountnullable objectyesThe 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.idstring (uuid)when chartAccount is setUUID of the chart-of-accounts record.
gifiMapping.chartAccount.naturalKeystring, 1 to 517 characterswhen chartAccount is setThe 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.accountTypeone of asset, liability, equity, revenue, expense, unassignedwhen chartAccount is setThe account's accounting class. unassigned when no class has been established for it.
gifiMapping.chartAccount.accountSubtypenullable string, up to 256 characterswhen chartAccount is setThe account type as the source accounting system named it, kept verbatim. Null when the source supplied none.
gifiMapping.chartAccount.subtypeSourcenullable string, up to 64 characterswhen chartAccount is setWhich accounting system's vocabulary accountSubtype is written in. Null when accountSubtype is null.
gifiMapping.chartAccount.accountTypeSourceone of inferred, firm_template, connector, coa_file, userwhen chartAccount is setWhere 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.isContrabooleanwhen chartAccount is setTrue when the account runs against its class, such as accumulated depreciation against fixed assets.
gifiMapping.chartAccount.isActivebooleanwhen chartAccount is setTrue while the account is part of the current chart. False once it has stopped appearing in imported books.
gifiMapping.chartAccount.defaultGifiCodenullable string, matching ^[0-9]{4}$when chartAccount is setThe four-digit GIFI code that seeds each new year for this account. Null when no default has been established.
gifiMapping.chartAccount.defaultGifiSourcenullable one of inferred, firm_template, carried, connector, coa_file, userwhen chartAccount is setWhere 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.parentAccountCodenullable string, up to 128 characterswhen chartAccount is setAccount 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.updatedAtstring (date-time)when chartAccount is setWhen the chart-of-accounts record was last written, ISO-8601 in UTC.
adjustmentSummaryobjectyesTotals and counts for the adjustments touching this account.
adjustmentSummary.postedBookAdjustmentTotaldecimal string, 1 to 96 charactersyesNet effect of posted adjusting entries on this account, as a decimal string in the reporting currency. Debits are positive and credits negative.
adjustmentSummary.adjustedBookBalancedecimal string, 1 to 96 charactersyesThe 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.postedBookAdjustmentCountinteger, at least 0yesHow many adjusting journal entries touching this account post.
adjustmentSummary.blockedBookAdjustmentCountinteger, at least 0yesHow 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.workpaperTaxAdjustmentSourceCountinteger, at least 0yesNumber of linked workpapers with a tax adjustment. A workpaper may be linked to several accounts.
adjustmentSummary.schedule1ImpactEvaluatedboolean, always falseyesAlways false. Schedule 1 contributions require a separate computation.
projectionobjectyesWhat this response does and does not include.
projection.savedStateOnlyboolean, always trueyesAlways true. The response uses saved state.
projection.classificationIncludedboolean, always trueyesAlways true. The account's saved classification is included.
projection.gifiMappingIncludedboolean, always trueyesAlways true. The account's saved GIFI assignment is included.
projection.postedBookAdjustmentsSummarizedboolean, always trueyesAlways true. Use the account adjustments endpoint for individual adjusting entries.
projection.workpaperTaxAdjustmentsSummarizedboolean, always trueyesAlways true. Use the account adjustments endpoint for individual workpaper tax adjustments.
projection.workpaperAmountsAllocatedToAccountboolean, always falseyesAlways false. Workpaper amounts apply to the whole workpaper.
projection.schedule1ImpactEvaluatedboolean, always falseyesAlways false. Schedule 1 contributions are not computed by this operation.
projection.computationEvaluatedboolean, always falseyesAlways false. No schedule or return computation is run to produce this response.
projection.lineageIncludedboolean, always falseyesAlways false. No per-amount lineage or derivation trail is returned.
projection.readinessEvaluatedboolean, always falseyesAlways false. Filing readiness is not evaluated by this operation.
projection.storageMetadataIncludedboolean, always falseyesAlways 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the account belongs to. An engagement is one corporation's taxation year.
account_idpathstring, 1 to 128 charactersyesIdentifier 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

FieldTypeAlways presentDescription
contextobjectyesThe engagement, reporting currency and sign convention these amounts belong to.
context.engagementIdstring (uuid)yesUUID of the engagement this account belongs to. An engagement is one corporation's taxation year.
context.yearEndstring (date)yesThe engagement's taxation year end, YYYY-MM-DD.
context.revisionNumberinteger, at least 1yesFiling revision counter for this year end. The original engagement is 1. Each amendment opened from it adds one.
context.statusone of in_progress, review, completeyesPreparation 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.sourceone of prepared, importedyesHow 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.filedAtnullable string (date-time)yesWhen 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.referenceOnlybooleanyesTrue when source is imported, meaning the engagement is a reference-only record whose balances are comparatives.
context.reportingCurrencystring, matching ^[A-Z]{3}$yesISO 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.currencyBasisone of canadian_currency, functional_currency, reversionaryyesWhat 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.balanceConventionstring, always "debits_positive_credits_negative"yesAlways debits_positive_credits_negative. Every balance in this response is signed with debits positive and credits negative.
sourceRevisionobjectyesVersion counters and fingerprints for the saved state this response was read from.
sourceRevision.classifiedReportVersioninteger, at least 0yesSaved trial-balance version, including account classifications, incremented on every save. Zero before the first save.
sourceRevision.gifiAssignmentsVersioninteger, at least 0yesWrite counter for the saved GIFI code assignments, on the same convention as classifiedReportVersion.
sourceRevision.gifiCarriedForwardVersioninteger, at least 0yesWrite counter for the record of which GIFI assignments were carried forward from the prior year, on the same convention as classifiedReportVersion.
sourceRevision.gifiAutoAppliedAccountIdsVersioninteger, at least 0yesWrite counter for the record of which GIFI assignments were applied in bulk from suggestions, on the same convention as classifiedReportVersion.
sourceRevision.ajeEntriesVersioninteger, at least 0yesWrite counter for the saved adjusting journal entries, on the same convention as classifiedReportVersion.
sourceRevision.lastModifiedAtstring (date-time)yesWhen any part of the engagement was last saved, ISO-8601 in UTC.
sourceRevision.workpaperRevisionDigeststring, matching ^sha256:[0-9a-f]{64}$yesFingerprint 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.snapshotDigeststring, matching ^sha256:[0-9a-f]{64}$yesFingerprint 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.
accountobjectyesThe account these adjustments were read for, with its balances before any adjustment.
account.idstring, 1 to 128 charactersyesIdentifier 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.accountCodenullable string, up to 128 charactersyesAccount number as it appears in the client's chart of accounts. Null for a trial balance whose accounts are identified by name only.
account.accountNamestring, up to 512 charactersyesAccount name as it appears in the client's chart of accounts. May be an empty string when the source file carried no name.
account.currentYearBalancedecimal string, 1 to 96 charactersyesClosing balance for the taxation year, before adjusting entries and tax adjustments. Exact decimal string in the reporting currency, debits positive and credits negative.
account.priorYearBalancenullable decimal string, 1 to 96 charactersyesThe 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.changeStatusone of new, removed, significant_increase, significant_decrease, minor_change, unchanged, unknownyesHow 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.
projectionobjectyesWhat this response does and does not include.
projection.postedBookAdjustmentsIncludedboolean, always trueyesAlways true. Adjusting journal entries that post to this account are listed.
projection.blockedBookAdjustmentsIncludedboolean, always trueyesAlways true. Entries touching this account that cannot post are listed too.
projection.workpaperTaxAdjustmentSourcesIncludedboolean, always trueyesAlways true. The workpapers holding a tax adjustment that are linked to this account are listed.
projection.unrelatedBookAdjustmentLinesIncludedboolean, always falseyesAlways false. Returned lines belong to the requested account.
projection.workpaperAmountsAllocatedToAccountboolean, always falseyesAlways false. Workpaper amounts apply to the whole workpaper.
projection.schedule1ImpactEvaluatedboolean, always falseyesAlways false. Schedule 1 contributions are not computed by this operation.
projection.computationEvaluatedboolean, always falseyesAlways false. No schedule or return computation is run to produce this response.
projection.readinessEvaluatedboolean, always falseyesAlways false. Filing readiness is not evaluated by this operation.
projection.storageMetadataIncludedboolean, always falseyesAlways false. No workpaper payloads, rows, notes, attachments or internal storage details are returned.
postedBookAdjustmentTotaldecimal string, 1 to 96 charactersyesNet 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.
adjustedBookBalancedecimal string, 1 to 96 charactersyesThe 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.
bookAdjustmentsarray of objects, up to 5000 itemsyesAll adjusting entries with lines for this account, including their posting status and this account's lines. Empty when no entries apply.
bookAdjustments[].kindstring, always "book_aje"yesAlways book_aje, marking this as an adjusting journal entry.
bookAdjustments[].idstring, 1 to 128 charactersyesIdentifier of the entry within this engagement.
bookAdjustments[].numberinteger, at least 1yesThe entry's number in the engagement, as shown to preparers.
bookAdjustments[].namestring, up to 512 charactersyesShort title the preparer gave the entry. May be an empty string.
bookAdjustments[].descriptionstring, up to 4000 charactersyesThe preparer's longer explanation of the entry. May be an empty string.
bookAdjustments[].entryTypeone of adjusting, reclassifying, tax_provision, potentialyesWhat 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[].sourceone of preparer, clientyesWho the entry came from. preparer for one raised in the engagement. client for one already in the client's own books.
bookAdjustments[].entryDatenullable string (date)yesThe date the preparer put on the entry, YYYY-MM-DD. Null when the entry carries no date.
bookAdjustments[].recurringbooleanyesTrue when the preparer marked the entry as one that recurs each year.
bookAdjustments[].postingStatusone of posted, blocked, excludedyesWhether 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[].blockReasonnullable one of unbalanced, unlinked_accountyesWhy 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[].totalDebitsdecimal string, 1 to 96 charactersyesTotal debits across all accounts in the entry, as a non-negative decimal string in the reporting currency.
bookAdjustments[].totalCreditsdecimal string, 1 to 96 charactersyesTotal credits across all accounts in the entry, as a non-negative decimal string in the reporting currency. Equals totalDebits for a balanced entry.
bookAdjustments[].accountEffectnullable decimal string, 1 to 96 charactersyesNet 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[].requestedAccountLinesarray of objects, up to 2000 itemsyesLines for the requested account. totalDebits and totalCredits cover the entire entry across all accounts.
bookAdjustments[].requestedAccountLines[].idstring, 1 to 128 charactersyesIdentifier of the line within its entry.
bookAdjustments[].requestedAccountLines[].typeone of debit, credityesWhich 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[].accountIdstring, 1 to 128 charactersyesThe account this line posts to, always the account you asked for. Lines of the same entry that post elsewhere are not returned.
bookAdjustments[].requestedAccountLines[].accountCodenullable string, up to 128 charactersyesAccount number as written on the line. Null when the line carries none.
bookAdjustments[].requestedAccountLines[].accountNamestring, up to 512 charactersyesAccount name as written on the line. May be an empty string.
bookAdjustments[].requestedAccountLines[].amountdecimal string, 1 to 96 charactersyesNon-negative line amount as a decimal string in the reporting currency. type identifies the debit or credit direction.
workpaperTaxAdjustmentsarray of objects, up to 2000 itemsyesEvery 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[].kindstring, always "workpaper_tax_adjustment"yesAlways workpaper_tax_adjustment, marking this as a workpaper source.
workpaperTaxAdjustments[].workpaperIdstring (uuid)yesUUID of the workpaper.
workpaperTaxAdjustments[].templateIdnullable string, up to 256 charactersyesIdentifier of the template the workpaper was built from. Null for a workpaper that was not built from a template.
workpaperTaxAdjustments[].displayNamenullable string, up to 512 charactersyesThe name a preparer gave the workpaper. Null when it carries no custom name. The product then shows the template's name.
workpaperTaxAdjustments[].versioninteger, at least 1yesWrite counter for the workpaper, starting at 1 and increasing on every save.
workpaperTaxAdjustments[].lifecycleStatusstring, 1 to 64 charactersyesWorkpaper preparation status, such as draft. Treat unknown values as informational.
workpaperTaxAdjustments[].tieOutStatusnullable one of matched, difference, pending, incompleteyesWhether 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[].adjustmentAmountnullable decimal string, 1 to 96 charactersyesThe 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[].adjustmentTypenullable one of addition, deductionyesWhich way the adjustment goes: addition adds the amount back to income, deduction takes it out. Null when the workpaper does not say.
workpaperTaxAdjustments[].adjustmentStatusstring, 1 to 128 charactersyesWhether 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[].requiresManualReviewbooleanyesTrue whenever adjustmentStatus is anything other than ok. A preparer has to review the workpaper before its amount can be relied on.
workpaperTaxAdjustments[].linkedAccountCountinteger, at least 1yesHow 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[].linkedAccountIdsarray of strings, 1 to 20000 itemsyesIdentifiers of every trial-balance account the workpaper is linked to, always including the one you asked for.
workpaperTaxAdjustments[].allocationBasisstring, always "workpaper_total_not_account_allocated"yesAlways workpaper_total_not_account_allocated. adjustmentAmount applies to the whole workpaper.
workpaperTaxAdjustments[].schedule1ImpactEvaluatedboolean, always falseyesAlways 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID 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.

FieldTypeRequiredDescription
computearray of strings, 1 to 100 itemsyesSchedule/result keys to compute. Dependencies run automatically.
inputsobjectnoReplacements 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.*anynoMembers not listed here.
viewone of full, summaryno'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.
resultsOffsetinteger, 0 to 10000noPosition of the first target result to return, in target-name order, counting from 0; continue from the previous page's resultsPage.nextOffset.
resultsLimitnullable integer, 1 to 200noMost 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

FieldTypeAlways presentDescription
dataobjectyesThe computed results, the cells this request replaced, and the lineage hash of the saved state they were applied to.
data.engagementIdstring (uuid)yesUUID of the engagement whose saved inputs this computation was built from, the same id the request path names.
data.sourceStateSha256stringyesLineage 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.appliedOverridesarray of stringsyesThe input cells this request replaced, in sorted order. Every other cell came from the engagement's saved inputs.
data.resultsobjectyesEngine 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.*anynoThe target's output cells; see the computation reference.
data.returnFindingsarray of objectsnoQuestions 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[].*anynoMembers not listed here.
data.summarynullable objectnoHeadline figures, each target's state and the findings, bounded, so the result can be read without the per-target results.
data.summary.headlinearray of objectswhen summary is setThe 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[].labelstringwhen summary is setWhat the figure is, with its T2 line where it has one.
data.summary.headline[].targetstringwhen summary is setThe target whose result carries the figure.
data.summary.headline[].outputstringwhen summary is setThe result member the figure is read from, dotted for a nested one.
data.summary.headline[].valuenullable number or integer or string or booleanwhen summary is setThe 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[].targetStateone of computed, provisional, held, not_computedwhen summary is setThe 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.targetsarray of objectswhen summary is setEvery computed target in target-name order, with its state and finding counts.
data.summary.targets[].targetstringwhen summary is setThe target id.
data.summary.targets[].stateone of computed, provisional, held, not_computedwhen 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[].provisionalnullable booleanwhen summary is setThe result's own provisional flag. Null when it states none.
data.summary.targets[].readynullable booleanwhen summary is setThe result's own ready flag. Null when it states none.
data.summary.targets[].errorCountinteger, at least 0when summary is setError findings the result carries.
data.summary.targets[].warningCountinteger, at least 0when summary is setWarning findings the result carries.
data.summary.targets[].heldByarray of stringswhen summary is setThe producers the target waits on, when it is held by a dependency.
data.summary.findingsarray of objects, up to 25 itemswhen summary is setThe error and warning findings, errors first, at most 25; findingCount counts them all.
data.summary.findings[].targetstringwhen summary is setThe target whose result carries the finding.
data.summary.findings[].codenullable stringwhen summary is setThe finding's code. Null when it has none.
data.summary.findings[].severityone of error, warningwhen summary is setErrors are listed first.
data.summary.findings[].messagestringwhen summary is setThe finding's message, cut at 400 characters; the full text is in the target's result.
data.summary.findingCountinteger, at least 0when summary is setHow many error and warning findings the results carry.
data.resultsPagenullable objectnoWhich per-target results this answer carries, and the next page.
data.resultsPage.viewone of full, summarywhen resultsPage is setThe view the request asked for.
data.resultsPage.offsetinteger, at least 0when resultsPage is setPosition of the first result carried, in target-name order.
data.resultsPage.limitnullable integerwhen resultsPage is setThe page size asked for. Null when every result from offset was asked for.
data.resultsPage.totalinteger, at least 0when resultsPage is setHow many target results the computation produced.
data.resultsPage.returnedinteger, at least 0when resultsPage is setHow many target results this answer carries.
data.resultsPage.nextOffsetnullable integerwhen resultsPage is setThe 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.
computeVersionstringyesIdentifier 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.
engineSchemaVersionstringyesSemantic 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.
ratesVersionstringyesContent hash of the rate tables. Two responses with equal computeVersion and ratesVersion were computed on identical engine code and identical rate tables.
timestampstring (date-time)yesWhen 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 code result_not_representable means 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

FieldTypeAlways presentDescription
dataobjectyesThe batch targets, their dependencies, and the rollover targets.
data.batchTargetsarray of stringsyesEvery 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.batchDependenciesmap of array of stringsyesFor 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 stringsper keyFor 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.rolloverTargetsarray of stringsyesEvery 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
target_idpathstring, 1 to 64 characters, matching ^[a-z][a-z0-9_-]*$yesBatch or rollover target id from the computation catalog.
viewqueryone 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.
inputPathquerynullable string, up to 512 charactersnoWith 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.
inputsOffsetqueryinteger, 0 to 10000, default 0noWith view 'inputs', the position of the first name to list, counting from 0; continue from the previous page's inputIndex.nextOffset.
inputsLimitqueryinteger, 1 to 50, default 50noWith 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

FieldTypeAlways presentDescription
dataobjectyesThe requested target's published contract.
data.idstringyesThe target's id, as the computation catalog lists it and as a compute request names it.
data.kindone of batch, rolloveryesWhich operation computes this target: 'batch' for POST /api/v1/computations/batch, 'rollover' for POST /api/v1/computations/rollovers/{target}.
data.titlenullable stringnoThe 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.groupnullable stringnoThe 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.programnullable stringnoThe return or product line this target belongs to. Null for a catalogued engine node that publishes no contract document.
data.jurisdictionnullable stringnoThe taxing jurisdiction this target computes for. Null for a catalogued engine node that publishes no contract document.
data.statusnullable stringnoLifecycle 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.supportedTaxYearsnullable array of objectsnoInclusive taxation-year windows for schedule targets. Null where a year window does not apply, as for rollover targets.
data.supportedTaxYears[].firstintegerwhen supportedTaxYears is setFirst taxation year this target supports, as a four-digit year. The window is inclusive of this year.
data.supportedTaxYears[].lastnullable integerwhen supportedTaxYears is setLast supported taxation year. Null means open-ended.
data.contractnullable objectnoThe selector to send as payloadContract to validate a request against this target's pinned schema pair.
data.contract.boundaryProfileIdstring, 1 to 128 characters, matching ^[a-z][a-z0-9_.-]*$when contract is setIdentifier of the strict computation boundary profile.
data.contract.payloadSchemaVersionstring, up to 64 characters, matching ^[0-9]+\.[0-9]+\.[0-9]+$when contract is setSemantic version of the target payload schema pair.
data.inputsarray of objectsyesEvery 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[].pathstringyesDotted path of this cell inside the request's inputs object. A [] segment addresses the elements of an array.
data.inputs[].typesarray of stringsyesEvery JSON type the boundary admits at this position.
data.inputs[].requiredbooleanyesWhether this cell is required when using the strict payload contract. See requiredOnDefaultBoundary for requirements that apply to every request.
data.inputs[].requiredOnDefaultBoundarybooleanyesWhether 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[].strictPinnedbooleanyesWhether the strict payload contract fixes this cell to a single value.
data.inputs[].*anynoMembers not listed here.
data.outputsarray of objectsyesEvery 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[].pathstringyesDotted path of this cell inside the target's result object. A [] segment addresses the elements of an array.
data.outputs[].typesarray of stringsyesEvery JSON type this result position can carry.
data.outputs[].*anynoMembers not listed here.
data.exampleInputnullable objectnoAn example request body the target accepts and computes. It is the value a caller sends as inputs. Null in view 'inputs'.
data.exampleInput.*anynoMembers not listed here.
data.dependenciesnullable array of stringsnoBatch targets that run automatically with this one. The request may also carry their input cells. Null for rollovers.
data.inputIndexnullable objectnoIn 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.pathstringwhen inputIndex is setThe position read, dotted. Empty for the top level of the target's input.
data.inputIndex.membersarray of objectswhen inputIndex is setThe names directly under path, in name order, from offset.
data.inputIndex.members[].namestringwhen inputIndex is setThe input's name at this position.
data.inputIndex.members[].pathstringwhen inputIndex is setIts 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[].typesarray of stringswhen inputIndex is setThe 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[].requiredOnDefaultBoundarybooleanwhen inputIndex is setWhether every request needs this input, as the cell of the same name in the full contract says.
data.inputIndex.members[].cellsinteger, at least 1when inputIndex is setHow 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[].summarynullable stringwhen inputIndex is setThe first sentence of its description, cut at 128 characters. Null when the contract describes it nowhere at this path.
data.inputIndex.offsetinteger, at least 0when inputIndex is setPosition of the first member listed, counting from 0.
data.inputIndex.limitinteger, at least 1when inputIndex is setThe most members this page could list (inputsLimit, at most 50).
data.inputIndex.totalinteger, at least 0when inputIndex is setHow many names sit directly under path.
data.inputIndex.returnedinteger, at least 0when inputIndex is setHow 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.nextOffsetnullable integerwhen inputIndex is setThe 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 code result_not_representable means 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.

FieldTypeRequiredDescription
computearray of strings, 1 to 100 itemsyesSchedule/result keys to compute. Dependencies run automatically.
inputsobjectnoInputs 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.*anynoMembers not listed here.
vendorInputobjectnoVendor 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.vendorone of taxcycle, taxprepwhen vendorInput is setVendor vocabulary used by every key in cells.
vendorInput.cellsobjectwhen vendorInput is setCase-sensitive import field codes or cell IDs of the selected tax software. Supply values in their canonical Filemark JSON types and units.
vendorInput.cells.*anynoMembers not listed here.
payloadContractobjectnoStrict contract to validate the request against. Omit this property for the default boundary. Null is not valid.
payloadContract.boundaryProfileIdstring, 1 to 128 characters, matching ^[a-z][a-z0-9_.-]*$when payloadContract is setIdentifier of the strict computation boundary profile.
payloadContract.payloadSchemaVersionstring, up to 64 characters, matching ^[0-9]+\.[0-9]+\.[0-9]+$when payloadContract is setSemantic version of the target payload schema pair.
handoffobjectnoOptional 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.vendorone of taxcycle, taxprep, ifirmwhen handoff is setReceiving 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.formatone of json, file, gfiwhen handoff is setContainer 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.languageone of en, frwhen handoff is setImport-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.corporationNamestring, 1 to 200 characterswhen handoff is setCorporation name that identifies a format 'gfi' artifact. Optional on the selector. Format 'gfi' refuses when it is absent.
handoff.businessNumberstring, 9 to 32 characterswhen handoff is setCorporation BN9 or RC program-account number for the format 'gfi' header. Optional on the selector. Format 'gfi' refuses when it is absent or malformed.
calculationReferenceDatestring (date)noCalendar 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

FieldTypeAlways presentDescription
dataobjectyesComputed 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.resultsmap of objectnoOne 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}objectper keyThe target's result. It carries the readiness flags, the warnings, and the published output cells.
data.results.{key}.readybooleanper keyTrue 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}.provisionalbooleanper keyTrue 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}.warningsarray of anyper keyFindings 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}.*anynoThe target's output cells; see the computation reference.
data.notComputedarray of stringsnoRequested 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.calculationReferenceDatestring (date)noThe calculationReferenceDate the request sent, which the date-sensitive claims were computed as of. Present only when the request sent it.
data.calculationReferenceobjectnoServer 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.schemaVersioninteger, always 1when calculationReference is set
data.calculationReference.referenceInstantstring (date-time)when calculationReference is set
data.calculationReference.calendarDatestring (date)when calculationReference is set
data.calculationReference.timeZonestringwhen calculationReference is set
data.calculationReference.authorityone of advisorywhen calculationReference is set
data.resultobjectnoThe 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.readybooleanwhen result is setTrue 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.provisionalbooleanwhen result is setTrue 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.warningsarray of anywhen result is setFindings 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.*anynoThe target's output cells; see the computation reference.
data.handoffobjectnoThe 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.blockednullable objectwhen handoff is setNull 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.warningsarray of anywhen handoff is setOne 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.*anynoMembers not listed here.
data.vendorInputobjectnoMapping provenance for accepted vendor cells. Present only when vendorInput was submitted. Values are never echoed.
data.vendorInput.vendorone of taxcycle, taxprepwhen vendorInput is setThe vendor vocabulary the submitted cells were resolved in, echoing the selector.
data.vendorInput.mappingTableVersionstringwhen vendorInput is setVersion of the Filemark mapping table the cells were resolved through.
data.vendorInput.vendorEditionnullable stringwhen vendorInput is setThe product release these cell identifiers are valid for. Null when the table does not record one for this vendor.
data.vendorInput.fieldMapVersionstringwhen vendorInput is setVersion of the vendor field map used to resolve the cells. Present only for vendors that resolve through a field map.
data.vendorInput.appliedarray of objectswhen vendorInput is setOne entry per accepted vendor cell, in cell order, naming the canonical input it became.
data.vendorInput.applied[].cellstringwhen vendorInput is setThe vendor cell identifier as submitted.
data.vendorInput.applied[].schedulestringwhen vendorInput is setThe schedule the cell belongs to in the mapping table.
data.vendorInput.applied[].filemarkConceptstringwhen vendorInput is setThe Filemark concept the cell maps to.
data.vendorInput.applied[].inputPathstringwhen vendorInput is setThe dotted path inside inputs the submitted value was written to.
data.vendorInput.applied[].cardinalitystring, always "one_to_one"when vendorInput is setAlways one_to_one. Each accepted vendor cell resolves to exactly one canonical input.
data.readinessobjectnoFiling-readiness summary. Published only by responses computed from a saved filing. A stateless computation does not evaluate readiness and omits it.
data.readiness.readybooleanwhen readiness is setTrue when the saved filing has no open readiness blocker.
data.readiness.*anynoMembers not listed here.
data.*anynoMembers not listed here.
computeVersionstringyesIdentifier 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.
engineSchemaVersionstringyesSemantic 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.
ratesVersionstringyesContent hash of the rate tables. Two responses with equal computeVersion and ratesVersion were computed on identical engine code and identical rate tables.
timestampstring (date-time)yesWhen 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
targetpathstringyesRollover, reorganization, or screening target id, as listed under rolloverTargets in the computation catalog.

Request body

Inputs for one rollover, reorganization, or screening engine.

FieldTypeRequiredDescription
inputsobjectyesCanonical 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.*anynoMembers not listed here.
payloadContractobjectnoStrict contract to validate the request against. Omit this property for the default boundary. Null is not valid.
payloadContract.boundaryProfileIdstring, 1 to 128 characters, matching ^[a-z][a-z0-9_.-]*$when payloadContract is setIdentifier of the strict computation boundary profile.
payloadContract.payloadSchemaVersionstring, up to 64 characters, matching ^[0-9]+\.[0-9]+\.[0-9]+$when payloadContract is setSemantic 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

FieldTypeAlways presentDescription
dataobjectyesComputed 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.resultsmap of objectnoOne 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}objectper keyThe target's result. It carries the readiness flags, the warnings, and the published output cells.
data.results.{key}.readybooleanper keyTrue 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}.provisionalbooleanper keyTrue 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}.warningsarray of anyper keyFindings 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}.*anynoThe target's output cells; see the computation reference.
data.notComputedarray of stringsnoRequested 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.calculationReferenceDatestring (date)noThe calculationReferenceDate the request sent, which the date-sensitive claims were computed as of. Present only when the request sent it.
data.calculationReferenceobjectnoServer 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.schemaVersioninteger, always 1when calculationReference is set
data.calculationReference.referenceInstantstring (date-time)when calculationReference is set
data.calculationReference.calendarDatestring (date)when calculationReference is set
data.calculationReference.timeZonestringwhen calculationReference is set
data.calculationReference.authorityone of advisorywhen calculationReference is set
data.resultobjectnoThe 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.readybooleanwhen result is setTrue 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.provisionalbooleanwhen result is setTrue 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.warningsarray of anywhen result is setFindings 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.*anynoThe target's output cells; see the computation reference.
data.handoffobjectnoThe 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.blockednullable objectwhen handoff is setNull 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.warningsarray of anywhen handoff is setOne 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.*anynoMembers not listed here.
data.vendorInputobjectnoMapping provenance for accepted vendor cells. Present only when vendorInput was submitted. Values are never echoed.
data.vendorInput.vendorone of taxcycle, taxprepwhen vendorInput is setThe vendor vocabulary the submitted cells were resolved in, echoing the selector.
data.vendorInput.mappingTableVersionstringwhen vendorInput is setVersion of the Filemark mapping table the cells were resolved through.
data.vendorInput.vendorEditionnullable stringwhen vendorInput is setThe product release these cell identifiers are valid for. Null when the table does not record one for this vendor.
data.vendorInput.fieldMapVersionstringwhen vendorInput is setVersion of the vendor field map used to resolve the cells. Present only for vendors that resolve through a field map.
data.vendorInput.appliedarray of objectswhen vendorInput is setOne entry per accepted vendor cell, in cell order, naming the canonical input it became.
data.vendorInput.applied[].cellstringwhen vendorInput is setThe vendor cell identifier as submitted.
data.vendorInput.applied[].schedulestringwhen vendorInput is setThe schedule the cell belongs to in the mapping table.
data.vendorInput.applied[].filemarkConceptstringwhen vendorInput is setThe Filemark concept the cell maps to.
data.vendorInput.applied[].inputPathstringwhen vendorInput is setThe dotted path inside inputs the submitted value was written to.
data.vendorInput.applied[].cardinalitystring, always "one_to_one"when vendorInput is setAlways one_to_one. Each accepted vendor cell resolves to exactly one canonical input.
data.readinessobjectnoFiling-readiness summary. Published only by responses computed from a saved filing. A stateless computation does not evaluate readiness and omits it.
data.readiness.readybooleanwhen readiness is setTrue when the saved filing has no open readiness blocker.
data.readiness.*anynoMembers not listed here.
data.*anynoMembers not listed here.
computeVersionstringyesIdentifier 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.
engineSchemaVersionstringyesSemantic 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.
ratesVersionstringyesContent hash of the rate tables. Two responses with equal computeVersion and ratesVersion were computed on identical engine code and identical rate tables.
timestampstring (date-time)yesWhen 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
client_idpathstring (uuid)yesUUID of the client to archive.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the client.
namestringyesClient display name.
createdAtstring (date-time)yesISO-8601 creation timestamp (UTC).
updatedAtstring (date-time)yesISO-8601 last-modified timestamp (UTC).
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or recoverable-archived.
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-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

ParameterInTypeRequiredDescription
client_idpathstring (uuid)yesUUID of the archived client.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the client.
namestringyesClient display name.
createdAtstring (date-time)yesISO-8601 creation timestamp (UTC).
updatedAtstring (date-time)yesISO-8601 last-modified timestamp (UTC).
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or recoverable-archived.
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-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

ParameterInTypeRequiredDescription
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
clientIdstring (uuid)yesUUID of the client the entity belongs to.
namestring, 1 to 300 charactersyesEntity display name, unique within the client.
legalNamenullable string, up to 300 charactersnoRegistered 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).
businessNumbernullable string, up to 40 charactersnoThe 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.
naicsCodenullable string, matching ^\d{2,6}$noNAICS industry code, 2 to 6 digits. An exported return states the six-digit code of the main revenue-generating activity.
incorporationJurisdictionnullable string, up to 2 charactersnoCorporate-law jurisdiction of incorporation or continuance: 'CA' for the federal CBCA, or a two-letter province or territory code. Null clears it.
incorporationDatenullable string (date)noYYYY-MM-DD the current jurisdiction took effect (original incorporation or the latest continuance). Not in the future. Null clears it.
registeredOfficenullable objectnoThe 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.addressLinenullable string, up to 300 characterswhen registeredOffice is setStreet address on one line.
registeredOffice.citynullable string, up to 100 characterswhen registeredOffice is setCity or municipality.
registeredOffice.provinceCodenullable string, up to 2 characterswhen registeredOffice is setTwo-letter province or territory code, such as ON. Required for a Canadian address.
registeredOffice.postalCodenullable string, up to 10 characterswhen registeredOffice is setPostal code, such as L4W 5S2.
registeredOffice.countryCodenullable string, up to 2 characterswhen registeredOffice is setTwo-letter country code; CA or omitted for Canada.
ontarioCorporationNumbernullable string, matching ^\s*[0-9]{1,10}\s*$noThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the entity.
clientIdstring (uuid)yesUUID of the entity's parent client.
namestringyesEntity display name.
legalNamenullable stringnoRegistered legal name, when known.
corporationNumbernullable stringnoCorporate registry number, when known.
businessNumbernullable stringnoCanadian business number, when known.
naicsCodenullable stringnoNAICS industry code, when assigned.
prescribedLsvccHistorynullable objectnoPrescribed 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.wasPrescribedbooleanwhen prescribedLsvccHistory is setEver prescribed, including a former registration (Reg. 6701).
prescribedLsvccHistory.firstPrescribedDatenullable string (date)when prescribedLsvccHistory is setFirst prescribed date; ITA 131(11) applies from it.
dissolvedAtnullable string (date)noEntity dissolution date, when applicable.
incorporationJurisdictionnullable stringnoCorporate-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.
incorporationDatenullable string (date)noDate 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.
registeredOfficenullable objectnoThe 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.addressLinenullable stringwhen registeredOffice is setStreet address on one line.
registeredOffice.citynullable stringwhen registeredOffice is setCity or municipality.
registeredOffice.provinceCodenullable stringwhen registeredOffice is setTwo-letter province or territory code, such as ON.
registeredOffice.postalCodenullable stringwhen registeredOffice is setPostal code, such as L4W 5S2.
registeredOffice.countryCodenullable stringwhen registeredOffice is setTwo-letter country code: CA.
ontarioCorporationNumbernullable stringnoThe Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or recoverable-archived.
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoConditions 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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
entity_idpathstring (uuid)yesUUID of the entity to archive.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the entity.
clientIdstring (uuid)yesUUID of the entity's parent client.
namestringyesEntity display name.
legalNamenullable stringnoRegistered legal name, when known.
corporationNumbernullable stringnoCorporate registry number, when known.
businessNumbernullable stringnoCanadian business number, when known.
naicsCodenullable stringnoNAICS industry code, when assigned.
prescribedLsvccHistorynullable objectnoPrescribed 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.wasPrescribedbooleanwhen prescribedLsvccHistory is setEver prescribed, including a former registration (Reg. 6701).
prescribedLsvccHistory.firstPrescribedDatenullable string (date)when prescribedLsvccHistory is setFirst prescribed date; ITA 131(11) applies from it.
dissolvedAtnullable string (date)noEntity dissolution date, when applicable.
incorporationJurisdictionnullable stringnoCorporate-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.
incorporationDatenullable string (date)noDate 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.
registeredOfficenullable objectnoThe 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.addressLinenullable stringwhen registeredOffice is setStreet address on one line.
registeredOffice.citynullable stringwhen registeredOffice is setCity or municipality.
registeredOffice.provinceCodenullable stringwhen registeredOffice is setTwo-letter province or territory code, such as ON.
registeredOffice.postalCodenullable stringwhen registeredOffice is setPostal code, such as L4W 5S2.
registeredOffice.countryCodenullable stringwhen registeredOffice is setTwo-letter country code: CA.
ontarioCorporationNumbernullable stringnoThe Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or recoverable-archived.
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoConditions 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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
entity_idpathstring (uuid)yesUUID of the archived entity.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the entity.
clientIdstring (uuid)yesUUID of the entity's parent client.
namestringyesEntity display name.
legalNamenullable stringnoRegistered legal name, when known.
corporationNumbernullable stringnoCorporate registry number, when known.
businessNumbernullable stringnoCanadian business number, when known.
naicsCodenullable stringnoNAICS industry code, when assigned.
prescribedLsvccHistorynullable objectnoPrescribed 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.wasPrescribedbooleanwhen prescribedLsvccHistory is setEver prescribed, including a former registration (Reg. 6701).
prescribedLsvccHistory.firstPrescribedDatenullable string (date)when prescribedLsvccHistory is setFirst prescribed date; ITA 131(11) applies from it.
dissolvedAtnullable string (date)noEntity dissolution date, when applicable.
incorporationJurisdictionnullable stringnoCorporate-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.
incorporationDatenullable string (date)noDate 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.
registeredOfficenullable objectnoThe 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.addressLinenullable stringwhen registeredOffice is setStreet address on one line.
registeredOffice.citynullable stringwhen registeredOffice is setCity or municipality.
registeredOffice.provinceCodenullable stringwhen registeredOffice is setTwo-letter province or territory code, such as ON.
registeredOffice.postalCodenullable stringwhen registeredOffice is setPostal code, such as L4W 5S2.
registeredOffice.countryCodenullable stringwhen registeredOffice is setTwo-letter country code: CA.
ontarioCorporationNumbernullable stringnoThe Ontario Corporation Number on the Ontario Business Registry, digits only. Null when not recorded.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or recoverable-archived.
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoConditions 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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement to archive.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
clientobjectyesThe client the engagement belongs to.
client.idstring (uuid)yesUUID of the client or entity.
client.namestringyesDisplay name of the client or entity.
client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
entityobjectyesThe legal entity the engagement belongs to.
entity.idstring (uuid)yesUUID of the client or entity.
entity.namestringyesDisplay name of the client or entity.
entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
periodStartnullable string (date)noFirst 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.
statusone of in_progress, review, completeyesWorkflow 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.
sourceone of prepared, importedyesValues: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference.
engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.
priorYearEngagementIdnullable string (uuid)noUUID 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.
isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
documentsPagenullable stringnoFilemark 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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
recordLifecycleobjectyesLive or recoverable-archived (distinct from the return's filing lifecycle).
recordLifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
recordLifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
recordLifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoNon-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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the archived engagement.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
clientobjectyesThe client the engagement belongs to.
client.idstring (uuid)yesUUID of the client or entity.
client.namestringyesDisplay name of the client or entity.
client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
entityobjectyesThe legal entity the engagement belongs to.
entity.idstring (uuid)yesUUID of the client or entity.
entity.namestringyesDisplay name of the client or entity.
entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
periodStartnullable string (date)noFirst 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.
statusone of in_progress, review, completeyesWorkflow 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.
sourceone of prepared, importedyesValues: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference.
engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.
priorYearEngagementIdnullable string (uuid)noUUID 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.
isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
documentsPagenullable stringnoFilemark 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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
recordLifecycleobjectyesLive or recoverable-archived (distinct from the return's filing lifecycle).
recordLifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
recordLifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
recordLifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoNon-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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
engagementIdstring (uuid)yesUUID of the engagement.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
editablebooleanyesTrue when the engagement accepts writes: not filed, not partner-signed, not archived.
lockReasonnullable one of filed, signed, archivednoWhy writes are refused. Null when editable.
filedAtnullable string (date-time)noISO-8601 in UTC. Null while unfiled.
revisionNumberinteger, at least 1yesPosition in the amendment chain; 1 is the original.
isLockedbooleanyesTrue while a practitioner holds the collaborative edit lock.
recordLifecycleobjectyesLive or recoverable-archived.
recordLifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
recordLifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
recordLifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
domainsobjectyesPer-domain answer, all false when not editable.
domains.engagementRecordbooleanyesUpdate, archive, or restore the engagement record.
domains.taxDatabooleanyesSaved trial-balance, mapping, classification, and adjustment actions.
domains.documentsbooleanyesDocument staging, ingestion, replacement, and archive.
domains.workpapersbooleanyesWorkpaper create, update, archive, restore, links, and carry-forward.
domains.reviewbooleanyesAccount notes and question or correction marks.
evaluatedAtstring (date-time)yesISO-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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement.
accountIdquerynullable string, up to 512 charactersnoReturn only the notes on this trial-balance account.
limitqueryinteger, 1 to 200, default 50noPage size; at most 200.
offsetqueryinteger, 0 to 100000, default 0noZero-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

FieldTypeAlways presentDescription
dataarray of objectsyesThe notes on this page, newest first.
data[].idstring (uuid)yesUUID of the note.
data[].engagementIdstring (uuid)yesUUID of the engagement the note belongs to.
data[].accountIdstring, 1 to 512 charactersyesIdentifier 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[].textstring, 1 to 4000 charactersyesThe note text.
data[].authorNamenullable string, up to 300 charactersnoDisplay 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[].lastEditedByNamenullable string, up to 300 charactersnoDisplay name of whoever last edited the text. Null until it is edited.
data[].revisioninteger, at least 1yesEdit counter; 1 for a note that has never been edited.
data[].createdAtstring (date-time)yesISO-8601 in UTC. When the note was created.
data[].updatedAtnullable string (date-time)noISO-8601 in UTC. When the text was last edited. Null until it is edited.
data[].lifecycleobjectyesLive or withdrawn.
data[].lifecycle.stateone of active, archivedyes'active' while the annotation stands, 'archived' once it was withdrawn.
data[].lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When the annotation was withdrawn. Null while active.
data[].resourceVersionstring, 1 to 256 charactersyesOpaque 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.
paginationobjectyesOffset pagination state for this page.
pagination.limitinteger, 1 to 200yesPage size applied.
pagination.offsetinteger, 0 to 100000yesZero-based row offset applied.
pagination.totalinteger, at least 0yesTotal matching resources.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
accountIdstring, 1 to 512 charactersyesIdentifier 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.
textstring, 1 to 4000 charactersyesThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the note.
engagementIdstring (uuid)yesUUID of the engagement the note belongs to.
accountIdstring, 1 to 512 charactersyesIdentifier 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.
textstring, 1 to 4000 charactersyesThe note text.
authorNamenullable string, up to 300 charactersnoDisplay 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.
lastEditedByNamenullable string, up to 300 charactersnoDisplay name of whoever last edited the text. Null until it is edited.
revisioninteger, at least 1yesEdit counter; 1 for a note that has never been edited.
createdAtstring (date-time)yesISO-8601 in UTC. When the note was created.
updatedAtnullable string (date-time)noISO-8601 in UTC. When the text was last edited. Null until it is edited.
lifecycleobjectyesLive or withdrawn.
lifecycle.stateone of active, archivedyes'active' while the annotation stands, 'archived' once it was withdrawn.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When the annotation was withdrawn. Null while active.
resourceVersionstring, 1 to 256 charactersyesOpaque 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

ParameterInTypeRequiredDescription
note_idpathstring (uuid)yesUUID of the note to edit.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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.

FieldTypeRequiredDescription
textstring, 1 to 4000 charactersyesThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the note.
engagementIdstring (uuid)yesUUID of the engagement the note belongs to.
accountIdstring, 1 to 512 charactersyesIdentifier 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.
textstring, 1 to 4000 charactersyesThe note text.
authorNamenullable string, up to 300 charactersnoDisplay 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.
lastEditedByNamenullable string, up to 300 charactersnoDisplay name of whoever last edited the text. Null until it is edited.
revisioninteger, at least 1yesEdit counter; 1 for a note that has never been edited.
createdAtstring (date-time)yesISO-8601 in UTC. When the note was created.
updatedAtnullable string (date-time)noISO-8601 in UTC. When the text was last edited. Null until it is edited.
lifecycleobjectyesLive or withdrawn.
lifecycle.stateone of active, archivedyes'active' while the annotation stands, 'archived' once it was withdrawn.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When the annotation was withdrawn. Null while active.
resourceVersionstring, 1 to 256 charactersyesOpaque 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

ParameterInTypeRequiredDescription
note_idpathstring (uuid)yesUUID of the note to withdraw.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the note.
engagementIdstring (uuid)yesUUID of the engagement the note belongs to.
accountIdstring, 1 to 512 charactersyesIdentifier 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.
textstring, 1 to 4000 charactersyesThe note text.
authorNamenullable string, up to 300 charactersnoDisplay 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.
lastEditedByNamenullable string, up to 300 charactersnoDisplay name of whoever last edited the text. Null until it is edited.
revisioninteger, at least 1yesEdit counter; 1 for a note that has never been edited.
createdAtstring (date-time)yesISO-8601 in UTC. When the note was created.
updatedAtnullable string (date-time)noISO-8601 in UTC. When the text was last edited. Null until it is edited.
lifecycleobjectyesLive or withdrawn.
lifecycle.stateone of active, archivedyes'active' while the annotation stands, 'archived' once it was withdrawn.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When the annotation was withdrawn. Null while active.
resourceVersionstring, 1 to 256 charactersyesOpaque 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement.
fieldPathquerynullable string, up to 512 charactersnoReturn only the marks on this field path.
limitqueryinteger, 1 to 200, default 50noPage size; at most 200.
offsetqueryinteger, 0 to 100000, default 0noZero-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

FieldTypeAlways presentDescription
dataarray of objectsyesThe marks on this page, newest first.
data[].idstring (uuid)yesUUID of the mark.
data[].engagementIdstring (uuid)yesUUID of the engagement the mark belongs to.
data[].fieldPathstring, 1 to 512 charactersyesDotted path of the reviewed field, exactly as the product records it (for example 'schedule1.line104' or 'accounts.abc123.balance').
data[].markTypeone of question, correctionyes'question' asks the preparer something; 'correction' says the value is wrong. Sign-off marks are never exposed here.
data[].notesnullable string, up to 2000 charactersnoFree text explaining the mark.
data[].authorNamenullable string, up to 300 charactersnoDisplay 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[].lastEditedByNamenullable string, up to 300 charactersnoDisplay name of whoever last edited the notes. Null until they are edited.
data[].revisioninteger, at least 1yesEdit counter; 1 for a mark whose notes have never been edited.
data[].markedAtstring (date-time)yesISO-8601 in UTC. When the mark was placed.
data[].updatedAtnullable string (date-time)noISO-8601 in UTC. When the notes were last edited. Null until they are edited.
data[].lifecycleobjectyesLive or cleared. A mark is cleared in Filemark by a reviewer; the public API never clears one.
data[].lifecycle.stateone of active, archivedyes'active' while the annotation stands, 'archived' once it was withdrawn.
data[].lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When the annotation was withdrawn. Null while active.
data[].resourceVersionstring, 1 to 256 charactersyesOpaque 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.
paginationobjectyesOffset pagination state for this page.
pagination.limitinteger, 1 to 200yesPage size applied.
pagination.offsetinteger, 0 to 100000yesZero-based row offset applied.
pagination.totalinteger, at least 0yesTotal matching resources.
pagination.hasMorebooleanyesTrue when another page is available.
importedEvidencearray of objectsnoHistorical source evidence, excluded from active marks and sign-off counts.
importedEvidence[].idstringwhen importedEvidence is set
importedEvidence[].sourceRevisionIdstringwhen importedEvidence is set
importedEvidence[].fieldPathnullable stringwhen importedEvidence is set
importedEvidence[].nativeFieldIdnullable objectwhen importedEvidence is set
importedEvidence[].nativeFieldId.*anynoMembers not listed here.
importedEvidence[].preparationanywhen importedEvidence is set
importedEvidence[].sourceActornullable stringwhen importedEvidence is set
importedEvidence[].sourceMarkedAtnullable stringwhen importedEvidence is set
importedEvidence[].sourceTimenullable objectwhen importedEvidence is set
importedEvidence[].sourceTime.*anynoMembers not listed here.
importedEvidence[].sourceValuenullable objectwhen importedEvidence is set
importedEvidence[].sourceValue.*anynoMembers not listed here.
importedEvidence[].valueWithheldbooleanwhen importedEvidence is set
importedEvidence[].sourceDetailsnullable objectwhen importedEvidence is set
importedEvidence[].sourceDetails.*anynoMembers not listed here.
importedEvidence[].evidenceKindstringwhen importedEvidence is set
importedEvidence[].isHistoricalboolean, always truewhen importedEvidence is set
importedEvidence[].countsForSignoffboolean, always falsewhen 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
fieldPathstring, 1 to 512 charactersyesDotted path of the reviewed field, exactly as the product records it (for example 'schedule1.line104' or 'accounts.abc123.balance').
markTypeone of question, correctionyes'question' or 'correction' only. A sign-off mark ('first_review', 'partner_review') is reviewer authority and is refused.
notesnullable string, up to 2000 charactersnoFree 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the mark.
engagementIdstring (uuid)yesUUID of the engagement the mark belongs to.
fieldPathstring, 1 to 512 charactersyesDotted path of the reviewed field, exactly as the product records it (for example 'schedule1.line104' or 'accounts.abc123.balance').
markTypeone of question, correctionyes'question' asks the preparer something; 'correction' says the value is wrong. Sign-off marks are never exposed here.
notesnullable string, up to 2000 charactersnoFree text explaining the mark.
authorNamenullable string, up to 300 charactersnoDisplay 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.
lastEditedByNamenullable string, up to 300 charactersnoDisplay name of whoever last edited the notes. Null until they are edited.
revisioninteger, at least 1yesEdit counter; 1 for a mark whose notes have never been edited.
markedAtstring (date-time)yesISO-8601 in UTC. When the mark was placed.
updatedAtnullable string (date-time)noISO-8601 in UTC. When the notes were last edited. Null until they are edited.
lifecycleobjectyesLive or cleared. A mark is cleared in Filemark by a reviewer; the public API never clears one.
lifecycle.stateone of active, archivedyes'active' while the annotation stands, 'archived' once it was withdrawn.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When the annotation was withdrawn. Null while active.
resourceVersionstring, 1 to 256 charactersyesOpaque 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

ParameterInTypeRequiredDescription
mark_idpathstring (uuid)yesUUID of the mark to edit.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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.

FieldTypeRequiredDescription
notesnullable string, up to 2000 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the mark.
engagementIdstring (uuid)yesUUID of the engagement the mark belongs to.
fieldPathstring, 1 to 512 charactersyesDotted path of the reviewed field, exactly as the product records it (for example 'schedule1.line104' or 'accounts.abc123.balance').
markTypeone of question, correctionyes'question' asks the preparer something; 'correction' says the value is wrong. Sign-off marks are never exposed here.
notesnullable string, up to 2000 charactersnoFree text explaining the mark.
authorNamenullable string, up to 300 charactersnoDisplay 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.
lastEditedByNamenullable string, up to 300 charactersnoDisplay name of whoever last edited the notes. Null until they are edited.
revisioninteger, at least 1yesEdit counter; 1 for a mark whose notes have never been edited.
markedAtstring (date-time)yesISO-8601 in UTC. When the mark was placed.
updatedAtnullable string (date-time)noISO-8601 in UTC. When the notes were last edited. Null until they are edited.
lifecycleobjectyesLive or cleared. A mark is cleared in Filemark by a reviewer; the public API never clears one.
lifecycle.stateone of active, archivedyes'active' while the annotation stands, 'archived' once it was withdrawn.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When the annotation was withdrawn. Null while active.
resourceVersionstring, 1 to 256 charactersyesOpaque 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement to change.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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.

FieldTypeRequiredDescription
actionone of assignGifi, clearGifi, setAccountClassification, applyClassificationAnswers, applyGifiSuggestions, upsertAdjustingEntry, removeAdjustingEntry, setReturnAnswersyesThe action to perform.
accountIdnullable string, up to 128 charactersnoIdentifier 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.
gifiCodenullable string, up to 8 characters, matching ^[0-9]{4}$noCRA GIFI code from the RC4088 master list. Auto-calculated subtotal codes are refused: they are form items, not account classifications.
classificationnullable objectnoThe classification answers to set (setAccountClassification).
classification.incomeTypenullable one of active_business, property, rental, capitalwhen classification is setCharacter of the income the account carries. Drives the Schedule 7 split and the small-business-deduction base. Null clears the answer.
classification.foreignSourcenullable booleanwhen classification is setTrue 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.incidentToActiveBusinessnullable booleanwhen classification is setFor 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.bookTreatmentnullable one of accept_as_booked, reclassify, accrue_adjust, write_off, requires_review, unclassifiedwhen classification is setThe card's Book answer: whether the booked amount needs a journal entry. It has no Schedule 1 effect and cannot be cleared.
classification.taxTreatmentnullable one of fully_deductible, non_deductible_full, partially_deductible, capital_cca, timing_difference, deduct_other_schedule, non_taxable, disclosure_only, informational, unclassifiedwhen classification is setThe 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.deductibilityPercentagenullable integer, 0 to 100when classification is setWhole 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.adjustmentTypenullable one of addition, deductionwhen classification is setThe 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.s1Linenullable string, up to 16 characters, matching ^[0-9A-Za-z]{1,16}$when classification is setSchedule 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.shareholderOriginnullable one of borrowed_principal, unpaid_remuneration, unpaid_related_expenses, shareholder_advancewhen classification is set
classification.salesTaxAttributionnullable objectwhen classification is setOpening and ledger-leg registration allocations, with debit-positive decimal amounts. Null reopens attribution.
classification.salesTaxAttribution.*anynoMembers not listed here.
classificationAnswersnullable objectnoA host model's submit_classifications input for a deferred account-classification request (applyClassificationAnswers).
classificationAnswers.classificationsarray of objects, 1 to 500 itemswhen classificationAnswers is setOne 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_codestringwhen classificationAnswers is set
classificationAnswers.classifications[].assumptionstringwhen classificationAnswers is set
classificationAnswers.classifications[].treatmentone of no_adjustment, full_addition, partial_addition, conditional, timing_difference, capital, requires_reviewwhen classificationAnswers is set
classificationAnswers.classifications[].adjustment_typeone of addition, deduction, nullwhen classificationAnswers is set
classificationAnswers.classifications[].deductibility_percentagenumber,null, 0 to 100when classificationAnswers is set
classificationAnswers.classifications[].deductibility_rulestring,nullwhen classificationAnswers is set
classificationAnswers.classifications[].schedule1_relevantbooleanwhen classificationAnswers is set
classificationAnswers.classifications[].conditional_onstring,nullwhen classificationAnswers is set
classificationAnswers.classifications[].reasoningstring,nullwhen classificationAnswers is set
classificationAnswers.classifications[].confidencenumber, 0 to 1when classificationAnswers is set
classificationAnswers.classifications[].follow_up_questionstring,nullwhen classificationAnswers is set
classificationAnswers.classifications[].template_idstring,nullwhen classificationAnswers is set
classificationAnswers.classifications[].ita_referencesarray of stringswhen classificationAnswers is set
classificationAnswers.classifications[].gifi_suggestionsarray of strings, up to 5 itemswhen classificationAnswers is set0 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).
gifiSuggestionAnswersnullable objectnoA host model's submit_gifi_suggestions input for a deferred GIFI suggestion request (applyGifiSuggestions).
gifiSuggestionAnswers.suggestionsarray of objects, 1 to 500 itemswhen gifiSuggestionAnswers is setOne entry per account. The applied summary lists the entries not taken and why.
gifiSuggestionAnswers.suggestions[].account_codestring, 1 to 128 characterswhen gifiSuggestionAnswers is setThe account_code the request carried: the account's identifier in the trial-balance and account reads.
gifiSuggestionAnswers.suggestions[].gifi_suggestionsarray of strings, 1 to 5 itemswhen gifiSuggestionAnswers is setGIFI codes from the RC4088 master list, most likely first. Subtotal codes are dropped.
entrynullable objectnoThe adjusting journal entry to create or replace.
entry.idstring, 1 to 128 characterswhen entry is setIdentifier of the entry; reused to replace it.
entry.namenullable string, up to 300 characterswhen entry is setShort entry name.
entry.descriptionnullable string, up to 2000 characterswhen entry is setWhat the entry adjusts and why.
entry.entryTypeone of adjusting, reclassifying, tax_provision, potentialwhen entry is setCategory of the entry. 'potential' is tracked but never booked; the other three post identically.
entry.entryDatenullable string, up to 32 characterswhen entry is setYYYY-MM-DD the entry is dated, when known.
entry.linesarray of objects, 1 to 500 itemswhen entry is setThe entry's debit and credit lines.
entry.lines[].idstring, 1 to 128 characterswhen entry is setIdentifier of the line, unique in the entry.
entry.lines[].typeone of debit, creditwhen entry is setSide of the entry this line posts to.
entry.lines[].accountIdstring, 1 to 128 characterswhen entry is setIdentifier 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[].accountCodestring, 1 to 128 characterswhen entry is setThe account's code. It is the durable anchor the posting rule resolves through, so it must identify exactly one live account.
entry.lines[].accountNamenullable string, up to 512 characterswhen entry is setThe account's name, for display.
entry.lines[].amountnumber, at least 0 or stringwhen entry is setNon-negative amount in dollars, at most two decimal places. Send it as a string to keep exact cents.
entryIdnullable string, up to 128 charactersnoIdentifier of the adjusting entry to remove.
returnAnswersnullable objectnosetReturnAnswers: the return's own answers that no trial balance or computation supplies, saved leaf by leaf.
returnAnswers.t2Jacketobjectwhen returnAnswers is setT2 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.identificationobjectwhen returnAnswers is setT2 box 001. Send it as an object; to clear one answer in it, send that answer as null.
returnAnswers.t2Jacket.identification.businessNumbernullable string, up to 20 characterswhen returnAnswers is setT2 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.addressesobjectwhen returnAnswers is setT2 lines 010 to 018. Send it as an object; to clear one answer in it, send that answer as null.
returnAnswers.t2Jacket.addresses.headOfficenullable objectwhen returnAnswers is setHead 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.street1nullable string, up to 300 characterswhen returnAnswers is setFirst street line.
returnAnswers.t2Jacket.addresses.headOffice.street2nullable string, up to 300 characterswhen returnAnswers is setSecond street line.
returnAnswers.t2Jacket.addresses.headOffice.citynullable string, up to 100 characterswhen returnAnswers is setCity.
returnAnswers.t2Jacket.addresses.headOffice.provinceOrStatenullable string, up to 2 characterswhen returnAnswers is setTwo-letter province or territory code (ON), or a US state code.
returnAnswers.t2Jacket.addresses.headOffice.countrynullable string, up to 100 characterswhen returnAnswers is setCountry other than Canada; omit it for a Canadian address (T2 line 017).
returnAnswers.t2Jacket.addresses.headOffice.postalOrZipnullable string, up to 10 characterswhen returnAnswers is setPostal code (A1A 1A1) or ZIP code.
returnAnswers.t2Jacket.addresses.headOfficeChangednullable booleanwhen returnAnswers is setT2 line 010: has the head office address changed since the CRA was last told?
returnAnswers.t2Jacket.certificationobjectwhen returnAnswers is setT2 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.signerLastNamenullable string, up to 100 characterswhen returnAnswers is setT2 line 950: the authorized signing officer's last name.
returnAnswers.t2Jacket.certification.signerFirstNamenullable string, up to 100 characterswhen returnAnswers is setT2 line 951: first name.
returnAnswers.t2Jacket.certification.signerPositionnullable string, up to 100 characterswhen returnAnswers is setT2 line 954: position, office or rank.
returnAnswers.t2Jacket.certification.signerTelephonenullable string, up to 20 characterswhen returnAnswers is setT2 line 956: telephone number, ten digits; punctuation is dropped.
returnAnswers.t2Jacket.certification.signerTitlenullable string, up to 20 characterswhen returnAnswers is setCourtesy title for the client letter, such as Ms., Mr. or Dr.
returnAnswers.t2Jacket.certification.signerEmailnullable string, up to 254 characterswhen returnAnswers is setT183 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.signatureDatenullable string (date)when returnAnswers is setT2 line 955: the date the officer signs, YYYY-MM-DD, on or after the tax year-end.
returnAnswers.t2Jacket.certification.contactSameAsSignernullable booleanwhen returnAnswers is setT2 line 957: is the contact person the authorized signing officer?
returnAnswers.t2Jacket.certification.otherContactNamenullable string, up to 200 characterswhen returnAnswers is setT2 line 958: the other contact's full name (when line 957 is No).
returnAnswers.t2Jacket.certification.otherContactTelephonenullable string, up to 20 characterswhen returnAnswers is setT2 line 959: the other contact's telephone number.
returnAnswers.t2Jacket.certification.languagenullable one of 1, 2when returnAnswers is setT2 line 990, language of correspondence: 1 English, 2 French.
returnAnswers.t2Jacket.additionalInfoobjectwhen returnAnswers is setT2 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.productsOrServicesnullable array of objects, up to 3 itemswhen returnAnswers is setT2 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[].descriptionstring, 1 to 300 characterswhen returnAnswers is setThe product mined, manufactured, sold or constructed, or the service provided.
returnAnswers.t2Jacket.additionalInfo.productsOrServices[].percentOfTotalRevenuenumber, 0 to 100 or stringwhen returnAnswers is setIts approximate share of total revenue, in percent (100 for all of it).
returnAnswers.t2Jacket.additionalInfo.section94_1OffshoreInvestmentFundPropertyAppliesnullable booleanwhen returnAnswers is setWhether 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.ontarioSpecialtyTypenullable one of 01, 02, 03, 04, 06, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 99when returnAnswers is setSchedule 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.ontarioSpecialtyTypePreviousYearnullable one of 01, 02, 03, 04, 06, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 99when returnAnswers is setThe 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.instalmentComplianceRemittedOnTimenullable booleanwhen returnAnswers is setITA 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.instalmentComplianceFiledOnTimenullable booleanwhen returnAnswers is setITA 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.applicabilityobjectwhen returnAnswers is setT2 line 259. Send it as an object; to clear one answer in it, send that answer as null.
returnAnswers.t2Jacket.applicability.specifiedForeignPropertyOver100Knullable booleanwhen returnAnswers is setT2 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.wasAssociatedInPrecedingYearnullable booleanwhen returnAnswers is setWas the corporation associated with any corporation in its preceding tax year?
returnAnswers.schedule141objectwhen returnAnswers is setSchedule 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.identifyPersonnullable one of Yes, Nowhen returnAnswers is setLine 111: can you identify the person primarily involved with the financial information (more than 50% of the work)?
returnAnswers.schedule141.professionalDesignationnullable one of Yes, Nowhen returnAnswers is setLine 095: does that person have a professional designation in accounting?
returnAnswers.schedule141.accountantConnectednullable one of Yes, Nowhen returnAnswers is setLine 097: is that person connected with the corporation (over-10% shareholder, director, officer, employee, or not at arm's length)?
returnAnswers.schedule141.involvementAuditorReportnullable booleanwhen returnAnswers is setLine 300: completed an auditor's report.
returnAnswers.schedule141.involvementReviewEngagementnullable booleanwhen returnAnswers is setLine 301: completed a review engagement report.
returnAnswers.schedule141.involvementCompilationEngagementnullable booleanwhen returnAnswers is setLine 302: conducted a compilation engagement.
returnAnswers.schedule141.involvementAccountingServicesnullable booleanwhen returnAnswers is setLine 303: provided accounting services.
returnAnswers.schedule141.involvementBookkeepingServicesnullable booleanwhen returnAnswers is setLine 304: provided bookkeeping services.
returnAnswers.schedule141.involvementOthernullable string, up to 2000 characterswhen returnAnswers is setLine 305: other involvement, described. Null or empty text clears it.
returnAnswers.schedule141.accountantReservationnullable one of Yes, Nowhen returnAnswers is setLine 099 (after 300 or 301): has the person expressed a reservation?
returnAnswers.schedule141.notesPreparednullable one of Yes, Nowhen returnAnswers is setLine 101: were notes to the financial statements prepared?
returnAnswers.schedule141.subsequentEventsnullable one of Yes, Nowhen returnAnswers is setLine 104: any subsequent events?
returnAnswers.schedule141.reevaluationOfAssetsnullable one of Yes, Nowhen returnAnswers is setLine 105: did the corporation re-evaluate its assets in the year?
returnAnswers.schedule141.contingentLiabilitiesnullable one of Yes, Nowhen returnAnswers is setLine 106: any contingent liabilities in the year?
returnAnswers.schedule141.commitmentsnullable one of Yes, Nowhen returnAnswers is setLine 107: any commitments in the year?
returnAnswers.schedule141.jointVentureInvestmentsnullable one of Yes, Nowhen returnAnswers is setLine 108: investments in joint ventures or partnerships?
returnAnswers.schedule141.impairmentRecognizednullable one of Yes, Nowhen returnAnswers is setLine 200: an impairment loss, its reversal, or a change in fair value recognized in net income or OCI in the year?
returnAnswers.schedule141.ppeNetIncomenullable number or stringwhen returnAnswers is setLine 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.ppeOCInullable number or stringwhen returnAnswers is setLine 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.intangibleNetIncomenullable number or stringwhen returnAnswers is setLine 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.intangibleOCInullable number or stringwhen returnAnswers is setLine 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.investmentPropertyNetIncomenullable number or stringwhen returnAnswers is setLine 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.biologicalNetIncomenullable number or stringwhen returnAnswers is setLine 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.financialInstrumentsNetIncomenullable number or stringwhen returnAnswers is setLine 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.financialInstrumentsOCInullable number or stringwhen returnAnswers is setLine 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.otherImpairmentNetIncomenullable number or stringwhen returnAnswers is setLine 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.otherImpairmentOCInullable number or stringwhen returnAnswers is setLine 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.derecognizeFinancialInstrumentnullable one of Yes, Nowhen returnAnswers is setLine 250: derecognized a financial instrument (other than trade receivables)?
returnAnswers.schedule141.appliedHedgeAccountingnullable one of Yes, Nowhen returnAnswers is setLine 255: applied hedge accounting?
returnAnswers.schedule141.discontinueHedgeAccountingnullable one of Yes, Nowhen returnAnswers is setLine 260: discontinued hedge accounting?
returnAnswers.schedule141.openingEquityAdjustmentnullable one of Yes, Nowhen returnAnswers is setLine 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.preparerHasAccountingDesignationnullable booleanwhen returnAnswers is setPart 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.preparerIsPart1Personnullable booleanwhen returnAnswers is setPart 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.preparedTaxAndFInullable booleanwhen returnAnswers is setLine 310: the T2 preparer prepared the return and its financial information.
returnAnswers.schedule141.clientProvidedFSnullable booleanwhen returnAnswers is setLine 311: the client provided the financial statements.
returnAnswers.schedule141.clientProvidedTBnullable booleanwhen returnAnswers is setLine 312: the client provided a trial balance.
returnAnswers.schedule141.clientProvidedGLnullable booleanwhen returnAnswers is setLine 313: the client provided a general ledger.
returnAnswers.schedule141.preparedOthernullable string, up to 2000 characterswhen returnAnswers is setLine 314: other, described. Null or empty text clears it.
returnAnswers.schedule50objectwhen returnAnswers is setSchedule 50, the shareholders holding 10% or more. Send it as an object whose rows replace the saved rows.
returnAnswers.schedule50.rowsarray of objects, up to 10 itemswhen returnAnswers is setEvery shareholder holding 10% or more of the common or preferred shares. Replaces the rows the return held.
returnAnswers.schedule50.rows[].namestring, 1 to 300 characterswhen returnAnswers is setLine 100: the shareholder's name.
returnAnswers.schedule50.rows[].shareholderTypeone of corporation, partnership, individual, trustwhen returnAnswers is setWhich identifier the row carries: corporation or partnership (line 200), individual (line 300), trust (line 350).
returnAnswers.schedule50.rows[].businessNumbernullable string, up to 20 characterswhen returnAnswers is setLine 200: business number, or NR for a corporation without one.
returnAnswers.schedule50.rows[].socialInsuranceNumbernullable string, up to 11 characterswhen returnAnswers is setLine 300: social insurance number of an individual.
returnAnswers.schedule50.rows[].trustNumbernullable string, up to 12 characterswhen returnAnswers is setLine 350: trust account number (T followed by eight digits).
returnAnswers.schedule50.rows[].percentCommonSharesnullable number, 0 to 100 or stringwhen returnAnswers is setLine 400: percentage of common shares.
returnAnswers.schedule50.rows[].percentPreferredSharesnullable number, 0 to 100 or stringwhen returnAnswers is setLine 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
clientobjectyesThe client the engagement belongs to.
client.idstring (uuid)yesUUID of the client or entity.
client.namestringyesDisplay name of the client or entity.
client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
entityobjectyesThe legal entity the engagement belongs to.
entity.idstring (uuid)yesUUID of the client or entity.
entity.namestringyesDisplay name of the client or entity.
entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
periodStartnullable string (date)noFirst 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.
statusone of in_progress, review, completeyesWorkflow 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.
sourceone of prepared, importedyesValues: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference.
engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.
priorYearEngagementIdnullable string (uuid)noUUID 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.
isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
documentsPagenullable stringnoFilemark 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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
recordLifecycleobjectyesLive or recoverable-archived (distinct from the return's filing lifecycle).
recordLifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
recordLifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
recordLifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoNon-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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable explanation of the warning.
actionone of assignGifi, clearGifi, setAccountClassification, applyClassificationAnswers, applyGifiSuggestions, upsertAdjustingEntry, removeAdjustingEntry, setReturnAnswersyesThe action that ran.
accountIdnullable stringnoThe account the action targeted, when it targeted one.
appliedobjectyesBounded summary of the change.
applied.changedbooleanyesFalse when the engagement already held the requested state and nothing was written.
applied.gifiCodenullable stringnoThe GIFI code now assigned to the account, if any.
applied.previousGifiCodenullable stringnoThe GIFI code the account carried before, if any.
applied.fieldsarray of stringsnoNames 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.entryIdnullable stringnoIdentifier of the adjusting entry that was written or removed.
applied.creatednullable booleannoTrue when the entry was new, false when an existing entry was replaced.
applied.postsnullable booleannoTrue when the entry books to the trial balance. False for a 'potential' entry, which is tracked and never booked.
applied.lineCountnullable integer, at least 0noNumber of lines on the written entry.
applied.totalDebitsnullable stringnoCanonical decimal total of the entry's debit lines.
applied.totalCreditsnullable stringnoCanonical decimal total of the entry's credit lines.
applied.remainingEntryCountnullable integer, at least 0noAdjusting entries left on the engagement after a removal.
applied.answeredCountnullable integer, at least 0noAccounts the answers were saved for (applyClassificationAnswers, applyGifiSuggestions).
applied.unchangedCountnullable integer, at least 0noAnswers the account already held, so nothing was written for them.
applied.skippednullable array of objects, up to 500 itemsnoAnswers that were not applied, each with the reason.
applied.skipped[].answerIndexinteger, 0 to 499when skipped is setPosition of the answer in the submitted list (classificationAnswers.classifications or gifiSuggestionAnswers.suggestions), counting from 0.
applied.skipped[].accountIdstring, up to 128 characterswhen skipped is setThe saved account the answer named. An answer naming no live account is not listed here: see unmatchedAnswerIndexes.
applied.skipped[].reasonone of not_awaiting_answer, answered_by_practitionerwhen skipped is setnot_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.unmatchedAnswerIndexesnullable array of integers, up to 500 itemsnoPositions, 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.remainingDeferredCountnullable integer, at least 0noAccounts 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeRequiredDescription
previewIdstring, 1 to 36 charactersyesThe previewId a landing or a document ingestion's import_preview outcome returned.
decisionsobjectno
decisions.conflictsmap of one of rename, new_accountwhen decisions is set
decisions.conflicts.{key}one of rename, new_accountper key
decisions.restoresmap of one of restore, skipwhen decisions is set
decisions.restores.{key}one of restore, skipper key
decisions.missingmap of one of keep, deletewhen decisions is set
decisions.missing.{key}one of keep, deleteper key
decisions.duplicatesmap of one of combine, first, skipwhen decisions is set
decisions.duplicates.{key}one of combine, first, skipper key
decisions.coveragemap of booleanwhen decisions is set
decisions.coverage.{key}booleanper key
decisions.accountResolutionsmap of objectwhen decisions is set
decisions.accountResolutions.{key}objectper key
decisions.accountResolutions.{key}.accountNamestring, 1 to 300 charactersper key
decisions.accountResolutions.{key}.accountCodestring, up to 64 charactersper key
decisions.accountResolutions.{key}.gifiCodestring, matching ^\d{4}$per key
decisions.accountResolutions.{key}.manualbooleanper key
replaceMissingbooleannoDelete 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the engagement, the same identifier the tax-year resources use.
clientobjectyesThe client the engagement belongs to.
client.idstring (uuid)yesUUID of the client or entity.
client.namestringyesDisplay name of the client or entity.
client.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
entityobjectyesThe legal entity the engagement belongs to.
entity.idstring (uuid)yesUUID of the client or entity.
entity.namestringyesDisplay name of the client or entity.
entity.legalNamenullable stringnoRegistered legal name of the entity, when one is recorded. Always null on the client party.
yearEndstring (date)yesFiscal year-end date, YYYY-MM-DD.
periodStartnullable string (date)noFirst 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.
statusone of in_progress, review, completeyesWorkflow 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.
sourceone of prepared, importedyesValues: prepared when the engagement was built in Filemark, imported when it was read out of a prior-year return file and kept for reference.
engagementTypenullable one of compilation, review, audit, tax_onlynoService 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.
priorYearEngagementIdnullable string (uuid)noUUID 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.
isLockedbooleanyesTrue when the engagement has been locked and no longer accepts edits.
lifecycleobjectyesFiling time, revision number, and the links to the neighbouring revisions of this return.
lifecycle.filedAtnullable string (date-time)noISO-8601 in UTC. When this revision was recorded as filed. Null while it has not been filed.
lifecycle.revisionNumberinteger, at least 1yesPosition of this revision in its amendment chain. 1 is the original return and each amendment adds one.
lifecycle.parentEngagementIdnullable string (uuid)noUUID of the revision this one amends. Null on the original return.
lifecycle.amendedByEngagementIdnullable string (uuid)noUUID of the revision that amends this one. Null when this is the newest revision in the chain.
createdAtstring (date-time)yesISO-8601 in UTC. When the engagement was created.
updatedAtstring (date-time)yesISO-8601 in UTC. When the engagement was last modified.
documentsPagenullable stringnoFilemark 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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
recordLifecycleobjectyesLive or recoverable-archived (distinct from the return's filing lifecycle).
recordLifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
recordLifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
recordLifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
warningsarray of objectsnoNon-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[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable explanation of the warning.
importStatusone of applied, needs_answers, blockedno
appliednullable objectnoWhat this call imported: the engagement's books after the import. Null when nothing was imported (importStatus needs_answers or blocked).
applied.accountCountinteger, at least 0when applied is setLive accounts on the engagement after the import.
applied.classifiedCountinteger, at least 0when applied is setLive accounts with a classification other than unclassified.
applied.unclassifiedCountinteger, at least 0when applied is setLive 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.gifiSeededCountinteger, at least 0when applied is setLive accounts with a GIFI assignment, including validated source codes and chart defaults.
applied.totalDebitsnullable stringwhen applied is setCanonical decimal debit total of the submitted source rows.
applied.totalCreditsnullable stringwhen applied is setCanonical decimal credit total of the submitted source rows.
applied.balancedbooleanwhen applied is setTrue when both exact source amounts and the saved cent amounts balance within a dollar. rows mode can import an unbalanced subset.
applied.warningsarray of strings, up to 50 itemswhen applied is setParser warnings, for the practitioner.
submittednullable objectnoThe 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.totalDebitsnullable stringwhen submitted is setCanonical decimal debit total of the submitted rows.
submitted.totalCreditsnullable stringwhen submitted is setCanonical decimal credit total of the submitted rows.
submitted.balancedbooleanwhen submitted is setTrue when the submitted debits and credits balance within a dollar.
submitted.warningsarray of strings, up to 50 itemswhen submitted is setParser warnings, for the practitioner.
previewIdnullable stringnoPrincipal- and engagement-scoped preview for the apply capability; expires after six hours.
questionsnullable objectno
questions.modeone of books, rowswhen questions is set
questions.confirmationCountsobjectwhen questions is set
questions.confirmationCounts.balancesUpdatedinteger, at least 0when questions is set
questions.confirmationCounts.renamedinteger, at least 0when questions is set
questions.confirmationCounts.addedinteger, at least 0when questions is set
questions.confirmationCounts.restoredinteger, at least 0when questions is set
questions.confirmationCounts.deletedinteger, at least 0when questions is set
questions.confirmationCounts.unchangedinteger, at least 0when questions is set
questions.confirmationCounts.skippedinteger, at least 0when questions is set
questions.conflictsarray of objects, up to 5000 itemswhen questions is set
questions.conflicts[].plansnullable map of objectwhen questions is set
questions.conflicts[].plans.{key}objectper key
questions.conflicts[].plans.{key}.balancesUpdatedinteger, at least 0per key
questions.conflicts[].plans.{key}.renamedinteger, at least 0per key
questions.conflicts[].plans.{key}.addedinteger, at least 0per key
questions.conflicts[].plans.{key}.restoredinteger, at least 0per key
questions.conflicts[].plans.{key}.deletedinteger, at least 0per key
questions.conflicts[].plans.{key}.unchangedinteger, at least 0per key
questions.conflicts[].plans.{key}.skippedinteger, at least 0per key
questions.conflicts[].accountIdstringwhen questions is set
questions.conflicts[].codestringwhen questions is set
questions.conflicts[].existingNamestringwhen questions is set
questions.conflicts[].incomingNamestringwhen questions is set
questions.conflicts[].incomingBalancestringwhen questions is set
questions.conflicts[].incomingCodenullable stringwhen questions is set
questions.conflicts[].crossKeybooleanwhen questions is set
questions.restoresarray of objects, up to 5000 itemswhen questions is set
questions.restores[].plansnullable map of objectwhen questions is set
questions.restores[].plans.{key}objectper key
questions.restores[].plans.{key}.balancesUpdatedinteger, at least 0per key
questions.restores[].plans.{key}.renamedinteger, at least 0per key
questions.restores[].plans.{key}.addedinteger, at least 0per key
questions.restores[].plans.{key}.restoredinteger, at least 0per key
questions.restores[].plans.{key}.deletedinteger, at least 0per key
questions.restores[].plans.{key}.unchangedinteger, at least 0per key
questions.restores[].plans.{key}.skippedinteger, at least 0per key
questions.restores[].accountIdstringwhen questions is set
questions.restores[].codestringwhen questions is set
questions.restores[].namestringwhen questions is set
questions.restores[].deletedAtnullable stringwhen questions is set
questions.restores[].incomingBalancestringwhen questions is set
questions.missingarray of objects, up to 5000 itemswhen questions is set
questions.missing[].plansmap of objectwhen questions is set
questions.missing[].plans.{key}objectper key
questions.missing[].plans.{key}.balancesUpdatedinteger, at least 0per key
questions.missing[].plans.{key}.renamedinteger, at least 0per key
questions.missing[].plans.{key}.addedinteger, at least 0per key
questions.missing[].plans.{key}.restoredinteger, at least 0per key
questions.missing[].plans.{key}.deletedinteger, at least 0per key
questions.missing[].plans.{key}.unchangedinteger, at least 0per key
questions.missing[].plans.{key}.skippedinteger, at least 0per key
questions.missing[].accountIdstringwhen questions is set
questions.missing[].codestringwhen questions is set
questions.missing[].namestringwhen questions is set
questions.missing[].balancestringwhen questions is set
questions.missing[].refsobjectwhen questions is set
questions.missing[].refs.ajeLinesintegerwhen questions is set
questions.missing[].refs.workpapersintegerwhen questions is set
questions.missing[].refs.groupsintegerwhen questions is set
questions.duplicatesarray of objects, up to 5000 itemswhen questions is set
questions.duplicates[].codestringwhen questions is set
questions.duplicates[].combinedBalancestringwhen questions is set
questions.duplicates[].rowsarray of objects, up to 5000 itemswhen questions is set
questions.duplicates[].rows[].namestringwhen questions is set
questions.duplicates[].rows[].balancestringwhen questions is set
questions.duplicates[].rowsCountinteger, at least 0when questions is setMember rows in this source group, listed or not.
questions.duplicates[].rowsTruncatedbooleanwhen questions is setOnly 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[].plansobjectwhen questions is setApply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner.
questions.duplicates[].plans.combinearray of objects, 1 to 2 itemswhen questions is set
questions.duplicates[].plans.combine[].conflictsmap of one of rename, new_accountwhen questions is set
questions.duplicates[].plans.combine[].conflicts.{key}one of rename, new_accountper key
questions.duplicates[].plans.combine[].restoresmap of one of restore, skipwhen questions is set
questions.duplicates[].plans.combine[].restores.{key}one of restore, skipper key
questions.duplicates[].plans.combine[].countsobjectwhen questions is set
questions.duplicates[].plans.combine[].counts.balancesUpdatedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.renamedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.addedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.restoredinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.deletedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.unchangedinteger, at least 0when questions is set
questions.duplicates[].plans.combine[].counts.skippedinteger, at least 0when questions is set
questions.duplicates[].plans.firstarray of objects, 1 to 2 itemswhen questions is set
questions.duplicates[].plans.first[].conflictsmap of one of rename, new_accountwhen questions is set
questions.duplicates[].plans.first[].conflicts.{key}one of rename, new_accountper key
questions.duplicates[].plans.first[].restoresmap of one of restore, skipwhen questions is set
questions.duplicates[].plans.first[].restores.{key}one of restore, skipper key
questions.duplicates[].plans.first[].countsobjectwhen questions is set
questions.duplicates[].plans.first[].counts.balancesUpdatedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.renamedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.addedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.restoredinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.deletedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.unchangedinteger, at least 0when questions is set
questions.duplicates[].plans.first[].counts.skippedinteger, at least 0when questions is set
questions.duplicates[].plans.skiparray of objects, 1 to 1 itemswhen questions is set
questions.duplicates[].plans.skip[].conflictsmap of one of rename, new_accountwhen questions is set
questions.duplicates[].plans.skip[].conflicts.{key}one of rename, new_accountper key
questions.duplicates[].plans.skip[].restoresmap of one of restore, skipwhen questions is set
questions.duplicates[].plans.skip[].restores.{key}one of restore, skipper key
questions.duplicates[].plans.skip[].countsobjectwhen questions is set
questions.duplicates[].plans.skip[].counts.balancesUpdatedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.renamedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.addedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.restoredinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.deletedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.unchangedinteger, at least 0when questions is set
questions.duplicates[].plans.skip[].counts.skippedinteger, at least 0when questions is set
questions.lowMatchnullable objectwhen questions is set
questions.lowMatch.matchedRowsintegerwhen questions is set
questions.lowMatch.rowsintegerwhen questions is set
questions.blockednullable objectwhen questions is set
questions.blocked.reasonstringwhen questions is set
questions.blocked.messagestringwhen questions is set
questions.coveragenullable objectwhen questions is set
questions.coverage.fiscalPeriodobjectwhen questions is set
questions.coverage.fiscalPeriod.startnullable stringwhen questions is set
questions.coverage.fiscalPeriod.endnullable stringwhen questions is set
questions.coverage.filesarray of objectswhen questions is set
questions.coverage.files[].glIdstringwhen questions is set
questions.coverage.files[].filenamestringwhen questions is set
questions.coverage.files[].accountsarray of stringswhen questions is set
questions.coverage.files[].reportRangenullable objectwhen questions is set
questions.coverage.files[].reportRange.startnullable stringwhen questions is set
questions.coverage.files[].reportRange.endnullable stringwhen questions is set
questions.coverage.files[].observedRangenullable objectwhen questions is set
questions.coverage.files[].observedRange.startnullable stringwhen questions is set
questions.coverage.files[].observedRange.endnullable stringwhen questions is set
questions.coverage.missingRangesarray of objectswhen questions is set
questions.coverage.missingRanges[].startnullable stringwhen questions is set
questions.coverage.missingRanges[].endnullable stringwhen questions is set
questions.coverage.requirementsarray of objectswhen questions is set
questions.coverage.requirements[].idstringwhen questions is set
questions.coverage.requirements[].messagestringwhen questions is set
questions.priorYearMismatchbooleanwhen questions is set
questions.missingCountinteger, at least 0when questions is set
questions.missingTruncatedbooleanwhen questions is setOnly the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account.
questions.conflictsCountinteger, at least 0when questions is setName conflicts the import found, listed or not.
questions.conflictsTruncatedbooleanwhen questions is setOnly the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest.
questions.restoresCountinteger, at least 0when questions is setRows matching a deleted account, listed or not.
questions.restoresTruncatedbooleanwhen questions is setOnly the first 5,000 rows matching a deleted account are listed; omitted answers skip the row.
questions.duplicatesCountinteger, at least 0when questions is setDuplicated account codes in the source, listed or not.
questions.duplicatesTruncatedbooleanwhen questions is setOnly 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeRequiredDescription
limitnullable integer, 1 to 200noMaximum 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

FieldTypeAlways presentDescription
kindstring, always "ai-job/v1"yes
jobIdstring (uuid)yes
statusUrlstringyes
pollMethodstring, always "POST"yes
pollHeadersobjectyes
pollHeaders.Idempotency-Keystring (uuid)yes
pollHeaders.X-Filemark-Jobstring (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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement.
limitqueryinteger, 1 to 200, default 50noPage size (1-200).
cursorquerynullable string, up to 512 charactersnoOpaque 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

FieldTypeAlways presentDescription
dataarray of objectsyesThe contacts on this page.
data[].idstring (uuid)yesStable UUID of the contact.
data[].clientIdstring (uuid)yesUUID of the client the contact belongs to.
data[].fullNamestringyesThe contact's name, as the invitation addresses it.
data[].emailstringyesThe address an invitation or reminder is mailed to.
data[].rolenullable stringnoFree-text role at the client, when recorded.
data[].isPrimarybooleanyesTrue for the client's primary contact.
data[].createdAtstring (date-time)yesISO-8601 in UTC. When the contact was recorded.
paginationobjectyesKeyset pagination state for this page.
pagination.limitintegeryesPage size applied (1-200).
pagination.nextCursornullable stringnoOpaque cursor for the next page. Null on the final page.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement.
limitqueryinteger, 1 to 200, default 50noPage size (1-200).
cursorquerynullable string, up to 512 charactersnoOpaque 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

FieldTypeAlways presentDescription
dataarray of objectsyesThe requests on this page.
data[].idstring (uuid)yesStable UUID of the portal request.
data[].engagementIdstring (uuid)yesUUID of the engagement the request belongs to.
data[].sessionIdnullable string (uuid)noUUID of the invitation that carried it. Null for a draft.
data[].accountIdnullable stringnoIdentifier of the trial-balance account the question is about.
data[].accountCodenullable stringnoAccount code, when known.
data[].accountNamenullable stringnoAccount name, when known.
data[].questionTextstringyesThe question the client was asked.
data[].questionCategorynullable stringnoWhat the question is about; 'account' for an account follow-up.
data[].responseTypenullable stringnoHow the client answers: confirm, text, date_range, amount, choice, or document.
data[].sourceConceptnullable stringnoFilemark concept that generated the question, when it came from one.
data[].statusone of draft, sent, responded, accepted, applied, rejectedyes'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[].responseobjectyesThe client's answer, all-null while unanswered.
data[].response.textnullable stringnoFree-text answer.
data[].response.confirmednullable booleannoAnswer to a confirm question.
data[].response.dateStartnullable stringnoYYYY-MM-DD start of a date-range answer.
data[].response.dateEndnullable stringnoYYYY-MM-DD end of a date-range answer.
data[].response.amountnullable stringnoCanonical decimal string of an amount answer. Never a float.
data[].response.choicenullable stringnoSelected option of a choice question.
data[].response.respondedAtnullable string (date-time)noISO-8601 in UTC. When the client answered.
data[].evidenceobjectyesAttachment and comment counts.
data[].evidence.uploadCountinteger, at least 0yesDocuments the client attached to this request.
data[].evidence.unreadCountinteger, at least 0yesAttached 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.commentCountinteger, at least 0yesComments on the request, both sides.
data[].createdAtstring (date-time)yesISO-8601 in UTC.
data[].updatedAtstring (date-time)yesISO-8601 in UTC.
data[].resourceVersionstring, 1 to 256 charactersyesOpaque 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.
paginationobjectyesKeyset pagination state for this page.
pagination.limitintegeryesPage size applied (1-200).
pagination.nextCursornullable stringnoOpaque cursor for the next page. Null on the final page.
pagination.hasMorebooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
request_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the portal request.
engagementIdstring (uuid)yesUUID of the engagement the request belongs to.
sessionIdnullable string (uuid)noUUID of the invitation that carried it. Null for a draft.
accountIdnullable stringnoIdentifier of the trial-balance account the question is about.
accountCodenullable stringnoAccount code, when known.
accountNamenullable stringnoAccount name, when known.
questionTextstringyesThe question the client was asked.
questionCategorynullable stringnoWhat the question is about; 'account' for an account follow-up.
responseTypenullable stringnoHow the client answers: confirm, text, date_range, amount, choice, or document.
sourceConceptnullable stringnoFilemark concept that generated the question, when it came from one.
statusone of draft, sent, responded, accepted, applied, rejectedyes'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.
responseobjectyesThe client's answer, all-null while unanswered.
response.textnullable stringnoFree-text answer.
response.confirmednullable booleannoAnswer to a confirm question.
response.dateStartnullable stringnoYYYY-MM-DD start of a date-range answer.
response.dateEndnullable stringnoYYYY-MM-DD end of a date-range answer.
response.amountnullable stringnoCanonical decimal string of an amount answer. Never a float.
response.choicenullable stringnoSelected option of a choice question.
response.respondedAtnullable string (date-time)noISO-8601 in UTC. When the client answered.
evidenceobjectyesAttachment and comment counts.
evidence.uploadCountinteger, at least 0yesDocuments the client attached to this request.
evidence.unreadCountinteger, at least 0yesAttached 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.commentCountinteger, at least 0yesComments on the request, both sides.
createdAtstring (date-time)yesISO-8601 in UTC.
updatedAtstring (date-time)yesISO-8601 in UTC.
resourceVersionstring, 1 to 256 charactersyesOpaque 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
request_idpathstring (uuid)yesUUID of the portal request.

Request body

What to evaluate. Supply exactly the payload the chosen kind names.

FieldTypeRequiredDescription
kindone of applyToWorkpaper, comment, send, remind, cancelyes'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.
applyToWorkpapernullable objectno
applyToWorkpaper.workpaperIdstring (uuid)when applyToWorkpaper is setUUID of the workpaper in the request's engagement.
applyToWorkpaper.acknowledgeUnreadUploadsbooleanwhen applyToWorkpaper is setSet 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.
commentnullable objectno
comment.bodystring, 1 to 4000 characterswhen comment is setThe comment text.
sendnullable objectno
send.requestIdsarray of strings, 1 to 200 itemswhen send is setTrial-balance account identifiers whose follow-up questions the invitation carries. An account with no follow-up question is skipped.
send.contactIdnullable string (uuid)when send is setUUID of an existing contact of the engagement's client. Use this or the name and email pair.
send.contactNamenullable string, up to 200 characterswhen send is setName of a new contact to create and address.
send.contactEmailnullable string, up to 320 characterswhen send is setEmail of a new contact to create and address.
send.expiresDaysnullable integer, 1 to 365when send is setDays the client's link stays valid. Defaults to the firm's portal setting, else 30.
send.messagenullable string, up to 2000 characterswhen send is setOptional note included in the invitation email.
remindnullable objectno
remind.sessionIdstring (uuid)when remind is setUUID of the portal session (invitation).
cancelnullable objectno
cancel.sessionIdstring (uuid)when cancel is setUUID 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

FieldTypeAlways presentDescription
kindone of applyToWorkpaper, comment, send, remind, cancelyesThe evaluated action.
requestIdstring (uuid)yesUUID of the portal request the preview was taken on.
engagementIdstring (uuid)yesUUID of that request's engagement.
requestResourceVersionstring, 1 to 256 charactersyesOpaque 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.
summarystringyesOne sentence naming what would happen.
effectsarray of stringsyesEach concrete change the action would make, in the order it would make them.
blockersarray of objectsnoConditions that would refuse the action as requested. Empty when it would proceed; an unread-evidence blocker clears by acknowledging it.
blockers[].codestringwhen blockers is setStable warning identifier.
blockers[].messagestringwhen blockers is setReadable explanation of the warning.
confirmationRequiredbooleanyesTrue for the actions that reach the client and must present a token.
confirmationTokennullable stringnoSingle-use secret bound to this principal, target and exact action. Present it once, before it expires; a changed action needs a new preview.
confirmationExpiresAtnullable string (date-time)noISO-8601 in UTC. After this the token is dead.
confirmationTargetnullable objectnoWhere to spend the token. Null when none was minted.
confirmationTarget.capabilityIdstringwhen confirmationTarget is setThe public capability that spends the token.
confirmationTarget.resourceTypeone of portal_request, engagementwhen confirmationTarget is setWhat the token is bound to.
confirmationTarget.resourceIdstring (uuid)when confirmationTarget is setUUID of the bound resource; POST the effect against it.
actionSha256string, matching ^[0-9a-f]{64}$yesCanonical hash of the action the preview evaluated, 64 lowercase hex characters.
evaluatedAtstring (date-time)yesISO-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 code result_not_representable means 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

ParameterInTypeRequiredDescription
request_idpathstring (uuid)yesUUID of the portal request.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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.

FieldTypeRequiredDescription
kindstring, always "applyToWorkpaper"yesOnly 'applyToWorkpaper' is reversible; comment, send, remind and cancel reach the client.
applyToWorkpaperobjectyesWhich workpaper to write, and whether unread evidence is acknowledged.
applyToWorkpaper.workpaperIdstring (uuid)yesUUID of the workpaper in the request's engagement.
applyToWorkpaper.acknowledgeUnreadUploadsbooleannoSet 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

FieldTypeAlways presentDescription
kindstring, always "applyToWorkpaper"yesThe action performed.
requestIdstring (uuid)yesUUID of the portal request.
engagementIdstring (uuid)yesUUID of the engagement.
workpaperIdstring (uuid)yesUUID of the workpaper the rows were written to.
rowsAddedinteger, at least 0yesRows the apply added to the workpaper.
requestobjectyesThe portal request after the change.
request.idstring (uuid)yesStable UUID of the portal request.
request.engagementIdstring (uuid)yesUUID of the engagement the request belongs to.
request.sessionIdnullable string (uuid)noUUID of the invitation that carried it. Null for a draft.
request.accountIdnullable stringnoIdentifier of the trial-balance account the question is about.
request.accountCodenullable stringnoAccount code, when known.
request.accountNamenullable stringnoAccount name, when known.
request.questionTextstringyesThe question the client was asked.
request.questionCategorynullable stringnoWhat the question is about; 'account' for an account follow-up.
request.responseTypenullable stringnoHow the client answers: confirm, text, date_range, amount, choice, or document.
request.sourceConceptnullable stringnoFilemark concept that generated the question, when it came from one.
request.statusone of draft, sent, responded, accepted, applied, rejectedyes'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.responseobjectyesThe client's answer, all-null while unanswered.
request.response.textnullable stringnoFree-text answer.
request.response.confirmednullable booleannoAnswer to a confirm question.
request.response.dateStartnullable stringnoYYYY-MM-DD start of a date-range answer.
request.response.dateEndnullable stringnoYYYY-MM-DD end of a date-range answer.
request.response.amountnullable stringnoCanonical decimal string of an amount answer. Never a float.
request.response.choicenullable stringnoSelected option of a choice question.
request.response.respondedAtnullable string (date-time)noISO-8601 in UTC. When the client answered.
request.evidenceobjectyesAttachment and comment counts.
request.evidence.uploadCountinteger, at least 0yesDocuments the client attached to this request.
request.evidence.unreadCountinteger, at least 0yesAttached 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.commentCountinteger, at least 0yesComments on the request, both sides.
request.createdAtstring (date-time)yesISO-8601 in UTC.
request.updatedAtstring (date-time)yesISO-8601 in UTC.
request.resourceVersionstring, 1 to 256 charactersyesOpaque 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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
incompleteEvidencenullable stringnoNull on a complete evidence set. Otherwise the verbatim disclosure of what was not read, so a partial total is never an unqualified success.
warningsarray of objectsnoNon-blocking conditions: a failed lifecycle advance, extractor warnings on the evidence.
warnings[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
request_idpathstring (uuid)yesUUID of the portal request.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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.

FieldTypeRequiredDescription
bodystring, 1 to 4000 charactersyesThe comment text.
confirmationTokenstring, 1 to 128 charactersyesThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesStable UUID of the comment.
requestIdstring (uuid)yesUUID of the portal request it belongs to.
authorTypeone of practitioner, clientyesWhich side of the conversation wrote it.
authorNamestringyesDisplay name the portal shows next to the comment.
bodystringyesThe comment text.
createdAtstring (date-time)yesISO-8601 in UTC.
requestResourceVersionstring, 1 to 256 charactersyesOpaque 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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.

FieldTypeRequiredDescription
kindone of send, remind, cancelyes'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.
sendnullable objectno
send.requestIdsarray of strings, 1 to 200 itemswhen send is setTrial-balance account identifiers whose follow-up questions the invitation carries. An account with no follow-up question is skipped.
send.contactIdnullable string (uuid)when send is setUUID of an existing contact of the engagement's client. Use this or the name and email pair.
send.contactNamenullable string, up to 200 characterswhen send is setName of a new contact to create and address.
send.contactEmailnullable string, up to 320 characterswhen send is setEmail of a new contact to create and address.
send.expiresDaysnullable integer, 1 to 365when send is setDays the client's link stays valid. Defaults to the firm's portal setting, else 30.
send.messagenullable string, up to 2000 characterswhen send is setOptional note included in the invitation email.
remindnullable objectno
remind.sessionIdstring (uuid)when remind is setUUID of the portal session (invitation).
cancelnullable objectno
cancel.sessionIdstring (uuid)when cancel is setUUID of the portal session (invitation).
confirmationTokenstring, 1 to 128 charactersyesThe 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

FieldTypeAlways presentDescription
kindone of send, remind, cancelyesThe delivery performed.
engagementIdstring (uuid)yesUUID of the engagement.
sessionIdstring (uuid)yesUUID of the invitation created, reminded, or revoked.
sessionStatusone of active, completed, revokedyesThe invitation's state after the delivery.
requestCountinteger, at least 0yesQuestions the invitation carries, or still outstanding on a reminder.
mailDispatchedbooleanyesTrue only when the mail transport accepted the message. False for a cancellation, and for a send or reminder the transport did not accept.
deliveryStatestringyesTransport state of the message: 'smtp_accepted', 'not_queued', a failure state, or 'not_applicable'.
supersededSessionIdnullable string (uuid)noThe live invitation this send revoked, when there was one.
deliveryResourceVersionstring, 1 to 256 charactersyesOpaque 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.
evaluatedAtstring (date-time)yesISO-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

FieldTypeAlways presentDescription
dataarray of objectsyesEvery connector visible to your organization, ordered by name.
data[].connectorIdstringyesStable connector identifier.
data[].displayNamestringyesConnector name as Filemark shows it.
data[].capabilitiesarray of stringsyesWhat the connector can read, sorted. Only read_trial_balance, read_gl_detail and read_chart_of_accounts are syncable through this API.
data[].enrollmentStatusone of active, disabled, not_enrolled, coming_soonyes'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[].lastSyncedAtnullable string (date-time)noISO-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 code result_not_representable means 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

ParameterInTypeRequiredDescription
connector_idpathstring, 1 to 64 characters, matching ^[a-z][a-z0-9_-]*$yesIdentifier 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

FieldTypeAlways presentDescription
kindstring, always "ai-job/v1"yes
jobIdstring (uuid)yes
statusUrlstringyes
pollMethodstring, always "GET"yes
pollHeadersobjectyes
pollHeaders.Idempotency-Keystring (uuid)yes
pollHeaders.X-Filemark-Jobstring (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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the sync would land in.

Request body

FieldTypeRequiredDescription
connectorIdstring, 1 to 64 characters, matching ^[a-z][a-z0-9_-]*$yesIdentifier of the connector, as listed by the connections read.
sourceIdstring, 1 to 256 charactersyesThe company to read, from the sources list.
sourceNamenullable string, up to 256 charactersnoLabel for the landed data, when you want one other than the source id. Defaults to the source id.
capabilitiesnullable array of strings, up to 3 itemsnoNarrow 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

FieldTypeAlways presentDescription
kindstring, always "ai-job/v1"yes
jobIdstring (uuid)yes
statusUrlstringyes
pollMethodstring, always "POST"yes
pollHeadersobjectyes
pollHeaders.Idempotency-Keystring (uuid)yes
pollHeaders.X-Filemark-Jobstring (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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the sync lands in.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
previewIdstring (uuid)yesThe previewId the preview returned.
confirmationTokenstring, 4 to 128 charactersyesThe confirmationToken from that same preview. Single use.
decisionsobjectnoAnswers to the trial-balance questions the preview returned.
decisions.conflictsmap of one of rename, new_accountwhen decisions is set
decisions.conflicts.{key}one of rename, new_accountper key
decisions.restoresmap of one of restore, skipwhen decisions is set
decisions.restores.{key}one of restore, skipper key
decisions.missingmap of one of keep, deletewhen decisions is set
decisions.missing.{key}one of keep, deleteper key
decisions.duplicatesmap of one of combine, first, skipwhen decisions is set
decisions.duplicates.{key}one of combine, first, skipper key
decisions.coveragemap of booleanwhen decisions is set
decisions.coverage.{key}booleanper key
decisions.accountResolutionsmap of objectwhen decisions is set
decisions.accountResolutions.{key}objectper key
decisions.accountResolutions.{key}.accountNamestring, 1 to 300 charactersper key
decisions.accountResolutions.{key}.accountCodestring, up to 64 charactersper key
decisions.accountResolutions.{key}.gifiCodestring, matching ^\d{4}$per key
decisions.accountResolutions.{key}.manualbooleanper key
replaceMissingbooleannoDelete 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the sync record; the same value as its previewId.
engagementIdstring (uuid)yesUUID of the engagement the sync landed in.
connectorIdstringyesThe connector that was read.
sourceIdstringyesThe company that was read.
sourceNamestringyesLabel the landed data carries in Filemark.
statusone of previewed, applied, applied_pending, failedyes'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.
capabilitiesarray of stringsyesThe previewed capabilities, in canonical order.
appliedarray of objectsnoWhat each capability landed. An applied_pending sync includes completed capabilities and any committed trial-balance merge.
applied[].capabilitystringwhen applied is setThe connector capability.
applied[].addedinteger, at least 0when applied is setRecords added.
applied[].removedinteger, at least 0when applied is setRecords removed.
applied[].modifiedinteger, at least 0when applied is setRecords changed.
lastSyncedAtStampednullable booleannoWhether the connection's last-sync timestamp was updated. False means the data landed but the stamp did not. Null unless applied.
errornullable objectnoWhy the sync failed or its remaining steps need a retry. Null otherwise.
error.codestringwhen error is setStable failure identifier.
error.messagestringwhen error is setReadable explanation, never provider text.
createdAtstring (date-time)yesISO-8601 in UTC. When the preview was taken.
appliedAtnullable string (date-time)noISO-8601 in UTC. When the sync settled. Null while previewed or applied_pending.
expiresAtstring (date-time)yesISO-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

ParameterInTypeRequiredDescription
sync_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the sync record; the same value as its previewId.
engagementIdstring (uuid)yesUUID of the engagement the sync landed in.
connectorIdstringyesThe connector that was read.
sourceIdstringyesThe company that was read.
sourceNamestringyesLabel the landed data carries in Filemark.
statusone of previewed, applied, applied_pending, failedyes'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.
capabilitiesarray of stringsyesThe previewed capabilities, in canonical order.
appliedarray of objectsnoWhat each capability landed. An applied_pending sync includes completed capabilities and any committed trial-balance merge.
applied[].capabilitystringwhen applied is setThe connector capability.
applied[].addedinteger, at least 0when applied is setRecords added.
applied[].removedinteger, at least 0when applied is setRecords removed.
applied[].modifiedinteger, at least 0when applied is setRecords changed.
lastSyncedAtStampednullable booleannoWhether the connection's last-sync timestamp was updated. False means the data landed but the stamp did not. Null unless applied.
errornullable objectnoWhy the sync failed or its remaining steps need a retry. Null otherwise.
error.codestringwhen error is setStable failure identifier.
error.messagestringwhen error is setReadable explanation, never provider text.
createdAtstring (date-time)yesISO-8601 in UTC. When the preview was taken.
appliedAtnullable string (date-time)noISO-8601 in UTC. When the sync settled. Null while previewed or applied_pending.
expiresAtstring (date-time)yesISO-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 code result_not_representable means 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

ParameterInTypeRequiredDescription
export_job_idpathstring (uuid)yesUUID of the export job to deliver.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of this delivery.
exportJobIdstring (uuid)yesUUID of the export job that was delivered.
engagementIdstring (uuid)yesUUID of the engagement the package belongs to.
downloadUrlstringyesShort-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.
expiresAtstring (date-time)yesISO-8601 in UTC. When downloadUrl stops working.
fileNamestringyesSuggested file name for the package.
mediaTypestringyesMedia type of the package.
byteLengthinteger, at least 0yesExact size of the package in bytes.
sha256string, matching ^[0-9a-f]{64}$yesSHA-256 of the package bytes. Verify what you download against it.
deliveredAtstring (date-time)yesISO-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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404.
document_idpathstring, 1 to 256 characters, matching ^(gl|fs|wp|sd|basis|pu|ev|upload):[A-Za-z0-9_.:%@-]{1,200}$yesIdentifier 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

FieldTypeAlways presentDescription
idstring, 1 to 256 charactersyesIdentifier 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.
fileNamestring, 1 to 512 charactersyesName to show for the document. When the stored record carries no file name, a generic label for its kind is returned.
kindone 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, inboxyesWhat 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.
sourceone of onboarding, manual, portal, connector, api, unknownyesHow 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.
uploadedAtnullable string (date-time)noISO-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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or archived.
lifecycle.stateone of active, archivedyes'active' for a live document, 'archived' once it was removed.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
capabilitiesobjectyesWhich document operations this document currently admits.
capabilities.replacebooleanyesTrue when documents.replace can swap this document's object for a completed staged upload. Only supporting documents admit it.
capabilities.archivebooleanyesTrue when documents.archive can remove this document. Supporting documents and staged uploads admit it; basis files an engagement was built from never do.
capabilities.deliverbooleanyesTrue 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404.
document_idpathstring, 1 to 256 characters, matching ^(gl|fs|wp|sd|basis|pu|ev|upload):[A-Za-z0-9_.:%@-]{1,200}$yesIdentifier 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-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeRequiredDescription
uploadIdstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
idstring, 1 to 256 charactersyesIdentifier 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.
fileNamestring, 1 to 512 charactersyesName to show for the document. When the stored record carries no file name, a generic label for its kind is returned.
kindone 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, inboxyesWhat 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.
sourceone of onboarding, manual, portal, connector, api, unknownyesHow 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.
uploadedAtnullable string (date-time)noISO-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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or archived.
lifecycle.stateone of active, archivedyes'active' for a live document, 'archived' once it was removed.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
capabilitiesobjectyesWhich document operations this document currently admits.
capabilities.replacebooleanyesTrue when documents.replace can swap this document's object for a completed staged upload. Only supporting documents admit it.
capabilities.archivebooleanyesTrue when documents.archive can remove this document. Supporting documents and staged uploads admit it; basis files an engagement was built from never do.
capabilities.deliverbooleanyesTrue 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404.
document_idpathstring, 1 to 256 characters, matching ^(gl|fs|wp|sd|basis|pu|ev|upload):[A-Za-z0-9_.:%@-]{1,200}$yesIdentifier 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-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring, 1 to 256 charactersyesIdentifier 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.
fileNamestring, 1 to 512 charactersyesName to show for the document. When the stored record carries no file name, a generic label for its kind is returned.
kindone 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, inboxyesWhat 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.
sourceone of onboarding, manual, portal, connector, api, unknownyesHow 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.
uploadedAtnullable string (date-time)noISO-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.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
lifecycleobjectyesLive or archived.
lifecycle.stateone of active, archivedyes'active' for a live document, 'archived' once it was removed.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
capabilitiesobjectyesWhich document operations this document currently admits.
capabilities.replacebooleanyesTrue when documents.replace can swap this document's object for a completed staged upload. Only supporting documents admit it.
capabilities.archivebooleanyesTrue when documents.archive can remove this document. Supporting documents and staged uploads admit it; basis files an engagement was built from never do.
capabilities.deliverbooleanyesTrue 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404.
document_idpathstring, 1 to 256 characters, matching ^(gl|fs|wp|sd|basis|pu|ev|upload):[A-Za-z0-9_.:%@-]{1,200}$yesIdentifier 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-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeAlways presentDescription
documentIdstring, 1 to 256 charactersyesIdentifier 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.
fileNamestring, 1 to 512 charactersyesName to save the file under.
urlstring, 1 to 4096 charactersyesPresigned download URL. Short-lived and single-purpose: it is the only place a storage reference appears anywhere in this API.
expiresAtstring (date-time)yesISO-8601 in UTC. When the URL stops being accepted.
resourceVersionstring, 1 to 256 charactersyesOpaque 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
fileNamestring, 1 to 512 characters, matching ^[^/\\]+$yesFile name including its extension. The extension must be one the Documents inbox accepts. No path separators.
byteLengthinteger, 1 to 52428800yesExact size of the file in bytes. Maximum 50 MB.
sha256string, matching ^[0-9a-fA-F]{64}$yesLowercase hex SHA-256 of the file's bytes.
mediaTypenullable string, up to 255 charactersnoMedia 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

FieldTypeAlways presentDescription
uploadobjectyesThe staged upload.
upload.idstring (uuid)yesUUID of the staged upload.
upload.engagementIdstring (uuid)yesUUID of the engagement it stages a file for.
upload.stateone of staged, completed, consumed, abandonedyes'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.fileNamestring, 1 to 512 charactersyesThe file name you declared.
upload.byteLengthinteger, at least 1yesThe byte length you declared.
upload.sha256string, matching ^[0-9a-f]{64}$yesThe SHA-256 you declared; completion refuses anything else.
upload.mediaTypestring, 1 to 255 charactersyesThe media type you declared.
upload.docTypeIdnullable string, up to 32 charactersnoThe confirmed or suggested document type. Null until classified.
upload.classificationnullable objectnoThe classifier's answer. Null until classified.
upload.classification.docTypeIdnullable string, up to 32 characterswhen classification is setThe suggested document type, or null when the classifier could not place the file. Confirm or override it when you start an ingestion.
upload.classification.labelnullable string, up to 200 characterswhen classification is setReadable name of the suggested document type. Null when unknown.
upload.classification.confidencenullable number, 0 to 1when classification is set0 to 1. Null when the classification failed or is unknown.
upload.classification.statusone of classified, failedwhen 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.tierUsedone of filename, structure, llm, user, fallbackwhen classification is setHow 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.alternativesarray of objectswhen classification is setOther document types the classifier considered, most likely first.
upload.classification.alternatives[].docTypeIdstring, up to 32 characterswhen classification is setA document type also considered.
upload.classification.alternatives[].confidencenumber, 0 to 1when classification is set0 to 1 for this alternative.
upload.classification.warningsarray of stringswhen classification is setNon-blocking notes about the classification.
upload.resourceVersionstring, 1 to 256 charactersyesOpaque 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.createdAtstring (date-time)yesISO-8601 in UTC. When staging started.
upload.updatedAtstring (date-time)yesISO-8601 in UTC. Last change to the row.
upload.completedAtnullable string (date-time)noISO-8601 in UTC. When the object was verified.
upload.expiresAtstring (date-time)yesISO-8601 in UTC. After this, a staged upload can no longer be completed and its object is swept.
uploadInstructionsobjectyesWhere and how to send the file. Returned by start only.
uploadInstructions.urlstring, 1 to 4096 charactersyesPresigned S3 URL. Send the file to it and nothing else; the signature pins the object, its media type and its byte length.
uploadInstructions.methodstring, always "PUT"yesHTTP method to use for the upload.
uploadInstructions.headersmap of stringyesHeaders the upload must carry verbatim. Sending different values makes S3 refuse the request.
uploadInstructions.headers.{key}stringper keyHeaders the upload must carry verbatim. Sending different values makes S3 refuse the request.
uploadInstructions.expiresAtstring (date-time)yesISO-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

ParameterInTypeRequiredDescription
upload_idpathstring (uuid)yesUUID of the staged upload.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the staged upload.
engagementIdstring (uuid)yesUUID of the engagement it stages a file for.
stateone of staged, completed, consumed, abandonedyes'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.
fileNamestring, 1 to 512 charactersyesThe file name you declared.
byteLengthinteger, at least 1yesThe byte length you declared.
sha256string, matching ^[0-9a-f]{64}$yesThe SHA-256 you declared; completion refuses anything else.
mediaTypestring, 1 to 255 charactersyesThe media type you declared.
docTypeIdnullable string, up to 32 charactersnoThe confirmed or suggested document type. Null until classified.
classificationnullable objectnoThe classifier's answer. Null until classified.
classification.docTypeIdnullable string, up to 32 characterswhen classification is setThe suggested document type, or null when the classifier could not place the file. Confirm or override it when you start an ingestion.
classification.labelnullable string, up to 200 characterswhen classification is setReadable name of the suggested document type. Null when unknown.
classification.confidencenullable number, 0 to 1when classification is set0 to 1. Null when the classification failed or is unknown.
classification.statusone of classified, failedwhen classification is set'classified' when the classifier produced a decision (which may still be an unknown type), 'failed' when it could not run.
classification.tierUsedone of filename, structure, llm, user, fallbackwhen classification is setHow 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.alternativesarray of objectswhen classification is setOther document types the classifier considered, most likely first.
classification.alternatives[].docTypeIdstring, up to 32 characterswhen classification is setA document type also considered.
classification.alternatives[].confidencenumber, 0 to 1when classification is set0 to 1 for this alternative.
classification.warningsarray of stringswhen classification is setNon-blocking notes about the classification.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
createdAtstring (date-time)yesISO-8601 in UTC. When staging started.
updatedAtstring (date-time)yesISO-8601 in UTC. Last change to the row.
completedAtnullable string (date-time)noISO-8601 in UTC. When the object was verified.
expiresAtstring (date-time)yesISO-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

ParameterInTypeRequiredDescription
upload_idpathstring (uuid)yesUUID of the staged upload.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
kindstring, always "ai-job/v1"yes
jobIdstring (uuid)yes
statusUrlstringyes
pollMethodstring, always "POST"yes
pollHeadersobjectyes
pollHeaders.Idempotency-Keystring (uuid)yes
pollHeaders.X-Filemark-Jobstring (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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement. An engagement outside your organization, or outside what a delegated token may see, answers 404.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
filesarray of objects, 1 to 50 itemsyesThe completed staged uploads to ingest, with their confirmed types.
files[].uploadIdstring (uuid)yesUUID of a completed staged upload to ingest.
files[].docTypeIdstring, 1 to 32 characters, matching ^[A-Za-z0-9_.-]+$yesThe document type to ingest the file as. Confirm the suggestion documents.classify returned, or send your own.
files[].sheetNamenullable string, up to 31 charactersnoWorksheet to read, for a workbook that needs one named.
files[].columnMappingnullable objectnoAccount-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.headerRowinteger, 0 to 10000when columnMapping is set
files[].columnMapping.columnMapmap of integerwhen columnMapping is set
files[].columnMapping.columnMap.{key}integerper 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the ingestion job.
engagementIdstring (uuid)yesUUID of the engagement being ingested into.
statusone of queued, running, succeeded, failed, cancelledyes'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.
attemptCountinteger, at least 0yesHow many times the worker has run it.
requestedUploadIdsarray of stringsyesThe staged uploads the job was asked to ingest, in order.
outcomesarray of objectsnoOne entry per file the job finished. Empty while queued.
outcomes[].uploadIdstring (uuid)when outcomes is setUUID of the staged upload this outcome is for.
outcomes[].statusone of ingested, merged, duplicate, unsupported, import_preview, prior_year_input, errorwhen 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[].messagestring, up to 1000 characterswhen outcomes is setReadable explanation of the outcome.
outcomes[].artifactnullable objectwhen outcomes is setWhat the file became. Null when nothing was created.
outcomes[].artifact.kindstring, up to 64 characterswhen outcomes is setThe artifact family: workpaper, supporting-doc, gl-file, coa, prior-return.
outcomes[].artifact.refnullable string, up to 256 characterswhen outcomes is setIdentifier of the artifact within the engagement. Null when it has none.
outcomes[].previewIdnullable stringwhen outcomes is setFor '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[].targetEngagementIdnullable string (uuid)when outcomes is setFor '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[].questionsnullable objectwhen outcomes is setFor 'import_preview': what the import asks before it merges.
outcomes[].questions.modeone of books, rowswhen outcomes is set
outcomes[].questions.confirmationCountsobjectwhen outcomes is set
outcomes[].questions.confirmationCounts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.addedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.conflictsarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.conflicts[].plansnullable map of objectwhen outcomes is set
outcomes[].questions.conflicts[].plans.{key}objectper key
outcomes[].questions.conflicts[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.conflicts[].accountIdstringwhen outcomes is set
outcomes[].questions.conflicts[].codestringwhen outcomes is set
outcomes[].questions.conflicts[].existingNamestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingNamestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingBalancestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingCodenullable stringwhen outcomes is set
outcomes[].questions.conflicts[].crossKeybooleanwhen outcomes is set
outcomes[].questions.restoresarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.restores[].plansnullable map of objectwhen outcomes is set
outcomes[].questions.restores[].plans.{key}objectper key
outcomes[].questions.restores[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.restores[].accountIdstringwhen outcomes is set
outcomes[].questions.restores[].codestringwhen outcomes is set
outcomes[].questions.restores[].namestringwhen outcomes is set
outcomes[].questions.restores[].deletedAtnullable stringwhen outcomes is set
outcomes[].questions.restores[].incomingBalancestringwhen outcomes is set
outcomes[].questions.missingarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.missing[].plansmap of objectwhen outcomes is set
outcomes[].questions.missing[].plans.{key}objectper key
outcomes[].questions.missing[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.missing[].accountIdstringwhen outcomes is set
outcomes[].questions.missing[].codestringwhen outcomes is set
outcomes[].questions.missing[].namestringwhen outcomes is set
outcomes[].questions.missing[].balancestringwhen outcomes is set
outcomes[].questions.missing[].refsobjectwhen outcomes is set
outcomes[].questions.missing[].refs.ajeLinesintegerwhen outcomes is set
outcomes[].questions.missing[].refs.workpapersintegerwhen outcomes is set
outcomes[].questions.missing[].refs.groupsintegerwhen outcomes is set
outcomes[].questions.duplicatesarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.duplicates[].codestringwhen outcomes is set
outcomes[].questions.duplicates[].combinedBalancestringwhen outcomes is set
outcomes[].questions.duplicates[].rowsarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.duplicates[].rows[].namestringwhen outcomes is set
outcomes[].questions.duplicates[].rows[].balancestringwhen outcomes is set
outcomes[].questions.duplicates[].rowsCountinteger, at least 0when outcomes is setMember rows in this source group, listed or not.
outcomes[].questions.duplicates[].rowsTruncatedbooleanwhen outcomes is setOnly 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[].plansobjectwhen outcomes is setApply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner.
outcomes[].questions.duplicates[].plans.combinearray of objects, 1 to 2 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.combine[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.combine[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.firstarray of objects, 1 to 2 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.first[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.first[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skiparray of objects, 1 to 1 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.skip[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.skip[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.lowMatchnullable objectwhen outcomes is set
outcomes[].questions.lowMatch.matchedRowsintegerwhen outcomes is set
outcomes[].questions.lowMatch.rowsintegerwhen outcomes is set
outcomes[].questions.blockednullable objectwhen outcomes is set
outcomes[].questions.blocked.reasonstringwhen outcomes is set
outcomes[].questions.blocked.messagestringwhen outcomes is set
outcomes[].questions.coveragenullable objectwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriodobjectwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriod.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriod.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.filesarray of objectswhen outcomes is set
outcomes[].questions.coverage.files[].glIdstringwhen outcomes is set
outcomes[].questions.coverage.files[].filenamestringwhen outcomes is set
outcomes[].questions.coverage.files[].accountsarray of stringswhen outcomes is set
outcomes[].questions.coverage.files[].reportRangenullable objectwhen outcomes is set
outcomes[].questions.coverage.files[].reportRange.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].reportRange.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].observedRangenullable objectwhen outcomes is set
outcomes[].questions.coverage.files[].observedRange.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].observedRange.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.missingRangesarray of objectswhen outcomes is set
outcomes[].questions.coverage.missingRanges[].startnullable stringwhen outcomes is set
outcomes[].questions.coverage.missingRanges[].endnullable stringwhen outcomes is set
outcomes[].questions.coverage.requirementsarray of objectswhen outcomes is set
outcomes[].questions.coverage.requirements[].idstringwhen outcomes is set
outcomes[].questions.coverage.requirements[].messagestringwhen outcomes is set
outcomes[].questions.priorYearMismatchbooleanwhen outcomes is set
outcomes[].questions.missingCountinteger, at least 0when outcomes is set
outcomes[].questions.missingTruncatedbooleanwhen outcomes is setOnly the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account.
outcomes[].questions.conflictsCountinteger, at least 0when outcomes is setName conflicts the import found, listed or not.
outcomes[].questions.conflictsTruncatedbooleanwhen outcomes is setOnly the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest.
outcomes[].questions.restoresCountinteger, at least 0when outcomes is setRows matching a deleted account, listed or not.
outcomes[].questions.restoresTruncatedbooleanwhen outcomes is setOnly the first 5,000 rows matching a deleted account are listed; omitted answers skip the row.
outcomes[].questions.duplicatesCountinteger, at least 0when outcomes is setDuplicated account codes in the source, listed or not.
outcomes[].questions.duplicatesTruncatedbooleanwhen outcomes is setOnly 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[].codenullable string, up to 64 characterswhen outcomes is setStable per-file refusal code when column mapping is needed.
outcomes[].mappingPreviewnullable objectwhen outcomes is setFor trial_balance_columns_unrecognized: inspect these rows and submit columnMapping in a new ingestion.
outcomes[].mappingPreview.sheetNamestring, up to 31 characterswhen outcomes is setWorksheet the parser read.
outcomes[].mappingPreview.headerRowinteger, 0 to 10000when outcomes is setSuggested one-based header row; zero means none detected.
outcomes[].mappingPreview.columnMapmap of integer, 1 to 16384when outcomes is setDetected roles and their one-based column numbers. May be empty.
outcomes[].mappingPreview.columnMap.{key}integer, 1 to 16384per keyDetected roles and their one-based column numbers. May be empty.
outcomes[].mappingPreview.rowsarray of arrays, up to 15 itemswhen outcomes is setFirst 15 rows, up to 128 columns each. Cells are text or null, truncated to 256 characters.
errornullable objectnoWhy the job failed. Null unless status is failed.
error.codestring, up to 64 characterswhen error is setStable failure identifier.
error.messagestring, up to 1000 characterswhen error is setReadable explanation.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
createdAtstring (date-time)yesISO-8601 in UTC. When the job was created.
updatedAtstring (date-time)yesISO-8601 in UTC. Last change to the job.
startedAtnullable string (date-time)noISO-8601 in UTC. When the worker claimed it.
finishedAtnullable string (date-time)noISO-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

ParameterInTypeRequiredDescription
ingestion_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the ingestion job.
engagementIdstring (uuid)yesUUID of the engagement being ingested into.
statusone of queued, running, succeeded, failed, cancelledyes'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.
attemptCountinteger, at least 0yesHow many times the worker has run it.
requestedUploadIdsarray of stringsyesThe staged uploads the job was asked to ingest, in order.
outcomesarray of objectsnoOne entry per file the job finished. Empty while queued.
outcomes[].uploadIdstring (uuid)when outcomes is setUUID of the staged upload this outcome is for.
outcomes[].statusone of ingested, merged, duplicate, unsupported, import_preview, prior_year_input, errorwhen 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[].messagestring, up to 1000 characterswhen outcomes is setReadable explanation of the outcome.
outcomes[].artifactnullable objectwhen outcomes is setWhat the file became. Null when nothing was created.
outcomes[].artifact.kindstring, up to 64 characterswhen outcomes is setThe artifact family: workpaper, supporting-doc, gl-file, coa, prior-return.
outcomes[].artifact.refnullable string, up to 256 characterswhen outcomes is setIdentifier of the artifact within the engagement. Null when it has none.
outcomes[].previewIdnullable stringwhen outcomes is setFor '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[].targetEngagementIdnullable string (uuid)when outcomes is setFor '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[].questionsnullable objectwhen outcomes is setFor 'import_preview': what the import asks before it merges.
outcomes[].questions.modeone of books, rowswhen outcomes is set
outcomes[].questions.confirmationCountsobjectwhen outcomes is set
outcomes[].questions.confirmationCounts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.addedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.conflictsarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.conflicts[].plansnullable map of objectwhen outcomes is set
outcomes[].questions.conflicts[].plans.{key}objectper key
outcomes[].questions.conflicts[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.conflicts[].accountIdstringwhen outcomes is set
outcomes[].questions.conflicts[].codestringwhen outcomes is set
outcomes[].questions.conflicts[].existingNamestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingNamestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingBalancestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingCodenullable stringwhen outcomes is set
outcomes[].questions.conflicts[].crossKeybooleanwhen outcomes is set
outcomes[].questions.restoresarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.restores[].plansnullable map of objectwhen outcomes is set
outcomes[].questions.restores[].plans.{key}objectper key
outcomes[].questions.restores[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.restores[].accountIdstringwhen outcomes is set
outcomes[].questions.restores[].codestringwhen outcomes is set
outcomes[].questions.restores[].namestringwhen outcomes is set
outcomes[].questions.restores[].deletedAtnullable stringwhen outcomes is set
outcomes[].questions.restores[].incomingBalancestringwhen outcomes is set
outcomes[].questions.missingarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.missing[].plansmap of objectwhen outcomes is set
outcomes[].questions.missing[].plans.{key}objectper key
outcomes[].questions.missing[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.missing[].accountIdstringwhen outcomes is set
outcomes[].questions.missing[].codestringwhen outcomes is set
outcomes[].questions.missing[].namestringwhen outcomes is set
outcomes[].questions.missing[].balancestringwhen outcomes is set
outcomes[].questions.missing[].refsobjectwhen outcomes is set
outcomes[].questions.missing[].refs.ajeLinesintegerwhen outcomes is set
outcomes[].questions.missing[].refs.workpapersintegerwhen outcomes is set
outcomes[].questions.missing[].refs.groupsintegerwhen outcomes is set
outcomes[].questions.duplicatesarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.duplicates[].codestringwhen outcomes is set
outcomes[].questions.duplicates[].combinedBalancestringwhen outcomes is set
outcomes[].questions.duplicates[].rowsarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.duplicates[].rows[].namestringwhen outcomes is set
outcomes[].questions.duplicates[].rows[].balancestringwhen outcomes is set
outcomes[].questions.duplicates[].rowsCountinteger, at least 0when outcomes is setMember rows in this source group, listed or not.
outcomes[].questions.duplicates[].rowsTruncatedbooleanwhen outcomes is setOnly 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[].plansobjectwhen outcomes is setApply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner.
outcomes[].questions.duplicates[].plans.combinearray of objects, 1 to 2 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.combine[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.combine[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.firstarray of objects, 1 to 2 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.first[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.first[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skiparray of objects, 1 to 1 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.skip[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.skip[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.lowMatchnullable objectwhen outcomes is set
outcomes[].questions.lowMatch.matchedRowsintegerwhen outcomes is set
outcomes[].questions.lowMatch.rowsintegerwhen outcomes is set
outcomes[].questions.blockednullable objectwhen outcomes is set
outcomes[].questions.blocked.reasonstringwhen outcomes is set
outcomes[].questions.blocked.messagestringwhen outcomes is set
outcomes[].questions.coveragenullable objectwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriodobjectwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriod.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriod.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.filesarray of objectswhen outcomes is set
outcomes[].questions.coverage.files[].glIdstringwhen outcomes is set
outcomes[].questions.coverage.files[].filenamestringwhen outcomes is set
outcomes[].questions.coverage.files[].accountsarray of stringswhen outcomes is set
outcomes[].questions.coverage.files[].reportRangenullable objectwhen outcomes is set
outcomes[].questions.coverage.files[].reportRange.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].reportRange.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].observedRangenullable objectwhen outcomes is set
outcomes[].questions.coverage.files[].observedRange.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].observedRange.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.missingRangesarray of objectswhen outcomes is set
outcomes[].questions.coverage.missingRanges[].startnullable stringwhen outcomes is set
outcomes[].questions.coverage.missingRanges[].endnullable stringwhen outcomes is set
outcomes[].questions.coverage.requirementsarray of objectswhen outcomes is set
outcomes[].questions.coverage.requirements[].idstringwhen outcomes is set
outcomes[].questions.coverage.requirements[].messagestringwhen outcomes is set
outcomes[].questions.priorYearMismatchbooleanwhen outcomes is set
outcomes[].questions.missingCountinteger, at least 0when outcomes is set
outcomes[].questions.missingTruncatedbooleanwhen outcomes is setOnly the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account.
outcomes[].questions.conflictsCountinteger, at least 0when outcomes is setName conflicts the import found, listed or not.
outcomes[].questions.conflictsTruncatedbooleanwhen outcomes is setOnly the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest.
outcomes[].questions.restoresCountinteger, at least 0when outcomes is setRows matching a deleted account, listed or not.
outcomes[].questions.restoresTruncatedbooleanwhen outcomes is setOnly the first 5,000 rows matching a deleted account are listed; omitted answers skip the row.
outcomes[].questions.duplicatesCountinteger, at least 0when outcomes is setDuplicated account codes in the source, listed or not.
outcomes[].questions.duplicatesTruncatedbooleanwhen outcomes is setOnly 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[].codenullable string, up to 64 characterswhen outcomes is setStable per-file refusal code when column mapping is needed.
outcomes[].mappingPreviewnullable objectwhen outcomes is setFor trial_balance_columns_unrecognized: inspect these rows and submit columnMapping in a new ingestion.
outcomes[].mappingPreview.sheetNamestring, up to 31 characterswhen outcomes is setWorksheet the parser read.
outcomes[].mappingPreview.headerRowinteger, 0 to 10000when outcomes is setSuggested one-based header row; zero means none detected.
outcomes[].mappingPreview.columnMapmap of integer, 1 to 16384when outcomes is setDetected roles and their one-based column numbers. May be empty.
outcomes[].mappingPreview.columnMap.{key}integer, 1 to 16384per keyDetected roles and their one-based column numbers. May be empty.
outcomes[].mappingPreview.rowsarray of arrays, up to 15 itemswhen outcomes is setFirst 15 rows, up to 128 columns each. Cells are text or null, truncated to 256 characters.
errornullable objectnoWhy the job failed. Null unless status is failed.
error.codestring, up to 64 characterswhen error is setStable failure identifier.
error.messagestring, up to 1000 characterswhen error is setReadable explanation.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
createdAtstring (date-time)yesISO-8601 in UTC. When the job was created.
updatedAtstring (date-time)yesISO-8601 in UTC. Last change to the job.
startedAtnullable string (date-time)noISO-8601 in UTC. When the worker claimed it.
finishedAtnullable string (date-time)noISO-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 code result_not_representable means 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

ParameterInTypeRequiredDescription
ingestion_idpathstring (uuid)yesUUID of the ingestion job.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the ingestion job.
engagementIdstring (uuid)yesUUID of the engagement being ingested into.
statusone of queued, running, succeeded, failed, cancelledyes'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.
attemptCountinteger, at least 0yesHow many times the worker has run it.
requestedUploadIdsarray of stringsyesThe staged uploads the job was asked to ingest, in order.
outcomesarray of objectsnoOne entry per file the job finished. Empty while queued.
outcomes[].uploadIdstring (uuid)when outcomes is setUUID of the staged upload this outcome is for.
outcomes[].statusone of ingested, merged, duplicate, unsupported, import_preview, prior_year_input, errorwhen 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[].messagestring, up to 1000 characterswhen outcomes is setReadable explanation of the outcome.
outcomes[].artifactnullable objectwhen outcomes is setWhat the file became. Null when nothing was created.
outcomes[].artifact.kindstring, up to 64 characterswhen outcomes is setThe artifact family: workpaper, supporting-doc, gl-file, coa, prior-return.
outcomes[].artifact.refnullable string, up to 256 characterswhen outcomes is setIdentifier of the artifact within the engagement. Null when it has none.
outcomes[].previewIdnullable stringwhen outcomes is setFor '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[].targetEngagementIdnullable string (uuid)when outcomes is setFor '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[].questionsnullable objectwhen outcomes is setFor 'import_preview': what the import asks before it merges.
outcomes[].questions.modeone of books, rowswhen outcomes is set
outcomes[].questions.confirmationCountsobjectwhen outcomes is set
outcomes[].questions.confirmationCounts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.addedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.conflictsarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.conflicts[].plansnullable map of objectwhen outcomes is set
outcomes[].questions.conflicts[].plans.{key}objectper key
outcomes[].questions.conflicts[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.conflicts[].accountIdstringwhen outcomes is set
outcomes[].questions.conflicts[].codestringwhen outcomes is set
outcomes[].questions.conflicts[].existingNamestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingNamestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingBalancestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingCodenullable stringwhen outcomes is set
outcomes[].questions.conflicts[].crossKeybooleanwhen outcomes is set
outcomes[].questions.restoresarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.restores[].plansnullable map of objectwhen outcomes is set
outcomes[].questions.restores[].plans.{key}objectper key
outcomes[].questions.restores[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.restores[].accountIdstringwhen outcomes is set
outcomes[].questions.restores[].codestringwhen outcomes is set
outcomes[].questions.restores[].namestringwhen outcomes is set
outcomes[].questions.restores[].deletedAtnullable stringwhen outcomes is set
outcomes[].questions.restores[].incomingBalancestringwhen outcomes is set
outcomes[].questions.missingarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.missing[].plansmap of objectwhen outcomes is set
outcomes[].questions.missing[].plans.{key}objectper key
outcomes[].questions.missing[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.missing[].accountIdstringwhen outcomes is set
outcomes[].questions.missing[].codestringwhen outcomes is set
outcomes[].questions.missing[].namestringwhen outcomes is set
outcomes[].questions.missing[].balancestringwhen outcomes is set
outcomes[].questions.missing[].refsobjectwhen outcomes is set
outcomes[].questions.missing[].refs.ajeLinesintegerwhen outcomes is set
outcomes[].questions.missing[].refs.workpapersintegerwhen outcomes is set
outcomes[].questions.missing[].refs.groupsintegerwhen outcomes is set
outcomes[].questions.duplicatesarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.duplicates[].codestringwhen outcomes is set
outcomes[].questions.duplicates[].combinedBalancestringwhen outcomes is set
outcomes[].questions.duplicates[].rowsarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.duplicates[].rows[].namestringwhen outcomes is set
outcomes[].questions.duplicates[].rows[].balancestringwhen outcomes is set
outcomes[].questions.duplicates[].rowsCountinteger, at least 0when outcomes is setMember rows in this source group, listed or not.
outcomes[].questions.duplicates[].rowsTruncatedbooleanwhen outcomes is setOnly 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[].plansobjectwhen outcomes is setApply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner.
outcomes[].questions.duplicates[].plans.combinearray of objects, 1 to 2 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.combine[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.combine[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.firstarray of objects, 1 to 2 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.first[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.first[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skiparray of objects, 1 to 1 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.skip[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.skip[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.lowMatchnullable objectwhen outcomes is set
outcomes[].questions.lowMatch.matchedRowsintegerwhen outcomes is set
outcomes[].questions.lowMatch.rowsintegerwhen outcomes is set
outcomes[].questions.blockednullable objectwhen outcomes is set
outcomes[].questions.blocked.reasonstringwhen outcomes is set
outcomes[].questions.blocked.messagestringwhen outcomes is set
outcomes[].questions.coveragenullable objectwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriodobjectwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriod.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriod.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.filesarray of objectswhen outcomes is set
outcomes[].questions.coverage.files[].glIdstringwhen outcomes is set
outcomes[].questions.coverage.files[].filenamestringwhen outcomes is set
outcomes[].questions.coverage.files[].accountsarray of stringswhen outcomes is set
outcomes[].questions.coverage.files[].reportRangenullable objectwhen outcomes is set
outcomes[].questions.coverage.files[].reportRange.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].reportRange.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].observedRangenullable objectwhen outcomes is set
outcomes[].questions.coverage.files[].observedRange.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].observedRange.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.missingRangesarray of objectswhen outcomes is set
outcomes[].questions.coverage.missingRanges[].startnullable stringwhen outcomes is set
outcomes[].questions.coverage.missingRanges[].endnullable stringwhen outcomes is set
outcomes[].questions.coverage.requirementsarray of objectswhen outcomes is set
outcomes[].questions.coverage.requirements[].idstringwhen outcomes is set
outcomes[].questions.coverage.requirements[].messagestringwhen outcomes is set
outcomes[].questions.priorYearMismatchbooleanwhen outcomes is set
outcomes[].questions.missingCountinteger, at least 0when outcomes is set
outcomes[].questions.missingTruncatedbooleanwhen outcomes is setOnly the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account.
outcomes[].questions.conflictsCountinteger, at least 0when outcomes is setName conflicts the import found, listed or not.
outcomes[].questions.conflictsTruncatedbooleanwhen outcomes is setOnly the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest.
outcomes[].questions.restoresCountinteger, at least 0when outcomes is setRows matching a deleted account, listed or not.
outcomes[].questions.restoresTruncatedbooleanwhen outcomes is setOnly the first 5,000 rows matching a deleted account are listed; omitted answers skip the row.
outcomes[].questions.duplicatesCountinteger, at least 0when outcomes is setDuplicated account codes in the source, listed or not.
outcomes[].questions.duplicatesTruncatedbooleanwhen outcomes is setOnly 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[].codenullable string, up to 64 characterswhen outcomes is setStable per-file refusal code when column mapping is needed.
outcomes[].mappingPreviewnullable objectwhen outcomes is setFor trial_balance_columns_unrecognized: inspect these rows and submit columnMapping in a new ingestion.
outcomes[].mappingPreview.sheetNamestring, up to 31 characterswhen outcomes is setWorksheet the parser read.
outcomes[].mappingPreview.headerRowinteger, 0 to 10000when outcomes is setSuggested one-based header row; zero means none detected.
outcomes[].mappingPreview.columnMapmap of integer, 1 to 16384when outcomes is setDetected roles and their one-based column numbers. May be empty.
outcomes[].mappingPreview.columnMap.{key}integer, 1 to 16384per keyDetected roles and their one-based column numbers. May be empty.
outcomes[].mappingPreview.rowsarray of arrays, up to 15 itemswhen outcomes is setFirst 15 rows, up to 128 columns each. Cells are text or null, truncated to 256 characters.
errornullable objectnoWhy the job failed. Null unless status is failed.
error.codestring, up to 64 characterswhen error is setStable failure identifier.
error.messagestring, up to 1000 characterswhen error is setReadable explanation.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
createdAtstring (date-time)yesISO-8601 in UTC. When the job was created.
updatedAtstring (date-time)yesISO-8601 in UTC. Last change to the job.
startedAtnullable string (date-time)noISO-8601 in UTC. When the worker claimed it.
finishedAtnullable string (date-time)noISO-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

ParameterInTypeRequiredDescription
ingestion_idpathstring (uuid)yesUUID of the ingestion job.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the ingestion job.
engagementIdstring (uuid)yesUUID of the engagement being ingested into.
statusone of queued, running, succeeded, failed, cancelledyes'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.
attemptCountinteger, at least 0yesHow many times the worker has run it.
requestedUploadIdsarray of stringsyesThe staged uploads the job was asked to ingest, in order.
outcomesarray of objectsnoOne entry per file the job finished. Empty while queued.
outcomes[].uploadIdstring (uuid)when outcomes is setUUID of the staged upload this outcome is for.
outcomes[].statusone of ingested, merged, duplicate, unsupported, import_preview, prior_year_input, errorwhen 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[].messagestring, up to 1000 characterswhen outcomes is setReadable explanation of the outcome.
outcomes[].artifactnullable objectwhen outcomes is setWhat the file became. Null when nothing was created.
outcomes[].artifact.kindstring, up to 64 characterswhen outcomes is setThe artifact family: workpaper, supporting-doc, gl-file, coa, prior-return.
outcomes[].artifact.refnullable string, up to 256 characterswhen outcomes is setIdentifier of the artifact within the engagement. Null when it has none.
outcomes[].previewIdnullable stringwhen outcomes is setFor '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[].targetEngagementIdnullable string (uuid)when outcomes is setFor '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[].questionsnullable objectwhen outcomes is setFor 'import_preview': what the import asks before it merges.
outcomes[].questions.modeone of books, rowswhen outcomes is set
outcomes[].questions.confirmationCountsobjectwhen outcomes is set
outcomes[].questions.confirmationCounts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.addedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.confirmationCounts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.conflictsarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.conflicts[].plansnullable map of objectwhen outcomes is set
outcomes[].questions.conflicts[].plans.{key}objectper key
outcomes[].questions.conflicts[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.conflicts[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.conflicts[].accountIdstringwhen outcomes is set
outcomes[].questions.conflicts[].codestringwhen outcomes is set
outcomes[].questions.conflicts[].existingNamestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingNamestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingBalancestringwhen outcomes is set
outcomes[].questions.conflicts[].incomingCodenullable stringwhen outcomes is set
outcomes[].questions.conflicts[].crossKeybooleanwhen outcomes is set
outcomes[].questions.restoresarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.restores[].plansnullable map of objectwhen outcomes is set
outcomes[].questions.restores[].plans.{key}objectper key
outcomes[].questions.restores[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.restores[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.restores[].accountIdstringwhen outcomes is set
outcomes[].questions.restores[].codestringwhen outcomes is set
outcomes[].questions.restores[].namestringwhen outcomes is set
outcomes[].questions.restores[].deletedAtnullable stringwhen outcomes is set
outcomes[].questions.restores[].incomingBalancestringwhen outcomes is set
outcomes[].questions.missingarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.missing[].plansmap of objectwhen outcomes is set
outcomes[].questions.missing[].plans.{key}objectper key
outcomes[].questions.missing[].plans.{key}.balancesUpdatedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.renamedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.addedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.restoredinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.deletedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.unchangedinteger, at least 0per key
outcomes[].questions.missing[].plans.{key}.skippedinteger, at least 0per key
outcomes[].questions.missing[].accountIdstringwhen outcomes is set
outcomes[].questions.missing[].codestringwhen outcomes is set
outcomes[].questions.missing[].namestringwhen outcomes is set
outcomes[].questions.missing[].balancestringwhen outcomes is set
outcomes[].questions.missing[].refsobjectwhen outcomes is set
outcomes[].questions.missing[].refs.ajeLinesintegerwhen outcomes is set
outcomes[].questions.missing[].refs.workpapersintegerwhen outcomes is set
outcomes[].questions.missing[].refs.groupsintegerwhen outcomes is set
outcomes[].questions.duplicatesarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.duplicates[].codestringwhen outcomes is set
outcomes[].questions.duplicates[].combinedBalancestringwhen outcomes is set
outcomes[].questions.duplicates[].rowsarray of objects, up to 5000 itemswhen outcomes is set
outcomes[].questions.duplicates[].rows[].namestringwhen outcomes is set
outcomes[].questions.duplicates[].rows[].balancestringwhen outcomes is set
outcomes[].questions.duplicates[].rowsCountinteger, at least 0when outcomes is setMember rows in this source group, listed or not.
outcomes[].questions.duplicates[].rowsTruncatedbooleanwhen outcomes is setOnly 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[].plansobjectwhen outcomes is setApply counts for each duplicate answer and any accompanying conflict or restore answer, computed by the shared import planner.
outcomes[].questions.duplicates[].plans.combinearray of objects, 1 to 2 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.combine[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.combine[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.combine[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.firstarray of objects, 1 to 2 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.first[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.first[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.first[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skiparray of objects, 1 to 1 itemswhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].conflictsmap of one of rename, new_accountwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].conflicts.{key}one of rename, new_accountper key
outcomes[].questions.duplicates[].plans.skip[].restoresmap of one of restore, skipwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].restores.{key}one of restore, skipper key
outcomes[].questions.duplicates[].plans.skip[].countsobjectwhen outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.balancesUpdatedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.renamedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.addedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.restoredinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.deletedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.unchangedinteger, at least 0when outcomes is set
outcomes[].questions.duplicates[].plans.skip[].counts.skippedinteger, at least 0when outcomes is set
outcomes[].questions.lowMatchnullable objectwhen outcomes is set
outcomes[].questions.lowMatch.matchedRowsintegerwhen outcomes is set
outcomes[].questions.lowMatch.rowsintegerwhen outcomes is set
outcomes[].questions.blockednullable objectwhen outcomes is set
outcomes[].questions.blocked.reasonstringwhen outcomes is set
outcomes[].questions.blocked.messagestringwhen outcomes is set
outcomes[].questions.coveragenullable objectwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriodobjectwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriod.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.fiscalPeriod.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.filesarray of objectswhen outcomes is set
outcomes[].questions.coverage.files[].glIdstringwhen outcomes is set
outcomes[].questions.coverage.files[].filenamestringwhen outcomes is set
outcomes[].questions.coverage.files[].accountsarray of stringswhen outcomes is set
outcomes[].questions.coverage.files[].reportRangenullable objectwhen outcomes is set
outcomes[].questions.coverage.files[].reportRange.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].reportRange.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].observedRangenullable objectwhen outcomes is set
outcomes[].questions.coverage.files[].observedRange.startnullable stringwhen outcomes is set
outcomes[].questions.coverage.files[].observedRange.endnullable stringwhen outcomes is set
outcomes[].questions.coverage.missingRangesarray of objectswhen outcomes is set
outcomes[].questions.coverage.missingRanges[].startnullable stringwhen outcomes is set
outcomes[].questions.coverage.missingRanges[].endnullable stringwhen outcomes is set
outcomes[].questions.coverage.requirementsarray of objectswhen outcomes is set
outcomes[].questions.coverage.requirements[].idstringwhen outcomes is set
outcomes[].questions.coverage.requirements[].messagestringwhen outcomes is set
outcomes[].questions.priorYearMismatchbooleanwhen outcomes is set
outcomes[].questions.missingCountinteger, at least 0when outcomes is set
outcomes[].questions.missingTruncatedbooleanwhen outcomes is setOnly the first 5,000 missing accounts are listed; omitted answers keep accounts. replaceMissing addresses every missing ordinary account.
outcomes[].questions.conflictsCountinteger, at least 0when outcomes is setName conflicts the import found, listed or not.
outcomes[].questions.conflictsTruncatedbooleanwhen outcomes is setOnly the first 5,000 conflicts are listed. Every conflict needs an answer: import this trial balance in Filemark to answer the rest.
outcomes[].questions.restoresCountinteger, at least 0when outcomes is setRows matching a deleted account, listed or not.
outcomes[].questions.restoresTruncatedbooleanwhen outcomes is setOnly the first 5,000 rows matching a deleted account are listed; omitted answers skip the row.
outcomes[].questions.duplicatesCountinteger, at least 0when outcomes is setDuplicated account codes in the source, listed or not.
outcomes[].questions.duplicatesTruncatedbooleanwhen outcomes is setOnly 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[].codenullable string, up to 64 characterswhen outcomes is setStable per-file refusal code when column mapping is needed.
outcomes[].mappingPreviewnullable objectwhen outcomes is setFor trial_balance_columns_unrecognized: inspect these rows and submit columnMapping in a new ingestion.
outcomes[].mappingPreview.sheetNamestring, up to 31 characterswhen outcomes is setWorksheet the parser read.
outcomes[].mappingPreview.headerRowinteger, 0 to 10000when outcomes is setSuggested one-based header row; zero means none detected.
outcomes[].mappingPreview.columnMapmap of integer, 1 to 16384when outcomes is setDetected roles and their one-based column numbers. May be empty.
outcomes[].mappingPreview.columnMap.{key}integer, 1 to 16384per keyDetected roles and their one-based column numbers. May be empty.
outcomes[].mappingPreview.rowsarray of arrays, up to 15 itemswhen outcomes is setFirst 15 rows, up to 128 columns each. Cells are text or null, truncated to 256 characters.
errornullable objectnoWhy the job failed. Null unless status is failed.
error.codestring, up to 64 characterswhen error is setStable failure identifier.
error.messagestring, up to 1000 characterswhen error is setReadable explanation.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
createdAtstring (date-time)yesISO-8601 in UTC. When the job was created.
updatedAtstring (date-time)yesISO-8601 in UTC. Last change to the job.
startedAtnullable string (date-time)noISO-8601 in UTC. When the worker claimed it.
finishedAtnullable string (date-time)noISO-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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID of the workpaper.
cursorquerynullable string, 1 to 4096 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the workpaper.
engagementIdstring (uuid)yesUUID of the engagement it belongs to.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
templateIdnullable stringnoConcept template the workpaper was built from.
namestringyesPractitioner-facing display name.
customNamenullable stringnoThe one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name).
kindone of row_grid, narrativeyesBody shape.
originstringyesHow the workpaper came to exist: blank, uploaded, generated, carryforward.
statusone of draft, completeyesPreparer status.
categorynullable stringnoPractitioner grouping label.
versioninteger, at least 1yesMonotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number.
isReviewedbooleanyesTrue once a practitioner marked it reviewed in Filemark. The API never sets it.
reviewedAtnullable string (date-time)noISO-8601 in UTC. Null while unreviewed.
parseStatusnullable stringnoOutcome of parsing the uploaded source file, when there is one.
detectedTypenullable stringnoLayout the parser detected in the source file.
sourceFileNamenullable string, up to 512 charactersnoFile name of the uploaded source, when there is one.
assumptionnullable stringnoThe preparer's stated assumption for the working paper.
notesnullable stringnoPreparer notes.
tieOutobjectyesAgreement with the linked trial-balance accounts.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
linkedAccountIdsarray of stringsyesSaved trial-balance account ids the workpaper is linked to.
columnsarray of objectsyesDeclared grid columns. Empty for a narrative workpaper.
columns[].keystringyesRow key this column occupies.
columns[].labelstringyesPractitioner-facing column heading.
columns[].typeone of text, number, date, select, cohortsyesDeclared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields.
columns[].optionsnullable array of stringsnoClosed option list for a 'select' column. Null for every other type.
columns[].sectionIdnullable stringnoTemplate section this column belongs to. Null for the main grid.
columns[].entryFieldsnullable array of objectsnoThe declared fields of each entry of a 'cohorts' column. Null for every other type.
columns[].entryFields[].keystringwhen entryFields is setEntry key this field occupies.
columns[].entryFields[].labelstringwhen entryFields is setPractitioner-facing field heading.
columns[].entryFields[].typeone of text, number, date, selectwhen entryFields is setDeclared value type. A written entry value must match it.
columns[].entryFields[].optionsnullable array of stringswhen entryFields is setClosed option list for a 'select' field. Null for every other type.
rowsarray of objectsyesOne page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper.
rows[].rowKeystringyesStable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one.
rows[].sectionIdnullable stringnoTemplate section the row sits in. Null for the main grid.
rows[].cellsobjectnoCell 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.*anynoMembers not listed here.
rows[].untypedCellsarray of stringsnoDeclared 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.
rowCountinteger, at least 0yesTotal rows stored, across every page.
rowsTruncatedbooleanyesTrue when more rows follow this page; nextCursor reads them.
narrativearray of objectsyesOne page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper.
narrative[].idstringyesStable block identifier.
narrative[].kindone of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divideryesBlock kind.
narrative[].contentnullable stringnoPlain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally.
narrative[].levelnullable integer, 1 to 3noHeading level, 1 to 3. Null for other kinds.
narrative[].variantnullable one of note, warning, risk, conclusionnoCallout variant. Null for other kinds.
narrative[].needsReviewbooleanyesTrue while a carried-forward block still awaits practitioner review.
narrativeBlockCountinteger, at least 0yesTotal narrative blocks stored, across every page.
narrativeTruncatedbooleanyesTrue when more narrative blocks follow this page; nextCursor reads them.
nextCursornullable string, up to 4096 charactersnoContinues 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.
completenessStatusnullable one of incomplete, completenoNarrative completeness checklist state. Null for a row-grid workpaper.
lifecycleobjectyesLive or archived (recoverable in Filemark).
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
createdAtstring (date-time)yesISO-8601 in UTC.
updatedAtstring (date-time)yesISO-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 (code conflict). Read it again from the first page.
  • 422: One or more request parameters are invalid. The code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID of the workpaper.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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.

FieldTypeRequiredDescription
statusnullable one of draft, completenoPreparer status.
customNamenullable string, up to 200 charactersno
categorynullable string, up to 100 charactersno
assumptionnullable string, up to 4000 charactersno
notesnullable string, up to 8000 charactersno
rowsnullable array of objectsnoGrid 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[].rowKeynullable string, up to 200 characterswhen rows is setStable key of the row to replace. Omit to append a new row.
rows[].sectionIdnullable string, up to 100 characterswhen rows is setTemplate section the row belongs to.
rows[].cellsobjectwhen rows is setCell values keyed by the template's declared column keys.
rows[].cells.*anynoMembers not listed here.
replaceRowsbooleannoTrue 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.
narrativenullable array of objectsnoReplacement 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[].idnullable string, up to 200 characterswhen narrative is setBlock identifier. Omit for a new block.
narrative[].kindone of heading, paragraph, bulletList, numberedList, callout, table, exhibit, dividerwhen narrative is setBlock kind.
narrative[].contentnullable string, up to 100000 characterswhen narrative is setPlain-text content in the token grammar.
narrative[].levelnullable integer, 1 to 3when narrative is setHeading level.
narrative[].variantnullable one of note, warning, risk, conclusionwhen narrative is setCallout 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the workpaper.
engagementIdstring (uuid)yesUUID of the engagement it belongs to.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
templateIdnullable stringnoConcept template the workpaper was built from.
namestringyesPractitioner-facing display name.
customNamenullable stringnoThe one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name).
kindone of row_grid, narrativeyesBody shape.
originstringyesHow the workpaper came to exist: blank, uploaded, generated, carryforward.
statusone of draft, completeyesPreparer status.
categorynullable stringnoPractitioner grouping label.
versioninteger, at least 1yesMonotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number.
isReviewedbooleanyesTrue once a practitioner marked it reviewed in Filemark. The API never sets it.
reviewedAtnullable string (date-time)noISO-8601 in UTC. Null while unreviewed.
parseStatusnullable stringnoOutcome of parsing the uploaded source file, when there is one.
detectedTypenullable stringnoLayout the parser detected in the source file.
sourceFileNamenullable string, up to 512 charactersnoFile name of the uploaded source, when there is one.
assumptionnullable stringnoThe preparer's stated assumption for the working paper.
notesnullable stringnoPreparer notes.
tieOutobjectyesAgreement with the linked trial-balance accounts.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
linkedAccountIdsarray of stringsyesSaved trial-balance account ids the workpaper is linked to.
columnsarray of objectsyesDeclared grid columns. Empty for a narrative workpaper.
columns[].keystringyesRow key this column occupies.
columns[].labelstringyesPractitioner-facing column heading.
columns[].typeone of text, number, date, select, cohortsyesDeclared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields.
columns[].optionsnullable array of stringsnoClosed option list for a 'select' column. Null for every other type.
columns[].sectionIdnullable stringnoTemplate section this column belongs to. Null for the main grid.
columns[].entryFieldsnullable array of objectsnoThe declared fields of each entry of a 'cohorts' column. Null for every other type.
columns[].entryFields[].keystringwhen entryFields is setEntry key this field occupies.
columns[].entryFields[].labelstringwhen entryFields is setPractitioner-facing field heading.
columns[].entryFields[].typeone of text, number, date, selectwhen entryFields is setDeclared value type. A written entry value must match it.
columns[].entryFields[].optionsnullable array of stringswhen entryFields is setClosed option list for a 'select' field. Null for every other type.
rowsarray of objectsyesOne page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper.
rows[].rowKeystringyesStable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one.
rows[].sectionIdnullable stringnoTemplate section the row sits in. Null for the main grid.
rows[].cellsobjectnoCell 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.*anynoMembers not listed here.
rows[].untypedCellsarray of stringsnoDeclared 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.
rowCountinteger, at least 0yesTotal rows stored, across every page.
rowsTruncatedbooleanyesTrue when more rows follow this page; nextCursor reads them.
narrativearray of objectsyesOne page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper.
narrative[].idstringyesStable block identifier.
narrative[].kindone of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divideryesBlock kind.
narrative[].contentnullable stringnoPlain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally.
narrative[].levelnullable integer, 1 to 3noHeading level, 1 to 3. Null for other kinds.
narrative[].variantnullable one of note, warning, risk, conclusionnoCallout variant. Null for other kinds.
narrative[].needsReviewbooleanyesTrue while a carried-forward block still awaits practitioner review.
narrativeBlockCountinteger, at least 0yesTotal narrative blocks stored, across every page.
narrativeTruncatedbooleanyesTrue when more narrative blocks follow this page; nextCursor reads them.
nextCursornullable string, up to 4096 charactersnoContinues 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.
completenessStatusnullable one of incomplete, completenoNarrative completeness checklist state. Null for a row-grid workpaper.
lifecycleobjectyesLive or archived (recoverable in Filemark).
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
createdAtstring (date-time)yesISO-8601 in UTC.
updatedAtstring (date-time)yesISO-8601 in UTC.
warningsarray of objectsnoNon-blocking conditions: an unknown template, a dropped cell, a partial seed.
warnings[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID of the workpaper.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the workpaper.
engagementIdstring (uuid)yesUUID of the engagement it belongs to.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
templateIdnullable stringnoConcept template the workpaper was built from.
namestringyesPractitioner-facing display name.
customNamenullable stringnoThe one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name).
kindone of row_grid, narrativeyesBody shape.
originstringyesHow the workpaper came to exist: blank, uploaded, generated, carryforward.
statusone of draft, completeyesPreparer status.
categorynullable stringnoPractitioner grouping label.
versioninteger, at least 1yesMonotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number.
isReviewedbooleanyesTrue once a practitioner marked it reviewed in Filemark. The API never sets it.
reviewedAtnullable string (date-time)noISO-8601 in UTC. Null while unreviewed.
parseStatusnullable stringnoOutcome of parsing the uploaded source file, when there is one.
detectedTypenullable stringnoLayout the parser detected in the source file.
sourceFileNamenullable string, up to 512 charactersnoFile name of the uploaded source, when there is one.
assumptionnullable stringnoThe preparer's stated assumption for the working paper.
notesnullable stringnoPreparer notes.
tieOutobjectyesAgreement with the linked trial-balance accounts.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
linkedAccountIdsarray of stringsyesSaved trial-balance account ids the workpaper is linked to.
columnsarray of objectsyesDeclared grid columns. Empty for a narrative workpaper.
columns[].keystringyesRow key this column occupies.
columns[].labelstringyesPractitioner-facing column heading.
columns[].typeone of text, number, date, select, cohortsyesDeclared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields.
columns[].optionsnullable array of stringsnoClosed option list for a 'select' column. Null for every other type.
columns[].sectionIdnullable stringnoTemplate section this column belongs to. Null for the main grid.
columns[].entryFieldsnullable array of objectsnoThe declared fields of each entry of a 'cohorts' column. Null for every other type.
columns[].entryFields[].keystringwhen entryFields is setEntry key this field occupies.
columns[].entryFields[].labelstringwhen entryFields is setPractitioner-facing field heading.
columns[].entryFields[].typeone of text, number, date, selectwhen entryFields is setDeclared value type. A written entry value must match it.
columns[].entryFields[].optionsnullable array of stringswhen entryFields is setClosed option list for a 'select' field. Null for every other type.
rowsarray of objectsyesOne page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper.
rows[].rowKeystringyesStable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one.
rows[].sectionIdnullable stringnoTemplate section the row sits in. Null for the main grid.
rows[].cellsobjectnoCell 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.*anynoMembers not listed here.
rows[].untypedCellsarray of stringsnoDeclared 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.
rowCountinteger, at least 0yesTotal rows stored, across every page.
rowsTruncatedbooleanyesTrue when more rows follow this page; nextCursor reads them.
narrativearray of objectsyesOne page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper.
narrative[].idstringyesStable block identifier.
narrative[].kindone of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divideryesBlock kind.
narrative[].contentnullable stringnoPlain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally.
narrative[].levelnullable integer, 1 to 3noHeading level, 1 to 3. Null for other kinds.
narrative[].variantnullable one of note, warning, risk, conclusionnoCallout variant. Null for other kinds.
narrative[].needsReviewbooleanyesTrue while a carried-forward block still awaits practitioner review.
narrativeBlockCountinteger, at least 0yesTotal narrative blocks stored, across every page.
narrativeTruncatedbooleanyesTrue when more narrative blocks follow this page; nextCursor reads them.
nextCursornullable string, up to 4096 charactersnoContinues 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.
completenessStatusnullable one of incomplete, completenoNarrative completeness checklist state. Null for a row-grid workpaper.
lifecycleobjectyesLive or archived (recoverable in Filemark).
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
createdAtstring (date-time)yesISO-8601 in UTC.
updatedAtstring (date-time)yesISO-8601 in UTC.
warningsarray of objectsnoNon-blocking conditions: an unknown template, a dropped cell, a partial seed.
warnings[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID of the workpaper.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the workpaper.
engagementIdstring (uuid)yesUUID of the engagement it belongs to.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
templateIdnullable stringnoConcept template the workpaper was built from.
namestringyesPractitioner-facing display name.
customNamenullable stringnoThe one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name).
kindone of row_grid, narrativeyesBody shape.
originstringyesHow the workpaper came to exist: blank, uploaded, generated, carryforward.
statusone of draft, completeyesPreparer status.
categorynullable stringnoPractitioner grouping label.
versioninteger, at least 1yesMonotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number.
isReviewedbooleanyesTrue once a practitioner marked it reviewed in Filemark. The API never sets it.
reviewedAtnullable string (date-time)noISO-8601 in UTC. Null while unreviewed.
parseStatusnullable stringnoOutcome of parsing the uploaded source file, when there is one.
detectedTypenullable stringnoLayout the parser detected in the source file.
sourceFileNamenullable string, up to 512 charactersnoFile name of the uploaded source, when there is one.
assumptionnullable stringnoThe preparer's stated assumption for the working paper.
notesnullable stringnoPreparer notes.
tieOutobjectyesAgreement with the linked trial-balance accounts.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
linkedAccountIdsarray of stringsyesSaved trial-balance account ids the workpaper is linked to.
columnsarray of objectsyesDeclared grid columns. Empty for a narrative workpaper.
columns[].keystringyesRow key this column occupies.
columns[].labelstringyesPractitioner-facing column heading.
columns[].typeone of text, number, date, select, cohortsyesDeclared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields.
columns[].optionsnullable array of stringsnoClosed option list for a 'select' column. Null for every other type.
columns[].sectionIdnullable stringnoTemplate section this column belongs to. Null for the main grid.
columns[].entryFieldsnullable array of objectsnoThe declared fields of each entry of a 'cohorts' column. Null for every other type.
columns[].entryFields[].keystringwhen entryFields is setEntry key this field occupies.
columns[].entryFields[].labelstringwhen entryFields is setPractitioner-facing field heading.
columns[].entryFields[].typeone of text, number, date, selectwhen entryFields is setDeclared value type. A written entry value must match it.
columns[].entryFields[].optionsnullable array of stringswhen entryFields is setClosed option list for a 'select' field. Null for every other type.
rowsarray of objectsyesOne page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper.
rows[].rowKeystringyesStable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one.
rows[].sectionIdnullable stringnoTemplate section the row sits in. Null for the main grid.
rows[].cellsobjectnoCell 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.*anynoMembers not listed here.
rows[].untypedCellsarray of stringsnoDeclared 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.
rowCountinteger, at least 0yesTotal rows stored, across every page.
rowsTruncatedbooleanyesTrue when more rows follow this page; nextCursor reads them.
narrativearray of objectsyesOne page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper.
narrative[].idstringyesStable block identifier.
narrative[].kindone of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divideryesBlock kind.
narrative[].contentnullable stringnoPlain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally.
narrative[].levelnullable integer, 1 to 3noHeading level, 1 to 3. Null for other kinds.
narrative[].variantnullable one of note, warning, risk, conclusionnoCallout variant. Null for other kinds.
narrative[].needsReviewbooleanyesTrue while a carried-forward block still awaits practitioner review.
narrativeBlockCountinteger, at least 0yesTotal narrative blocks stored, across every page.
narrativeTruncatedbooleanyesTrue when more narrative blocks follow this page; nextCursor reads them.
nextCursornullable string, up to 4096 charactersnoContinues 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.
completenessStatusnullable one of incomplete, completenoNarrative completeness checklist state. Null for a row-grid workpaper.
lifecycleobjectyesLive or archived (recoverable in Filemark).
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
createdAtstring (date-time)yesISO-8601 in UTC.
updatedAtstring (date-time)yesISO-8601 in UTC.
warningsarray of objectsnoNon-blocking conditions: an unknown template, a dropped cell, a partial seed.
warnings[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID of the workpaper.
cursorquerynullable string, 1 to 4096 charactersnoThe 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

FieldTypeAlways presentDescription
workpaperIdstring (uuid)yesUUID of the workpaper.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
evidencearray of objectsyesOne 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[].documentIdstringyesPublic document id. 'source' for the workpaper's own uploaded file, otherwise the id listEngagementDocuments publishes for the attachment.
evidence[].kindone of source_file, supportingyes'source_file' is the parsed original; 'supporting' is an attachment.
evidence[].fileNamenullable string, up to 512 charactersno
evidence[].labelnullable string, up to 512 charactersnoPractitioner label.
evidence[].mimenullable string, up to 200 charactersno
evidence[].contentSha256nullable string, up to 64 charactersnoSHA-256 digest of the stored file, when the store recorded one.
evidence[].parseStatusnullable string, up to 100 charactersno
evidence[].detectedTypenullable string, up to 100 charactersno
evidence[].uploadedAtnullable string (date-time)noISO-8601 in UTC.
totalinteger, at least 0yesTotal evidence items, across every page.
hasMorebooleannoTrue when more evidence follows this page; nextCursor is set whenever it is true.
nextCursornullable string, up to 4096 charactersnoPass 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 (code conflict). Read it again from the first page.
  • 422: One or more request parameters are invalid. The code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID of the workpaper.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeRequiredDescription
actionone of attach, detachyes'attach' links a document to the workpaper; 'detach' unlinks it.
documentIdstring, 1 to 200 charactersyesThe 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the workpaper.
engagementIdstring (uuid)yesUUID of the engagement it belongs to.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
templateIdnullable stringnoConcept template the workpaper was built from.
namestringyesPractitioner-facing display name.
customNamenullable stringnoThe one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name).
kindone of row_grid, narrativeyesBody shape.
originstringyesHow the workpaper came to exist: blank, uploaded, generated, carryforward.
statusone of draft, completeyesPreparer status.
categorynullable stringnoPractitioner grouping label.
versioninteger, at least 1yesMonotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number.
isReviewedbooleanyesTrue once a practitioner marked it reviewed in Filemark. The API never sets it.
reviewedAtnullable string (date-time)noISO-8601 in UTC. Null while unreviewed.
parseStatusnullable stringnoOutcome of parsing the uploaded source file, when there is one.
detectedTypenullable stringnoLayout the parser detected in the source file.
sourceFileNamenullable string, up to 512 charactersnoFile name of the uploaded source, when there is one.
assumptionnullable stringnoThe preparer's stated assumption for the working paper.
notesnullable stringnoPreparer notes.
tieOutobjectyesAgreement with the linked trial-balance accounts.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
linkedAccountIdsarray of stringsyesSaved trial-balance account ids the workpaper is linked to.
columnsarray of objectsyesDeclared grid columns. Empty for a narrative workpaper.
columns[].keystringyesRow key this column occupies.
columns[].labelstringyesPractitioner-facing column heading.
columns[].typeone of text, number, date, select, cohortsyesDeclared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields.
columns[].optionsnullable array of stringsnoClosed option list for a 'select' column. Null for every other type.
columns[].sectionIdnullable stringnoTemplate section this column belongs to. Null for the main grid.
columns[].entryFieldsnullable array of objectsnoThe declared fields of each entry of a 'cohorts' column. Null for every other type.
columns[].entryFields[].keystringwhen entryFields is setEntry key this field occupies.
columns[].entryFields[].labelstringwhen entryFields is setPractitioner-facing field heading.
columns[].entryFields[].typeone of text, number, date, selectwhen entryFields is setDeclared value type. A written entry value must match it.
columns[].entryFields[].optionsnullable array of stringswhen entryFields is setClosed option list for a 'select' field. Null for every other type.
rowsarray of objectsyesOne page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper.
rows[].rowKeystringyesStable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one.
rows[].sectionIdnullable stringnoTemplate section the row sits in. Null for the main grid.
rows[].cellsobjectnoCell 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.*anynoMembers not listed here.
rows[].untypedCellsarray of stringsnoDeclared 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.
rowCountinteger, at least 0yesTotal rows stored, across every page.
rowsTruncatedbooleanyesTrue when more rows follow this page; nextCursor reads them.
narrativearray of objectsyesOne page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper.
narrative[].idstringyesStable block identifier.
narrative[].kindone of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divideryesBlock kind.
narrative[].contentnullable stringnoPlain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally.
narrative[].levelnullable integer, 1 to 3noHeading level, 1 to 3. Null for other kinds.
narrative[].variantnullable one of note, warning, risk, conclusionnoCallout variant. Null for other kinds.
narrative[].needsReviewbooleanyesTrue while a carried-forward block still awaits practitioner review.
narrativeBlockCountinteger, at least 0yesTotal narrative blocks stored, across every page.
narrativeTruncatedbooleanyesTrue when more narrative blocks follow this page; nextCursor reads them.
nextCursornullable string, up to 4096 charactersnoContinues 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.
completenessStatusnullable one of incomplete, completenoNarrative completeness checklist state. Null for a row-grid workpaper.
lifecycleobjectyesLive or archived (recoverable in Filemark).
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
createdAtstring (date-time)yesISO-8601 in UTC.
updatedAtstring (date-time)yesISO-8601 in UTC.
warningsarray of objectsnoNon-blocking conditions: an unknown template, a dropped cell, a partial seed.
warnings[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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 /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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
workpaperIdstring (uuid)yesUUID of the workpaper.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
accountsarray of objectsyesLinked accounts with their saved trial-balance balances.
accounts[].accountIdstringyesSaved trial-balance account id.
accounts[].accountCodenullable stringnoAccount code, when known.
accounts[].accountNamenullable stringnoAccount name, when known.
accounts[].balancenullable numbernoCurrent-year balance from the saved trial balance.
accounts[].accountTypenullable stringnoClassified account type.
totalinteger, at least 0yesTotal 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 code result_not_representable means 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.

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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID of the workpaper.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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-Matchheadernullable string, up to 258 charactersnoThe 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

FieldTypeRequiredDescription
actionone of set, add, removeyes'set' replaces the link set; 'add' and 'remove' change it.
accountIdsarray of strings, 1 to 500 itemsyesSaved 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

FieldTypeAlways presentDescription
workpaperIdstring (uuid)yesUUID of the workpaper.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
accountsarray of objectsyesLinked accounts with their saved trial-balance balances.
accounts[].accountIdstringyesSaved trial-balance account id.
accounts[].accountCodenullable stringnoAccount code, when known.
accounts[].accountNamenullable stringnoAccount name, when known.
accounts[].balancenullable numbernoCurrent-year balance from the saved trial balance.
accounts[].accountTypenullable stringnoClassified account type.
totalinteger, at least 0yesTotal 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
workpaperIdstring (uuid)yesUUID of the workpaper.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
validbooleanyesTrue when no error-severity finding was raised.
findingsarray of objectsyesEvery finding, errors first.
findings[].codestringyesStable finding identifier.
findings[].severityone of error, warningyes'error' blocks a save in Filemark; 'warning' is advisory.
findings[].messagestringyesWhat is wrong and what to do about it.
findings[].pathnullable stringnoWhere the finding is, e.g. rows[3].openingBalance.
tieOutobjectyesTie-out as it would stand; nothing is written back.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
validationnullable objectnoComplete comparison receipt. Null when no tie-out comparison could be run.
validation.statusone of matched, difference, incompletewhen validation is setCurrent comparison verdict; incomplete asserts no agreement.
validation.tbBalancenullable numberwhen validation is setAbsolute net balance of the linked accounts.
validation.workpaperValuenullable numberwhen validation is setAbsolute workpaper amount compared.
validation.differencenullable numberwhen validation is setComparison difference projected to cents.
validation.tolerancenumberwhen validation is setStrict comparison tolerance, in dollars.
validation.linkedAccountsarray of objectswhen validation is setEvery linked account operand used by this evaluation.
validation.linkedAccounts[].accountCodenullable stringwhen validation is setLinked account code.
validation.linkedAccounts[].accountNamenullable stringwhen validation is setLinked account name.
validation.linkedAccounts[].balancenullable numberwhen validation is setBalance used in the comparison.
validation.errorsnullable map of stringwhen validation is setAmount fields the validator could not represent, with corrective actions.
validation.errors.{key}stringper keyAmount fields the validator could not represent, with corrective actions.
evaluatedAtstring (date-time)yesISO-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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
workpaperIdstring (uuid)yesUUID of the workpaper.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
tieOutobjectyesStored tie-out state.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
accountsarray of objectsyesLinked accounts with the balances the comparison uses.
accounts[].accountIdstringyesSaved trial-balance account id.
accounts[].accountCodenullable stringnoAccount code, when known.
accounts[].accountNamenullable stringnoAccount name, when known.
accounts[].balancenullable numbernoCurrent-year balance from the saved trial balance.
accounts[].accountTypenullable stringnoClassified account type.
trialBalanceTotalnullable numbernoNet 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 code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID of the workpaper.
cursorquerynullable string, 1 to 4096 charactersnoThe 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

FieldTypeAlways presentDescription
workpaperIdstring (uuid)yesUUID of the workpaper.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
versionsarray of objectsyesOne page of the recorded history, most recent change first, at most 200.
versions[].versioninteger, at least 1yesWorkpaper version this change produced.
versions[].operationone of created, updated, soft_deleted, restoredyesWhat the change did.
versions[].changedFieldsarray of stringsyesTop-level workpaper fields the change touched. Values are never published.
versions[].changeSetIdstring (uuid)yesGroups changes committed together.
versions[].editedAtstring (date-time)yesISO-8601 in UTC.
totalinteger, at least 0yesNumber of recorded changes to the workpaper, across every page.
hasMorebooleannoTrue when older changes follow this page; nextCursor is set whenever it is true.
nextCursornullable string, up to 4096 charactersnoPass 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 (code conflict). Read it again from the first page.
  • 422: One or more request parameters are invalid. The code result_not_representable means 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
templateIdstring, 1 to 100 charactersyesTemplate with a general-ledger builder. The server loads the engagement's own saved accounts and enabled GL; no ledger data is sent.
linkedAccountIdsarray of strings, 1 to 500 itemsyesSaved trial-balance account ids the generated workpaper covers.
customNamenullable string, up to 200 charactersno

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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the workpaper.
engagementIdstring (uuid)yesUUID of the engagement it belongs to.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
templateIdnullable stringnoConcept template the workpaper was built from.
namestringyesPractitioner-facing display name.
customNamenullable stringnoThe one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name).
kindone of row_grid, narrativeyesBody shape.
originstringyesHow the workpaper came to exist: blank, uploaded, generated, carryforward.
statusone of draft, completeyesPreparer status.
categorynullable stringnoPractitioner grouping label.
versioninteger, at least 1yesMonotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number.
isReviewedbooleanyesTrue once a practitioner marked it reviewed in Filemark. The API never sets it.
reviewedAtnullable string (date-time)noISO-8601 in UTC. Null while unreviewed.
parseStatusnullable stringnoOutcome of parsing the uploaded source file, when there is one.
detectedTypenullable stringnoLayout the parser detected in the source file.
sourceFileNamenullable string, up to 512 charactersnoFile name of the uploaded source, when there is one.
assumptionnullable stringnoThe preparer's stated assumption for the working paper.
notesnullable stringnoPreparer notes.
tieOutobjectyesAgreement with the linked trial-balance accounts.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
linkedAccountIdsarray of stringsyesSaved trial-balance account ids the workpaper is linked to.
columnsarray of objectsyesDeclared grid columns. Empty for a narrative workpaper.
columns[].keystringyesRow key this column occupies.
columns[].labelstringyesPractitioner-facing column heading.
columns[].typeone of text, number, date, select, cohortsyesDeclared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields.
columns[].optionsnullable array of stringsnoClosed option list for a 'select' column. Null for every other type.
columns[].sectionIdnullable stringnoTemplate section this column belongs to. Null for the main grid.
columns[].entryFieldsnullable array of objectsnoThe declared fields of each entry of a 'cohorts' column. Null for every other type.
columns[].entryFields[].keystringwhen entryFields is setEntry key this field occupies.
columns[].entryFields[].labelstringwhen entryFields is setPractitioner-facing field heading.
columns[].entryFields[].typeone of text, number, date, selectwhen entryFields is setDeclared value type. A written entry value must match it.
columns[].entryFields[].optionsnullable array of stringswhen entryFields is setClosed option list for a 'select' field. Null for every other type.
rowsarray of objectsyesOne page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper.
rows[].rowKeystringyesStable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one.
rows[].sectionIdnullable stringnoTemplate section the row sits in. Null for the main grid.
rows[].cellsobjectnoCell 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.*anynoMembers not listed here.
rows[].untypedCellsarray of stringsnoDeclared 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.
rowCountinteger, at least 0yesTotal rows stored, across every page.
rowsTruncatedbooleanyesTrue when more rows follow this page; nextCursor reads them.
narrativearray of objectsyesOne page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper.
narrative[].idstringyesStable block identifier.
narrative[].kindone of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divideryesBlock kind.
narrative[].contentnullable stringnoPlain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally.
narrative[].levelnullable integer, 1 to 3noHeading level, 1 to 3. Null for other kinds.
narrative[].variantnullable one of note, warning, risk, conclusionnoCallout variant. Null for other kinds.
narrative[].needsReviewbooleanyesTrue while a carried-forward block still awaits practitioner review.
narrativeBlockCountinteger, at least 0yesTotal narrative blocks stored, across every page.
narrativeTruncatedbooleanyesTrue when more narrative blocks follow this page; nextCursor reads them.
nextCursornullable string, up to 4096 charactersnoContinues 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.
completenessStatusnullable one of incomplete, completenoNarrative completeness checklist state. Null for a row-grid workpaper.
lifecycleobjectyesLive or archived (recoverable in Filemark).
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
createdAtstring (date-time)yesISO-8601 in UTC.
updatedAtstring (date-time)yesISO-8601 in UTC.
warningsarray of objectsnoNon-blocking conditions: an unknown template, a dropped cell, a partial seed.
warnings[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
priorEngagementIdstring (uuid)yesUUID of the prior-year engagement holding the workpaper to carry.
priorWorkpaperIdstring (uuid)yesUUID 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

FieldTypeAlways presentDescription
idstring (uuid)yesUUID of the workpaper.
engagementIdstring (uuid)yesUUID of the engagement it belongs to.
resourceVersionstring, 1 to 256 charactersyesOpaque 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.
templateIdnullable stringnoConcept template the workpaper was built from.
namestringyesPractitioner-facing display name.
customNamenullable stringnoThe one practitioner-set name. For a multi-instance concept it is also the instance identifier (vehicle plate, shareholder name).
kindone of row_grid, narrativeyesBody shape.
originstringyesHow the workpaper came to exist: blank, uploaded, generated, carryforward.
statusone of draft, completeyesPreparer status.
categorynullable stringnoPractitioner grouping label.
versioninteger, at least 1yesMonotonic write counter. resourceVersion encodes it; use resourceVersion for compare-and-swap rather than this number.
isReviewedbooleanyesTrue once a practitioner marked it reviewed in Filemark. The API never sets it.
reviewedAtnullable string (date-time)noISO-8601 in UTC. Null while unreviewed.
parseStatusnullable stringnoOutcome of parsing the uploaded source file, when there is one.
detectedTypenullable stringnoLayout the parser detected in the source file.
sourceFileNamenullable string, up to 512 charactersnoFile name of the uploaded source, when there is one.
assumptionnullable stringnoThe preparer's stated assumption for the working paper.
notesnullable stringnoPreparer notes.
tieOutobjectyesAgreement with the linked trial-balance accounts.
tieOut.modenullable one of sum, cellno'sum' totals the grid; 'cell' reads one validated spreadsheet cell. Null for a narrative workpaper, which does not tie out.
tieOut.statusnullable one of pending, matched, difference, incompletenoFilemark'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.expectednullable numbernoTrial-balance total the workpaper must agree with.
tieOut.actualnullable numbernoAmount the workpaper asserts.
tieOut.differencenullable numbernoexpected minus actual, to the cent.
linkedAccountIdsarray of stringsyesSaved trial-balance account ids the workpaper is linked to.
columnsarray of objectsyesDeclared grid columns. Empty for a narrative workpaper.
columns[].keystringyesRow key this column occupies.
columns[].labelstringyesPractitioner-facing column heading.
columns[].typeone of text, number, date, select, cohortsyesDeclared cell type. A written cell must match it. A 'cohorts' cell is a list of entries shaped by entryFields.
columns[].optionsnullable array of stringsnoClosed option list for a 'select' column. Null for every other type.
columns[].sectionIdnullable stringnoTemplate section this column belongs to. Null for the main grid.
columns[].entryFieldsnullable array of objectsnoThe declared fields of each entry of a 'cohorts' column. Null for every other type.
columns[].entryFields[].keystringwhen entryFields is setEntry key this field occupies.
columns[].entryFields[].labelstringwhen entryFields is setPractitioner-facing field heading.
columns[].entryFields[].typeone of text, number, date, selectwhen entryFields is setDeclared value type. A written entry value must match it.
columns[].entryFields[].optionsnullable array of stringswhen entryFields is setClosed option list for a 'select' field. Null for every other type.
rowsarray of objectsyesOne page of grid rows, at most 500, main grid first and then each section. Empty for a narrative workpaper.
rows[].rowKeystringyesStable server-owned identity of the row. Send it back in an update to change this row; omit it to append a new one.
rows[].sectionIdnullable stringnoTemplate section the row sits in. Null for the main grid.
rows[].cellsobjectnoCell 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.*anynoMembers not listed here.
rows[].untypedCellsarray of stringsnoDeclared 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.
rowCountinteger, at least 0yesTotal rows stored, across every page.
rowsTruncatedbooleanyesTrue when more rows follow this page; nextCursor reads them.
narrativearray of objectsyesOne page of narrative body blocks, at most 400, in document order. Empty for a row-grid workpaper.
narrative[].idstringyesStable block identifier.
narrative[].kindone of heading, paragraph, bulletList, numberedList, callout, table, exhibit, divideryesBlock kind.
narrative[].contentnullable stringnoPlain-text content in the narrative token grammar. Null for a divider and for a block whose content is carried structurally.
narrative[].levelnullable integer, 1 to 3noHeading level, 1 to 3. Null for other kinds.
narrative[].variantnullable one of note, warning, risk, conclusionnoCallout variant. Null for other kinds.
narrative[].needsReviewbooleanyesTrue while a carried-forward block still awaits practitioner review.
narrativeBlockCountinteger, at least 0yesTotal narrative blocks stored, across every page.
narrativeTruncatedbooleanyesTrue when more narrative blocks follow this page; nextCursor reads them.
nextCursornullable string, up to 4096 charactersnoContinues 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.
completenessStatusnullable one of incomplete, completenoNarrative completeness checklist state. Null for a row-grid workpaper.
lifecycleobjectyesLive or archived (recoverable in Filemark).
lifecycle.stateone of active, archivedyes'active' for a live record, 'archived' while it can still be restored.
lifecycle.archivedAtnullable string (date-time)noISO-8601 in UTC. When it was archived. Null while active.
lifecycle.restorableUntilnullable string (date-time)noISO-8601 in UTC. The moment the archived record is purged for good. Null while active.
createdAtstring (date-time)yesISO-8601 in UTC.
updatedAtstring (date-time)yesISO-8601 in UTC.
warningsarray of objectsnoNon-blocking conditions: an unknown template, a dropped cell, a partial seed.
warnings[].codestringwhen warnings is setStable warning identifier.
warnings[].messagestringwhen warnings is setReadable 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

ParameterInTypeRequiredDescription
engagement_idpathstring (uuid)yesUUID of the engagement the workpaper belongs to.
workpaper_idpathstring (uuid)yesUUID of the workpaper.
Idempotency-Keyheaderstring, 36 to 36 charactersyesUUID 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

FieldTypeRequiredDescription
templateIdstring, 1 to 100 charactersyesDraft template the governed narrative proposal is built from.
factsobjectnoCaller-supplied facts the draft may use. Everything else comes from the engagement's own stored workpaper.
facts.*anynoMembers 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

FieldTypeAlways presentDescription
workpaperIdstring (uuid)yesUUID of the workpaper the draft is for.
draftEventIdstring (uuid)yesIdentifier of the recorded draft event; the practitioner accepts it in Filemark.
templateIdstringyesDraft template used.
modelstringyesModel that produced the proposal.
proposedTextstringyesThe proposed narrative text. Nothing was written.
evidenceLinksarray of objectsnoWhere each claim in the proposal comes from.
evidenceLinks[].*anynoMembers not listed here.
redactionsarray of objectsnoWhat the governance boundary removed before the call.
redactions[].*anynoMembers not listed here.
warningsarray of objectsnoNon-blocking conditions the drafter reported.
warnings[].*anynoMembers not listed here.
acceptedboolean, always falseyesAlways 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.
Filemark | REST reference