Conventions

Formats, idempotency, pagination, errors and rate limits of the v1 API.

Updated:

The basics

  • Base URL https://api.fiuit.com/v1. Breaking changes ship as a new version; additive changes (new fields) can arrive at any time, so ignore fields you do not know.
  • JSON in and out (Content-Type: application/json).
  • Dates in ISO 8601 with offset (2026-10-15T09:45:00-03:00). They are stored in UTC.
  • Amounts in integer cents plus currency (BRL, ARS, PYG, USD).
  • IDs are opaque strings. Fields without a value arrive as explicit null.
  • The machine-readable contract is packages/openapi/openapi.yaml (OpenAPI 3.1) and the recent changes summarize what is new.

Idempotency

POST /holds, POST /holds/{id}/confirm, POST /bookings, POST /bookings/{id}/reschedule and POST /bookings/{id}/actions/{key} carry the Idempotency-Key header (for example a UUID). Repeating a request with the same key within 24 hours returns the original response. Reusing the key with a different body returns 422 with code: "idempotency_conflict".

POST /customers accepts Idempotency-Key optionally. POST /webhook-endpoints does not accept it, because its response contains the secret.

Pagination

List endpoints use cursors:

curl "https://api.fiuit.com/v1/bookings?limit=50" -H "Authorization: Bearer $WAGEND_KEY"
# → { "data": [...], "next_cursor": "eyJpZCI6..." }
curl "https://api.fiuit.com/v1/bookings?limit=50&cursor=eyJpZCI6..." -H "Authorization: Bearer $WAGEND_KEY"

When next_cursor is null, there are no more pages.

Errors

Errors follow RFC 9457 (application/problem+json):

{
  "type": "https://fiuit.com/docs/api/conventions#errors",
  "title": "Slot no longer available",
  "status": 409,
  "code": "slot_taken",
  "alternatives": [{ "start": "2026-10-15T10:15:00-03:00", "end": "2026-10-15T10:45:00-03:00" }]
}
codeStatusWhen
validation_error422Body or parameter validation failed
invalid_api_key401Key missing, invalid or revoked
idempotency_key_required400The Idempotency-Key header is missing on an operation that needs it
insufficient_scope403The key lacks the scope
not_found404Unknown id (or from another project)
slot_taken409Someone else took the time
already_claimed409Another team member took the queue task first
customer_exists409A customer with that phone already exists
invalid_transition409For example confirming a cancelled booking
version_conflict409Someone edited the same resource first (stale version)
hold_expired410The hold expired before confirming
idempotency_conflict422Same key, different body
rate_limited429Too many requests

Every error response carries code: use it to decide what to do, not the text of title.

Rate limits

Limits apply per key and per project (a bucket with a burst of 120 requests and a refill of 2 per second). Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. On a 429, wait Retry-After seconds.