TaneiPay Developer Hub

Integrate payments, manage sub-merchants, and build on top of the TaneiPay platform. All traffic routes through a single endpoint at taneipay.com.

Merchant API

Build payment integrations

Submit transactions, receive real-time webhooks, export data, and manage your POS environment via REST.

Get started ›
Partner API

White-label & resell

Provision sub-merchants, apply your branding, manage settlements, and integrate as a reseller or embedded platform.

Get started ›
All API calls go to https://taneipay.com/api — use https://sandbox.taneipay.com/api for testing
Base URL
https://taneipay.com/api
Sandbox

Response format

All responses are JSON. Successful responses include the requested data. Errors return an error field.

// Success { "ok": true, "id": "A1B2C3D4" } // Error { "error": "Missing field: grand_total" }

HTTP status codes

CodeMeaning
200Success
201Created
400Bad request — missing or invalid fields
401Unauthorized — missing or expired token
403Forbidden — insufficient role
404Not found
409Conflict — duplicate idempotency key
429Rate limited — 120 req/min per IP
500Internal server error

Authentication

TaneiPay uses Firebase Authentication. Call POST /api/auth/login with your credentials to receive a short-lived ID token (~1 hour). Pass it as a Bearer token on every subsequent request.

Roles

RoleAccess
adminFull access — users, branding, webhooks, exports, audit log
cashierSubmit transactions and view own transaction history only

Login

POST /api/auth/login Public Get Firebase ID token
FieldTypeRequiredDescription
usernamestringMerchant username
pinstring4-digit PIN
totp_codestring6-digit TOTP (required if 2FA is enabled)
curl
Node.js
Python
curl -X POST https://taneipay.com/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","pin":"1234"}' # Response { "token": "eyJhbGci...", "user": { "role": "admin", "name": "Admin" } }
const res = await fetch('https://taneipay.com/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'admin', pin: '1234' }) }); const { token, user } = await res.json();
import requests r = requests.post('https://taneipay.com/api/auth/login', json={'username': 'admin', 'pin': '1234'}) token = r.json()['token']
Token expiry: Tokens expire after ~1 hour. Call /api/auth/login again to refresh. A 401 response means the token has expired or been revoked.
2FA: If TOTP is enabled on the account, the login returns {"totp_required": true}. Re-submit with the same credentials plus totp_code.

Quick Start — submit your first transaction in 3 steps

1

Authenticate

Exchange your username and PIN for a Bearer token.

curl -X POST https://taneipay.com/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","pin":"YOUR_PIN"}'
2

Verify connectivity

Call the public health endpoint to confirm routing is working.

curl https://taneipay.com/api/health # { "status": "ok", "version": "1.3.0", "region": "europe-west1" }
3

Submit a transaction

POST a transaction from your ECR or backend. Always include an Idempotency-Key header to prevent duplicates.

curl -X POST https://taneipay.com/api/transactions \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: ecr-uuid-001" \ -d '{ "ecr_txn_id": "ecr-uuid-001", "user_id": "u-cashier1", "user_name": "Jan Smit", "status": "approved", "grand_total": 12.50, "items": [{"name":"Coffee","qty":1,"priceExVat":2.48,"vatRate":21}] }' # { "ok": true, "id": "A1B2C3D4" }

Transactions

POST /api/transactions Auth Submit a transaction

Creates a new transaction. If a record with the same ecr_txn_id already exists the status is updated (idempotent). Fires a transaction.created webhook for new records.

Headers

HeaderDescription
Idempotency-KeyUnique key per transaction (use the ECR UUID). Prevents double-submission.

Body

FieldTypeRequiredDescription
ecr_txn_idstringUnique ECR transaction UUID
user_idstringCashier user ID
user_namestringCashier display name
statusstringapproved | failed | pending
grand_totalnumberTotal incl. VAT and tip (€)
itemsarrayLine items — see schema below
tip_amountnumberTip (€)
payment_brandstringe.g. Visa, Mastercard
wpiResponseobjectRaw Worldline terminal response
timestampstringISO 8601 timestamp
curl
Node.js
Python
curl -X POST https://taneipay.com/api/transactions \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: ecr-9a7f3b21" \ -d '{ "ecr_txn_id": "ecr-9a7f3b21", "user_id": "u-cashier1", "user_name": "Jan Smit", "status": "approved", "grand_total": 12.50, "tip_amount": 1.00, "items": [ { "name": "Espresso", "qty": 2, "priceExVat": 2.48, "vatRate": 21 }, { "name": "Croissant", "qty": 1, "priceExVat": 2.07, "vatRate": 9 } ], "payment_brand": "Visa", "wpiResponse": { "MaskedPAN": "****4242", "AuthorizationCode": "123456" } }' # { "ok": true, "id": "A1B2C3D4" }
const res = await fetch('https://taneipay.com/api/transactions', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'ecr-9a7f3b21' }, body: JSON.stringify({ ecr_txn_id: 'ecr-9a7f3b21', user_id: 'u-cashier1', user_name: 'Jan Smit', status: 'approved', grand_total: 12.50, items: [{ name: 'Espresso', qty: 2, priceExVat: 2.48, vatRate: 21 }] }) });
import requests r = requests.post('https://taneipay.com/api/transactions', headers={'Authorization': f'Bearer {token}', 'Idempotency-Key': 'ecr-9a7f3b21'}, json={ 'ecr_txn_id': 'ecr-9a7f3b21', 'user_id': 'u-cashier1', 'user_name': 'Jan Smit', 'status': 'approved', 'grand_total': 12.50 })
GET /api/transactions Auth List transactions

