factuarea/factuarea-php

Maintainers

Package info

github.com/factuarea/factuarea-php

pkg:composer/factuarea/factuarea-php

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-06-05 09:06 UTC

This package is auto-updated.

Last update: 2026-06-07 11:13:42 UTC


README

License: MIT PHP 8.2+

Official PHP SDK for the Factuarea public API — invoicing, quotes, proformas, delivery notes, products, clients, suppliers, taxes, VeriFactu and webhooks for Spanish businesses.

Type-safe, PSR-4, built on Guzzle, with automatic retries, automatic idempotency, cursor auto-pagination, typed errors and a webhook signature verifier.

Status: pre-GA (0.x). The method surface follows the stable SDK method naming contract and is protected by SemVer, but 0.x signals the API may still evolve before GA.

Installation

Not yet on Packagist — coming soon. Until the package is published you can install it directly from GitHub.

Once published:

composer require factuarea/factuarea-php

Until then, install from the repository by adding it to your composer.json:

{
    "repositories": [
        { "type": "vcs", "url": "https://github.com/factuarea/factuarea-php" }
    ],
    "require": {
        "factuarea/factuarea-php": "dev-main"
    }
}

then run composer update.

Requirements: PHP 8.2 or higher, with the json and mbstring extensions (both bundled with standard PHP builds).

Quickstart

<?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'));

// List the first page of invoices.
$response = $factuarea->invoices->publicApiV1InvoicesList();
foreach ($response->paginatedList?->data ?? [] as $invoice) {
    echo $invoice->id, PHP_EOL;
}

FactuareaClient::create() is the recommended entry point: it wires Bearer authentication and registers the automatic Idempotency-Key behaviour for you. For advanced configuration (custom Guzzle client, custom retry policy, staging base URL) the generated builder is still available:

use Factuarea\Sdk\Factuarea;
use Factuarea\Sdk\Models\Components\Security;

$factuarea = Factuarea::builder()
    ->setSecurity(new Security(bearerAuth: getenv('FACTUAREA_API_KEY')))
    ->build();

Server-side only. A secret API key must never ship in a browser or mobile app. Load it from an environment variable or secret manager.

Sandbox (test mode)

Every API key belongs to one of two environments, decided by its prefix — there is no environment flag in the SDK:

Prefix Environment External effects
fact_test_ sandbox Neutralized — VeriFactu/AEAT, emails and webhooks are off
fact_live_ production Real fiscal numbering, VeriFactu→AEAT, emails, webhooks

A fact_test_ key operates on an isolated sandbox company with the same functional surface as production. Always integrate against a fact_test_ key first, then switch the prefix to fact_live_ to go live.

$sandbox = FactuareaClient::create('fact_test_…');
$prod    = FactuareaClient::create('fact_live_…');

Runtime features

Automatic retries

Transient failures (429 and 5xx) are retried with exponential backoff, honouring the server's Retry-After header. Client errors (4xx other than 429) are never retried. Retries reuse the same request — including its Idempotency-Key — so a retried mutation is idempotent end to end.

Automatic idempotency

Every mutating request (POST, PUT, PATCH, DELETE) gets a generated Idempotency-Key (UUID v4) unless you supply one explicitly:

// Auto-generated key — safe to retry.
$factuarea->invoices->publicApiV1InvoicesCreate($body);

// Or pin your own key for app-level deduplication.
$factuarea->invoices->publicApiV1InvoicesCreate($body, idempotencyKey: 'order-4711');

Replaying a request with the same key returns the original result and the response carries Idempotent-Replayed: true.

Cursor auto-pagination

List endpoints return the cursor envelope { data, has_more, next_cursor }. The PageIterator helper streams every item across all pages without manual cursor handling:

use Factuarea\Sdk\Custom\Pagination\PageIterator;
use Factuarea\Sdk\Models\Operations\PublicApiV1InvoicesListRequest;

$pages = new PageIterator(
    fn (?string $cursor) => $factuarea->invoices->publicApiV1InvoicesList(
        new PublicApiV1InvoicesListRequest(startingAfter: $cursor),
    )->rawResponse,
);

foreach ($pages->items() as $invoice) {
    // one invoice at a time, across every page
}

Typed errors

Documented API errors (401, 403, 409, 422, 429, 5xx) are thrown as a typed ErrorThrowable exposing the full error envelope — including the request_id you can quote to support. The API key is never included in any exception message.

use Factuarea\Sdk\Models\Errors\ErrorThrowable;

