Factuarea APIDevelopers

Scopes & irreversibility

How to read the required scope and irreversibility of each endpoint from the OpenAPI spec — the x-required-scope and x-irreversible extensions — and the catalog of irreversible operations.

Every public endpoint declares two pieces of safety metadata in the OpenAPI spec: the exact scope it enforces and whether the operation can be undone. Clients (the CLI, agents, your own tooling) read them to fail fast — block a call when the key lacks the scope, confirm before an irreversible action — instead of discovering the problem from a 403 or an unrecoverable mutation.

Reading it from the spec

Each operation in the OpenAPI spec carries two custom extensions:

{
  "operationId": "public-api.v1.invoices.delete",
  "x-required-scope": "invoices:delete",
  "x-irreversible": true
}
  • x-required-scope — the single resource:action scope the API key must hold to call the operation. Present on every operation except GET /v1/me, which needs no scope: any valid credential can introspect itself.
  • x-irreversible — true only on operations that cannot be undone. Absent (treated as false) on everything else.

These are x-* vendor extensions, so a generic OpenAPI viewer may not render them — but any client that parses the spec (like the CLI) reads them directly. Generate a client from the spec and you inherit both.

Scopes

Scopes are resource:action (e.g. invoices:read, contacts:delete) — the same closed catalog the REST API and the API keys use. A request whose key lacks the scope returns 403 with code: insufficient_scope. Request only the scopes your integration needs.

One operation enforces a second scope on top of x-required-scope when a field widens what it reads: POST /v1/companies/{company}/purchase-invoices with source_purchase_invoice_id also needs purchase_invoices:read, because it copies the lines of another document. See Measured purchase lines.

Time-tracking operations carry their own scopes — employees:*, time_entries:*, work_schedules:*, absences:*, presence:read, holidays:read and payroll_exports:read — all gated behind the control_horario module. See Time tracking.

Automations do the same with automations:read, automations:write, automations:delete and automation_runs:read, gated behind the automations module. See Automations.

The account model adds its own scopes. account:read, account:write and account:owner cover the account itself (its keys, its usage and its ownership), members:*, invitations:* and nif_transfers:* its team and its tax ID transfers (they need the user_management module), and companies:* its tax ID portfolio. See Authentication › Account administration.

Two scopes are privileged: accounts:read and accounts:write. They let an account provision, administer and charge managed accounts for its clients, and they work differently from every other scope: the super-scope * does not cover them, Factuarea grants the privilege account by account and checks it on every request (403 privileged_scope_required when it fails), and OAuth consent never grants them, so use an API key. See Privileged scopes and Managed accounts.

Transport control documents (DeCA) use transport_documents:read, transport_documents:write (drafts and email delivery), transport_documents:delete (drafts only) and transport_documents:transition (issue, modify, close, reopen, cancel and supersede), gated behind the transport_documents module, which in turn requires delivery_notes. The PDF of each version enforces pdfs:read; the proof of issuance (GET /v1/companies/{company}/transport-documents/{transport_document}/issuance-proof) is not a PDF of the document but the export of its issuance evidence, so it enforces transport_documents:read. See Transport control documents.

Projects and tasks use projects:read|write|delete, tasks:read|write|delete, users:read and notifications:read|write. projects:* and tasks:* are gated behind the tasks module when the credential is issued: a key with those scopes for a company without the module is rejected with scope_not_allowed_by_plan, and without the scope each request gets insufficient_scope. users:read and notifications:* are cross-cutting and belong to no module. The two delete scopes are API-key-only: an OAuth consent never grants them. Invoicing the hours of a project (POST /v1/companies/{company}/projects/{project}/time-invoices) requires invoices:write, not a project scope, because it creates an invoice. See Projects and tasks.

The full catalog — every scope, what it grants, and the sensitive ones — is in Scopes & permissions. A scope is not always derivable from the resource name: some endpoints enforce a different scope than you'd guess (PDF downloads enforce pdfs:read, payment methods enforce invoices:read, state transitions enforce a :transition scope, VeriFactu actions enforce verifactu:*). Always read x-required-scope rather than inferring it.

An API key may hold the super-scope *, which satisfies any x-required-scope except the privileged accounts:read and accounts:write. * does not cover accounts:*: a key that holds only * answers 403 insufficient_scope on those operations, however broad it looks. List accounts:* explicitly, on an account that holds the privilege. See the super-scope.

