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."
}
]
}
}| Camp | Tipus | Significat |
|---|---|---|
total | integer | Files processades (successful + failed). |
successful | integer | Files aplicades (eliminades, creades o validades). |
failed | integer | Files que no s'han pogut processar. Igual a la longitud de failures en mode persistent. |
failures | array | Un 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.
| Camp | Tipus | Significat |
|---|---|---|
id | string | UUID v7 del recurs existent que no s'ha pogut processar. |
index | integer | Posició (base 0) de la fila dins del lot. |
error_code | string | Codi llegible per màquina del catàleg d'errors v1 (estable entre idiomes). Ramifica segons això. |
error_message | string | Motiu llegible per humans, en castellà. Per mostrar, no per ramificar. |
errors | array | Problemes bloquejants per camp (FieldIssue[]). Presents en fluxos validate-only / bulk-create; absents en bulk-delete. |
warnings | array | Avisos 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: truevalida cada fila sense persistir res i retorna unresults[]per fila. Cada entrada porta el seuindex, unstatus, i elserrors[]/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: falsecrea només les files vàlides. Les files que fallen la validació no es creen i tornen afailures[], cadascuna identificada pel seuindex(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 binari — no 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.zipEl 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:
| Recurs | new_status permès |
|---|---|
invoices | sent, paid |
quotes | approved, rejected |
proformas | accepted, rejected |
delivery_notes | delivered, cancelled |
purchase_invoices | paid |
products | active, inactive (idempotent) |
suppliers | active, 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ó | Recursos | Files màx. | Forma de resposta |
|---|---|---|---|
bulk-create | invoices (100), clients (500) | 100 / 500 | BulkCreateResult |
bulk-pdf | invoices, quotes, proformas, delivery_notes | 50 | ZIP binari + X-Bulk-* |
bulk-send | invoices, quotes, proformas, delivery_notes | 200 | BulkPartialSuccessResult |
bulk-status | invoices, quotes, proformas, delivery_notes, purchase_invoices, products, suppliers | 50 | BulkPartialSuccessResult |
bulk-delete | els nou recursos | — | BulkPartialSuccessResult |
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.