try {
    $factuarea->invoices->publicApiV1InvoicesCreate($body);
} catch (ErrorThrowable $e) {
    $error = $e->container->error;
    echo $error->type->value;   // e.g. "invalid_request_error"
    echo $error->code;          // e.g. "parameter_invalid"
    echo $error->param;         // e.g. "client_id"
    echo $error->requestId;     // e.g. "req_abc123" — quote this to support
}

Webhook signature verification

Factuarea signs every webhook delivery with HMAC-SHA256 in the Factuarea-Signature header (t=<unix>,v1=<hex>). Verify deliveries with a constant-time check and replay protection:

use Factuarea\Sdk\Custom\Webhooks\WebhookVerifier;
use Factuarea\Sdk\Custom\Webhooks\WebhookSignatureException;

$verifier = new WebhookVerifier();
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_FACTUAREA_SIGNATURE'] ?? '';

try {
    $event = $verifier->verify($rawBody, $signature, getenv('FACTUAREA_WEBHOOK_SECRET'));
    // $event is the decoded, authenticated payload
} catch (WebhookSignatureException $e) {
    http_response_code(400);
}

The verifier accepts deliveries during a secret-rotation grace window (the header may carry both the new and previous v1 signatures) and rejects payloads whose timestamp is outside the tolerance (default 5 minutes, configurable).

API reference

The full, generated reference for all resources and operations lives in docs/sdks/. Method names follow the deterministic operationId → method mapping (e.g. public-api.v1.invoices.mark_paid$factuarea->invoices->publicApiV1InvoicesMarkPaid(...)).

Versioning

The SDK pins the Factuarea-Version it was generated against (currently 2026-06-04) and sends it on every request, so the API behaves consistently until you upgrade. SemVer applies to the SDK's public surface: new operations are a minor bump, renames/removals or a behaviour-changing Factuarea-Version bump are a major bump, fixes are a patch. The SDK stays on 0.x until the API's GA, which ships 1.0.0.

See docs/VERSIONING.md for the full Factuarea-Version ↔ SDK-version mapping and CHANGELOG.md for release history.

Support

Aspect Supported
PHP 8.2, 8.3, 8.4 (tested in CI)
Stability Pre-GA 0.x — the public surface may change before 1.0.0
Factuarea-Version 2026-06-04 (pinned, sent on every request)

Full runtime-support matrix and the deprecation / breaking-change policy live in SUPPORT.md.

Development

This SDK is generated with Speakeasy from the pinned OpenAPI document in spec/openapi.json. Generated code lives in src/ (managed by Speakeasy); hand-written runtime helpers live in src/Custom/ and are never overwritten by regeneration. See docs/REGENERATION.md for how to regenerate (and the documented fallback to openapi-generator).

composer install
composer test   # PHPUnit, HTTP fully mocked — no network
composer stan   # PHPStan static analysis

License

MIT © Factuarea. Contact: info@factuarea.com

Summary

Factuarea Public API: Public REST API for invoicing in Spain — manage invoices, quotes, proformas, delivery notes, products, clients and webhooks.

Authentication

The API authenticates with a secret API key, sent on every request as:

Authorization: Bearer fact_live_xxxxxxxxxxxxxxxxxxxxxxxx

or, alternatively, in the X-API-Key header. The key is issued from the Factuarea portal and must NEVER be exposed in public clients (browser, mobile apps).

Environments: live and test

Each API key belongs to one of two environments, unambiguously identifiable by its prefix:

Prefix Environment Data it operates on External effects
fact_live_ live (production) Your real company Real: legal fiscal numbering, VeriFactu→AEAT, emails to clients, outbound webhooks
fact_test_ test (sandbox) An isolated sandbox company Neutralized (see below)

The prefix is the source of truth for the environment: a fact_test_ key always operates in test mode and a fact_live_ key always in live. There is no request parameter that changes the environment — it is determined by the key.

Data isolation (sandbox)

fact_test_ keys operate on a dedicated sandbox company (a technical "twin company" of the account holder, automatically provisioned the first time test mode is used, which inherits the plan of your real company for faithful feature-gating). Thanks to per-company data isolation:

  • Resources created with a fact_test_ key are not visible from a fact_live_ key, and vice versa.
  • Test fiscal numbering uses the sandbox's own series and never consumes or alters the sequential numbering of your production series.

The functional surface is identical in both environments: the same endpoints and operations are available in test as in live.

External effects neutralized in test

When you operate with a fact_test_ key (sandbox environment), outbound effects are disabled so you can test your integration without real consequences:

  • VeriFactu: the Alta record is created locally, but not transmitted to AEAT.
  • Email: document emails are not delivered to real recipients.
  • Webhooks: events are not delivered to your external HTTP endpoints.

