Factuarea APIDevelopers

Packs de productes

Ven diversos productes o serveis com un sol article. Llegeix el catàleg des del teu servidor, sincronitza'l amb webhooks, mostra preus amb i sense IVA i mapeja'l a schema.org.

Un pack agrupa dos o més productes o serveis sota un nom i un preu. El pack no té stock propi: la seva disponibilitat surt dels components, i en facturar-lo es converteix en un grup de línies fiscals normals. Els packs estan disponibles a tots els plans que inclouen productes, des d'Esencial. A Esencial un compte admet fins a 5 packs sense arxivar i la resta de plans no té límit.

Aquesta pàgina cobreix la lectura que necessita una web. Per vendre un pack, llegeix La teva botiga amb Factuarea. Per posar-lo en oferta, llegeix Ofertes i preu anterior.

Ús de servidor a servidor

L'API de packs està pensada per cridar-se des del servidor de la teva web (el backend que pinta les pàgines), mai des del navegador. No existeixen claus publicables: una crida des del navegador amb una API key la rebutja el preflight de CORS, i una clau dins del teu JavaScript exposaria tot l'abast del teu catàleg.

Crea una credencial per botiga amb els scopes mínims:

  • Una API key de l'entorn live, guardada només al teu servidor i enviada com Authorization: Bearer fact_live_….
  • products:read i packs:read per llegir el catàleg.
  • invoices:write només si aquest mateix servidor factura les comandes.
  • L'empresa va al camí (/v1/companies/{company}/…), mai en una capçalera.

Una clau de només lectura que intenta canviar un pack rep 403 insufficient_scope i el pack no canvia. Consulta API keys i Scopes i irreversibilitat.

Operacions

OperacióScope
GET /v1/companies/{company}/packs i GET …/packs/{pack}packs:read
GET …/packs/{pack}/price-breakdownpacks:read
POST /v1/companies/{company}/packs i PATCH …/packs/{pack}packs:write
POST …/packs/{pack}/archive i POST …/packs/{pack}/restorepacks:write
POST …/packs/{pack}/images, PATCH …/images/{image} i DELETE …/images/{image}packs:write
DELETE …/packs/{pack}packs:delete

Els llistats usen paginació per cursor (limit, starting_after, ending_before) i filtren per search, slug, sku, tag, channel, status, availability[in] i updated[gte]. sort admet created, name i price. Un pack porta almenys dos components, o un amb quantitat superior a 1. Si no, la resposta és 422 pack_composition_too_small.

El preu d'un pack es defineix amb pricing{mode, value, basis}:

modeSignificat
fixedUn preu fix per a tot el pack
percent_offLa suma dels components menys un percentatge
amount_offLa suma dels components menys un import
sumLa suma simple dels components

basis indica si value és net (sense IVA) o gross (amb IVA). En PATCH, components i pricing substitueixen el valor anterior de manera atòmica.

Dues vistes: all i storefront

Tota lectura admet view=all (per defecte) o view=storefront.

view=allview=storefront
Quins packsTots, també els arxivatsNomés els actius, amb canal web, en vigència i vendibles
Qualsevol altre packEs retorna amb el seu estatEl detall respon 404 pack_not_found i la cerca per slug surt buida
Camps extrastatus, channels, pricing, sellable, availability.limited_by, attentionCap
Mai es retornaCostos ni ofertes de proveïdorCostos, ofertes de proveïdor, limited_by, attention ni metadata

Qualsevol altre valor de view respon 400 parameter_invalid_enum amb param: "view". Fes servir storefront per pintar pàgines i all per sincronitzar.

Disponibilitat i quantitats exactes

availability.status val in_stock, low, out_of_stock o untracked. untracked vol dir que cap component controla stock i la teva web ha de tractar el pack com a disponible. L'stock mai bloqueja una venda: un pack out_of_stock es pot facturar igualment.

L'aparador mai dona quantitats exactes tret que l'empresa ho triï. Ho decideix l'ajust d'empresa web_stock_display:

web_stock_displayQuè retorna l'aparador
statusNomés l'estat
low_only (per defecte)L'estat, i quantity només quan l'estat és low
exactL'estat i quantity sempre

L'ajust és el mateix per a productes, variants i presentacions. El component que limita l'stock no s'exposa mai a la web.

Preu amb i sense IVA

price és el preu del pack amb la tarifa base, al règim de l'empresa i per a avui. Porta tots els imports que necessites per a una etiqueta de preu:

