Asesia API v2
A versioned REST + JSON API for accounting firms and companies that connect their own tools: ERPs, online shops, CRMs and in-house software. Everything the API does goes through the same services as the app — the same VAT rules, quotas, VERI*FACTU / TicketBAI / SII issuing and document sanitisation.
Machine-readable specification (OpenAPI 3.1)Authentication
- Create credentials in Settings → API v2 (firm administrators, or the company owner when the firm allows it). Secrets are shown once and stored hashed; rotate them with a 24-hour grace period, or invalidate them at once if you suspect a leak. Every token issued, every write and (grouped) every read is audited.
- OAuth 2.0 client credentials: exchange client_id + client_secret for a 1-hour access token. Ask for a subset of the client's scopes with
scope. - Personal API keys (
asesia_pk_…) are sent directly as a Bearer token, with the scopes fixed at creation. - A firm credential reaches the firm's companies (same market only) and only the sections each company shares with its firm. A company credential reaches its own company.
curl -u "$CLIENT_ID:$CLIENT_SECRET" \ -d grant_type=client_credentials -d "scope=invoices:read invoices:write" \ https://<host>/api/v2/oauth/token curl -H "Authorization: Bearer $ACCESS_TOKEN" \ https://<host>/api/v2/companies
companies:read · documents:read · documents:write · captures:read · expenses:read · invoices:read · invoices:write · invoices:issue · contacts:read · contacts:write · taxes:read · bank:read · webhooks:manage
Conventions
- Every resource lives under
/companies/{companyId}. Ids of companies or objects you cannot reach answer404, exactly like ids that do not exist. - Pagination:
limit(1–200, default 50) and an opaquecursor. Lists returndata,total,truncatedandnextCursor. - Idempotency: send
Idempotency-Keyon POST creations. A retry returns the same response for 24 h (Idempotent-Replayed: true); the same key with a different body is rejected with422. - Errors:
{ "error": { "code", "params"?, "detail"?, "requestId" } }. Program againstcode(a stable catalogue shared with the app);detailis a hint and may be localized. Every response carriesX-Request-Id. - Rate limits are per credential (
X-RateLimit-Limit,X-RateLimit-Remaining,Retry-Afteron429). - Reads carry a weak
ETag; send it back asIf-None-Matchto get304 Not Modified. - Amounts are in EUR; invoices also keep their original currency.
Endpoints
Authentication
post/api/v2/oauth/token
publicGet an access token (OAuth 2.0 client credentials)
Authenticate the client with HTTP Basic (client_id:client_secret, preferred) or client_secret_post. Tokens last 1 hour. Errors follow RFC 6749 §5.2.
Companies
get/api/v2/companies
scope companies:readList the companies this credential reaches
get/api/v2/companies/{companyId}
scope companies:readGet a company
Documents
get/api/v2/companies/{companyId}/documents
scope documents:readList uploaded documents and their processing status
post/api/v2/companies/{companyId}/documents
scope documents:writeUpload a document
multipart/form-data, field document. Same pipeline as the app: content-type and magic bytes, content disarm & reconstruction, antivirus, storage and OCR quotas. Answers at once; OCR runs in the background (poll GET …/documents/{id} or subscribe to document.processed).
Supports Idempotency-Key
get/api/v2/companies/{companyId}/documents/{id}
scope documents:readGet the status of a document
Captures
get/api/v2/companies/{companyId}/captures
scope captures:readList captures with extracted data and review status
get/api/v2/companies/{companyId}/captures/{id}
scope captures:readGet a capture
Expenses
get/api/v2/companies/{companyId}/expenses
scope expenses:readList expenses
get/api/v2/companies/{companyId}/expenses/{id}
scope expenses:readGet an expense
Invoices
get/api/v2/companies/{companyId}/invoices
scope invoices:readList issued invoices
post/api/v2/companies/{companyId}/invoices
scope invoices:writeCreate a draft invoice
Same validation as the app (VAT rates of the company's country, contact ownership, currency, withholding, exemption grounds, plan quota).
Supports Idempotency-Key
get/api/v2/companies/{companyId}/invoices/{id}
scope invoices:readGet an invoice with its lines and VERI*FACTU status
post/api/v2/companies/{companyId}/invoices/{id}/issue
scope invoices:issueIssue a draft (irreversible)
Exactly the same issuing service as the "Issue" button: series number and, where the company has it, the VERI*FACTU record in the same transaction; then TicketBAI / SII as configured. If anything fails the invoice stays a draft and no number is used. Follow the tax-authority verdict with the invoice.verifactu_status webhook.
Supports Idempotency-Key
Contacts
get/api/v2/companies/{companyId}/contacts
scope contacts:readList contacts (customers and suppliers)
post/api/v2/companies/{companyId}/contacts
scope contacts:writeCreate a contact
Supports Idempotency-Key
get/api/v2/companies/{companyId}/contacts/{id}
scope contacts:readGet a contact
patch/api/v2/companies/{companyId}/contacts/{id}
scope contacts:writeUpdate a contact
Taxes
get/api/v2/companies/{companyId}/tax-calendar
scope taxes:readUpcoming tax deadlines of the company
get/api/v2/companies/{companyId}/tax-models
scope taxes:readStatus of tax returns by period (pending / prepared / filed)
get/api/v2/companies/{companyId}/tax-models/{model}
scope taxes:readCalculated result of an AEAT model for a period (ES)
Read-only calculation from the company's data; does not file anything. Models: 303, 130, 111, 115, 180, 190, 390, 123, 193, 202, 200.
Bank
get/api/v2/companies/{companyId}/bank-movements
scope bank:readList bank movements
Webhooks
get/api/v2/webhooks
scope webhooks:manageList webhook endpoints of the credential's owner
post/api/v2/webhooks
scope webhooks:manageRegister a webhook endpoint
https only; the host must resolve to public IPv4 addresses (checked again, pinned, on every delivery). The signing secret is returned ONCE.
Supports Idempotency-Key
get/api/v2/webhooks/{id}
scope webhooks:manageGet a webhook endpoint
patch/api/v2/webhooks/{id}
scope webhooks:manageUpdate, pause or resume a webhook endpoint
delete/api/v2/webhooks/{id}
scope webhooks:manageDelete a webhook endpoint
get/api/v2/webhooks/{id}/deliveries
scope webhooks:manageDelivery log
post/api/v2/webhooks/{id}/deliveries/{deliveryId}/redeliver
scope webhooks:manageRedeliver an event (same event id)
Webhooks
- Register https endpoints (
POST /webhooksor Settings). The host must resolve to public IPv4 addresses; this is checked when you save it and again, pinned to the connected address, on every delivery (no private, loopback or link-local destinations, no redirects). - Each delivery is signed:
X-Asesia-Signature: v1=<hex HMAC-SHA256 of timestamp + "." + raw body>with your endpoint secret, plusX-Asesia-Timestamp(seconds). Reject anything older than 5 minutes. - Deduplicate on
X-Asesia-Event-Id(theidin the body): it is the same across retries and manual redeliveries. - Failed deliveries are retried after 1, 5, 30, 120 and 360 minutes (408, 429 and 5xx; other 4xx are not retried). 15 consecutive failures pause the endpoint. The delivery log and manual redelivery are in the API and in Settings.
- Firm endpoints receive events only for what each company shares with its firm.
import crypto from 'node:crypto'
function verify(rawBody, secret, signature, timestamp) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false
const mac = crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const a = Buffer.from(`v1=${mac}`), b = Buffer.from(signature)
return a.length === b.length && crypto.timingSafeEqual(a, b)
}Events
- document.received
- A document entered the company (upload, email, WhatsApp, API).
- document.processed
- OCR finished: extracted data is available.
- capture.approved
- A human (or an approval rule) approved a captured document; it is now an accounting fact.
- capture.rejected
- A captured document was rejected.
- expense.created
- An expense was created.
- invoice.created
- A sales invoice (draft or issued) was created.
- invoice.issued
- A draft was issued: it has its series number (and, where enabled, its tax-authority record).
- invoice.paid
- An invoice was marked as paid.
- invoice.verifactu_status
- The VERI*FACTU record of an invoice changed state (accepted, accepted with errors, rejected).
- model.ready
- A tax return was prepared or filed for a period (status PREPARADO or PRESENTADO).
- bank.reconciled
- A bank movement was reconciled.
Migrating from v1
/api/v1keeps working unchanged. It is deprecated: new features land only in v2.- v1 keys (
asesia_sk_…) are not accepted by v2 and v2 credentials are not accepted by v1. Create a v2 credential with the equivalent scopes, switch the base URL to/api/v2, and move the company from theX-Workspace-Idheader into the path (/companies/{companyId}/…). - Field names change from the internal names to the v2 contract (e.g.
nif→taxId,next_cursor→nextCursor), and errors move into theerrorobject. - v1 webhooks keep firing; re-register your endpoints as v2 webhooks to get the new events (
invoice.issued,invoice.verifactu_status,model.ready…), pinned SSRF protection and the delivery log. Then delete the v1 endpoint to avoid duplicates. - When every integration has moved, revoke the v1 keys.