In live, all these effects fire normally.

Recommendation: always integrate and test first with a fact_test_ key. Once your flow works, switch to the fact_live_ prefix to operate in production.

Table of Contents

SDK Installation

Tip

To finish publishing your SDK you must run your first generation action.

The SDK relies on Composer to manage its dependencies.

To install the SDK first add the below to your composer.json file:

{
    "repositories": [
        {
            "type": "github",
            "url": "<UNSET>.git"
        }
    ],
    "require": {
        "factuarea/factuarea-php": "*"
    }
}

Then run the following command:

composer update

SDK Example Usage

Example

declare(strict_types=1);

require 'vendor/autoload.php';

use Factuarea\Sdk;
use Factuarea\Sdk\Models\Components;

$sdk = Sdk\Factuarea::builder()
    ->setSecurity(
        new Components\Security(
            http: '<YOUR_BEARER_TOKEN_HERE>',
        )
    )
    ->build();

$body = new Components\AcceptProformaRequest(
    reason: 'Cliente confirma pedido por telefono',
);

$response = $sdk->proformas->publicApiV1ProformasAccept(
    proforma: '<value>',
    idempotencyKey: '01928f10-7c0e-7c4a-9b7d-2f8a6e3c1d4b',
    body: $body

);

if ($response->object !== null) {
    // handle response
}

Authentication

Per-Client Security Schemes

This SDK supports the following security schemes globally:

Name Type Scheme
http http HTTP Bearer
bearerAuth http HTTP Bearer
apiKeyAuth apiKey API key

You can set the security parameters through the setSecurity function on the SDKBuilder when initializing the SDK. The selected scheme will be used by default to authenticate with the API for all operations that support it. For example:

declare(strict_types=1);

require 'vendor/autoload.php';

use Factuarea\Sdk;
use Factuarea\Sdk\Models\Components;

$sdk = Sdk\Factuarea::builder()
    ->setSecurity(
        new Components\Security(
            http: '<YOUR_BEARER_TOKEN_HERE>',
        )
    )
    ->build();

$body = new Components\AcceptProformaRequest(
    reason: 'Cliente confirma pedido por telefono',
);

$response = $sdk->proformas->publicApiV1ProformasAccept(
    proforma: '<value>',
    idempotencyKey: '01928f10-7c0e-7c4a-9b7d-2f8a6e3c1d4b',
    body: $body

);

if ($response->object !== null) {
    // handle response
}

Available Resources and Operations

Available methods

Account

Clients

DeliveryNotes

DeliveryNotes.PublicLink

DeliveryNotes.SignatureAudits

EventCatalog

Events

Invoices

Invoices.Quarterly

Products

Products.Gallery

Products.Video

Proformas

PurchaseInvoices

Quotes

RecurringInvoices

Series

Suppliers

Taxes

TaxReports

Verifactu

Verifactu.AeatAccess

Verifactu.Certificates

Verifactu.Chain

Verifactu.Declaracion

Verifactu.Events

Verifactu.Records

Verifactu.Settings

WebhookEndpoints

WebhookEndpoints.Deliveries

Pagination

Some of the endpoints in this SDK support pagination. To use pagination, you make your SDK calls as usual, but the returned object will be a Generator instead of an individual response.

Working with generators is as simple as iterating over the responses in a foreach loop, and you can see an example below:

declare(strict_types=1);

require 'vendor/autoload.php';

use Factuarea\Sdk;
use Factuarea\Sdk\Models\Components;

$sdk = Sdk\Factuarea::builder()
    ->setSecurity(
        new Components\Security(
            http: '<YOUR_BEARER_TOKEN_HERE>',
        )
    )
    ->build();



$responses = $sdk->recurringInvoices->publicApiV1RecurringInvoicesLogs(
    recurringInvoice: '<value>',
    perPage: '25'

);


foreach ($responses as $response) {
    if ($response->statusCode === 200) {
        // handle response
    }
}

Retries

Some of the endpoints in this SDK support retries. If you use the SDK without any configuration, it will fall back to the default retry strategy provided by the API. However, the default retry strategy can be overridden on a per-operation basis, or across the entire SDK.

To change the default retry strategy for a single API call, simply provide an Options object built with a RetryConfig object to the call:

declare(strict_types=1);

require 'vendor/autoload.php';

use Factuarea\Sdk;
use Factuarea\Sdk\Models\Components;
use Factuarea\Sdk\Utils\Retry;

$sdk = Sdk\Factuarea::builder()
    ->setSecurity(
        new Components\Security(
            http: '<YOUR_BEARER_TOKEN_HERE>',
        )
    )
    ->build();