{
  "id": "019f1c2e-7b40-7a11-9c35-2d4e6f8a0b12",
  "object": "pack",
  "name": "Pack Huerto urbano",
  "slug": "pack-huerto-urbano",
  "sku": "PACK-HUERTO",
  "currency": "EUR",
  "price": { "net": "50.53", "tax": "9.37", "gross": "59.90" },
  "tax_breakdown": [
    { "rate": 21, "base": "39.27", "tax": "8.24" },
    { "rate": 10, "base": "11.26", "tax": "1.13" }
  ],
  "separately": { "gross": "72.91" },
  "savings": { "gross": "13.01", "percent": 17.8 },
  "availability": { "status": "in_stock" },
  "promotion": null
}

L'exemple està retallat als camps de preu. Els imports són text decimal amb dos decimals, i els percentatges i tipus d'IVA són números. separately és el que costen els components comprats un a un. Quan no hi ha estalvi (un preu fixed per sobre de la suma, o components en oferta), savings val null i la teva web no ha de mostrar estalvi.

Canàries i operacions exemptes: fes servir net

price.gross és el preu amb l'IVA de l'empresa. Només és l'etiqueta correcta per a un client al qual s'aplica aquest IVA. En altres règims el pack conserva els seus imports nets i l'impost del document s'aplica a sobre. A Canàries, el Pack Huerto urbano costa 50,53 € més IGIC, mai «59,90 € amb IGIC». El mateix val per a vendes intracomunitàries, exportacions i inversió del subjecte passiu, on l'impost és 0.

Si la teva botiga ven a aquests clients, construeix el preu a partir de price.net i la teva pròpia regla d'impostos, i mostra'l com a «més impostos» quan correspongui. Consulta IVA, IGIC i IPSI.

Preu en un altre context

GET …/packs/{pack}/price-breakdown admet price_list_id, customer_id i date, i retorna l'import «per separat», el preu, l'estalvi, el tax_breakdown i la part de cada component amb aquesta tarifa. Serveix per a webs B2B amb tarifes de client.

Imatges

Un pack té fins a 5 imatges. Cadascuna porta id, url, thumb_url (480 px), alt (fins a 300 caràcters), width, height, position i content_type. cover es resol al servidor: la primera imatge del pack, si no la primera imatge del primer component que en tingui, i si tampoc null, amb source: "own" o "component". Cada component exposa image{url, alt} perquè componguis un mosaic pel teu compte.

Les URL de les imatges són públiques i permanents: absolutes, en WebP, sense dades EXIF, descarregables per HTTPS sense credencials, sense signatura i sense caducitat. El seu nom és un UUID impossible d'endevinar que no es reutilitza, així que pots enllaçar-les directament o copiar-les al teu CDN i guardar-les per sempre. Substituir una imatge crea una URL nova i esborrar-la fa que la seva URL respongui 404. Baixar-les no consumeix quota de l'API, i es serveixen amb Cache-Control: public, max-age=31536000, immutable.

Per pujar-ne una, envia multipart/form-data amb el camp photo (màxim 3 MB, els mateixos formats que la galeria de producte) a POST …/packs/{pack}/images. La capçalera Idempotency-Key és obligatòria: sense ella la resposta és 422 idempotency_key_required. Una sisena imatge respon 422 pack_image_limit_reached.

Mantén la teva còpia sincronitzada

No llegeixis el llistat sencer en bucle. Guarda una còpia del catàleg al teu servidor i actualitza-la amb dos mecanismes:

  1. Webhooks. Subscriu-te a pack.created, pack.updated, pack.archived, pack.restored i pack.deleted (consulta Webhooks). El payload dels quatre primers és el pack en view=all. pack.deleted només porta {id, object: "pack", deleted: true}, perquè el pack ja no es pot llegir.
  2. updated[gte] com a xarxa de seguretat després d'una caiguda: GET …/packs?view=all&updated[gte]=2026-10-25T00:00:00Z.

Sincronitza amb view=all, no amb storefront: l'aparador només llista el que avui és a la venda, així que no et diu què acaba de sortir-ne. Un pack que surt de l'aparador apareix amb sellable.web: false i el seu motiu, i la teva web el retira. Un pack esborrat només es coneix per pack.deleted o pel 404 de la seva lectura.

updated_at canvia davant de qualsevol canvi que alteri la resposta: dades, composició, preu derivat, imatges, estat de disponibilitat, un component que deixa de ser vendible, i l'entrada o sortida de la vigència. Un programador de mitjanit, a la zona horària de l'empresa, cobreix els canvis per data: un pack que acaba la temporada el 30 de juny emet pack.updated l'1 de juliol sense que ningú l'editi.

