Authentication
Create an API key with the permissions your site needs, send it with every request, and keep it safe.
Every Org API request carries your organization's API key in the X-Api-Key header. The key is the only thing that says which organization you're working with. There's no organization id or slug in the URL.
Create a key
You need the api-keys:manage permission to manage API keys. The admin role has it, and the member role doesn't.
Open the API keys page
In the dashboard, go to Settings → API keys. The direct address is lahuta.org/dash/<your-org>/api-keys, for example lahuta.org/dash/northside-pottery/api-keys.
Create the key
Select Create API key. Give it a Name that says where it's used, like "Website" or "Front desk tablet". In the Permissions list, choose what the key may do. Pick only what that site or tool needs: see Key permissions for which endpoints each permission opens. Then select Create API key again to confirm.
Copy it right away
The Save your key dialog shows the key once. Select Copy and close and paste it straight into your server's environment variables or secret manager. If you lose it, create a new key.
Denied by default
A key can only do what you give it. With no permissions chosen, it can still
read what your public pages show, like your events and products, and every
other endpoint answers 403.
Send the key
Put the key in the X-Api-Key header of every request.
curl https://api.lahuta.org/v1/events \
-H "X-Api-Key: $LAHUTA_API_KEY"What a key can do
- One key, one organization. A key belongs to the organization it was created in. If you run two organizations, create a key in each.
- Only what you chose. A key can call the endpoints its permissions cover, plus the ones open to every key. Anything else is refused with a
403. - Older keys for now. A key created before October 9, 2026 was made before keys had permissions, and it can't be given any. Until you replace it, it can do everything a key may do. Create a new key with only the permissions your site needs, and switch your site to it.
- Shared limits. All keys of one organization share the same rate limits.
Key permissions
Endpoints that read what your public pages already show work with any valid key, even one with no permissions:
| Endpoint | What it reads |
|---|---|
GET /v1/org | Your organization and whether it takes payments |
GET /v1/me | The signed-in visitor (it also needs their token) |
GET /v1/events, GET /v1/events/{shortCode} | Upcoming events and one event's page |
GET /v1/blog/posts, GET /v1/blog/posts/{slug} | Published blog posts |
GET /v1/products, GET /v1/products/{slug} | Your products |
GET /v1/collections, GET /v1/collections/{slug} | Your collections |
GET /v1/shelf | Your referral shelf |
GET /v1/services, GET /v1/services/{slug} | Your services |
GET /v1/bookings/types, GET /v1/bookings/types/{slug}/slots | Booking pages and their open times |
Every other endpoint needs a permission:
| Permission | Endpoints |
|---|---|
contacts:read | GET /v1/contacts, GET /v1/contacts/{contactId}, GET /v1/contacts/{contactId}/notes, GET /v1/contacts/{contactId}/groups, GET /v1/groups, GET /v1/groups/{group}, GET /v1/groups/{group}/rows, GET /v1/reminders, GET /v1/members |
contacts:write | PUT /v1/contacts, PATCH /v1/contacts/{contactId}, POST /v1/contacts/archive, POST /v1/contacts/{contactId}/notes, PUT /v1/groups, POST /v1/groups/{group}/rows/remove, POST /v1/reminders, PATCH and DELETE /v1/reminders/{reminderId}, POST /v1/uploads, POST /v1/notifications, POST /v1/sms-consents |
events:read | GET /v1/events/{shortCode}/registrations, GET /v1/registrations/{registrationId} |
inbox:write | POST /v1/emails, POST /v1/texts |
newsletter:write | POST /v1/newsletter/subscribe |
bookings:write | POST /v1/bookings |
orders:read | GET /v1/checkouts/{checkoutId}, GET /v1/products/checkouts/{checkoutId} |
orders:write | POST /v1/checkouts, POST /v1/products/checkouts |
workflows:publish | POST /v1/workflows/{workflowId}/start |
A write permission includes read for the same area, so a key with contacts:write can also look contacts up. A key with orders:write can open a checkout and check whether it was paid.
For example, a website with a newsletter signup box, a booking form and a pay button needs newsletter:write, bookings:write and orders:write. A site that only lists your classes and blog posts needs no permissions at all.
A key can never hold members:manage, api-keys:manage or payments:manage. If one of them is chosen when the key is created, Lahuta ignores it, so a leaked key can't change who's on your team, create more keys or touch your Stripe connection. Other permissions, like events:write, don't open any Org API endpoint.
Keep it secret
Anyone who has your key can do whatever its permissions allow, like adding contacts, emailing your customers from your organization or opening payment pages in your name. Treat it like a password.
- Keep it on your server, in an environment variable or secret manager. Never put it in browser JavaScript, a mobile app or a public repository.
- Have your site's front end call your own server, and let your server call Lahuta. Browsers can't call the Org API from other websites: the API doesn't allow the
X-Api-Keyheader in cross-site requests, so those calls fail before they reach Lahuta. - Use one key per site or tool. Then you can revoke one without breaking the others, and the Last used column tells you which ones are active.
Rotate or revoke a key
To rotate a key, create a new one with the same permissions, deploy it to your server, check that requests work, then revoke the old one. Do the same to give a key a permission it's missing.
To revoke a key, open the actions menu on its row, select Revoke key, and confirm with Revoke key in the Revoke API key? dialog. This can't be undone. Requests that use the key are refused from then on.
If a key leaks, revoke it first, then create a replacement.
When a key is refused
A missing, wrong or revoked key gets a 401 with this body:
{ "_tag": "Unauthorized" }The same response comes back if the key's organization no longer exists. Rarely, a 401 means Lahuta couldn't check the key at that moment. If a key that worked a minute ago gets a 401, retry once before you rotate it.
A valid key that doesn't have the permission an endpoint needs gets a 403. The body names the permission:
{ "_tag": "Forbidden", "permission": "contacts:write" }Create a key that has that permission and switch your server to it, as described in Rotate or revoke a key. Retrying with the same key gets the same 403.
The one exception: signed-in visitors
GET /v1/me needs a second credential. Next to your API key, you send the access token of the person signed in to your site, as Authorization: Bearer <token>. You'd use it when your site signs people in with their Lahuta account and you want to know who they are, or whether they're on your team.
Any other call can carry the same token, and it has to when a contact you send carries userId: Lahuta links a contact only to the visitor whose token the call proves. See Signed-in visitors.