Authentication and conventions
How API keys work, what each scope unlocks, how test mode is kept apart, and the rules every endpoint follows: amounts, dates, pagination, limits and errors.
Checked against the API code on October 13, 2026
Base URL
https://api.ezuspay.app/v1
There is one URL for live and test. The key you send decides which environment you reach.
Call the API from your server. It does not send CORS headers to third-party domains, so a browser on your site cannot call it directly. That is also what keeps your key private.
API keys
Send the key in the x-api-key header on every request. The API does not read Authorization: Bearer.
curl https://api.ezuspay.app/v1/ping -H "x-api-key: ezpay_live_4f1c..."
| Prefix | Environment | Format |
|---|---|---|
ezpay_live_ | Live agency | prefix + 64 lowercase hex characters |
ezpay_test_ | Test copy of the agency | prefix + 64 lowercase hex characters |
- Who creates keys. Agency admins, in Settings → Integrations → API keys. Other roles cannot see or create keys.
- Shown once. Ezus Pay stores only a hash of the key. Copy it when it is created.
- Expiry. Optional. The app offers no expiry, 90, 180 or 365 days.
- Revocation. From the same screen. A revoked key stops working on the next request.
- Check a key with
GET /v1/ping. It returns the agency, the environment and the key's scopes.
| Error | When |
|---|---|
401 API_KEY_MISSING | No x-api-key header |
401 API_KEY_INVALID | Wrong format, unknown key, or a test key used on a live agency |
401 API_KEY_REVOKED | The key was revoked, or the agency was closed |
401 API_KEY_EXPIRED | The expiry date has passed |
Scopes
A key only reaches the endpoints its scopes allow. Give each integration the smallest set it needs.
| Scope | Unlocks |
|---|---|
bookings:write | POST /v1/bookings, POST /v1/bookings/:id/resync |
payment-links:read | GET /v1/checkout-fields, GET /v1/bookings/:id/payment-links, GET /v1/payment-links/:id |
payment-links:write | POST /v1/bookings/:id/payment-links |
transactions:read | GET /v1/transactions, GET /v1/transactions/:id, GET /v1/bookings/:id/transactions, GET /v1/payment-links/:id/transactions |
refunds:write | POST /v1/transactions/:id/refunds |
GET /v1/ping needs no scope. A call without the right scope returns 403 SCOPE_INSUFFICIENT, with the missing scope in the message.
Test mode
Test mode is a full copy of your agency: its own bookings, links, payments, webhooks and a Stripe test account. Your settings, branding and checkout fields are shared with it.
- Create a test key while the app is in test mode. It starts with
ezpay_test_. - Every call with that key reads and writes test data only.
GET /v1/pingreturns"mode": "TEST". - Checkout links in test mode use your agency slug followed by
--sandbox, and show a Test badge. - A test key can never reach live data.
- An admin can reset test data from the app. Bookings, links and payments are erased. Keys, webhooks and the Stripe test account are kept.
Stripe test values that work on the checkout:
| Use | Value |
|---|---|
| Card that succeeds | 4242 4242 4242 4242, any future date, any CVC |
| Card that asks for 3D Secure | 4000 0027 6000 3184 |
| SEPA Direct Debit (EUR) | FR1420041010050500013M02606 |
Rate limits
Limits apply per API key, over a rolling minute.
| Requests | Limit |
|---|---|
Reads (GET) | 600 per minute |
Writes (POST) | 60 per minute |
Each response carries the standard RateLimit and RateLimit-Policy headers. Past the limit you get 429 RATE_LIMIT_EXCEEDED with a Retry-After header in seconds. Wait that long, then retry.
Responses and errors
Every response has the same envelope.
{ "success": true, "data": { } }
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"fields": [{ "path": "amountCents", "message": "Minimum amount is 1 unit" }]
}
}
- Branch on
error.code. Messages are for humans and some are in French. fieldsappears on validation errors only, with one entry per invalid field.- Unknown properties in a request body are ignored.
| Status | Meaning |
|---|---|
200 / 201 | Success. 201 when something was created |
202 | Accepted, finished later. Used by refunds |
401 | Key missing, invalid, revoked or expired |
403 | Scope missing |
404 | Resource not found in this agency |
409 | Conflict with the current state |
422 | Validation failed, or a business rule refused the request |
429 | Rate limit reached |
502 | Ezus or Stripe answered with an error |
Endpoint-specific codes are listed in the API reference.
Conventions
- Amounts are integers in the currency's smallest unit. Field names end in
Cents.144000in EUR is 1,440.00 €. The maximum is99999999. - Currencies use uppercase ISO 4217 codes. A booking defaults to the agency's currency. A payment link always uses its booking's currency.
- Timestamps are ISO 8601 in UTC, with milliseconds:
2026-04-22T14:30:00.000Z. - Calendar dates in requests use
YYYY-MM-DD. - IDs are UUIDs. The Ezus references
ezusProjectIdandezusAlternativeIdare free strings. - Pagination applies to transaction lists only:
page(from 1) andlimit(1 to 100, default 50). The response carriespagination.totalandpagination.totalPages. - Idempotency. There is no
Idempotency-Keyheader.POST /v1/bookingswith anezusProjectIdreturns the existing booking instead of a duplicate. Retrying a link or refund creation creates a second one, so check the first result before you retry. - Request bodies are JSON, up to 1 MB.