La quantitat disponible és l'excepció. Una venda que baixa la quantitat sense canviar l'estat no mou updated_at ni emet pack.updated. Si mostres quantitats exactes, llegeix el detall del pack en pintar-lo.

Si reanomenes un slug, l'anterior passa a previous_slugs (fins a 10) i una cerca per slug també el troba, perquè la teva web respongui un 301 a l'adreça nova. Els slugs són únics entre productes i packs, així que tots dos poden viure sota la mateixa ruta.

Lectures condicionals i cost d'un 304

Les lectures d'un pack, del llistat i del desglossament responen amb un ETag fort (sha1 del cos servit), Cache-Control: private, no-cache i Vary: Accept-Language, Authorization. Envia'l de tornada a If-None-Match i un recurs sense canvis respon 304 sense cos. view=all i view=storefront tenen ETag diferents.

Un 304 consumeix quota de l'API com qualsevol altra petició, així que estalvia ample de banda, no peticions. Per estalviar peticions, recolza't en els webhooks i en updated[gte], i serveix les teves pàgines des de la teva pròpia memòria cau. Consulta Límits de peticions.

Contingut i SEO

Un pack té summary (text pla, fins a 200), description (text pla amb salts de línia, fins a 5.000, mai HTML) i seo{title, description} (fins a 70 i 160). Els camps SEO buits es resolen amb el nom i el resum. L'API no retorna frases muntades al servidor com «Inclou…»: retorna components[].name, quantity i unit{code, label} perquè composis el text en el teu idioma. Cada component porta a més product_id i slug (null si aquell producte no és a la web), per enllaçar-ne la fitxa des del pack.

schema.org

Mapeja un pack a un Product amb una Offer, i el llistat a un ItemList:

schema.orgCamp del pack
Offer.priceprice.gross
Offer.priceCurrencycurrency
Product.skusku
Product.imagecover.url
Offer.priceValidUntilavailable_until (o promotion.ends_at durant una oferta)
Offer.availabilityInStock, LimitedAvailability o OutOfStock segons availability.status
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Pack Huerto urbano",
  "sku": "PACK-HUERTO",
  "image": "https://app.factuarea.com/uploads/packs/01931b3e-1111-7a2e-9a8b-3c5d6e7f8a01/019f1c2e-7b40-7a11-9c35-2d4e6f8a0b12/019f1c31-aa10-7c02-8d4f-0e1b2c3d4e5f.webp",
  "offers": {
    "@type": "Offer",
    "price": "59.90",
    "priceCurrency": "EUR",
    "availability": "https://schema.org/InStock"
  }
}

untracked es mapeja a InStock. Fes servir el preu amb IVA només per a clients als quals s'aplica l'IVA de l'empresa, com s'explica a dalt.

Errors

StatuscodeQuan
404pack_not_foundEl pack no existeix, és d'una altra empresa o és fora de l'aparador
409sku_already_existsL'SKU xoca amb un producte, una variant o un altre pack
409slug_already_existsEl slug el fa servir o el va fer servir un altre producte o pack
422pack_in_useEsborres un pack que és en documents
422pack_composition_too_smallEl pack té un únic component de quantitat 1
422invalid_pack_component i duplicate_pack_componentUn component no s'admet o està repetit
422invalid_pack_pricing, pack_price_unreachable i pack_price_allocation_impossibleEl preu no es pot fixar o repartir
422invalid_pack_status_transitionArxivar o restaurar no és possible des de l'estat actual
422pack_image_limit_reachedUna sisena imatge
402plan_limit_exceededCrees o restaures un pack i el compte Esencial ja té 5 packs sense arxivar (details.resource val packs)
403feature_not_available_in_planL'empresa té desactivat el mòdul de productes

Els errors de les línies de document (pack_not_sellable, pack_snapshot_invalid, pack_lines_must_be_expanded, pack_line_locked i pack_print_mode_not_allowed) s'expliquen a La teva botiga amb Factuarea. El servidor MCP replica cada operació amb els mateixos errors: list_packs, get_pack, create_pack, update_pack, archive_pack, restore_pack, delete_pack, get_pack_price_breakdown, upload_pack_image, update_pack_image i delete_pack_image.

En aquesta pàgina

Et donem un cop de mà?Contactar amb suport