Factuarea APIDevelopers

Operacions en lot

Contracte d'èxit parcial per a endpoints bulk — total, successful, failed i una llista failures per fila.

Els endpoints bulk processen diverses files en una sola petició i mai no fan fallar tot el lot perquè una fila es rebutgi. Cada fila s'avalua de manera independent i la resposta informa, fila a fila, de si s'ha aplicat o no. És el contracte d'èxit parcial (partial-success), compartit per tots els endpoints bulk de l'API pública.

La superfície bulk ara abasta operacions de delete, create, pdf, send i status sobre els recursos de document i de catàleg — no només la família original bulk-delete. Algunes retornen la forma BulkPartialSuccessResult de sota, bulk-create retorna la forma més rica BulkCreateResult, i bulk-pdf transmet un ZIP binari en lloc de l'embolcall JSON. Totes honoren l'èxit parcial: una fila dolenta mai no enfonsa el lot.

Per a contactes, fes servir POST /v1/contacts/bulk-create, POST /v1/contacts/bulk-delete, POST /v1/contacts/bulk/archive, POST /v1/contacts/bulk/status i la previsualització d’importació. L’estat del rol és direccional; tant delete com archive conserven el contacte complet perquè es pugui restaurar.

Forma de la resposta

Les operacions que fan servir BulkPartialSuccessResult retornen 200 OK amb aquesta forma dins de data:

{
  "data": {
    "total": 3,
    "successful": 2,
    "failed": 1,
    "failures": [
      {
        "id": "01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a01",
        "error_code": "resource_not_deletable",
        "error_message": "La factura ya está emitida y no se puede eliminar."
      }
    ]
  }
}
CampTipusSignificat
totalintegerFiles processades (successful + failed).
successfulintegerFiles aplicades (eliminades, creades o validades).
failedintegerFiles que no s'han pogut processar. Igual a la longitud de failures en mode persistent.
failuresarrayUn element per cada fila fallida. Sempre una llista — buida, mai null, quan no ha fallat res.

L'invariant total === successful + failed es compleix sempre. En mode persistent, failed === failures.length. En el dry-run de bulk-create, failures queda buit perquè no s'escriu res; les files invàlides s'informen a results.

Un element d'error

Cada entrada de failures identifica la fila i explica per què s'ha rebutjat. La identitat és polimòrfica:

  • id — l'UUID d'un recurs existent (bulk-delete i altres operacions sobre recursos existents).
  • index — la posició (base 0) de la fila al lot, per a files noves que encara no tenen recurs (mode persistent de bulk-create i import CSV).

Exactament un d'id / index és present.

CampTipusSignificat
idstringUUID v7 del recurs existent que no s'ha pogut processar.
indexintegerPosició (base 0) de la fila dins del lot.
error_codestringCodi llegible per màquina del catàleg d'errors v1 (estable entre idiomes). Ramifica segons això.
error_messagestringMotiu llegible per humans, en castellà. Per mostrar, no per ramificar.
errorsarrayProblemes bloquejants per camp (FieldIssue[]). Presents en fluxos validate-only / bulk-create; absents en bulk-delete.
warningsarrayAvisos no bloquejants per camp (FieldIssue[]).

