Workflow tools
The curated MCP tool set for the compliance loop: what each tool does, which are operations and which compose them, and the loop they make.
The workflows tool set is for agents that work better with a few larger tools. With it alone an agent can run the whole compliance loop: see what needs doing, take the most urgent task, read its packet or package and playbook, do the work in the authority's portal, report back with evidence, and follow the approval.
Most workflow tools are registry operations, so the REST API has them too, with the same arguments. Four are MCP-only compositions: they call a few registry operations in a row with your credentials, each with its own permission, risk tier, approvals, idempotency and audit, and decide nothing an operation would decide.
| Tool | What it does | Kind |
|---|---|---|
entity_compliance_overview | Compliance overview | operation compliance.overview, see Compliance tools |
tasks_next | Claim the next task | operation tasks.claim_next, see Tasks tools |
registration_get_packet | Get a registration packet | operation registrations.packet, see Registrations tools |
filing_prepare_and_get_package | Prepare a return and get its package | MCP composition of filing_periods.get, filing_periods.lock, filing_periods.prepare, filing_periods.package (below) |
task_complete | Complete or submit a task | MCP composition of tasks.get, tasks.submit, tasks.complete (below) |
approval_status | Approval status | MCP composition of approvals.get (below) |
explain_tax_decision | Explain a tax decision | operation tax.quotes.explain, see Tax tools |
subscription_change_preview | Preview a plan change | MCP composition of subscriptions.preview_change (below) |
tasks_documents_upload | Upload a task document | operation tasks.documents.upload, see Tasks tools |
tasks_fail | Fail a compliance task | operation tasks.fail, see Tasks tools |
tasks_heartbeat | Extend a task's lease | operation tasks.heartbeat, see Tasks tools |
tasks_release | Release a compliance task | operation tasks.release, see Tasks tools |
The loop
entity_compliance_overview: registrations that need attention, the next deadlines, unfinished tasks and returns due.tasks_next: claim the most urgent task you can finish (a 15-minute lease; keep it withtasks_heartbeat). Its result links the task's packet or package and the regime's playbook as resources.- For a registration,
registration_get_packet; for a return,filing_prepare_and_get_package(locks and prepares the period, which may need approval, then returns the package and links the report files). - Do the work in the authority's portal, under an authorization valid for the regime. Upload the portal's confirmation with
tasks_documents_upload. task_completewith the reference, the receipt and the authorization: dry-run it first. Submissions are compliance tier and usually answerapproval_required.approval_statusuntil it isexecuted; the registration or filing period then changes, with your evidence attached.- If you can't finish:
tasks_release, ortasks_failwith a reason and a retry date.
explain_tax_decision explains a tax quote in plain words; subscription_change_preview shows what a plan change would cost before anyone makes it.
approval_status
Approval status. Workflow tool (MCP only) · calls approvals.get · tool set workflows
The status of an approval request you got from an approval_required answer (approvals.get): pending,
approved (running), executed (done exactly as you sent it), rejected, expired or stale (approved,
but it could no longer run). finished is true once nothing more will happen; remediation says what to do
next. The full request (its input and dry-run result) is in approvals_get.
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
id | ID (apr_…) | yes | The approval request (apr_…). |
- Read-only: changes nothing.
Example
Approved and done
Arguments:
{
"id": "apr_01m4ttwd3veh9apbewxheq5xa1"
}
Result output:
{
"object": "approval_status",
"id": "apr_01m4ttwd3veh9apbewxheq5xa1",
"status": "executed",
"finished": true,
"operationId": "tasks.submit",
"summary": "Submit the task File the One-Stop Shop, Union scheme 2026-Q3 return",
"approvals": 1,
"requiredApprovals": 1,
"rejectionReason": null,
"failure": null,
"resultRef": "task_01m4ttwd3veh9apbewxheq5xa9",
"approveUrl": "https://ownrecord.io/console/acme/approvals/apr_01m4ttwd3veh9apbewxheq5xa1",
"expiresAt": "2026-11-11T09:10:00.000Z",
"executedAt": "2026-11-08T10:02:00.000Z",
"remediation": "Approved and done: the call ran exactly as you sent it. resultRef names what it created or changed."
}
filing_prepare_and_get_package
Prepare a return and get its package. Workflow tool (MCP only) · calls filing_periods.get, filing_periods.lock, filing_periods.prepare, filing_periods.package · tool set workflows
Takes a filing period as far as it can go towards filing, then returns its filing package: it locks an
open period (filing_periods.lock, compliance tier: under your approval policy this answers
approval_required), prepares a locked one (filing_periods.prepare: the report is generated from the
ledger and invoices and must reconcile with the ledger), and once the period is prepared (or later) reads
filing_periods.package. Steps already done are skipped, so call it again after an approval with the same
idempotency_key and it continues. steps says what each step did.
The result links the package and the report files as resources (package.json, report.csv,
report.xlsx) and the regime's filing playbook: read them there rather than asking for more tool output. A
dry_run stops after the first step that would change the period (the next one needs it done). When the
period is prepared, its filing.prepare task finishes by itself.
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
id | ID (fp_…) | yes | The filing period (fp_…). |
entity_id | ID (ent_…) | no | The legal entity to act on (ent_…), when your credentials cover more than one. |
idempotency_key | string | no | A unique string for this call. Retrying with the same key and arguments never acts twice and continues where the last call stopped. When left out, the server generates one and returns it in the result's idempotency_key. (1–200 characters) |
dry_run | boolean | no | true: check permissions and rules and compute the first change's result, then roll it back. Nothing is saved. (default false) |
- Legal entity: pass
entity_idwith an organization credential. - Idempotency: the result returns the
idempotency_keyused. A composition with several changes derives one key per step from it (<key>:lock,<key>:prepare), so a retry with the same key replays the steps already done and continues where it stopped. - Dry run: supported, for the first step that would change something.
Example
A locked period: prepared, then its package
Arguments:
{
"id": "fp_01m4ttwd3veh9apbewxheq5xa7"
}
Result output:
{
"object": "filing_preparation",
"steps": [
{
"operation": "filing_periods.lock",
"outcome": "not_needed"
},
{
"operation": "filing_periods.prepare",
"outcome": "done"
}
],
"filingPeriod": {
"id": "fp_01m4ttwd3veh9apbewxheq5xa7",
"status": "prepared"
},
"package": {
"object": "filing_package",
"filingPeriodId": "fp_01m4ttwd3veh9apbewxheq5xa7"
}
}
subscription_change_preview
Preview a plan change. Workflow tool (MCP only) · calls subscriptions.preview_change · tool set workflows
Shows exactly what a plan or quantity change of a subscription would do, without changing anything
(subscriptions.preview_change): upgrade or downgrade, when it applies, the invoice it would issue now
(prorations, discounts, tax at today's rates, credit balance applied, amount due) or the scheduled change, and
the monthly recurring amount before and after. Send the same arguments to subscriptions_update to make the
change (financial tier: dry-run it, and expect an approval above your limits).
Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
id | ID (sub_…) | yes | – |
items | array of object | no | Item changes: { id, priceId } swaps a price, { id, quantity } changes seats, { priceId, quantity } adds an item, { id, deleted: true } removes one. Items not named stay. (1–40 items) |
prorationBehavior | one of create_prorations, always_invoice, none | no | create_prorations (credit unused time and charge the remaining time on the next invoice; a net credit goes to the customer credit balance), always_invoice (invoice the proration now) or none. |
effective | one of now, period_end | no | now, or at the end of the current period (period_end, stored as pendingUpdate). Default: upgrades and interval changes now with always_invoice, downgrades at the period end. |
paymentBehavior | one of allow_incomplete, pending_if_incomplete | no | For a change invoiced now: allow_incomplete (default; the change applies and a failed payment goes to dunning) or pending_if_incomplete (the old items stay until the invoice is paid). |
couponId | ID (cpn_…) | no | Apply a coupon to the subscription. |
promotionCode | string | no | A promotion code as the customer typed it (case doesn't matter). (1–50 characters) |
entity_id | ID (ent_…) | no | The legal entity to act on (ent_…), when your credentials cover more than one. |
brand_id | ID (brd_…) | no | The brand to act on (brd_…), when your credentials cover more than one. |
- Brand: pass
brand_idwith an organization or entity credential. - Read-only: changes nothing.
Example
What upgrading to Pro now costs
Arguments:
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf47z5",
"items": [
{
"id": "si_01m4tx5nj8emwbyf7y9vcf47za",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf47y3"
}
]
}
Result output:
{
"object": "subscription_change_preview",
"subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf47z5",
"direction": "upgrade",
"intervalChange": false,
"effective": "now",
"prorationBehavior": "always_invoice",
"settlement": "invoice_now",
"invoice": {
"id": "inv_01m4tx5nj8emwbyf7y9vcf47yk",
"object": "invoice",
"livemode": false,
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"number": null,
"numbering": null,
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf47ym",
"subscriptionId": null,
"collectionMethod": "send_invoice",
"kind": "invoice",
"status": "open",
"language": "en",
"issuedAt": "2026-10-20T09:00:00.000Z",
"issueDate": "2026-10-20",
"taxPoint": "2026-10-20T09:00:00.000Z",
"taxPointDate": "2026-10-20",
"daysUntilDue": 14,
"dueDate": "2026-11-03",
"seller": {
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
"legalName": "Acme Digital d.o.o.",
"tradingName": null,
"address": {
"line1": "Ilica 1",
"line2": null,
"city": "Zagreb",
"postalCode": "10000",
"region": null,
"country": "HR"
},
"companyRegistryId": "080000001",
"taxIds": [
{
"type": "eu_vat",
"value": "HR12345678901",
"regimeId": null
}
],
"brand": {
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"name": "Genno",
"primaryColor": "#0E6B5C",
"accentColor": null,
"supportEmail": "help@genno.example",
"supportUrl": null
},
"footerText": null
},
"buyer": {
"type": "b2b",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf47ym",
"name": "Alpen Apps GmbH",
"email": "billing@alpen.example",
"address": null,
"country": "AT",
"taxId": {
"type": "eu_vat",
"value": "ATU00000000",
"country": "AT"
}
},
"currency": "EUR",
"lines": [
{
"id": "1",
"description": "Implementation workshop (days)",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf47yr",
"priceId": null,
"quantity": 2,
"taxCategoryId": "consulting_remote",
"taxBehavior": "exclusive",
"unitAmount": {
"amount": "600000000",
"currency": "EUR"
},
"net": {
"amount": "1200000000",
"currency": "EUR"
},
"tax": {
"ratePpm": "0",
"amount": {
"amount": "0",
"currency": "EUR"
},
"jurisdictionId": "AT",
"regimeId": "EU_OSS_UNION",
"decision": "reverse_charge",
"rateKind": null,
"rateId": null,
"pendingRegistration": false
},
"gross": {
"amount": "1200000000",
"currency": "EUR"
},
"kind": "invoice_item",
"period": null,
"subscriptionItemId": null,
"invoiceItemId": "ii_01m4tx5nj8emwbyf7y9vcf47ys",
"discount": null,
"meteredQuantity": null
}
],
"totals": {
"net": {
"amount": "1200000000",
"currency": "EUR"
},
"tax": {
"amount": "0",
"currency": "EUR"
},
"gross": {
"amount": "1200000000",
"currency": "EUR"
},
"taxByRate": [
{
"ratePpm": "0",
"jurisdictionId": "AT",
"regimeId": "EU_OSS_UNION",
"decision": "reverse_charge",
"net": {
"amount": "1200000000",
"currency": "EUR"
},
"tax": {
"amount": "0",
"currency": "EUR"
}
}
]
},
"taxCurrencyTotals": null,
"creditApplied": {
"amount": "200000000",
"currency": "EUR"
},
"amountDue": {
"amount": "1000000000",
"currency": "EUR"
},
"notes": [
{
"code": "reverse_charge",
"text": "Prijenos porezne obveze (reverse charge) – čl. 17. st. 1. Zakona o PDV-u"
}
],
"fieldCheck": {
"invoicingRuleIds": [],
"missing": ["buyer_address"],
"unchecked": []
},
"pendingRegistration": false,
"paymentIds": [],
"creditNoteIds": [],
"ledgerTxnIds": ["txn_01m4tx5nj8emwbyf7y9vcf47yn"],
"quoteId": "tq_01m4tx5nj8emwbyf7y9vcf47xr",
"quoteIds": ["tq_01m4tx5nj8emwbyf7y9vcf47xr"],
"pdf": {
"status": "pending",
"fileId": null,
"sha256": null,
"size": null,
"templateVersion": null,
"renderedAt": null,
"url": "/api/v1/invoices/inv_01m4tx5nj8emwbyf7y9vcf47yk/pdf"
},
"finalizedAt": "2026-10-20T09:00:00.000Z",
"paidAt": null,
"payment": null,
"voidedAt": null,
"markedUncollectibleAt": null,
"emailedAt": null,
"sentAt": null,
"draft": null,
"metadata": {
"contract": "ENT-2026-07"
},
"version": 2,
"createdAt": "2026-10-20T08:30:00.000Z",
"updatedAt": "2026-10-20T09:00:00.000Z"
},
"pendingItems": [],
"credit": null,
"subscription": {
"id": "sub_01m4tx5nj8emwbyf7y9vcf47z5",
"object": "subscription",
"livemode": false,
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47zm",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf47z4",
"status": "active",
"items": [
{
"id": "si_01m4tx5nj8emwbyf7y9vcf47za",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf47y3",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
"quantity": 3,
"createdAt": "2026-10-13T12:00:00.000Z"
}
],
"currency": "EUR",
"mrr": {
"amount": "147000000",
"currency": "EUR"
},
"collectionMethod": "charge_automatically",
"daysUntilDue": null,
"defaultPaymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf47zd",
"billingCycleAnchor": "2026-10-13T12:00:00.000Z",
"currentPeriodStart": "2026-10-13T12:00:00.000Z",
"currentPeriodEnd": "2026-11-13T12:00:00.000Z",
"trialStart": null,
"trialEnd": null,
"trialEndBehavior": "convert",
"cancelAtPeriodEnd": false,
"cancelAt": null,
"canceledAt": null,
"endedAt": null,
"cancellation": null,
"pause": null,
"discountIds": [],
"pendingUpdate": null,
"latestInvoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47zn",
"incompleteExpiresAt": null,
"testClockId": null,
"dunning": null,
"usageThreshold": null,
"entitlementGraceUntil": null,
"metadata": {
"crm_account": "A-1042"
},
"version": 2,
"createdAt": "2026-10-13T12:00:00.000Z",
"updatedAt": "2026-10-13T12:00:00.000Z"
},
"mrrBefore": {
"amount": "87000000",
"currency": "EUR"
},
"mrrAfter": {
"amount": "147000000",
"currency": "EUR"
},
"mrrDelta": {
"amount": "60000000",
"currency": "EUR"
}
}
task_complete
Complete or submit a task. Workflow tool (MCP only) · calls tasks.get, tasks.submit, tasks.complete · tool set workflows
Finishes a compliance task the way its type needs (its completeWith): tasks.submit for the types whose
completion changes a registration or a filing period (filing.submit, filing.pay, registration.submit,
registration.confirm, deregistration.submit; compliance tier, so it may answer approval_required), else
tasks.complete (write tier). It reads the task first (tasks.get), then calls that one operation with your
arguments, your idempotency_key and dry_run.
Submissions take the authority's reference (and effectiveFrom for registration.confirm, paidOn for
filing.pay), the authorizationId you acted under (*.submit tasks), and outputs (receipt files uploaded
with tasks_documents_upload, more references, notes). Work tasks take only outputs: sending a reference or
an authorization for one is refused. Dry-run first. When the answer is approval_required, nothing changed
yet: follow it with approval_status.
Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: true · openWorldHint: false
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
id | ID (task_…) | yes | The task (task_…). |
reference | string | no | Submissions: the authority's reference (the return's, the application's, the payment's). (1–200 characters) |
effectiveFrom | string | no | registration.confirm: the first day the registration is effective. |
paidOn | string | no | filing.pay: the day the tax was paid. |
authorizationId | ID (atz_…) | no | *.submit tasks: the authorization the work was done under. |
outputs | object | no | Evidence: more references by name, when it was submitted, receipt files, notes. |
expectedVersion | integer | no | Refuse if the task changed since this version. (1–9007199254740991) |
entity_id | ID (ent_…) | no | The legal entity to act on (ent_…), when your credentials cover more than one. |
idempotency_key | string | no | A unique string for this call. Retrying with the same key and arguments never acts twice and continues where the last call stopped. When left out, the server generates one and returns it in the result's idempotency_key. (1–200 characters) |
dry_run | boolean | no | true: check permissions and rules and compute the first change's result, then roll it back. Nothing is saved. (default false) |
- Legal entity: pass
entity_idwith an organization credential. - Idempotency: the result returns the
idempotency_keyused. A composition with several changes derives one key per step from it (<key>:lock,<key>:prepare), so a retry with the same key replays the steps already done and continues where it stopped. - Dry run: supported, for the first step that would change something.
Example
Submit the filed Q3 return, which needs approval
Arguments:
{
"id": "task_01m4ttwd3veh9apbewxheq5xa9",
"reference": "HR/HR12345678901/Q3.2026",
"authorizationId": "atz_01m4ttwd3veh9apbewxheq5xa3",
"outputs": {
"files": ["file_01m4ttwd3veh9apbewxheq5xah"]
}
}
Result output:
{
"object": "task_completion",
"operation": "tasks.submit",
"task": {
"id": "task_01m4ttwd3veh9apbewxheq5xa9",
"type": "filing.submit",
"status": "done"
}
}