Transactions
Submit and query fiscalization transactions. All endpoints require authentication.
Zyntem never blocks the cash register. POST /v1/transactions always returns 201 Created with the fiscal ID and receipt data populated immediately. Submission to the tax authority happens automatically in the background.
Create a transaction
POST /v1/transactions
Submits a transaction for fiscalization. The prepare phase runs synchronously (no network calls) and populates fiscal_id and receipt data immediately. Submission to the tax authority happens automatically in the background. The response is always 201.
Request headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer token (zyn_test_... or zyn_live_...) |
Content-Type | Yes | application/json |
Idempotency-Key | No | UUID for replay protection. Auto-generated if omitted. |
X-Fiscalization-Timeout | No | Timeout hint in seconds for inline fiscalization |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
location_id | string | Yes | UUID of the merchant location |
timestamp | string | Yes | ISO 8601 transaction timestamp |
items | array | Yes | Non-empty array of line items |
pretax_amount | integer (int64) | Yes | Pre-tax amount in minor units, e.g. cents (>= 0) |
tax_amount | integer (int64) | Yes | Tax amount in minor units (>= 0) |
total_amount | integer (int64) | Yes | Total amount in minor units (> 0). Must equal pretax_amount + tax_amount exactly |
currency | string | Yes | ISO 4217 currency code (3 letters, e.g. EUR) |
payment_method | string | Yes | One of: cash, card, transfer, other |
customer_context | object | No | Optional customer data for the fiscal receipt |
Examples
curl:
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": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"timestamp": "2026-03-09T14:30:00Z",
"items": [
{"description": "Café con leche", "quantity": 2, "unit_price": 250, "tax_rate": 21.0},
{"description": "Tostada", "quantity": 1, "unit_price": 300, "tax_rate": 21.0}
],
"pretax_amount": 661,
"tax_amount": 139,
"total_amount": 800,
"currency": "EUR",
"payment_method": "card"
}'
Python:
import requests
response = requests.post(
"https://api.zyntem.dev/v1/transactions",
headers={
"Authorization": "Bearer zyn_test_abc123def456...",
"Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000",
},
json={
"location_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"timestamp": "2026-03-09T14:30:00Z",
"items": [
{"description": "Café con leche", "quantity": 2, "unit_price": 250, "tax_rate": 21.0},
{"description": "Tostada", "quantity": 1, "unit_price": 300, "tax_rate": 21.0},
],
"pretax_amount": 661,
"tax_amount": 139,
"total_amount": 800,
"currency": "EUR",
"payment_method": "card",
},
)
transaction = response.json()
print(transaction["id"], transaction["status"])
JavaScript (Node.js):
const response = await fetch("https://api.zyntem.dev/v1/transactions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer zyn_test_abc123def456...",
"Idempotency-Key": "550e8400-e29b-41d4-a716-446655440000",
},
body: JSON.stringify({
location_id: "7c9e6679-7425-40de-944b-e07fc1f90ae7",
timestamp: "2026-03-09T14:30:00Z",
items: [
{ description: "Café con leche", quantity: 2, unit_price: 250, tax_rate: 21.0 },
{ description: "Tostada", quantity: 1, unit_price: 300, tax_rate: 21.0 },
],
pretax_amount: 661,
tax_amount: 139,
total_amount: 800,
currency: "EUR",
payment_method: "card",
}),
});
const transaction = await response.json();
console.log(transaction.id, transaction.status);
Go:
payload := map[string]interface{}{
"location_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"timestamp": "2026-03-09T14:30:00Z",
"items": []map[string]interface{}{
{"description": "Café con leche", "quantity": 2, "unit_price": 250, "tax_rate": 21.0},
{"description": "Tostada", "quantity": 1, "unit_price": 300, "tax_rate": 21.0},
},
"pretax_amount": 661,
"tax_amount": 139,
"total_amount": 800,
"currency": "EUR",
"payment_method": "card",
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://api.zyntem.dev/v1/transactions", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer zyn_test_abc123def456...")
req.Header.Set("Idempotency-Key", "550e8400-e29b-41d4-a716-446655440000")
resp, err := http.DefaultClient.Do(req)
Response 201 Created
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"account_id": "9f8e7d6c-5b4a-3210-fedc-ba0987654321",
"location_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
"type": "sale",
"status": "pending",
"amount": 800,
"currency": "EUR",
"country": "ES",
"request_payload": { "...": "original request body" },
"fiscal_id": null,
"created_at": "2026-03-09T14:30:01Z"
}
All money fields (pretax_amount, tax_amount, total_amount, unit_price, amount) are integers in minor units (cents for EUR). A €8.00 sale is sent and returned as 800.
Errors
| Status | Error | Cause |
|---|---|---|
400 | Key: 'CreateRequest.LocationID'... | Missing required field |
400 | items must be a non-empty array | Empty or invalid items array |
400 | pretax_amount + tax_amount must equal total_amount | Amount mismatch (exact equality required since values are integer cents) |
400 | location not found | Invalid location_id or not owned by account |
400 | location is not active | Location has been deactivated |
Idempotency
Include an Idempotency-Key header to safely retry requests. If a transaction with the same key already exists for your account, the existing transaction is returned without creating a duplicate.
List transactions
GET /v1/transactions
Returns a paginated list of transactions for the authenticated account, ordered by created_at descending.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 20 | Items per page (max 100) |
offset | integer | 0 | Pagination offset (max 10000) |
status | string | -- | Filter by status: pending, processing, success, failed, failed_permanent, pending_batch, cancelled |
Examples
curl:
# List all transactions
curl https://api.zyntem.dev/v1/transactions \
-H "Authorization: Bearer zyn_test_abc123def456..."
# Filter by status and paginate
curl "https://api.zyntem.dev/v1/transactions?status=success&limit=10&offset=0" \
-H "Authorization: Bearer zyn_test_abc123def456..."
Python:
import requests
response = requests.get(
"https://api.zyntem.dev/v1/transactions",
headers={"Authorization": "Bearer zyn_test_abc123def456..."},
params={"status": "success", "limit": 10},
)
data = response.json()
for tx in data["data"]:
print(tx["id"], tx["status"], tx["amount"])
JavaScript (Node.js):
const params = new URLSearchParams({ status: "success", limit: "10" });
const response = await fetch(
`https://api.zyntem.dev/v1/transactions?${params}`,
{ headers: { "Authorization": "Bearer zyn_test_abc123def456..." } },
);
const { data, total } = await response.json();
console.log(`${total} transactions found`);
Go:
req, _ := http.NewRequest("GET", "https://api.zyntem.dev/v1/transactions?status=success&limit=10", nil)
req.Header.Set("Authorization", "Bearer zyn_test_abc123def456...")
resp, err := http.DefaultClient.Do(req)
Response 200 OK
{
"data": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"account_id": "9f8e7d6c-5b4a-3210-fedc-ba0987654321",
"location_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
"type": "sale",
"status": "success",
"amount": 800,
"currency": "EUR",
"country": "ES",
"request_payload": { "...": "original request body" },
"fiscal_id": "TBAI-12345678",
"created_at": "2026-03-09T14:30:01Z",
"submitted_at": "2026-03-09T14:30:02Z",
"completed_at": "2026-03-09T14:30:03Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}
Get a transaction
GET /v1/transactions/{id}
Returns a single transaction by ID, scoped to the authenticated account. Use this to poll for fiscalization status.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | uuid | Transaction UUID |
Examples
curl:
curl https://api.zyntem.dev/v1/transactions/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer zyn_test_abc123def456..."
Python:
import requests
tx_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
response = requests.get(
f"https://api.zyntem.dev/v1/transactions/{tx_id}",
headers={"Authorization": "Bearer zyn_test_abc123def456..."},
)
tx = response.json()
print(tx["status"], tx.get("fiscal_id"))
JavaScript (Node.js):
const txId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
const response = await fetch(
`https://api.zyntem.dev/v1/transactions/${txId}`,
{ headers: { "Authorization": "Bearer zyn_test_abc123def456..." } },
);
const tx = await response.json();
console.log(tx.status, tx.fiscal_id);
Go:
txID := "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
req, _ := http.NewRequest("GET", "https://api.zyntem.dev/v1/transactions/"+txID, nil)
req.Header.Set("Authorization", "Bearer zyn_test_abc123def456...")
resp, err := http.DefaultClient.Do(req)
Response 200 OK
Returns a Transaction object.
Errors
| Status | Error |
|---|---|
404 | transaction not found |
Cancel a transaction
POST /v1/transactions/{id}/cancel
Cancels a previously fiscalized transaction. A cancellation transaction is created and submitted to the fiscal authority.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | uuid | Transaction UUID to cancel |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Cancellation reason |
Example
curl -X POST https://api.zyntem.dev/v1/transactions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/cancel \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{"reason": "Customer returned items"}'
Response 201 Created
Returns a new transaction with type: "cancellation":
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"type": "cancellation",
"status": "pending",
"amount": 800,
"currency": "EUR",
"environment": "test",
"created_at": "2026-03-09T15:00:00Z"
}
Cancellable statuses
Only transactions in certain statuses can be cancelled:
| Status | Cancellable |
|---|---|
success | Yes |
pending_batch | Yes |
pending_submission | Yes |
pending | No |
processing | No |
failed | No |
failed_permanent | No |
cancelled | No |
Errors
| Status | Error | Cause |
|---|---|---|
404 | transaction not found | Invalid ID |
409 | transaction cannot be cancelled | Transaction is not in a cancellable status |
Idempotency
Cancellation is idempotent. Cancelling an already-cancelled transaction returns the existing cancellation transaction.
Fiscalize a transaction
POST /v1/transactions/{id}/fiscalize
Submits a pending_submission or pending transaction to the tax authority. In the two-phase model, POST /v1/transactions calls prepare() synchronously and populates the fiscal ID and receipt data immediately. This endpoint takes the prepared record and submits it to the tax authority.
For legacy pending transactions (created before the two-phase model), falls back to the full process_transaction flow.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | uuid | Transaction UUID to fiscalize |
Example
curl -X POST https://api.zyntem.dev/v1/transactions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/fiscalize \
-H "Authorization: Bearer zyn_test_abc123def456..."
Response 200 OK
Returns the updated Transaction object with status: "success" and the fiscal authority response in response_payload.
Errors
| Status | Error | Cause |
|---|---|---|
404 | transaction not found | Invalid ID |
409 | transaction cannot be fiscalized (current status: ...) | Transaction is not in a fiscalizable status (pending_submission or pending) |
Transaction object
| Field | Type | Description |
|---|---|---|
id | uuid | Transaction identifier |
account_id | uuid | Owning account identifier |
location_id | uuid | Merchant location identifier |
idempotency_key | string | Idempotency key for replay protection |
type | string | Transaction type: sale or cancellation |
status | string | Fiscalization status (see below) |
amount | integer | Total amount in cents |
currency | string | ISO 4217 currency code |
country | string | ISO 3166-1 alpha-2 country code |
request_payload | object | Original request body (stored for audit) |
response_payload | object | Fiscal authority response (when available) |
fiscal_id | string | Fiscal identifier assigned by the tax authority |
environment | string | test or live (determined by API key prefix) |
error_message | string | Error details (when status is failed) |
submitted_at | datetime | When the transaction was sent to the fiscal authority |
completed_at | datetime | When fiscalization completed |
created_at | datetime | Creation timestamp |
Transaction statuses
| Status | Description |
|---|---|
pending | Created, awaiting fiscalization |
processing | Being processed by the fiscal authority |
success | Fiscalized successfully -- fiscal_id is set |
failed | Fiscalization failed -- see error_message. Will be retried. |
failed_permanent | Fiscalization permanently failed (max retries exceeded, moved to dead letter) |
pending_batch | Inline fiscalization succeeded, awaiting batch submission. Response includes next_batch_at, batch_system, and batch_frequency fields. |
cancelled | Transaction was cancelled via the cancel endpoint |