Returns paginated transactions. Cashiers see only their own; admins see all.

Query paramTypeDescription
statusstringapproved | failed | pending
fromstringISO 8601 start date (e.g. 2026-04-01)
tostringISO 8601 end date
limitnumberMax results (default 200, max 1000)
offsetnumberPagination offset (default 0)
user_idstringFilter by cashier (admin only)
curl "https://taneipay.com/api/transactions?from=2026-04-01&status=approved" \ -H "Authorization: Bearer <token>" # { "transactions": [...], "total": 142, "limit": 200, "offset": 0 }

Transaction Exports

GET /api/transactions/export/standard Auth Standard report (xlsx/csv)

Downloads a formatted report. Supports Excel (.xlsx) and CSV. Up to 5,000 transactions per export.

ParamTypeDescription
formatstringxlsx (default) or csv
from / tostringISO 8601 date range
statusstringFilter by status

Columns: Transaction ID · Date/Time · Cashier · Status · Items · Subtotal · VAT 0%/9%/21% · Tip · Grand Total · Payment Brand · Card (masked) · Auth Code · Synced

// Browser download const r = await fetch('/api/transactions/export/standard?format=xlsx&from=2026-04-01', { headers: { 'Authorization': 'Bearer ' + token } }); const blob = await r.blob(); const a = Object.assign(document.createElement('a'), { href: URL.createObjectURL(blob), download: 'transactions.xlsx' }); a.click();
GET /api/transactions/export/bespoke Auth Custom-field report

Download a report with only the columns you specify, in the order you specify them. Use preview=true to see the first 5 rows as JSON without downloading.

ParamTypeDescription
fieldsstringComma-separated field keys (see below)
formatstringxlsx or csv
previewbooleanReturn JSON preview of first 5 rows
// Available field keys id, timestamp, user_name, status, items_summary, subtotal, vat_0, vat_9, vat_21, tip_amount, grand_total, payment_brand, masked_pan, auth_code, synced // Preview first 5 rows GET /api/transactions/export/bespoke?fields=timestamp,user_name,grand_total&preview=true

Dashboard Stats

GET /api/stats Auth Revenue stats and chart data
ParamValues
periodday | week | month | year
{ "period": "day", "current": { "count": 42, "revenue": 1284.50, "tips": 87.00, "avg_ticket": 30.58 }, "previous": { "count": 38, "revenue": 1102.00 }, "chart": [ { "label": "09:00", "total": 88.50, "count": 3 } ], "brands": [ { "payment_brand": "Visa", "count": 28, "total": 892.00 } ] }

Branding

Read and update the white-label branding applied to your merchant portal and receipts.

GET /api/branding Public Fetch current branding
{ "brand_name": "Tanei Pay", "logo_url": "https://...", "primary_color": "#0F2D6E", "accent_color": "#4ECDC4", "company_name": "Tanei Payments B.V.", "receipt_footer": "Thank you for your business!" }
PUT /api/branding Admin Update branding settings

