Read the customer list
The request your app makes forever — a credential, a site, and a path.
Everything up to here was setup you'll never write again. This is the shape of every request from now on: who you are, which site, what you want.
Read
GET /api/modules/clientscurl https://api.hyphabase.com/api/modules/clients \
-u "$HYPHABASE_SECRET_KEY:" \
-H "X-Location-Id: $HYPHABASE_LOCATION_ID" \
-H "Accept: application/json"Portland has no customers yet, so the list is empty — but it's a 200, which means
every one of the six setup steps worked:
{
"data": [],
"links": { "first": "…?page=1", "last": "…?page=1", "prev": null, "next": null },
"meta": { "current_page": 1, "last_page": 1, "per_page": 15, "total": 0 }
}Mind the trailing colon in -u "$HYPHABASE_SECRET_KEY:". The key goes in the username slot
and the password is empty; leave the colon off and curl stops to ask for one.
Write
POST /api/modules/clientsSarah's first customer:
curl -X POST https://api.hyphabase.com/api/modules/clients \
-u "$HYPHABASE_SECRET_KEY:" \
-H "X-Location-Id: $HYPHABASE_LOCATION_ID" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Idempotency-Key: first-customer-demo" \
-d '{
"first_name": "Dana",
"last_name": "Whitfield",
"email": "dana@example.com",
"phone": "+15035550143"
}'All four fields are required — including phone, which surprises people — and email
has to be unique within Portland. The Idempotency-Key means running this twice
creates one customer, not two. See Safe retries.
In your own app
A browser never sees the secret key. It signs the person in and acts as them, which is also what you want for an audit trail — "Dana booked this", not "the server booked this".
// Once, before the first sign-in.
await fetch('https://api.hyphabase.com/sanctum/csrf-cookie', {
credentials: 'include',
});
await fetch('https://api.hyphabase.com/api/auth/session/login', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': readCookie('XSRF-TOKEN'),
},
body: JSON.stringify({ email, password }),
});
// From then on, the session travels in a cookie you never touch.
const response = await fetch(
'https://api.hyphabase.com/api/modules/clients',
{ credentials: 'include', headers: { 'X-Location-Id': locationId } },
);
const { data: customers } = await response.json();The whole flow, including the 419 you'll eventually hit, is in
Browser apps.
Break it on purpose, once
Three failures you'll meet for real. Triggering them now means recognising them later instead of debugging blind.
Leave off the site header → 400:
{ "message": "Unable to resolve location context. Provide X-Location-Id." }Use a key you never granted → 403:
{ "message": "This API key does not have access to the requested location." }Try to write with the publishable key → 403, and it tells you what was missing:
{ "message": "You do not have permission to perform this action.",
"required_permission": "clients.create" }That required_permission field is the fastest fix in the whole API — it names exactly
what to add to the key.
Where the paths live
Business endpoints sit under /api/modules/{module}:
/api/modules/clients
/api/modules/employees/users
/api/modules/organizations/audit-logsNot /api/clients. A module also has to be switched on for the site, or its routes
return 403 however your key is scoped — see Modules.