Factuarea APIDevelopers

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-php

Requereix 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:

NecessitesFes 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ícitRequestOverrides::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 FACeoptions: 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. clear enumera les rutes JSON que s'envien com a null (separades per punts als objectes niats, com identity.logo_id); no se'n dedueix res més. headers afegeix capçaleres o substitueix les del mateix nom. Funciona als clients creats amb FactuareaClient::create(), que el connecta.
  • ResponseMeta exposa statusCode, etag, retryAfter (segons, a partir d'un nombre o d'una data HTTP, o null), body i json(). Llegeix-lo d'una resposta tipada amb ResponseMeta::fromResponse($response->rawResponse), o de l'excepció amb ResponseMeta::fromException($e).
  • NoRetry::options() atura els reintents generats davant 429 i 5xx per a una crida. Un error de transport (una connexió tallada, un timeout) es continua repetint amb el mateix Idempotency-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-Key automàtic a les mutacions, el Factuarea-Version fixat i qualsevol RequestOverrides actiu.
  • 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.

En aquesta pàgina

Et donem un cop de mà?Contactar amb suport