Partial update — only send fields you want to change. Colors must be hex (#RRGGBB). Logo: URL or base64 data URI (max ~500 KB).

{ "brand_name": "My Brand", "primary_color": "#1A2B3C", "receipt_footer": "Thank you!" }

Webhook Integration Guide

When a configured event occurs, TaneiPay sends a signed HTTP POST to your endpoint. Delivery is retried up to 3 times on failure. Respond with any 2xx status within 10 seconds.

1

Create your endpoint

Accept POST with Content-Type: application/json. Read the raw body before parsing to validate the signature.

Node.js
Python
PHP
const express = require('express'); const crypto = require('crypto'); const app = express(); app.use(express.raw({ type: 'application/json' })); app.post('/webhook', (req, res) => { const sig = req.headers['x-tanei-signature']; const expected = 'sha256=' + crypto .createHmac('sha256', process.env.WEBHOOK_SECRET) .update(req.body).digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.status(401).json({ error: 'Invalid signature' }); const event = JSON.parse(req.body); console.log(event.event, event.data); res.json({ ok: true }); });
import hmac, hashlib from flask import Flask, request, abort app = Flask(__name__) @app.route('/webhook', methods=['POST']) def webhook(): raw = request.get_data() sig = request.headers.get('X-Tanei-Signature', '') want = 'sha256=' + hmac.new( WEBHOOK_SECRET.encode(), raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(sig, want): abort(401) event = request.get_json(force=True) print(event['event'], event['data']) return {'ok': True}
<?php $raw = file_get_contents('php://input'); $sig = $_SERVER['HTTP_X_TANEI_SIGNATURE'] ?? ''; $want = 'sha256=' . hash_hmac('sha256', $raw, WEBHOOK_SECRET); if (!hash_equals($want, $sig)) { http_response_code(401); exit; } $event = json_decode($raw, true); echo json_encode(['ok' => true]);
2

Configure via API

curl -X PUT https://taneipay.com/api/webhook \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhook", "events": ["transaction.created","transaction.synced"], "secret": "your-signing-secret" }'
3

Test the connection

curl -X POST https://taneipay.com/api/webhook/test \ -H "Authorization: Bearer <token>" # { "ok": true, "status": 200 }

Payload envelope

{ "event": "transaction.created", "timestamp": "2026-04-28T10:23:00.000Z", "data": { "id": "A1B2C3D4", "status": "approved", "grand_total": 12.50, ... } }

Retry policy

Failed deliveries are retried up to 3 times with a 1-second delay. A failure is any non-2xx response or a 10-second timeout. After 3 failures the event is dropped.

Webhook Event Reference

GET /api/webhook Admin Get webhook config + available events
{ "configured": true, "active": true, "url": "https://your-server.com/webhook", "events": ["transaction.created"], "has_secret": true, "last_fired_at": "2026-04-28T10:23:00Z", "last_status": 200, "available_events": [ "transaction.created", "transaction.synced", "branding.updated", "user.created" ] }
transaction.created
Fired when a new transaction is submitted from the ECR app. Use this to sync transactions to your own systems in real time.
{ "event": "transaction.created", "timestamp": "2026-04-28T10:23:00Z", "data": { "id": "A1B2C3D4", "status": "approved", "grand_total": 12.50, "user_name": "Jan Smit", "payment_brand": "Visa", "card_last4": "4242", "items": [{ "name": "Coffee", "qty": 1, "priceExVat": 2.48, "vatRate": 21 }] } }
transaction.synced
Fired when a transaction status is updated (e.g. synced from an offline queue). Same payload structure as transaction.created.
branding.updated
Fired when an admin updates white-label branding. Useful for keeping external systems in sync.
{ "event": "branding.updated", "data": { "fields": ["brand_name","primary_color"] } }
user.created
Fired when a new cashier or admin user is provisioned in the portal.

Signature Verification

Every delivery includes X-Tanei-Signature — an HMAC-SHA256 of the raw request body. Always verify it to prevent spoofing.

Header format

X-Tanei-Signature: sha256=5c5d6e7f8a9b0c1d... X-Tanei-Event: transaction.created

Verification

Node.js
Python
PHP
function verify(rawBody, signature, secret) { const expected = 'sha256=' + require('crypto') .createHmac('sha256', secret).update(rawBody).digest('hex'); return require('crypto').timingSafeEqual( Buffer.from(signature), Buffer.from(expected)); }
import hmac, hashlib def verify(raw_body, signature, secret): expected = 'sha256=' + hmac.new( secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(signature, expected)
function verify(string $raw, string $sig, string $secret): bool { return hash_equals('sha256='.hash_hmac('sha256',$raw,$secret), $sig); }
Always use the raw body — before JSON parsing. Re-serializing JSON may change whitespace or key order, breaking the signature check.

API Explorer

Make live API calls. Authenticate first, then use the request builder below.

Note: This explorer calls the real API. Switch to Sandbox in the sidebar for safe testing.

1. Authenticate

Username
PIN
No token

2. Build request

Method
Path
Body (JSON — leave empty for GET)
Base URL
https://taneipay.com/api/partner/v1
Sandbox

What you can build

CapabilityAPI area
Provision sub-merchants under your brand/merchants
Apply white-label colours, logo, company name/branding
View settlement amounts and SLA metrics/settlements
Set pricing tiers and fee contracts/pricing-tiers
Configure partner-level webhooks/webhooks
Generate and rotate API credentials/api-keys
Scoping: All partner API responses are automatically scoped to your partner_id. You can only read and manage resources belonging to your partner account.

Authentication

The Partner API uses Firebase Authentication with a partner custom claim. Authenticate via the standard login flow and include the resulting ID token as a Bearer token.

POST /api/auth/login Public Get partner Firebase ID token

Partner accounts use the same login endpoint as merchants. The returned token will carry is_partner: true and partner_id as Firebase custom claims.

curl
Node.js
Python
curl -X POST https://taneipay.com/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"partner@example.com","pin":"YOUR_PIN"}' # Use the returned token as Bearer on all /api/partner/v1/* calls # Authorization: Bearer eyJhbGci...
const { token } = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'partner@example.com', pin: 'YOUR_PIN' }) }).then(r => r.json()); // Attach to every partner request const headers = { 'Authorization': `Bearer ${token}` };
import requests resp = requests.post('/api/auth/login', json={'username':'partner@example.com','pin':'YOUR_PIN'}) token = resp.json()['token'] headers = {'Authorization': f'Bearer {token}'}