Irreversible operations

An operation marked x-irreversible: true has no undo: it deletes data, emits a fiscal record, transitions a document to a terminal state, rotates or revokes a secret, or changes who owns, reaches or pays for an account. The CLI asks for a typed confirmation before running one; your own tooling should guard them the same way.

These are the categories that carry x-irreversible: true:

CategoryExamplesScope
Deletes (single)contacts.delete, invoices.delete, products.delete, purchase_invoices.delete, webhook_endpoints.delete…<resource>:delete
Catalog subresource deletesproducts.presentations.delete, products.variants.delete, products.supplier-offers.delete, price-lists.delete, price-lists.items.delete, price-lists.items.purge_retiredproducts:write / price_lists:write
Bulk deletesinvoices.bulk_delete, contacts.bulk_delete, products.bulk_delete…<resource>:delete
Task and project deletestasks.delete, tasks.bulk_delete, tasks.comments.delete, tasks.attachments.delete, tasks.time_entries.delete, task_labels.delete, projects.delete, projects.columns.delete, projects.custom_fields.delete — the nine irreversible operations of the moduletasks:delete / projects:delete
Fiscal emission / numberinginvoices.send, invoices.mark_sent, invoices.assign_real_number, invoices.corrective, invoices.substitute_simplifiedinvoices:send / invoices:write
Void / annulinvoices.void, invoices.annulinvoices:void
Terminal conversionsquotes.convert, proformas.convert, delivery_notes.convert<resource>:transition
Cancel / signdelivery_notes.cancel, delivery_notes.sign, recurring_invoices.cancel, recurring_invoices.generate<resource>:transition / :write
VeriFactu (AEAT)invoices.verifactu_create, verifactu.records.subsanar, verifactu.settings.update, verifactu.certificates.revokeverifactu:write
Secret / certificatewebhook_endpoints.rotate_secret, verifactu.certificates.revokewebhooks:write / verifactu:write
Tax ID transferaccount.nif_transfers.create, account.nif_transfers.accept, account.nif_transfers.revokenif_transfers:write
Account ownership and teamaccount.owner.transfer, account.members.deleteaccount:owner / members:write
Account keys and webhooksaccount.api_keys.revoke, account.api_keys.rotate_secret, account.webhooks.delete, account.webhooks.rotate_secretaccount:write / webhooks:delete / webhooks:write
Managed accounts (privileged)managed_accounts.create, managed_accounts.claim_tokens.create, managed_accounts.management.end, managed_accounts.plan.update, managed_accounts.imports.createaccounts:write
GDPR forgetdelivery_notes.signature_audits.forgetdelivery_notes:gdpr_forget
Transport control documentstransport_documents.issue, transport_documents.modify, transport_documents.cancel, transport_documents.supersede, transport_documents.sendtransport_documents:transition / transport_documents:write
FacturaE (B2G) submitinvoices.face_submissions.submit, face_submissions.cancelfacturae:write
Time-record sealmonthly_time_record_closes.sealtime_entries:write
Replay (re-execution)automations.runs.replay, automations.runs.steps.replay, integrations.events.replayautomations:write / integration_events:write

Removing a relation, an external link, an entity link or a label from a task is not irreversible — each can be created again — and needs only tasks:write.

This list is the human-readable summary. The machine-readable source of truth is x-irreversible in the spec — a client that reads it stays correct even as the catalog grows.

Putting it together

A safe client does two checks before a mutation:

  1. Scope-check — does the key hold x-required-scope? If not, stop locally (no wasted round trip). The CLI exits 4; see scope-check. An operation without the extension (GET /v1/me) needs no check, and for accounts:* the scope is necessary but not sufficient: the account must also hold the privilege, which GET /v1/me reports in account.is_provider and account.privileged_scopes.
  2. Irreversibility confirm — is x-irreversible true? If so, confirm before calling. The CLI requires --confirm <id>; see irreversible operations.

The official SDKs and the CLI do both for you. If you generate your own client from the spec, wire these two checks yourself from the extensions.

Store permissions

Store management uses stores:read and stores:write; provider-specific scopes are woocommerce_store:read, woocommerce_store:write, shopify_store:read and shopify_store:write. Connection-test operations require the provider write scope. These scopes are assigned to API keys and are outside OAuth consent. See ecommerce stores for the operation matrix.

On this page

Need a hand?Contact support