Documentation
Latchley Platform
Generate time-limited door access codes for your properties from any external system (your own booking flow, a PMS, or a no-code tool) with a single REST call. Authenticate with a per-property API key, and Latchley creates the code, emails it to the guest, and the door opens for exactly the window you set.
Overview
The Access Code API lets any external system generate a Latchley door code for a property it holds an API key for. You send the guest details and the validity window; Latchley picks (or accepts) a PIN, emails it to the guest, and the code opens the door for exactly that window. Codes are enforced on-device, so they keep working even if the door's internet drops.
| Step | You | Latchley |
|---|---|---|
| 1 | Generate a per-property API key in the dashboard | Scopes the key to that one property |
| 2 | POST /api/v1/codes with guest + validity window | Validates, creates a time-limited PIN |
| 3 | (nothing) | Emails the code to the guest and syncs it to the door |
| 4 | Guest enters the PIN at the door | Opens for the window; auto-expires after |
This API generates access codes. It does not manage reservations, availability, or payments. Pair it with whatever booking/PMS you already run: when a stay is confirmed on your side, call this endpoint to issue the guest their door code.
Quickstart
Generate a key under Settings → API Keys, then create a code. Replace YOUR_DOMAIN with your Latchley host.
curl -X POST https://YOUR_DOMAIN/api/v1/codes \
-H "Authorization: Bearer latchley_live_…" \
-H "Content-Type: application/json" \
-d '{
"guest_name": "Sarah Chen",
"guest_email": "sarah@example.com",
"valid_from": "2026-07-10T15:00:00Z",
"valid_until": "2026-07-13T11:00:00Z"
}'The response contains the generated pin. The guest also receives it by email automatically.
Authentication
Every request authenticates with a per-property API key sent as a Bearer token:
Authorization: Bearer latchley_live_…
A key is scoped to exactly one property, so you never pass a property id. The key itself determines which property the code is created for. Keys come in two flavours by prefix:
| Prefix | Mode | Behaviour |
|---|---|---|
latchley_live_ | Production | Creates real codes and emails the guest |
latchley_test_ | Test | Validates your request and returns a masked code, but creates nothing and sends no email |
A missing, malformed, or revoked key returns 401 INVALID_API_KEY.
Generating an API key
In the dashboard, open Settings → API Keys, pick the property in the switcher, and click Generate Key. Give it a name you'll recognise (e.g. “Booking website”).
The full key is displayed a single time, on creation, so copy it into your integration's secrets right away. Latchley only stores a hash, so it can never show you the key again; if you lose it, revoke it and generate a new one. Tick Test key to create a latchley_test_ key for wiring up safely.
GET /api/v1/doors
Lists the doors on the key's property, so you can discover the device_ids to scope a per-door code (and tell whether scoping is needed at all). The key determines the property, so there are no parameters.
Example response
{
"ok": true,
"door_scope_mode": "per_door",
"doors": [
{ "id": "a1b2…", "name": "Main entrance", "shared": true, "status": "online" },
{ "id": "c3d4…", "name": "Room 2", "shared": false, "status": "online" },
{ "id": "e5f6…", "name": "Room 3", "shared": false, "status": "offline" }
]
}| Field | Meaning |
|---|---|
door_scope_mode | per_door means a code must name its doors via device_ids. all_doors means every code opens all doors and device_ids is not accepted. |
doors[].id | The value to put in device_ids on POST /v1/codes. |
doors[].shared | A shared door (e.g. a main entrance). Always added to every code automatically, so you don't need to pass it. |
doors[].status | online, offline, or pending_config. |
POST /api/v1/codes
Creates a time-limited access code for the key's property and emails it to the guest.
| Field | Type | Required | Description |
|---|---|---|---|
guest_name | string | Yes | Guest name, shown in the dashboard and the email. |
guest_email | string | Yes | Where the code is emailed. Must be a valid address. |
valid_from | ISO 8601 | Yes | When the code becomes active. Use a UTC (…Z) timestamp to avoid ambiguity. |
valid_until | ISO 8601 | One of* | When the code expires. |
duration_hours | number | One of* | Alternative to valid_until: hours after valid_from. |
guest_phone | string | No | Optional contact number stored with the code. |
device_ids | string[] | Per-door | Which doors the code opens. Required when the property is in per-door mode; rejected otherwise. Shared doors are always added automatically. |
pin | string | No | Force a specific 6-digit PIN, or an nfc:<tag-id>. Omit to auto-generate a unique PIN. |
* Provide either valid_until or duration_hours.
Example response
{
"ok": true,
"code": {
"id": "8f3c…",
"pin": "486213",
"status": "active",
"guest_name": "Sarah Chen",
"guest_email": "sarah@example.com",
"valid_from": "2026-07-10T15:00:00.000Z",
"valid_until": "2026-07-13T11:00:00.000Z",
"device_ids": []
}
}If the property is in per-door mode, device_ids is required, so a code must name at least one door. Fetch the ids from GET /v1/doors. Any door marked as shared (e.g. a main entrance) is always included on top of your list, server-side, so you can't accidentally issue a code that skips it, and you don't need to pass it yourself. For all-doors properties, omit device_ids.
Error Codes
Errors return { "ok": false, "error": "CODE", "message": "…" } with the matching HTTP status.
| Error | HTTP | Meaning |
|---|---|---|
| INVALID_API_KEY | 401 | Missing, malformed, or revoked key. |
| INVALID_REQUEST | 400 | A field failed validation. The message says which. |
| MISSING_FIELDS | 400 | Request body wasn't valid JSON. |
| NOT_FOUND | 404 | The key's property no longer exists. |
| RATE_LIMIT_EXCEEDED | 429 | Over the per-key hourly limit. |
| SERVER_ERROR | 500 | Something failed on our side. Safe to retry. |
Rate Limits
Each API key is limited to 100 requests per hour. Over the limit returns 429 RATE_LIMIT_EXCEEDED. Back off and retry after the window resets. Issuing a code per confirmed stay stays comfortably within this; if you expect higher volume, email luca@eventcage.com.
Questions? Email luca@eventcage.com