Ramifica segons error_code, mai segons error_message — el missatge és text en castellà orientat a persones i pot canviar. Per a bulk-delete els codis són resource_not_found (l'UUID no existeix o pertany a una altra empresa) i resource_not_deletable (el recurs existeix però el seu estat impedeix eliminar-lo: un albarà signat, un pressupost facturat, un client amb documents, etc.).

Llegir el resultat

No tractis la crida com a tot-o-res. Inspecciona failures i actua fila a fila:

curl -s -X POST https://api.factuarea.com/v1/quotes/bulk-delete \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["01931b3e-...a01", "01931b3e-...a02", "01931b3e-...a03"] }' \
  | jq '.data | {total, successful, failed, failures}'
const { data } = await factuarea.quotes.bulkDelete({ ids });

if (data.failed > 0) {
  for (const f of data.failures) {
    // f.id, f.error_code, f.error_message
    console.warn(`${f.id} → ${f.error_code}: ${f.error_message}`);
  }
}
res = factuarea.quotes.bulk_delete(ids=ids)
data = res["data"]

for f in data["failures"]:
    # ramifica segons error_code, mostra error_message
    print(f["id"], f["error_code"], f["error_message"])

UUID aliens i desconeguts

Els UUID que no pertanyen a la teva empresa, o que no existeixen, mai no són un 404 global. El handler filtra per company_id, així que un UUID alien o desconegut es reporta com un error normal (resource_not_found) — mai no revela si un recurs existeix en un altre tenant.

Bulk create (només validar amb dry_run)

bulk-create accepta fins a 100 files per a factures i fins a 500 per a clients en una sola crida, i retorna un embolcall més ric, BulkCreateResult. El flag dry_run (per defecte false) alterna entre dos comportaments:

  • dry_run: true valida cada fila sense persistir res i retorna un results[] per fila. Cada entrada porta el seu index, un status, i els errors[] / warnings[] trobats per a aquella fila. No s'escriu res — fes-lo servir per mostrar els problemes a la teva interfície abans de confirmar.
  • dry_run: false crea només les files vàlides. Les files que fallen la validació no es creen i tornen a failures[], cadascuna identificada pel seu index (base 0).

L'embolcall porta tots dos arrays, així que el mateix parser serveix en qualsevol mode:

{
  "data": {
    "dry_run": true,
    "total": 2,
    "successful": 1,
    "failed": 1,
    "results": [
      { "index": 0, "status": "valid", "errors": [], "warnings": [] },
      {
        "index": 1,
        "status": "invalid",
        "errors": [{ "field": "client_id", "code": "required", "message": "El cliente es obligatorio." }],
        "warnings": []
      }
    ],
    "failures": [
      { "index": 1, "error_code": "validation_failed", "error_message": "Faltan campos obligatorios en la fila." }
    ]
  }
}

Valida primer amb dry_run: true, corregeix el que marqui results[] i torna a enviar el mateix payload amb dry_run: false per persistir les files que passen:

curl -s -X POST https://api.factuarea.com/v1/invoices/bulk-create \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9f1c2b7a-0e44-4c1a-8f3d-1a2b3c4d5e6f" \
  -d '{
    "dry_run": true,
    "invoices": [
      { "client_id": "01931b3e-...c01", "lines": [{ "description": "Consulting", "quantity": 1, "unit_price": "100.00", "tax_rate_id": "01931b3e-...t21" }] },
      { "lines": [{ "description": "Missing client", "quantity": 1, "unit_price": "50.00" }] }
    ]
  }' | jq '.data | {dry_run, total, successful, failed, results, failures}'

Bulk PDF (descàrrega ZIP)

bulk-pdf empaqueta els PDF de fins a 50 documents en un únic ZIP i transmet de tornada l'arxiu binarino retorna l'embolcall JSON. Els id que no es troben, o que no tenen PDF disponible, no aborten la petició: el ZIP porta només els documents vàlids, i els recomptes per id viatgen a les capçaleres de resposta X-Bulk-* perquè puguis conciliar què va entrar.

curl -s -X POST https://api.factuarea.com/v1/invoices/bulk-pdf \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["01931b3e-...a01", "01931b3e-...a02"] }' \
  -D - -o invoices.zip

El flag -D - bolca les capçaleres de resposta: llegeix X-Bulk-Total, X-Bulk-Successful i X-Bulk-Failed per saber quants id van entrar a l'arxiu.

Bulk send (enviament en lot)

bulk-send encua fins a 200 documents per enviar-los per email i retorna la forma BulkPartialSuccessResult. L'enviament és asíncron: una fila successful significa que l'email es va encuar, no que ja s'ha entregat. Els camps opcionals to, cc, subject, message i language sobreescriuen els valors per defecte per a tot el lot.

ids admet entre 1 i 200 elements. Un lot de més de 200 es rebutja amb 422 i error.param: "ids"; no s'envia res. Les quatre tools MCP bulk_send_* apliquen el mateix límit i rebutgen igual un lot que el superi, abans que surti cap correu.

