Factuarea APIDevelopers

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.

  • GET and HEAD answer without login, credentials or intermediate page: 200 with the PDF of the current version and Content-Type: application/pdf.
  • The file is served exactly as it was stored, never rendered on the request. The ETag is its sha256, which matches versions[].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 returns 429 with Retry-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. HEAD is not recorded.
  • An unknown token returns 404, and a document whose files were purged returns 410.

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_name is null only for auto_closed and public downloads; user_name is the current name of the person, when there is one. occurred_at is 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.
  • data always carries every entry that is not a download, and at most the first 100 downloads in chronological order. meta.downloads_total is the number of downloads recorded and meta.downloads_listed how many of them are in data.

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 reports source_changed: true with the differences in source_changes; delivering or invoicing it does not count, and neither does a weight added later to the catalog. A cancelled or deleted delivery note sets source_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_kg of the line); the delivery note publishes total_gross_weight_kg and lines_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.

On this page

Need a hand?Contact support