Transport control document (DeCA)
Issue the Spanish electronic administrative control document for road freight from the API — scope and exemptions, the issue → driver copy → modify → close flow, the permanent public URL for the inspection, delivery notes and gross weight.
From 5 October 2026 the administrative control document of Spanish public road freight transport must be digital. The DeCA (documento de control) identifies the parties, the shipments, the goods, the vehicle and the dates of a transport, and travels with the driver as a PDF whose QR code opens a permanent public URL. Both the contractual shipper and the effective carrier are obliged to formalise it (Orden FOM/2861/2012, art. 4).
The transport_documents resource covers the whole life of that document: drafts
(also pre-filled from your delivery notes), issuing, the copy for the driver,
modifications during the service, closing, cancelling and keeping the files for the
inspection. A DeCA is not an invoice nor a VeriFactu record, and it never changes the
numbering, stock or invoicing of your delivery notes.
All endpoints below live under https://api.factuarea.com/v1 and use the same
error envelope, cursor pagination,
idempotency and
scopes as the rest of the API.
When you need one
Required in public road freight transport, national or cabotage, with origin and
destination in Spain. International transport is documented with the CMR consignment
note: a shipment with origin or destination outside Spain cannot be issued
(transport_outside_spain).
Not required, in the wording of the Orden:
- private complementary transport, with the company's own vehicles and drivers — the documents on board must still prove the goods, the vehicle and the driver belong to the company;
- parcel delivery of a few packages;
- removals;
- transport of damaged or broken-down vehicles in special vehicles;
- transport that does not need a prior transport authorisation.
Official sources (in Spanish): Orden FOM/2861/2012, Resolution of 5 June 2026, Ministry DeCA page and its FAQ.
The transport_documents module is included wherever delivery notes are (Profesional,
Avanzado and Enterprise plans) and requires the delivery_notes module. Scopes:
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). The PDF of each version enforces pdfs:read; the proof of issuance
enforces transport_documents:read, like the issuance evidence it contains.
Your role and the parties
Each document declares whether your company acts as contractual shipper
(company_role: "shipper") or as effective carrier ("carrier"), including a
carrier that subcontracts the transport. Your own party is filled in with your company
data; the other one comes from contact_id or from the fields you send. A foreign
carrier (cabotage) is identified with its own tax_id and tax_id_country.
A tax ID or an address that does not pass validation in your company data or in the contact is left empty: the draft accepts it and issuing asks for it. What you send in the request is validated when it is saved. A Spanish address copied from your company or from a contact without a province takes the province of its postal code (its first two digits: 28001 is Madrid); an address you send is stored as you send it.
While the document is a draft, each copied party keeps following its source, without saving the draft again:
- Updating a contact copies its name, tax ID (or foreign identifier with its country) and address again into the drafts whose other party is linked to it.
- Changing the fiscal name, legal name, tax ID or registered address of your company copies them again into your own party of every draft. Other changes to your company data (email, phone, website, bank account, language) touch no draft.
- What the source has valid wins. An empty or invalid tax ID or an incomplete address in the source never erases what the draft already had, for example what you sent in the request. A party written by hand or linked to another contact does not change.
- The refresh runs a few seconds after the change and, when something changes,
publishes
transport_document.updated, so you can receive it for a draft you have not touched through the API. Archiving or deleting the contact leaves its drafts as they were.
An issued document never follows its sources: it keeps the data it was issued with.
Once issued, the parties cannot change: a modification that touches them returns
422 transport_document_parties_immutable. Cancel the document and issue a new one.
The flow: issue, hand the copy, modify, close
Create a draft. Only company_role is required: a draft admits incomplete data and
any date. Pass delivery_note_ids (1 to 50 of your delivery notes) to get one
shipment per delivery note, with its destination, date and goods — services left out
and weights frozen. A shipment that omits origin takes your default loading place or, without one,
your registered address (as carrier, the address of the shipper contact); an explicit
"origin": null leaves it without origin. A delivery note of another company returns
422 on delivery_note_ids.N.
curl -X POST https://api.factuarea.com/v1/transport_documents \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"company_role": "shipper",
"carrier": { "contact_id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a10" },
"vehicle": { "tractor_plate": "1234BCD", "trailer_plate": "R1234BCD" },
"transport_date": "2026-10-06",
"delivery_note_ids": [
"01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a11",
"01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a12"
]
}'Edit the draft with PUT /v1/transport_documents/{id} (an absent field is kept, an
explicit null clears it and shipments replaces the whole list) or delete it with DELETE /v1/transport_documents/{id}.
The server generates the id of the document. shipments[].id is optional: on
create, an identifier already in use, in your company or in any other, returns 422
on shipments.N.id; on PUT and modify it only identifies a shipment of that same
document (any other returns 422 parameter_invalid_value on shipments.N.id), and a
new shipment goes without id. Every UUID you send is normalised to lower case. Shipments, and the goods lines
of each shipment, are stored in the order you send them, on create, update, modify
and preview. A
goods line whose quantity × gross_weight_kg rounds to 0 kg or exceeds
9,999,999,999.9999 kg returns 422 invalid_gross_weight on
shipments.N.goods.M.gross_weight_kg when it is saved, not when it is issued. If a
concurrent request takes the same identifier between the check and the save, the API
returns 409 resource_conflict: nothing was saved and the request can be retried as
it is.
Issue it before the transport starts. POST /v1/transport_documents/{id}/issue
checks the minimum data — shipper name, tax ID and full address; carrier name and tax
ID; tractor plate; and in every shipment origin, destination, goods, declared measure
and a transport date of today or later (Europe/Madrid). In one atomic operation it
assigns the next number of the DC series (created on the first issue), the permanent
public URL and version 1 of the PDF. It is irreversible: send an Idempotency-Key.
When something is missing, the error lists it in error.details.missing:
{
"error": {
"type": "invalid_request_error",
"code": "transport_document_not_issuable",
"message": "No se puede emitir el DeCA. Faltan 3 datos: el NIF del transportista efectivo; la matrícula de la tractora o del vehículo rígido; la magnitud declarada del envío 1 (peso u otra).",
"details": {
"missing": ["carrier.tax_id", "vehicle.tractor_plate", "shipments.1.measure"]
}
}
}missing uses a closed vocabulary: shipper.name, shipper.tax_id,
shipper.address, carrier.name, carrier.tax_id, vehicle.tractor_plate,
transport_date, shipments (no shipment) and shipments.N.origin,
shipments.N.destination, shipments.N.goods or shipments.N.measure, where N
counts from 1 (unlike the param of a validation error, which counts from 0). If the
PDF would exceed 5 MB (5,000,000 bytes), the request returns
422 transport_document_pdf_too_large and nothing is issued: shorten the content or
split the shipments across several documents.
A past transport date returns transport_date_in_past: the document is created before
the service, and there is no way to regularise it afterwards.
Hand the copy to the driver. Any of these is valid: the public_url link, the QR
code (GET /v1/transport_documents/{id}/qr?format=svg|png, with the standard quiet zone
of 4 modules of ISO/IEC 18004, so it scans as downloaded when you print it or paste it
into another document), the PDF
(GET /v1/transport_documents/{id}/pdf) or an email to the carrier with
POST /v1/transport_documents/{id}/send (up to 5 distinct addresses across to, cc and bcc; an address repeated in any
list, ignoring case, counts and is sent once; without to, the email of the carrier contact). Sending is irreversible and
recorded in the issuance evidence. If the mail provider rejects the message or cannot
take it, the API returns 500 send_failed and nothing is recorded in the evidence:
retry with a new Idempotency-Key.
Modify it during the service. POST /v1/transport_documents/{id}/modify keeps the
same number, URL and QR and stores a new full version of the PDF with the new data, the
reason and the previous data marked as no longer valid. Send only what changes, plus
reason (3–500 characters) and data_origin: company or carrier_reported when the
carrier reported the change. A request that changes nothing returns transport_document_no_changes. A document
holds at most 50 versions and the last one is reserved for cancelling or superseding
it: with 49 versions, a modification returns
422 transport_document_version_limit_reached, so issue a new document and mark this
one as superseded. A modification that leaves minimum data missing returns
422 transport_document_cannot_be_modified with the same error.details.missing. Send the modified PDF to the driver again.
{
"reason": "Avería de la tractora",
"data_origin": "carrier_reported",
"vehicle": { "tractor_plate": "5678FGH" }
}Close it. POST /v1/transport_documents/{id}/close closes it by hand. Otherwise a
daily process closes it automatically 7 days after the end of its last transport date
(Europe/Madrid). A manually closed document can be reopened with a reason until
reopen_deadline; an automatic close cannot be reopened.
Two terminal exits exist for an issued document, both irreversible and both keeping the
original: POST /v1/transport_documents/{id}/cancel (with reason) stores a final
version marked «ANULADO», and POST /v1/transport_documents/{id}/supersede (with
reference and reason) records that the carrier's own document replaces it. An issued document is never deleted. Like issuing and modifying, cancelling and
superseding return 422 transport_document_pdf_too_large without changing anything
when the PDF would exceed 5 MB.
The public URL and the inspection
The public_url has the form https://app.factuarea.com/api/public/deca/{token},
with an opaque 43-character token. It is the URL the QR code encodes (the PDF and the
proof of issuance do not print it as text) and it does not change between versions.
GETandHEADanswer without login, credentials or intermediate page:200with the PDF of the current version andContent-Type: application/pdf.- The file is served exactly as it was stored, never rendered on the request. The
ETagis its sha256, which matchesversions[].sha256. - Requests have two limits, and neither depends on the IP alone, because the
inspection may reach the URL through a gateway with a shared address: one per token
and network prefix (/24 in IPv4, /48 in IPv6; 60 per minute by default), checked
first, and one per token (600 per minute by default, ten times the network one, so
two networks that exhaust theirs do not leave a third without the document). An IPv4
address mapped in IPv6 (
::ffff:a.b.c.d) counts as its /24. Exceeding either returns429withRetry-After. - Requests with a token that matches no document, including a malformed one (another
length or characters outside base64url), count against a limit per network prefix
(30 per minute by default), never per token: from one network, the 31st attempt of
the minute gets
429. - A download is recorded in the evidence only after the file is found, with the
version actually served.
HEADis not recorded. - An unknown token returns
404, and a document whose files were purged returns410.
Each version is a PDF of up to 5 MB (5,000,000 bytes) whose CreationDate is the first issue and whose
ModDate is the version date. Its history shows the date, reason, data origin and previous data
of each modification, but not who made it: the author of each version is only in the
authenticated resource, the evidence and the proof of issuance. The URL keeps serving the document for at least one year
after it is closed, cancelled or superseded (retention_until), even if the company
changes plan. After that, files are only purged when the account is closed: the subscription is
cancelled, or the managed company was archived by its accounting firm. A blocked
account (unpaid, expired trial) is not closed. Once purged, the
document and its data remain, and the PDF endpoints return
410 transport_document_file_unavailable. Asking
GET /v1/transport_documents/{id}/pdf?version=N for a version the document does not
have returns 404 transport_document_version_not_found with error.param = version.
Proof of issuance
GET /v1/transport_documents/{id}/evidence lists, in chronological order, the versions issued or modified, manual closes (closed) and automatic closes
(auto_closed), reopenings, cancellation or supersession, the email
deliveries with their recipients and the public downloads with truncated IP and user
agent. The response is { data, meta: { downloads_total, downloads_listed } }:
- Each entry names its actor in
actor_name, frozen with the name it had at that moment: the user in the app, the API key in the REST API and in MCP with an API key, and the OAuth client in MCP over OAuth. A key never lends the name of the user who created it.actor_nameisnullonly forauto_closedand public downloads;user_nameis the current name of the person, when there is one.occurred_atis ISO 8601 with its UTC offset. - A public download is recorded once per hour for each combination of version and network prefix. The user agent is stored but does not count, so changing it does not add entries and a repeated scan does not multiply them.
dataalways carries every entry that is not a download, and at most the first 100 downloads in chronological order.meta.downloads_totalis the number of downloads recorded andmeta.downloads_listedhow many of them are indata.
The MCP tool get_transport_document_evidence returns the same shape.
GET /v1/transport_documents/{id}/issuance_proof exports the evidence as a PDF generated on demand, with the author of each version. It
requires transport_documents:read: a key with only pdfs:read gets
403 insufficient_scope. It is the proof that frees the shipper who can show it issued the document
(art. 7.1 of the Orden). Every version is verified by recomputing the sha256 of its PDF.
Delivery notes and gross weight
- Shipments from delivery notes. Filter the documents of a delivery note with
GET /v1/transport_documents?delivery_note_id=…. When the delivery note changes after the current version in something the shipment declares (lines, quantities, unit, weight, delivery address or date), its shipment reportssource_changed: truewith the differences insource_changes; delivering or invoicing it does not count, and neither does a weight added later to the catalog. A cancelled or deleted delivery note setssource_missing: true. A cancelled delivery note cannot be the origin of a new draft (delivery_note_cancelled). - Gross weight in the catalog. Products, variants and fixed presentations accept
gross_weight_kg, the weight of one unit as it is loaded, packaging included. It is optional and only helps to fill in the declared measure of each shipment. - Weight on delivery notes. Each delivery note line freezes its weight when created
or edited in draft (
gross_weight_kgof the line); the delivery note publishestotal_gross_weight_kgandlines_without_weight_count, and prints the total on its PDF. Duplicating a delivery note copies the frozen weight of each line as it is.
Events
transport_document.created, .updated, .issued, .modified, .closed,
.reopened, .cancelled and .superseded carry the full resource under
data.object. transport_document.closed distinguishes a manual close from an automatic one with
close_mode. transport_document.updated also arrives when a draft follows a change
in its contact or in your company data (parties).
transport_document.email_sent and .email_failed report each email
of the document with its recipients and the PDF version attached. See Events.
What the public API does not cover
These stay in the app on purpose:
- Loading places. They are company settings. The public API and MCP identify the origin of a shipment by its address.
- The live preview of an unsaved document, an editor aid of the app.
- WebMCP, automation actions on the DeCA, AI assistants and bulk import.
Related
API reference.
the 15 tools of the transport control document.
causes and actions for each transport control document error.
transport_document.* events and their payloads.