PHP
Instal·la factuarea/factuarea-php amb Composer, autentica't i crea la teva primera factura. PSR-4, basat en Guzzle, PHP 8.2+.
L'SDK oficial de PHP és factuarea/factuarea-php
a Packagist — PSR-4, basat en Guzzle. Codi font:
github.com/factuarea/factuarea-php.
Embolcalla l'API REST v1 amb reintents automàtics, idempotency keys,
auto-paginació per cursor, una jerarquia d'errors tipada i verificació de
webhooks — tot plegat descrit a la introducció a l'SDK.
Instal·lació
composer require factuarea/factuarea-phpRequereix PHP 8.2 o superior amb les extensions json i mbstring (totes
dues incloses a les builds estàndard de PHP).
Autenticació
Passa la teva API key. El prefix de la clau selecciona l'entorn — no hi ha
cap flag a part: una clau fact_test_… sempre s'executa contra el
sandbox aïllat, i una clau fact_live_… contra producció.
<?php
require 'vendor/autoload.php';
use Factuarea\Sdk\Custom\FactuareaClient;
// The key prefix selects the environment:
// fact_test_… → sandbox fact_live_… → production
$factuarea = FactuareaClient::create(getenv('FACTUAREA_API_KEY'));FactuareaClient::create() és el punt d'entrada recomanat: configura
l'autenticació Bearer i registra per tu el comportament automàtic d'
Idempotency-Key. Per a configuració avançada (client Guzzle personalitzat,
política de reintents personalitzada, base URL de staging) el builder generat
encara està disponible:
use Factuarea\Sdk\Factuarea;
use Factuarea\Sdk\Models\Components\Security;
$factuarea = Factuarea::builder()
->setSecurity(new Security(bearerAuth: getenv('FACTUAREA_API_KEY')))
->setServerURL('https://api.factuarea.com/v1')
->build();Només al servidor. La teva API key és un secret. No distribueixis mai l'SDK amb una clau live en un client públic — fes-la servir des del teu backend.
Inici ràpid
Crea un contacte amb rol de client i una factura, i descarrega’n el PDF amb els mètodes publicats de l’SDK que apareixen a continuació. Per a altres operacions, comprova l’SDK instal·lat o fes servir l’exemple HTTP de la referència de l’API.
La creació del contacte fa servir HTTP amb Guzzle, que el SDK PHP ja requereix. Així funciona sense assumir que la versió PHP instal·lada té mètodes de contactes generats. El SDK de factures conserva clientId: passa l’id del contacte amb rol customer actiu.
<?php
require 'vendor/autoload.php';
use Factuarea\Sdk\Custom\FactuareaClient;
use Factuarea\Sdk\Models\Components;
use Factuarea\Sdk\Models\Operations;
use Brick\DateTime\LocalDate;
$factuarea = FactuareaClient::create(getenv('FACTUAREA_API_KEY'));
// Company axis: every company call carries the company id (`data.scope[].id` of GET /v1/me).
$company = getenv('FACTUAREA_COMPANY_ID');
// 1. Create a customer contact.
$http = new \GuzzleHttp\Client();
$response = $http->post('https://api.factuarea.com/v1/companies/' . $company . '/contacts', [
'headers' => [
'Authorization' => 'Bearer ' . getenv('FACTUAREA_API_KEY'),
'Idempotency-Key' => bin2hex(random_bytes(16)),
],
'json' => [
'name' => 'Cliente Demo SL',
'kind' => 'company',
'roles' => ['customer'],
'tax_id' => 'B12345674',
],
]);
$contact = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR)['data'];
// 2. Create an invoice (the API computes the totals).
$created = $factuarea->invoices->publicApiV1InvoicesCreate(
company: $company,
body: new Components\CreateInvoiceRequest(
clientId: $contact['id'],
seriesId: '01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0e',
issuedOn: LocalDate::parse('2026-06-05'),
dueOn: LocalDate::parse('2026-07-05'),
lines: [
new Components\CreateInvoiceRequestLine(
description: 'Consultoría — junio 2026',
quantity: 10,
unitPrice: 100,
taxRateId: '01931b3e-7c4a-7f2e-9a8b-3c5d6e7f8a0f',
),
],
),
);
// A new invoice comes in the 201 body; a 200 replays one that already exists.
$invoice = ($created->twoHundredAndOneApplicationJsonObject ?? $created->twoHundredApplicationJsonObject)->data;
// 3. Download the PDF. A 202 (still being generated) raises APIException:
// see "Conditional requests, null fields and PDF statuses" below.
$pdf = $factuarea->invoices->publicApiV1InvoicesPdf(
new Operations\PublicApiV1InvoicesPdfRequest(company: $company, invoice: $invoice->id),
);
file_put_contents('invoice.pdf', $pdf->bytes ?? '');Executa-ho tot primer amb una clau fact_test_ — els efectes del sandbox
(VeriFactu → AEAT, FACe, email, webhooks) estan desactivats. Quan el teu flux
funcioni d'extrem a extrem, canvia el prefix a fact_live_. Consulta
Mode de prova i sandbox.
Model de comptes
L'eix de compte (/v1/accounts/{account}/…) el cobreixen $factuarea->companies (la cartera de NIF), $factuarea->account->nifTransfers, $factuarea->managedAccounts (amb ->imports) i, per a webhooks i registres de peticions, $factuarea->account->webhooks i $factuarea->account->requestLogs. Arriben amb 0.5.0, la versió objectiu del model de comptes; encara no està publicada.
Els mètodes de l'eix de compte reben l'id del compte al paràmetre account. L'exemple llista els NIF del compte, traspassa un NIF a un altre compte, aplica una importació massiva de comptes gestionats amb Components\AccountsFile (accountsFile és obligatori, customersFile és opcional) i crea un webhook de compte. Els mètodes que escriuen de manera irreversible exigeixen un idempotencyKey:
<?php
require 'vendor/autoload.php';
use Factuarea\Sdk\Custom\FactuareaClient;
use Factuarea\Sdk\Models\Components;
use Factuarea\Sdk\Models\Operations;
$factuarea = FactuareaClient::create(getenv('FACTUAREA_API_KEY'));
// L'id del compte és `data->account->id` de `publicApiV1AccountShow()` (GET /v1/me).
$account = $factuarea->account->publicApiV1AccountShow()->object->data->account->id;
// 1. Llista els NIF del compte.
$nifs = $factuarea->companies->publicApiV1CompaniesList(
new Operations\PublicApiV1CompaniesListRequest(account: $account, limit: 50),
);
foreach ($nifs->companyList->data as $nif) {
echo $nif->id, ' ', $nif->taxId, PHP_EOL;
}
// 2. Traspassa un NIF a un altre compte (traspàs de NIF).
$transfer = $factuarea->account->nifTransfers->publicApiV1AccountNifTransfersCreate(
new Components\CreateNifTransferV1Request(
companyId: $companyId,
recipientAccountId: $recipientAccountId,
),
account: $account,
idempotencyKey: bin2hex(random_bytes(16)),
);
// El secret viatja només en aquesta resposta: lliura'l al compte receptor.
$secret = $transfer->object->data->secret;
// 3. Aplica una importació massiva de comptes gestionats (multipart/form-data).
$import = $factuarea->managedAccounts->imports->publicApiV1ManagedAccountsImportsCreate(
new Components\ManagedAccountImportV1Request(
accountsFile: new Components\AccountsFile(
fileName: 'accounts.csv',
content: file_get_contents('accounts.csv'),
),
options: new Components\ManagedAccountImportV1RequestOptions(sendEmail: false),
),
idempotencyKey: bin2hex(random_bytes(16)),
);
// 4. Crea un webhook de compte (exigeix una clau fact_live_).
$endpoint = $factuarea->account->webhooks->publicApiV1AccountWebhooksCreate(
new Components\CreateAccountWebhookV1Request(
url: 'https://example.com/webhooks/factuarea',
enabledEvents: ['account.nif_added', 'invoice.paid'],
accountRelationship: Components\CreateAccountWebhookV1RequestAccountRelationship::All,
),
account: $account,
);
// El secret `whsec_…` només es retorna ara: guarda'l per verificar els lliuraments.
$webhookSecret = $endpoint->object->data->secret;Un lliurament d'un webhook de compte es verifica amb WebhookVerifier, igual que un d'empresa; consulta Verificació de webhooks.
Peticions condicionals, camps null i estats de PDF
L'SDK inclou helpers escrits a mà a Factuarea\Sdk\Custom per al que els mètodes generats encara no poden expressar:
| Necessites | Fes servir |
|---|---|
Una capçalera per a la qual el mètode generat no té paràmetre (If-Match, If-None-Match), o un camp enviat com a null explícit | RequestOverrides::run() |
L'estat, l'ETag, el Retry-After o el cos JSON d'una resposta, inclòs un estat que el mètode generat llança com a APIException (el 202 d'un PDF que encara s'està generant) | ResponseMeta::fromResponse() i ResponseMeta::fromException() |
Una crida que no s'ha de repetir davant 429 o 5xx, com l'enviament i la cancel·lació a FACe | options: NoRetry::options() |
RequestOverrides::run($call, clear: [...], headers: [...])s'aplica a cada petició que es llança dins del callback, reintents inclosos, així que posa-hi una sola crida de l'SDK.clearenumera les rutes JSON que s'envien com anull(separades per punts als objectes niats, comidentity.logo_id); no se'n dedueix res més.headersafegeix capçaleres o substitueix les del mateix nom. Funciona als clients creats ambFactuareaClient::create(), que el connecta.ResponseMetaexposastatusCode,etag,retryAfter(segons, a partir d'un nombre o d'una data HTTP, onull),bodyijson(). Llegeix-lo d'una resposta tipada ambResponseMeta::fromResponse($response->rawResponse), o de l'excepció ambResponseMeta::fromException($e).NoRetry::options()atura els reintents generats davant429i5xxper a una crida. Un error de transport (una connexió tallada, un timeout) es continua repetint amb el mateixIdempotency-Key, al qual l'API respon reproduint el primer resultat.
<?php
require 'vendor/autoload.php';
use Factuarea\Sdk\Custom\FactuareaClient;
use Factuarea\Sdk\Custom\Request\NoRetry;
use Factuarea\Sdk\Custom\Request\RequestOverrides;
use Factuarea\Sdk\Custom\Response\ResponseMeta;
use Factuarea\Sdk\Models\Errors\APIException;
use Factuarea\Sdk\Models\Operations\PublicApiV1InvoicesPdfRequest;
$factuarea = FactuareaClient::create(getenv('FACTUAREA_API_KEY'));
$company = getenv('FACTUAREA_COMPANY_ID'); // `data.scope[].id` of GET /v1/me
// 1. Download the invoice PDF, or get a 304 when the copy you hold is current.
try {
$pdf = RequestOverrides::run(
fn () => $factuarea->invoices->publicApiV1InvoicesPdf(
new PublicApiV1InvoicesPdfRequest(company: $company, invoice: $invoiceId),
),
headers: ['If-None-Match' => $cachedEtag],
);
if ($pdf->statusCode === 200) {
file_put_contents('invoice.pdf', $pdf->bytes ?? '');
$cachedEtag = ResponseMeta::fromResponse($pdf->rawResponse)->etag;
}
// 304: no bytes, keep your copy.
} catch (APIException $e) {
$meta = ResponseMeta::fromException($e);
if ($meta->statusCode !== 202) {
throw $e;
}
// 202: the PDF is still being generated. Ask again after $meta->retryAfter seconds.
}
// 2. Submit to FACe once: no automatic retry on 429 or 5xx.
$factuarea->invoices->faceSubmissions->publicApiV1InvoicesFaceSubmissionsSubmit(
company: $company,
invoice: $invoiceId,
idempotencyKey: $submissionKey, // reuse it if you ever repeat this submission
options: NoRetry::options(),
);Després d'un error a FACe el resultat pot ser incert: consulta els enviaments abans de tornar a enviar. Consulta Com responen les descàrregues.
Plantilles PDF
Els mètodes tipats de la biblioteca de plantilles PDF, les vistes prèvies, els artefactes PDF, la presentació de cada document i la plantilla d'una sèrie (/v1/companies/{company}/pdf-templates, /v1/companies/{company}/pdf-artifacts, /v1/companies/{company}/invoices/{invoice}/presentation…) encara no estan generats en aquest SDK; arribaran amb la pròxima versió de l'SDK. Mentrestant, crida aquestes operacions amb RawRequest::send(), que passa pel mateix client autenticat:
- Reutilitza la clau Bearer, l'
Idempotency-Keyautomàtic a les mutacions, elFactuarea-Versionfixat i qualsevolRequestOverridesactiu. - La ruta és relativa a la URL base, que ja acaba en
/v1, així que comença per/companies/{company}/…. Cada marcador{name}pren el seu valor de l'últim argument, codificat per a URL. - No aplica reintents, mai llança una excepció davant un estat HTTP d'error i retorna la resposta PSR-7, que llegeixes amb
ResponseMeta::fromResponse().
<?php
require 'vendor/autoload.php';
use Factuarea\Sdk\Custom\FactuareaClient;
use Factuarea\Sdk\Custom\Request\RawRequest;
use Factuarea\Sdk\Custom\Response\ResponseMeta;
$factuarea = FactuareaClient::create(getenv('FACTUAREA_API_KEY'));
$company = getenv('FACTUAREA_COMPANY_ID');
// 1. List the library, one page per call. A list returns `items` and `meta`.
$list = ResponseMeta::fromResponse(RawRequest::send(
$factuarea,
'GET',
'/companies/{company}/pdf-templates',
['query' => ['page' => 1, 'per_page' => 20]],
['company' => $company],
));
foreach ($list->json()['items'] ?? [] as $template) {
echo $template['id'], ' ', $template['name'], PHP_EOL;
}
// 2. Read one template and keep its ETag.
$read = ResponseMeta::fromResponse(RawRequest::send(
$factuarea,
'GET',
'/companies/{company}/pdf-templates/{template}',
[],
['company' => $company, 'template' => $templateId],
));
// 3. Rename it with the literal ETag as If-Match. `null` clears the description.
$update = ResponseMeta::fromResponse(RawRequest::send(
$factuarea,
'PATCH',
'/companies/{company}/pdf-templates/{template}',
[
'headers' => ['If-Match' => $read->etag],
'json' => ['name' => 'Factura corporativa', 'description' => null],
],
['company' => $company, 'template' => $templateId],
));
if ($update->statusCode !== 200) {
// 412: the ETag is stale. 428: no precondition. Read it again and retry.
throw new RuntimeException('HTTP '.$update->statusCode.': '.$update->body);
}
// 4. Download an exact PDF artifact: it answers 503 with Retry-After until it is ready.
for ($attempt = 0; $attempt < 12; $attempt++) {
$response = RawRequest::send(
$factuarea,
'GET',
'/companies/{company}/pdf-artifacts/{artifact}/download',
['headers' => ['Accept' => 'application/pdf']],
['company' => $company, 'artifact' => $artifactId],
);
if ($response->getStatusCode() === 200) {
file_put_contents('invoice.pdf', (string) $response->getBody());
break;
}
if ($response->getStatusCode() !== 503) {
throw new RuntimeException('HTTP '.$response->getStatusCode());
}
sleep(max(1, ResponseMeta::fromResponse($response)->retryAfter ?? 1));
}Consulta la biblioteca de plantilles PDF, Concurrència i Vistes prèvies i artefactes PDF per al model.
Següents passos
El comportament en runtime — reintents, idempotència, auto-paginació per cursor, la jerarquia d'errors tipada i la verificació de webhooks — és comú a tots dos SDK i està documentat una sola vegada a la introducció a l'SDK:
Cobertura de les versions
La referència de setembre de 2026 és 0.2.0 per a tots dos SDK. Els specs de les seves branques principals contenen 413 entrades i encara els falten 39 endpoints REST. Cal actualitzar el paquet per disposar de nous mètodes generats. La referència de l’API ofereix exemples HTTP complets amb fetch de TypeScript, cURL de PHP i terminal per al contracte documentat; aquests exemples no pressuposen que existeixi cap mètode de l’SDK.