curl -s -X POST https://api.factuarea.com/v1/quotes/bulk-send \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c3e1f90-2a11-4b22-9d44-5e6f7a8b9c0d" \
  -d '{ "ids": ["01931b3e-...a01", "01931b3e-...a02"], "language": "es" }' \
  | jq '.data | {total, successful, failed, failures}'

Transicions d'estat en lot

bulk-status mou fins a 50 documents a un nou estat, i cada transició passa per la guarda de l'Aggregate — una fila l'estat actual de la qual prohibeix el moviment falla de manera individual i cau a failures[], mentre que la resta sí que transiciona. El new_status destí ha de pertànyer al conjunt tancat permès per a aquell recurs (consulta la taula de sota).

Per a factures, payment_date és obligatori quan new_status és paid. Per a factures de compra, payment_date també és obligatori i es propaga tal qual — mai no es reemplaça en silenci per now(). Per a productes i proveïdors la transició és idempotent: un recurs que ja està en l'estat sol·licitat compta com a successful sense canviar res.

curl -s -X POST https://api.factuarea.com/v1/invoices/bulk-status \
  -H "Authorization: Bearer $FACTUAREA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3b8d2e10-4f55-4c66-8a77-9b0c1d2e3f40" \
  -d '{ "ids": ["01931b3e-...a01", "01931b3e-...a02"], "new_status": "paid", "payment_date": "2026-03-20" }' \
  | jq '.data | {total, successful, failed, failures}'

Els valors new_status permesos per recurs:

Recursnew_status permès
invoicessent, paid
quotesapproved, rejected
proformasaccepted, rejected
delivery_notesdelivered, cancelled
purchase_invoicespaid
productsactive, inactive (idempotent)
suppliersactive, inactive (idempotent)

Endpoints i límits

Cada operació limita el lot a un nombre fix de files. Trossejar una feina més gran en blocs dins d'aquests límits queda de la teva part:

OperacióRecursosFiles màx.Forma de resposta
bulk-createinvoices (100), clients (500)100 / 500BulkCreateResult
bulk-pdfinvoices, quotes, proformas, delivery_notes50ZIP binari + X-Bulk-*
bulk-sendinvoices, quotes, proformas, delivery_notes200BulkPartialSuccessResult
bulk-statusinvoices, quotes, proformas, delivery_notes, purchase_invoices, products, suppliers50BulkPartialSuccessResult
bulk-deleteels nou recursosBulkPartialSuccessResult

Fes servir Idempotency-Key a les operacions bulk que muten. bulk-create, bulk-send, bulk-status i bulk-delete accepten totes la capçalera Idempotency-Key, així que un reintent després d'una connexió caiguda repeteix el resultat original en lloc d'executar el lot dues vegades. bulk-pdf és una lectura pura i no necessita clau.

Versionat — la forma legacy

La forma d'èxit parcial és el contracte actual. Els integradors ancorats a una versió anterior a 2026-09-01 (mitjançant el header Factuarea-Version o un pin a l'API key) continuen rebent la forma anterior de bulk-delete, de manera que cap integració existent no es trenca:

{
  "object": "bulk_delete_result",
  "deleted": 2,
  "failed": [
    { "id": "01931b3e-...a01", "reason": "La factura ya está emitida y no se puede eliminar." }
  ]
}

El mapatge entre ambdues formes és mecànic: deleted és el nou successful, i cada failed[].reason legacy és el nou failures[].error_message (la nova forma hi afegeix a sobre l'error_code estable i el comptador total). No enviïs header —o envia una data igual o posterior a 2026-09-01— per obtenir la forma d'èxit parcial.

Ancora una versió només per congelar un contracte del qual ja depens. Les integracions noves haurien d'usar la forma d'èxit parcial: porta un error_code estable i independent de l'idioma pel qual ramificar, cosa que la cadena reason legacy no ofereix.

En aquesta pàgina

Et donem un cop de mà?Contactar amb suport