$body = new Components\AcceptProformaRequest(
    reason: 'Cliente confirma pedido por telefono',
);

$response = $sdk->proformas->publicApiV1ProformasAccept(
    proforma: '<value>',
    idempotencyKey: '01928f10-7c0e-7c4a-9b7d-2f8a6e3c1d4b',
    body: $body,
    options: Utils\Options->builder()->setRetryConfig(
        new Retry\RetryConfigBackoff(
            initialInterval: 1,
            maxInterval:     50,
            exponent:        1.1,
            maxElapsedTime:  100,
            retryConnectionErrors: false,
        ))->build()

);

if ($response->object !== null) {
    // handle response
}

If you'd like to override the default retry strategy for all operations that support retries, you can pass a RetryConfig object to the SDKBuilder->setRetryConfig function when initializing the SDK:

declare(strict_types=1);

require 'vendor/autoload.php';

use Factuarea\Sdk;
use Factuarea\Sdk\Models\Components;
use Factuarea\Sdk\Utils\Retry;

$sdk = Sdk\Factuarea::builder()
    ->setRetryConfig(
        new Retry\RetryConfigBackoff(
            initialInterval: 1,
            maxInterval:     50,
            exponent:        1.1,
            maxElapsedTime:  100,
            retryConnectionErrors: false,
        )
  )
    ->setSecurity(
        new Components\Security(
            http: '<YOUR_BEARER_TOKEN_HERE>',
        )
    )
    ->build();

$body = new Components\AcceptProformaRequest(
    reason: 'Cliente confirma pedido por telefono',
);

$response = $sdk->proformas->publicApiV1ProformasAccept(
    proforma: '<value>',
    idempotencyKey: '01928f10-7c0e-7c4a-9b7d-2f8a6e3c1d4b',
    body: $body

);

if ($response->object !== null) {
    // handle response
}

Error Handling

Handling errors in this SDK should largely match your expectations. All operations return a response object or throw an exception.

By default an API error will raise a Errors\APIException exception, which has the following properties:

Property Type Description
$message string The error message
$statusCode int The HTTP status code
$rawResponse ?\Psr\Http\Message\ResponseInterface The raw HTTP response
$body string The response content

When custom error responses are specified for an operation, the SDK may also throw their associated exception. You can refer to respective Errors tables in SDK docs for more details on possible exception types for each operation. For example, the publicApiV1ProformasAccept method throws the following exceptions:

Error Type Status Code Content Type
Errors\Error 401, 403, 409, 422, 429 application/json
Errors\Error 500 application/json
Errors\APIException 4XX, 5XX */*

Example

declare(strict_types=1);

require 'vendor/autoload.php';

use Factuarea\Sdk;
use Factuarea\Sdk\Models\Components;
use Factuarea\Sdk\Models\Errors;

$sdk = Sdk\Factuarea::builder()
    ->setSecurity(
        new Components\Security(
            http: '<YOUR_BEARER_TOKEN_HERE>',
        )
    )
    ->build();

try {
    $body = new Components\AcceptProformaRequest(
        reason: 'Cliente confirma pedido por telefono',
    );

    $response = $sdk->proformas->publicApiV1ProformasAccept(
        proforma: '<value>',
        idempotencyKey: '01928f10-7c0e-7c4a-9b7d-2f8a6e3c1d4b',
        body: $body

    );

    if ($response->object !== null) {
        // handle response
    }
} catch (Errors\ErrorThrowable $e) {
    // handle $e->$container data
    throw $e;
} catch (Errors\ErrorThrowable $e) {
    // handle $e->$container data
    throw $e;
} catch (Errors\APIException $e) {
    // handle default exception
    throw $e;
}

Server Selection

Override Server URL Per-Client

The default server can be overridden globally using the setServerUrl(string $serverUrl) builder method when initializing the SDK client instance. For example:

declare(strict_types=1);

require 'vendor/autoload.php';

use Factuarea\Sdk;
use Factuarea\Sdk\Models\Components;

$sdk = Sdk\Factuarea::builder()
    ->setServerURL('https://api.factuarea.com/v1')
    ->setSecurity(
        new Components\Security(
            http: '<YOUR_BEARER_TOKEN_HERE>',
        )
    )
    ->build();

$body = new Components\AcceptProformaRequest(
    reason: 'Cliente confirma pedido por telefono',
);

$response = $sdk->proformas->publicApiV1ProformasAccept(
    proforma: '<value>',
    idempotencyKey: '01928f10-7c0e-7c4a-9b7d-2f8a6e3c1d4b',
    body: $body

);

if ($response->object !== null) {
    // handle response
}