Test mode & sandbox
Build your integration safely with fact_test_ keys — isolated sandbox data, AEAT, email and webhooks switched off, and what a test key can and cannot do on your account.
Every Factuarea API key belongs to one of two environments, told apart by its prefix:
| Prefix | Environment | Operates on | External effects |
|---|---|---|---|
fact_live_ | live (production) | Your real company | Real: legal fiscal numbering, VeriFactu → AEAT, FACe submissions, emails to clients, outbound webhooks |
fact_test_ | test (sandbox) | An isolated sandbox company | Switched off (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. No
request parameter changes the environment — it is determined entirely by
the key you authenticate with.
Always build and test your integration with a fact_test_ key first.
Once your flow works end-to-end, switch the prefix to fact_live_ to go
to production. The API surface is identical in both environments.
Getting a test key
Test keys are created from the developer dashboard exactly like live keys, selecting the Test environment (app.factuarea.com/settings/developers/api-keys). The generated secret looks like:
fact_test_<24 alphanumeric characters>Example:
fact_test_3pXnR2VbY7TcA9eFmN5z8KqWSame format and entropy as a live key (24 base62 characters), same scopes
apart from those of the modules that test mode does not offer (for example
webhooks:write), same rate-limit tier. The only difference is the prefix and
what it points to. As with live keys, the secret is shown only once at creation — if
you lose it you must rotate.
You can also issue one with a live key, with
POST /v1/accounts/{account}/api-keys
and environment set to test. A test key never issues keys: see
the account axis.
Using a test key
Send it on every request just like a live key, via Authorization: Bearer
or X-API-Key:
curl https://api.factuarea.com/v1/companies/{company}/contacts \
-H "Authorization: Bearer fact_test_3pXnR2VbY7TcA9eFmN5z8KqW"The same endpoints and operations available in live are available in test — nothing is removed or stubbed — with the exceptions that the account axis and the webhook endpoints describe.
Data isolation: the sandbox company
A fact_test_ key operates on a dedicated sandbox company — a technical
"twin" of your real company, provisioned automatically the first time you
use test mode, that inherits your real company's plan so feature-gating is
faithful. Thanks to multi-tenant isolation by company:
- Resources created with a
fact_test_key are not visible to afact_live_key, and vice versa. - Test fiscal numbering uses the sandbox's own series and never consumes or alters the correlative numbering of your production series.
This is structural isolation, not a filter: test and live data live in separate companies, so there is no way for them to mix.
What is switched off in test
When you operate with a fact_test_ key (sandbox environment), effects that
reach the outside world are disabled so you can exercise your
integration without real-world consequences:
| Effect | In live | In test |
|---|---|---|
| VeriFactu | The Alta record is created and transmitted to the AEAT. | The Alta record is created locally, but never transmitted to the AEAT. |
| Document emails are delivered to real recipients. | Document emails are not delivered to real recipients. | |
| Webhooks | Subscribed events are delivered to your external HTTP endpoints. | Events are recorded with livemode: false (queryable via GET /v1/companies/{company}/events) but not delivered to your endpoints. An account endpoint of the test environment records each delivery and does not send it either: see account webhook endpoints in test. |
| FACe (FacturaE) | Submissions are presented to the real FACe web service. | The whole flow is simulated — no SOAP call leaves Factuarea and the registry number is synthetic (FACE-SANDBOX-*). See FACe invoicing. |
Everything else behaves identically: validation, totals, document state machines, idempotency, pagination, rate limits and error envelopes are the same as in production.
Because webhooks are not delivered in test, you cannot exercise your
webhook receiver against sandbox data. That includes test-event, which from
the sandbox is recorded and never delivered. A test key cannot call the webhook
endpoints of a tax ID at all: it cannot carry webhooks:write, and an
operation that it carries anyway answers
403 module_not_available_in_sandbox.
Exercise your receiver with a live key on a live endpoint instead. A
test-event is signed and retried like a
production delivery and carries "test": true, so it validates your signature
check and your real handler. POST /v1/companies/{company}/webhook-endpoints/{id}/ping
only confirms that your URL answers: it sends a bare
{ "type": "webhook.ping" } with no signature.
The account axis with a test key
A fact_test_ key belongs to your real account, so it reaches the account axis:
GET /v1/me and the
operations under /v1/accounts/{account}/…. The rule is short: it reads, and it
does not write on a live account.
You do with a fact_test_ key | What you get |
|---|---|
| Read your account: team, invitations, keys, tax ID portfolio, tax ID transfers, usage, employee seats and provisioning usage | 200. The key reads your account as a production key does. |
| Read the webhook endpoints, the deliveries and the request logs of your account | 200, but only those of the test environment. A test key never reads the live ones. |
| Write on your live account: team, invitations, ownership, tax ID transfers, tax ID portfolio and employee seats | 403 live_credential_required, with nothing written. Use a fact_live_ key. |
| Create, edit, rotate, test, retry or delete an account webhook endpoint | 403, because a test key cannot carry webhooks:write. See account webhook endpoints in test. |
| Issue, re-scope, rotate or revoke API keys | 403 live_credential_required, with nothing written. A test key never issues a key, so it never issues a live one. To get a test key, issue it with a live key and environment set to test, or create it in the app. |
| Operate the test managed accounts that your account provisions: change their level or plan, end their management | Allowed, with no charge: see test managed accounts. |
Test managed accounts
If you provision accounts for your clients, you can rehearse it with a test key.
POST /v1/accounts with a fact_test_ key creates a test account: a managed
account in the test environment, with a sandbox company as its tax ID and no
real company behind it. It is not charged, it has no claim voucher
(claim_token and claim_url are null) and nobody claims it, it does not
count against max_managed_accounts, and nothing reaches VeriFactu or a real
recipient. Only your fact_test_ keys and the app in Test mode reach it: a live
key answers 404 account_not_found. A test account
never becomes a live one, and its external_ref is unique across both
environments: repeating it with a live key answers
409 managed_account_external_ref_in_other_environment.
Two things differ from what you may expect:
- The bulk import. With a test key, the
managed_accountrows create or reconcile test accounts. Theown_nifrows and the options row come outERRORwithcodelive_credential_required, create nothing and add0to the totals. - The data of its tax ID. A test key reaches only the sandbox company of your
own account, so
/v1/companies/{company}/…with theidof a tax ID of a test account answers404 profile_not_found. To work with that data, switch the app to Test mode: your test accounts appear among your client accounts, and choosing one makes its sandbox company the active one.
Test managed accounts with fact_test_ walks through the whole path, from reading your account to reading the events of a test account.
Account webhook endpoints in test
An account webhook endpoint belongs to one
environment, live or test, fixed when it is created and never changed. An
endpoint of the test environment receives only the events of the test
environment, and a live endpoint never receives them. It never makes an outgoing
request: each delivery is recorded with its envelope and stays pending, with
attempt set to 0. So "receiving" an event means reading it, with
GET /v1/accounts/{account}/webhooks/{webhook}/deliveries.
A test key cannot create a test endpoint. The environment of an endpoint comes
from the credential or the session that creates it, and an environment in the
body is ignored. A fact_test_ key reads the endpoints and the deliveries of the
test environment with webhooks:read, but it cannot create, edit, rotate, test,
retry or delete an account endpoint. You create the test endpoint in the web app,
with Test mode on, from Settings → Developers → Webhooks, choosing Account:
test managed accounts shows the steps.
With the official SDKs
The TypeScript and PHP SDKs follow the same rule: the key
prefix selects the environment — there is no flag. Build against a
fact_test_ key, then swap the env var to fact_live_ to go to production.
No code changes.
import { Factuarea } from "@factuarea/sdk";
const sandbox = new Factuarea({ apiKey: "fact_test_…" });
sandbox.environment; // "test"
const prod = new Factuarea({ apiKey: "fact_live_…" });
prod.environment; // "live"The SDK exposes the resolved environment on .environment, derived from
the prefix — handy for guards and logging.
use Factuarea\Sdk\Custom\FactuareaClient;
$sandbox = FactuareaClient::create('fact_test_…'); // sandbox
$prod = FactuareaClient::create('fact_live_…'); // productionWebhooks are still not delivered in test even via the SDK. To exercise
your receiver's SDK verifier, send a
test-event with a live key: it is signed
like a production delivery. ping is not signed, so it cannot exercise the
verifier.
Switching environment in the app
Beyond API keys, the Factuarea web app lets you toggle between live and test at any time from the top bar. Switching to test:
- Re-issues your session without re-login, pointing it at the sandbox company (provisioning it if it doesn't exist yet). The previous token is invalidated.
- Shows a persistent "Test mode" banner across the whole interface so the active context is always obvious.
- Re-hydrates the client state so listings reflect sandbox data and never show cached production data.
Switching back to live re-issues the session against your real company. The sandbox is never shown as a real company in the company selector — it exists only to back the test environment.
Test mode is also where you create the test endpoints of your
account webhooks, and where you work with the data of the tax
IDs of your test managed accounts, which a test key does not
reach.
From test to production
When your integration works against fact_test_:
- Create a
fact_live_key in the dashboard (same scopes you validated in test). - Swap the key your client uses (environment variable / secret manager).
- No code changes are needed — the request shape is identical.
From that point, real effects (fiscal numbering, VeriFactu → AEAT, emails, webhooks) are active again.
Nothing you created in test moves to production. A test managed account is never
promoted: provision the live ones with a fact_live_ key and a different
external_ref. Create the live account webhook endpoints with a live key or in
the app in live mode, because a test endpoint never receives live events.
Store environment and delivery
A store’s environment is separate from the environment selected by the API-key prefix. New stores default to test, with automatic invoicing and sending disabled. Store test events can be discarded as store_environment_test; the isolated sandbox also suppresses real email and public webhook delivery. Changing the store flag does not promote a test credential to live. See stores and the n8n trigger.