API Key authentication (server-to-server)

For server-to-server calls, generate an API key in the portal and pass it via X-Partner-Key header instead of a Bearer token.

curl https://taneipay.com/api/partner/v1/merchants \ -H "X-Partner-Key: pk_live_..."
Key security: API keys are shown only once at creation. Store them in your secrets manager, not in code. Rotate immediately if compromised using DELETE /api/partner/v1/api-keys/<id>.

Quick Start — onboard your first sub-merchant

1

Authenticate

curl -X POST https://taneipay.com/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"partner@example.com","pin":"YOUR_PIN"}'
2

List your existing merchants

curl https://taneipay.com/api/partner/v1/merchants \ -H "Authorization: Bearer <token>"
3

Generate an API key for server-to-server calls

curl -X POST https://taneipay.com/api/partner/v1/api-keys \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"name":"Production server key"}' # Returns full key once: { "key": "pk_live_...", "prefix": "pk_live_xx" }
4

Configure webhooks to receive partner events

curl -X POST https://taneipay.com/api/partner/v1/webhooks \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/partner-webhook", "events": ["settlement.completed","merchant.created"], "secret": "your-signing-secret" }'

Sub-Merchant Management

GET /api/partner/v1/merchants Partner List all sub-merchants

Returns all merchants scoped to your partner account. Includes basic merchant info and transaction summary.

{ "merchants": [ { "id": "M-ABC123", "name": "Café Central", "status": "active", "terminal_count": 3, "last_txn_at": "2026-04-28T09:45:00Z", "volume_30d": 18420.50 } ], "total": 12 }
GET /api/partner/v1/revenue Partner Revenue summary across sub-merchants
{ "total_revenue": 284200.00, "merchant_count": 12, "active_terminals": 38, "period": "30d" }

Settlements

GET /api/partner/v1/settlements Partner List partner settlements
ParamTypeDescription
from / tostringISO 8601 date range
statusstringpending | completed | failed
{ "settlements": [ { "id": "SET-2026-04-001", "period": "2026-04-01/2026-04-07", "status": "completed", "gross_volume": 48200.00, "commission": 962.40, "net_payout": 47237.60, "settled_at": "2026-04-09T14:00:00Z" } ] }
GET /api/partner/v1/settlements/sla Partner Settlement SLA metrics
{ "avg_settlement_days": 1.8, "on_time_rate": 0.97, "last_settlement_at": "2026-04-28T14:00:00Z", "next_expected_at": "2026-04-29T14:00:00Z" }

