Skip to main content

Quickstart

Get up and running with the Cloud API in under 5 minutes. This guide uses curl -- no SDK required.

Looking for Embedded Local?

If your POS runs on-premise, on tablets, or needs offline support, see the SDK Quickstart instead.

Base URL

https://api.zyntem.dev

For local development:

http://localhost:8080

1. Create an account

Create an account to receive your API key. This is a public endpoint -- no authentication required.

For a typical user-signup flow, send the user's GDPR Art. 7(1) consent capture along with the account fields. Both terms_accepted_at (the wall-clock time the user accepted) and privacy_policy_version (the version of the policy text shown) are required and travel together.

curl -X POST https://api.zyntem.dev/v1/accounts \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp",
"billing_email": "admin@acme.com",
"terms_accepted_at": "2026-03-08T12:00:00Z",
"privacy_policy_version": "v1.0"
}'
Service-to-service provisioning

If you provision accounts from a backend that captured consent out-of-band (e.g. an existing onboarding flow), omit both terms_accepted_at and privacy_policy_version. The request body is a oneOf: either both consent fields are present, or both are absent — partial pairs are rejected with 400 Bad Request.

Response:

{
"account": {
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"name": "Acme Corp",
"billing_email": "admin@acme.com",
"status": "active",
"created_at": "2026-03-08T12:00:00Z"
},
"api_key": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"key_prefix": "zyn_test_",
"key": "zyn_test_abc123def456...",
"environment": "test",
"created_at": "2026-03-08T12:00:00Z"
}
}
Save your API key

The plaintext API key is returned only on creation and cannot be retrieved again. Store it securely.

2. Create a location

Use your API key to create a merchant location. Here's a Spanish location with TicketBAI:

country_config is validated at registration

country_config is parsed and validated against the country-specific schema at the moment you register the location. Invalid config returns 400 Bad Request immediately — there is no fallback "try again at transaction time". Once accepted, the config is sealed and cannot be partially overridden per transaction. Required fields differ per country; see the API reference for the full list of 400 errors.

curl -X POST https://api.zyntem.dev/v1/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"name": "Madrid Office",
"country": "ES",
"address": "Calle Gran Vía 1, 28013 Madrid",
"tax_id": "B12345674",
"country_config": {
"system": "ticketbai",
"territory_code": "48"
}
}'

Response:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"account_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"name": "Madrid Office",
"country": "ES",
"address": "Calle Gran Vía 1, 28013 Madrid",
"tax_id": "B12345674",
"country_config": {
"system": "ticketbai",
"territory_code": "48"
},
"status": "active",
"created_at": "2026-03-08T12:00:00Z",
"updated_at": "2026-03-08T12:00:00Z"
}

3. List your locations

curl https://api.zyntem.dev/v1/locations \
-H "Authorization: Bearer zyn_test_abc123def456..."

Response:

{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Madrid Office",
"country": "ES",
"status": "active"
}
],
"total": 1,
"limit": 20,
"offset": 0
}

4. Update a location

curl -X PATCH https://api.zyntem.dev/v1/locations/550e8400-e29b-41d4-a716-446655440000 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"name": "Madrid HQ"
}'

5. Delete a location (optional)

curl -X DELETE https://api.zyntem.dev/v1/locations/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer zyn_test_abc123def456..."

Returns 204 No Content on success.

6. Submit a transaction

Submit a sale for fiscalization. Zyntem never blocks the cash register -- the response is always 201 Created with the fiscal ID and receipt data populated immediately. Submission to the tax authority is handled automatically in the background.

Test keys default to the emulator -- no cert needed

On zyn_test_* keys, the request below works as-is: Zyntem synthesises a stubbed authority response without uploading a sandbox certificate or making a network call to the regulator. To exercise the real regulator sandbox instead (requires POST /v1/certificates first), opt in with the X-Zyntem-Sandbox header:

  -H "X-Zyntem-Sandbox: authority" \

See the Sandbox modes guide for the full emulator vs authority story and the X-Zyntem-Test-Scenario catalog for driving error paths.

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"location_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-03-09T14:30:00Z",
"items": [
{"description": "Café con leche", "quantity": 2, "unit_price": 250, "tax_rate": 21.0, "tax_amount": 105, "total_amount": 605},
{"description": "Tostada", "quantity": 1, "unit_price": 300, "tax_rate": 21.0, "tax_amount": 63, "total_amount": 363}
],
"pretax_amount": 800,
"tax_amount": 168,
"total_amount": 968,
"currency": "EUR",
"payment_method": "card"
}'

unit_price is pretax in minor currency units (cents). Each line's tax_amount is quantity × unit_price × tax_rate / 100, and total_amount is quantity × unit_price + tax_amount. The request-level totals must equal the sum of the per-line values.

Response:

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "sale",
"status": "pending",
"amount": 968,
"currency": "EUR",
"environment": "test",
"created_at": "2026-03-09T14:30:01Z"
}

7. Check transaction status

Poll for the fiscalization result:

curl https://api.zyntem.dev/v1/transactions/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer zyn_test_abc123def456..."

Response (once fiscalized):

{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "success",
"fiscal_id": "TBAI-12345678",
"environment": "test",
"submitted_at": "2026-03-09T14:30:02Z",
"completed_at": "2026-03-09T14:30:03Z"
}
Use webhooks instead of polling

Configure a webhook to receive fiscalization.completed events instead of polling for status.

Next steps