Pricing Tiers & Contracts

Define fee structures that apply to your sub-merchants. Pricing tiers set transaction fee rates; contracts link a tier to one or more merchants.

GET /api/partner/v1/pricing-tiers Partner List pricing tiers
{ "tiers": [ { "id": "tier-001", "name": "Standard", "flat_fee_cents": 20, "percentage_fee": 0.0175, "currency": "EUR", "merchant_count": 8 } ] }
POST /api/partner/v1/pricing-tiers/preview Partner Calculate fee for a given amount
// Request { "tier_id": "tier-001", "amount": 50.00, "currency": "EUR" } // Response { "amount": 50.00, "flat_fee": 0.20, "percentage_fee": 0.875, "total_fee": 1.075, "net": 48.925 }

Partner Webhooks

Subscribe to partner-level events — settlement completions, new sub-merchants, and more. Payloads are HMAC-SHA256 signed, same as the merchant webhook model.

GET /api/partner/v1/webhooks Partner List webhook subscriptions
{ "webhooks": [ { "id": "wh-001", "url": "https://your-server.com/partner-webhook", "events": ["settlement.completed","merchant.created"], "active": true, "last_fired_at": "2026-04-28T14:00:00Z", "last_status": 200 } ], "available_events": [ "settlement.completed", "settlement.failed", "merchant.created", "merchant.deactivated", "pricing_tier.updated" ] }
POST /api/partner/v1/webhooks Partner Create webhook subscription
FieldTypeRequiredDescription
urlstringHTTPS endpoint URL
eventsarrayEvent types to subscribe to (default: all)
secretstringHMAC signing secret (stored encrypted)
POST /api/partner/v1/webhooks/<id>/test Partner Send a test ping
// Response { "ok": true, "status": 200, "latency_ms": 142 }

White-Label Branding

Apply your brand to all sub-merchant portals and receipts. Branding changes propagate automatically to all merchants under your partner account.

GET /api/partner/v1/branding Partner Get current branding config
{ "logo_url": "https://storage.googleapis.com/.../logo.png", "primary_color": "#1A2B3C", "accent_color": "#4ECDC4", "company_name": "Acme Payments B.V.", "support_email": "support@acme.com" }
PUT /api/partner/v1/branding Partner Update branding — propagates to all sub-merchants

Partial update — only send fields you want to change. Changes apply to all sub-merchant portals within seconds.

curl -X PUT https://taneipay.com/api/partner/v1/branding \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "primary_color": "#1A2B3C", "company_name": "Acme Payments B.V.", "support_email": "support@acme.com" }'
POST /api/partner/v1/branding/logo Partner Upload partner logo

Multipart upload. Max 2 MB. Returns the GCS URL for the uploaded logo.

curl -X POST https://taneipay.com/api/partner/v1/branding/logo \ -H "Authorization: Bearer <token>" \ -F "logo=@/path/to/logo.png" # { "logo_url": "https://storage.googleapis.com/.../logo.png" }

API Keys & Credentials

Generate long-lived API keys for server-to-server calls. Keys are shown only once at creation; store them securely. You can have multiple active keys per environment.

GET /api/partner/v1/api-keys Partner List API keys (prefix only)
{ "keys": [ { "id": "key-abc123", "name": "Production server", "prefix": "pk_live_xx", "status": "active", "created_at": "2026-04-01T00:00:00Z", "last_used": "2026-04-28T09:30:00Z" } ] }
POST /api/partner/v1/api-keys Partner Generate new API key
// Request { "name": "Production server key" } // Response — full key shown ONCE { "id": "key-abc123", "name": "Production server key", "key": "pk_live_xxxxxxxxxxxxxxxxxxxxxx", "prefix": "pk_live_xx", "created_at": "2026-04-28T10:00:00Z" }
Save this key now. The full key is returned only once and cannot be retrieved again. If lost, revoke it and generate a new one.
DELETE /api/partner/v1/api-keys/<key_id> Partner Revoke a key — immediate effect
// Response { "ok": true, "revoked_at": "2026-04-28T10:05:00Z" }

Partner API Explorer

Make live partner API calls. Authenticate first with your partner credentials.

Note: Calls go to the real API. Use Sandbox environment in the sidebar for safe testing.

1. Authenticate

Username
PIN
No token

2. Build request

Method
Path
Body (JSON)