{
  "openapi": "3.1.0",
  "info": {
    "title": "KoreLock API",
    "version": "1.0",
    "summary": "Manage smart locks, credentials and access for KoreLock-powered devices.",
    "contact": {
      "name": "KoreLock",
      "url": "https://korelock.com"
    },
    "description": "The KoreLock API is a REST API for managing locks, locations and the people who can open them.\nRequests and responses use JSON, and every request is authenticated with an OAuth 2.0 access token.\n\n| | |\n|---|---|\n| **Base URL** | `https://api.korelock.com/v1` |\n| **Auth** | OAuth 2.0 (client credentials or authorization code + PKCE) |\n| **Format** | JSON over HTTPS |\n\n> **Using an AI assistant?** KoreLock has an MCP server, so Claude, ChatGPT and Codex can look up and manage your locks, people and schedules for you. See [Use with AI assistants](#description/use-with-ai-assistants).\n\n# Quick start\n\nThis guide takes you from a new account to unlocking a lock with a PIN in five steps.\n\n## 1. Get an access token\n\n1. [Create a KoreLock account](https://app.korelock.com/users/sign_up).\n2. In the web app, expand your user name in the left panel (account owners only) and click **OAuth Applications**.\n3. Click **New Application**, name it something like `QuickStart app`, and submit. Copy the generated `client_id` and `client_secret`.\n4. Exchange them for a token:\n\n```bash\ncurl -X POST https://api.korelock.com/oauth/token \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"grant_type\": \"client_credentials\",\n    \"client_id\": \"'\"$CLIENT_ID\"'\",\n    \"client_secret\": \"'\"$CLIENT_SECRET\"'\"\n  }'\n```\n\nTokens last 2 hours (`expires_in: 7200`). Request a new one before it expires; an expired token returns `401 Unauthorized`.\n\nTo check that it works, list your locks. A new account returns an empty `items` array:\n\n```bash\ncurl https://api.korelock.com/v1/locks \\\n  -H \"Authorization: Bearer $AUTH_TOKEN\"\n```\n\n> The client credentials flow only reaches the account that registered the application. If you're building an integration that other KoreLock customers will connect to, use the [authorization code flow](#tag/authentication) instead.\n\n## 2. Register a lock\n\nThe quickest way to register a lock is the KoreLock Admin mobile app, which also connects the lock to Wi-Fi.\n\n1. Install the Admin app and sign in with your KoreLock account.\n2. Put batteries in the lock and keep your phone close to it.\n3. Tap **Register Lock**, pick the lock's serial number, name it, install any firmware update and connect it to Wi-Fi.\n\nYour lock now appears in [`GET /v1/locks`](#tag/locks/GET/v1/locks). You can also [register locks through the API](#tag/locks/POST/v1/locks).\n\n## 3. Lock and unlock over Wi-Fi\n\nSend an unlock command with the lock's `id`:\n\n```bash\ncurl -X PUT https://api.korelock.com/v1/locks/$LOCK_ID/unlock \\\n  -H \"Authorization: Bearer $AUTH_TOKEN\"\n```\n\nCommands are **queued**, not instant. To save battery, a lock's Wi-Fi sleeps between check-ins, so the command waits in `pending_state` until the lock next syncs. Any activity on the lock, such as a keypad press or card tap, wakes it to sync. Once the lock runs the command, `state` updates and `pending_state` clears. See [how changes reach a lock](#description/how-changes-reach-a-lock).\n\n## 4. Give someone a PIN\n\nCreate a credential holder with a PIN and a grant for your lock:\n\n```bash\ncurl -X POST https://api.korelock.com/v1/credential_holders \\\n  -H \"Authorization: Bearer $AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"My PIN credential\",\n    \"pin\": \"44556\",\n    \"grants\": [{ \"lock_id\": \"'\"$LOCK_ID\"'\" }]\n  }'\n```\n\nThe grant starts with `sync_status: \"pending\"`. After the lock syncs it changes to `synced`, and typing `44556` on the keypad opens the lock.\n\n## 5. Read lock events\n\nEvery lock, unlock, credential sync and battery report creates an event:\n\n```bash\ncurl https://api.korelock.com/v1/locks/$LOCK_ID/events \\\n  -H \"Authorization: Bearer $AUTH_TOKEN\"\n```\n\n> In production, use [webhooks](#tag/webhooks) rather than polling for events. Webhook payloads match the event format.\n\n# Use with AI assistants\n\nKoreLock has an MCP ([Model Context Protocol](https://modelcontextprotocol.io)) server, so you can ask an AI assistant about your locks in plain language. For example: \"Which locks have a low battery?\", \"Who opened the front door this morning?\" or \"Give the cleaning crew access to the back door on weekdays.\"\n\n| | |\n|---|---|\n| **MCP server URL** | The address you sign in at, followed by `/mcp/v1`, such as `https://app.korelock.com/mcp/v1` |\n| **Sign-in** | OAuth with your KoreLock account |\n| **Works with** | Claude, Claude Desktop, Claude Code, ChatGPT and Codex |\n\nThe steps below use `https://app.korelock.com/mcp/v1`. If your organization signs in at a different address, use that address instead. Other MCP clients aren't supported yet.\n\nAny KoreLock user can connect except guest admins. On accounts managed by an integration partner, the assistant can look things up but can't make changes.\n\n## What the assistant can do\n\nWhat an assistant can do depends on the access you approve when you connect it. With read access only, it can look things up but can't change anything.\n\n### Look things up\n\n- Get account details\n- List locations, locks and lock models\n- Get a lock's settings\n- List credential holders, and the grants for a credential holder or a lock\n- List SmartPINs\n- List schedules\n- List lock events\n\n### Make changes\n\nWith write access, the assistant can also:\n\n- **Locks:** register a lock by its serial number, rename it or move it to another location, and change most of its [settings](#tag/settings). It can't change the programming code, the SmartPIN seed or whether Wi-Fi is on.\n- **Locations:** create, update and delete locations\n- **Credential holders:** create, update and remove them, and grant or revoke their access to each lock\n- **SmartPINs:** create a SmartPIN, and change its name or email\n- **Schedules:** create, update and delete week, holiday, action and auto-lock schedules\n\nChanges reach a lock the same way as through the API: they're queued and apply when the lock next syncs. See [how changes reach a lock](#description/how-changes-reach-a-lock).\n\n### Reveal hidden values\n\nPINs, card numbers, SmartPIN codes and lock programming codes are hidden in everything the assistant reads. With access to these values, the assistant can reveal them one at a time, and only when you ask:\n\n- The PIN, card number and UniKey card number of one credential holder\n- The code of one SmartPIN\n- The programming code of one lock\n\nAnyone with a lock's programming code can reconfigure the lock at its keypad, so share it with care. SmartPIN seeds and Bluetooth keys are never shown.\n\n### Guardrails\n\n- **You choose the access.** When you connect an assistant, you sign in to KoreLock and approve what that connection can do. Looking things up is always included; making changes and revealing hidden values are off unless you tick them. Each connection has its own permissions.\n- **No lock or unlock commands, and no removing locks.** The MCP server has no tools to lock or unlock a door, or to unregister a lock, whatever the assistant is asked to do. Use the KoreLock app or the [API](#tag/locks) for that. With write access, though, it can set up [action schedules](#tag/action-schedules), which lock and unlock at set times.\n- **Limits.** Each user can make 30 MCP requests a minute. Turning on the Entry app sends an invitation, and invitations are limited to 20 an hour per account.\n- **Disconnecting.** An account owner can disconnect an assistant under **API Integrations** in the KoreLock web app by clicking **Revoke access**. That disconnects it for everyone on the account.\n- **Confirm before changes.** The tools tell the assistant to check details with you before it adds or removes access, registers a lock, or changes a location's time zone. Some changes also email people: turning on the Entry app sends the credential holder an invitation.\n\n## Claude\n\n**Free, Pro, Max and Ultra plans:**\n\n1. In Claude, go to **Customize > Connectors**.\n2. Click **Add custom connector**.\n3. Name it `KoreLock` and enter the server URL: `https://app.korelock.com/mcp/v1`.\n4. For **Authentication**, choose **Sign in now**. For **OAuth client**, choose **Use Claude's published identity**.\n5. Click **Add**, then **Connect**. Sign in to KoreLock and approve access.\n\n**Team and Enterprise plans:** an Owner adds the connector once for the organization, then each member connects with their own KoreLock account.\n\n1. The Owner goes to **Organization settings > Connectors**, selects **Add**, then **Custom**, and chooses **Web** if asked.\n2. The Owner enters `https://app.korelock.com/mcp/v1` and clicks **Add**.\n3. Each member goes to **Customize > Connectors**, finds **KoreLock** (labelled **Custom**), clicks **Connect** and signs in to KoreLock.\n\nTo use it in a chat, click **+**, open **Connectors** and make sure KoreLock is on.\n\n## Claude Desktop\n\nThe desktop app uses the same connectors as your Claude account. Add KoreLock as described under [Claude](#description/claude), in the app or at claude.ai, while signed in to the same account. It then shows up in the app's **+** menu under **Connectors**.\n\n## ChatGPT\n\n1. In ChatGPT, go to [Plugins](https://chatgpt.com/plugins).\n2. Select **+**, then **Add custom MCP server**.\n3. Name it `KoreLock` and add a short description, such as \"Look up KoreLock locks, people and events.\"\n4. Under **Connection**, enter `https://app.korelock.com/mcp/v1` and choose OAuth for authentication.\n5. Review the warning, select **I understand and want to continue**, then **Create as a plugin**.\n6. Sign in to KoreLock and approve access.\n\nTo use it, start a new chat, type `@` and pick **KoreLock**. Whether you can add custom MCP servers depends on your ChatGPT plan and your workspace's policies; on a business workspace, an admin may need to allow it.\n\n## Claude Code\n\nIn a terminal, add the server, then sign in:\n\n```bash\nclaude mcp add --transport http korelock https://app.korelock.com/mcp/v1\n```\n\nThen run `/mcp` in Claude Code, select **korelock** and approve access in your browser.\n\n## Codex\n\nThe Codex CLI, the Codex IDE extension and the ChatGPT desktop app share one configuration, so you only set this up once. In a terminal:\n\n```bash\ncodex mcp add korelock --url https://app.korelock.com/mcp/v1\ncodex mcp login korelock\n```\n\n`codex mcp login` opens your browser to sign in to KoreLock. On a machine without a browser, add `--no-browser`.\n\nOr add it to `~/.codex/config.toml` yourself, then run `codex mcp login korelock`:\n\n```toml\n[mcp_servers.korelock]\nurl = \"https://app.korelock.com/mcp/v1\"\n```\n\n# API conventions\n\n## Identifiers\n\nEvery resource except lock models has a server-generated UUID `id`. You can't set it yourself: an `id`, or any other field an endpoint doesn't accept, in a request body returns `400` with `error_type: \"unpermitted_params\"`.\n\nTo link a record to one in your own system, set its optional `external_uuid`. Locations, credential holders, grants and schedules accept one, and you can use `external_uuid:<your uuid>` in place of their `id` in a path.\n\n## Pagination\n\nList endpoints return an `items` array and a `metadata` object. Use `page` and `per_page` (1–250, default 20) to page through results. Pages past the end return an empty `items` array, and a `page` or `per_page` out of range returns `400`. Event lists leave out `pages` and `total_count`; use `has_more_items` instead.\n\n```json\n{\n  \"items\": [ … ],\n  \"metadata\": { \"page\": 1, \"per_page\": 20, \"has_more_items\": true, \"pages\": 7, \"total_count\": 124 }\n}\n```\n\n## Errors\n\nErrors return JSON with an `error_type` you can check in code and an `error_message` for people. A `400` for invalid data also lists each problem in `validation_errors`:\n\n```json\n{\n  \"error_type\": \"validation_failed\",\n  \"error_message\": \"Validation failed\",\n  \"validation_errors\": [\n    { \"attribute\": \"name\", \"type\": \"blank\", \"message\": \"Name can't be blank\" }\n  ]\n}\n```\n\n`attribute` is `self` when the problem is with the record as a whole, such as a schedule that's still in use. The older `errors` array of messages is still returned but deprecated.\n\n## How changes reach a lock\n\nTo save battery, a lock's Wi-Fi sleeps between check-ins. So nothing you send through the API reaches a lock straight away: the API saves your change and **queues** it, and the lock applies it the next time it syncs.\n\nA lock syncs on its heartbeat ([`heartbeat_interval`](#tag/settings)) and, depending on its [`wake_wifi`](#tag/settings) setting, when someone uses it, such as a keypad press or card tap.\n\nEach kind of change shows that it's waiting in its own field:\n\n- **Lock and unlock commands:** the queued command is in the lock's `pending_state`. `state` keeps showing the last state the lock *reported* until it runs the command.\n- **Settings:** the lock's `pending_sync` is `true` while changes are waiting. It covers queued commands and credentials too.\n- **Grants**, including credentials and schedules applied through them: the grant's `sync_status` is `pending`, then `synced`, or `failed` if the lock couldn't apply it.\n\nDon't treat a change as done until the lock reports it. To hear about syncs as they happen, use [webhooks](#tag/webhooks).\n\n## Rate limits\n\nLimits reset every minute. They're counted per user for tokens from the authorization code flow, and per account for client credentials tokens.\n\n| Requests | Limit |\n|---|---|\n| All writes (POST, PUT, DELETE combined) | 60 / minute |\n| Lock events | 30 / minute |\n| All other reads | 120 / minute |\n\nOver the limit, the API returns `429 Too Many Requests` with `error_type: \"rate_limit_exceeded\"` and a `Retry-After` header. `Retry-After` is always `60`.\n"
  },
  "servers": [
    {
      "url": "https://api.korelock.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "oauth2": []
    },
    {
      "bearerToken": []
    }
  ],
  "tags": [
    {
      "name": "Authentication",
      "description": "KoreLock uses OAuth 2.0. Pick the flow that matches what you're building:\n\n- **Client credentials**: for scripts and back-end services that only touch your own account.\n- **Authorization code**: for integrations where other KoreLock customers grant your app access to their accounts. Public apps (mobile, desktop, single-page) must add **PKCE**.\n\n### Authorization code flow\n\n1. [Register an OAuth application](https://app.korelock.com/oauth/applications/new) with your redirect URL. Turn **Confidential** on only if the app runs on a server you control.\n2. Send the user to their brand's authorize URL:\n\n   `https://[BRAND].webmanager.cloud/oauth/authorize?response_type=code&client_id=<client_id>&redirect_uri=<redirect_uri>`\n\n   Each lock brand (an *Organization* in KoreLock) has its own login, so use the brand your customer signs in with. One OAuth application works across all brands.\n3. After the user clicks **Allow**, KoreLock redirects to your URL with `?code=…`.\n4. Exchange the code at [`POST /oauth/token`](#tag/authentication/POST/oauth/token) for an access token and refresh token.\n5. Refresh before the access token expires (`created_at + expires_in`).\n\n### PKCE (required for public apps)\n\nGenerate a random `code_verifier` for each authorization request and send its SHA-256 `code_challenge` on the authorize URL:\n\n```bash\nCODE_VERIFIER=$(openssl rand -hex 32)\nCODE_CHALLENGE=$(printf '%s' \"$CODE_VERIFIER\" \\\n  | openssl dgst -binary -sha256 | openssl base64 | tr '+/' '-_' | tr -d '=\\n')\n```\n\nAdd `&code_challenge=<code_challenge>&code_challenge_method=S256` to the authorize URL, then send `code_verifier` instead of `client_secret` when you exchange the code.\n"
    },
    {
      "name": "Locations",
      "description": "Locations group locks by physical place. Every lock belongs to exactly one location."
    },
    {
      "name": "Lock models",
      "x-displayName": "Models & capabilities",
      "description": "KoreLock powers locks from several product lines, from keypad locks to card-only locks. Each lock is a **model**, such as `K52-3505` or `PDQ-G1-C`, and each model has its own hardware: some have a keypad, some a card reader, some both.\n\n**Capabilities** describe what a model's hardware can do. Each one is a `true` or `false` flag on the model, in two groups:\n\n- **Credentials:** which ways of opening the lock it accepts, such as PINs, cards, SmartPINs or UniKey.\n- **Settings:** which optional [settings](#tag/settings) it supports, such as keypad lockout or an alarm.\n\nCheck a lock's capabilities before you offer a feature for it. If a model doesn't support a setting, the lock's settings leave it out. Settings with no capability flag below, such as `auto_lock`, `mute` and `heartbeat_interval`, are returned for every model.\n\nA [grant](#tag/grants) can't be created for a lock that takes neither PINs nor cards. It also can't give a credential holder who has only a PIN access to a lock without PIN support, or one who has only a card access to a lock without card support. Credential types a lock doesn't support are skipped on that lock.\n\n### Find a lock's capabilities\n\nA lock's `model_code` matches the `name` of its model.\n\n1. [Get the lock](#tag/locks/GET/v1/locks/{lock_id}) and read its `model_code`, such as `K52-3505`.\n2. [List models](#tag/lock-models/GET/v1/locks/models) and find the one with that `name`.\n\n```bash\nMODEL=$(curl -s https://api.korelock.com/v1/locks/$LOCK_ID \\\n  -H \"Authorization: Bearer $AUTH_TOKEN\" | jq -r .model_code)\n\ncurl -s \"https://api.korelock.com/v1/locks/models?per_page=50\" \\\n  -H \"Authorization: Bearer $AUTH_TOKEN\" \\\n  | jq --arg m \"$MODEL\" '.items[] | select(.name == $m)'\n```\n\n### Credential capabilities\n\nThese flags say which credentials a lock accepts. Give a credential holder at least one credential type the lock supports.\n\n- **PIN** (`pin_credential`): a keypad code, set with the credential holder's `pin`.\n- **Card** (`smart_card_credential`): a card, set with the credential holder's `card_number`.\n- **SmartPIN** (`smart_pin_credential`): a time-limited guest PIN that works offline. See [SmartPINs](#tag/smartpins); also turn on the lock's `smart_pin_enabled` setting.\n- **UniKey** (`unikey_credential`): a UniKey mobile credential, set with the credential holder's `unikey_enabled`. Also enables the `unikey_reader` and `unikey_read_range_mode` settings.\n- **Entry app** (`entry_app_credential`): an Entry app credential, set with the credential holder's `entry_app_enabled`.\n\n### Reader capabilities\n\nThese flags describe the card readers built into or attached to the lock. Each one unlocks a setting that turns the reader on or off or picks its type.\n\n- **NFC reader** (`nfc_reader`): supports `nfc_antenna_enabled`.\n- **Proximity reader** (`prox_reader`): supports `prox_antenna_enabled`.\n- **Attached UniKey reader** (`unikey_reader_attached`): supports `edge_controller_reader_type`.\n\n### Setting capabilities\n\nThese flags say which optional [settings](#tag/settings) the lock supports.\n\n- **Programming code** (`programming_code`): the `programming_code` setting, for admin access from the keypad.\n- **Skip the # key** (`no_enter_code`): the `no_enter_code` setting.\n- **Keypad lockout** (`keypad_lockout`): `keypad_lockout_count` and `keypad_lockout_time`.\n- **Auto-lock after manual opening** (`manual_auto_lock`): `manual_auto_lock_timeout`.\n- **Wi-Fi wake without the knob** (`wake_wifi_except_manual`): the `user_action_except_manual` value of `wake_wifi`. Every current model supports it.\n- **Alarm** (`alarm`): `alarm_trigger_time` and `mute_alarm`.\n- **Always connected** (`always_on`): the `always_on` setting.\n- **Card byte order** (`card_byte_order`): the `card_byte_order` setting.\n- **NFC on key press** (`nfc_on_keypress`): the `nfc_on_keypress` setting. No current model supports it.\n- **Deadbolt calibration** (`deadbolt_calibration`): the model is a deadbolt that supports calibration. New locks of these models start with `auto_lock` set to `disabled`. There's no API setting for calibration.\n\n### Example: keypad lock and card-only lock\n\n| | `K52-3505` | `PDQ-G1-C` |\n|---|---|---|\n| PIN | Yes | No |\n| Card | No | Yes |\n| SmartPIN | Yes | No |\n| UniKey | Yes | Yes |\n| NFC reader | No | Yes |\n| Keypad settings | Yes | No |\n\nThe `K52-3505` has a keypad: it takes PINs and SmartPINs but has no card reader. The `PDQ-G1-C` takes cards only, so it doesn't accept PINs and its keypad settings (`programming_code`, `no_enter_code`, `keypad_lockout`) are `false`. A credential holder with only a PIN can be granted access to the first lock but not the second.\n\n### Submodels\n\nSome product lines come in several submodels. A model's `name` includes the submodel (`K52-3505`), `submodel` holds it on its own (`3505`), and `submodel_number` is its numeric code. Models without submodels have `submodel: null`. When you [register a lock](#tag/locks/POST/v1/locks), send the submodel as `sub_model`.\n\n`mobile_credential` and `prox_nfc_reader` are deprecated. Use `entry_app_credential` and `prox_reader` instead.\n"
    },
    {
      "name": "Locks",
      "description": "A lock has **main attributes** you can set, like its name and location, and **state attributes** it reports on each sync, like battery level and whether it's locked.\n\nCommands such as lock and unlock are queued in `pending_state` and run the next time the lock syncs. `state` always reflects the last state the lock *reported*, so right after an unlock request you'll see `state: \"locked\"` with `pending_state: \"unlock\"`. See [how changes reach a lock](#description/how-changes-reach-a-lock).\n"
    },
    {
      "name": "Settings",
      "description": "Settings control how a lock behaves: auto-lock, sound, keypad lockout, card readers, Wi-Fi and more. Each lock has one set of settings.\n\nNot every model supports every setting. Check the lock's [model capabilities](#tag/lock-models) before you offer a setting. Settings that don't apply to a model are left out of the response.\n\nSetting changes are **queued**, like lock and unlock commands. The API saves them right away, but the lock applies them only when it next syncs. The lock's `pending_sync` is `true` while changes are waiting. See [how changes reach a lock](#description/how-changes-reach-a-lock).\n\n### Replaced settings\n\nSome older settings have newer, more specific replacements. The old ones still appear in responses but are marked deprecated:\n\n| Deprecated | Use instead |\n|---|---|\n- `nfc`: `nfc_antenna_enabled` and `nfc_on_keypress`\n- `reader_frequency_mode`: `nfc_antenna_enabled` and `prox_antenna_enabled`\n"
    },
    {
      "name": "Credential holders",
      "description": "A credential holder is a person, or a shared role such as \"Cleaning crew\", who has one or more credentials: a PIN, a card, an Entry app credential or a UniKey mobile credential.\n\nA credential holder can't open anything until you give them a **grant** for a lock. You can add grants when you create the credential holder, or later through the [Grants](#tag/grants) endpoints.\n\n### How credentials reach a lock\n\nCredentials are stored on each lock, so every grant has to sync to its lock. A new or changed grant starts as `pending`, then becomes `synced` once the lock next syncs, or `failed` if the lock couldn't apply it. The `total_pending`, `total_synced` and `total_failed` counts show where a credential holder's grants stand.\n\nChanging a credential holder's PIN, card, access times or schedules also has to sync to each of their locks, so those changes are **queued** too and their grants go back to `pending`. Changing only the name or email doesn't. Removing a credential holder or revoking a grant is queued the same way: the lock erases the credential when it next syncs. See [how changes reach a lock](#description/how-changes-reach-a-lock).\n\n### Times\n\n`start_time` and `end_time` are local times at each lock, in `YYYY-MM-DDTHH:MM` format with no time zone. A credential holder with access to locks in different time zones gets the same wall-clock times at each one. Don't add a time zone offset.\n\nResponses include PINs and card numbers, so keep them private.\n\n### Rules\n\n- `name` is required and must be unique in the account.\n- Every credential holder needs at least one credential: `pin`, `card_number`, `unikey_card_number`, `entry_app_enabled` or `unikey_enabled`.\n- `pin`, `card_number` and `unikey_card_number` must each be unique in the account.\n- `email` is required when `entry_app_enabled` or `unikey_enabled` is `true`, because KoreLock emails the person an invitation. Invitations are limited to 20 an hour per account; past that, requests that send one return `429`.\n- Your plan can limit how many credential holders you have, and whether UniKey is available. Over a limit, the request returns `400` (`billing_limited` or `feature_limited`).\n- `start_time` and `end_time` go together: set both or neither, with `end_time` after `start_time`.\n- `external_uuid` must be unique in the account.\n"
    },
    {
      "name": "Grants",
      "description": "A grant gives one credential holder access to one lock. Grants belong to a credential holder, so you create, update and revoke them under `/v1/credential_holders/{credential_holder_id}/grants`. You can also list the grants on a lock.\n\n### Access times and schedules\n\nBy default, a grant uses the credential holder's `start_time`, `end_time`, `week_schedule_id` and `holiday_schedule_id`. Set any of them on the grant to override the credential holder's value for that lock only.\n\n`expired` is worked out in the lock's local time.\n\n### Syncing\n\nGrants are **queued**. A new or changed grant has `sync_status: \"pending\"` until the lock next syncs, then `synced`, or `failed` if the lock couldn't apply it. See [how changes reach a lock](#description/how-changes-reach-a-lock).\n\n### Rules\n\n- A credential holder can have only one grant per lock, and a grant's `lock_id` can't be changed.\n- The lock must take PINs or cards. A credential holder with only a PIN needs a lock that takes PINs, and one with only a card needs a lock that takes cards. Credential types the lock doesn't support are skipped on that lock. Check the lock's [model capabilities](#tag/lock-models).\n- A grant's `external_uuid` must be unique in the account.\n\nLists on a lock are sorted by credential holder name; lists for a credential holder are sorted by lock name.\n"
    },
    {
      "name": "SmartPINs",
      "description": "A SmartPIN is a time-limited PIN for guests that works **without a network connection**. Use it for short stays, contractors and other temporary access.\n\n### How SmartPINs work\n\nKoreLock generates each SmartPIN from three things: a seed (by default, the lock's serial number), a start time and an end time. The access window is encoded in the PIN itself. When a guest types it in, the lock uses the same seed to decode it and check the time.\n\nBecause nothing has to be sent to the lock, a SmartPIN works as soon as you create it, even if the lock is offline. That's why `sync_status` is always `ready`.\n\nTo use SmartPINs, the lock's model needs the `smart_pin_credential` [capability](#tag/lock-models), and the lock's [`smart_pin_enabled` setting](#tag/settings) must be `true`. To let one SmartPIN open several locks, give them the same `smart_pin_seed` setting.\n\n### Time rules\n\nSmartPIN times are whole hours (minutes must be `:00`). There are two kinds:\n\n| Kind | Length | Start and end times |\n|---|---|---|\n| Short-term | Up to 32 days | Any hour |\n| Long-term | 33 days to 1 year | Must start at 08:00 and end at 20:00 |\n\n### SmartPINs are permanent\n\nAfter you create a SmartPIN, you can't change its times and you can't delete it, because the access window is built into the PIN. You can only change its `name` and `email`. Choose the times carefully.\n"
    },
    {
      "name": "Action schedules",
      "description": "Action schedules lock and unlock a lock automatically at set times each week. For example, unlock a lobby door at 08:00 on weekdays and lock it again at 18:00.\n\nEach entry is one of:\n\n- `unlock`: the lock unlocks itself.\n- `lock`: the lock locks itself.\n\nTo use an action schedule, set the lock's [`action_schedule_id` setting](#tag/settings). Like other setting changes, it's queued and applies when the lock next syncs. See [how changes reach a lock](#description/how-changes-reach-a-lock).\n\n### Schedule format\n\nAll four schedule types use the same building blocks.\n\n**Weekly schedules** (action, auto-lock and week schedules) are a JSON object keyed by day. Leave out days with nothing scheduled.\n\n| Day | Key |\n|---|---|\n| Monday | `mon` |\n| Tuesday | `tue` |\n| Wednesday | `wed` |\n| Thursday | `thu` |\n| Friday | `fri` |\n| Saturday | `sat` |\n| Sunday | `sun` |\n\nTimes are 24-hour `HH:MM`, such as `09:00` or `18:30`.\n\nFor action and auto-lock schedules, each day holds a list of up to 10 entries. Each entry is a one-key object, such as `{ \"unlock\": \"09:00\" }`. Entries can be any mix of actions and don't need to come in pairs:\n\n```json\n{\n  \"mon\": [{ \"unlock\": \"09:00\" }, { \"lock\": \"12:30\" }],\n  \"tue\": [{ \"lock\": \"18:30\" }],\n  \"wed\": [{ \"unlock\": \"09:00\" }],\n  \"thu\": [\n    { \"unlock\": \"10:00\" },\n    { \"lock\": \"12:30\" },\n    { \"unlock\": \"18:30\" },\n    { \"lock\": \"22:30\" }\n  ]\n}\n```\n\nThis unlocks on Monday at 09:00 and locks at 12:30. It locks on Tuesday at 18:30 and stays locked until Wednesday at 09:00. On Thursday it unlocks at 10:00 and 18:30 and locks at 12:30 and 22:30.\n\n[Week schedules](#tag/week-schedules) use `start` and `end` intervals instead of actions. [Holiday schedules](#tag/holiday-schedules) are a list of date ranges rather than a weekly object.\n"
    },
    {
      "name": "Auto-lock schedules",
      "description": "Auto-lock schedules turn a lock's auto-lock on and off at set times each week. For example, keep a door unlocked during office hours and auto-locking after hours.\n\nEach entry is one of:\n\n- `auto-lock-on`: the lock starts auto-locking.\n- `auto-lock-off`: the lock stops auto-locking.\n\nTo use an auto-lock schedule, set the lock's [`auto_lock` setting](#tag/settings) to `schedule` and `auto_lock_schedule_id` to the schedule's `id`. Like other setting changes, it's queued and applies when the lock next syncs. See [how changes reach a lock](#description/how-changes-reach-a-lock).\n\nAuto-lock schedules use the same weekly format as action schedules: up to 10 entries per day, in any order. See [schedule format](#tag/action-schedules).\n\n```json\n{\n  \"tue\": [{ \"auto-lock-on\": \"08:00\" }, { \"auto-lock-off\": \"22:30\" }],\n  \"wed\": [{ \"auto-lock-on\": \"17:30\" }],\n  \"fri\": [{ \"auto-lock-off\": \"09:00\" }],\n  \"sun\": [\n    { \"auto-lock-on\": \"09:00\" },\n    { \"auto-lock-off\": \"12:30\" },\n    { \"auto-lock-on\": \"15:30\" }\n  ]\n}\n```\n"
    },
    {
      "name": "Week schedules",
      "description": "Week schedules limit **when** a credential works. Outside the schedule's intervals, the credential holder's PIN or card doesn't open the lock.\n\nApply a week schedule to a [credential holder](#tag/credential-holders) to cover all their locks, or to a single [grant](#tag/grants) to override it for one lock. Set `week_schedule_id` to the schedule's `id`.\n\nWeek schedules use the same day keys and `HH:MM` times as the other [weekly schedules](#tag/action-schedules), but each day holds a list of `start` and `end` intervals:\n\n```json\n{\n  \"tue\": [\n    { \"start\": \"08:30\", \"end\": \"12:30\" },\n    { \"start\": \"14:00\", \"end\": \"18:45\" }\n  ],\n  \"thu\": [\n    { \"start\": \"08:30\", \"end\": \"12:30\" },\n    { \"start\": \"14:00\", \"end\": \"18:45\" }\n  ]\n}\n```\n\nThis allows access on Tuesdays and Thursdays from 08:30 to 12:30 and from 14:00 to 18:45.\n\nA week schedule can have up to 40 intervals across the whole week.\n"
    },
    {
      "name": "Holiday schedules",
      "description": "Holiday schedules block credentials on certain **dates**. On those days, the credential holder's PIN or card doesn't open the lock.\n\nApply a holiday schedule to a [credential holder](#tag/credential-holders) to cover all their locks, or to a single [grant](#tag/grants) to override it for one lock. Set `holiday_schedule_id` to the schedule's `id`.\n\nUnlike the [weekly schedules](#tag/action-schedules), a holiday schedule is a JSON **array** of date ranges. Each range has a `start` and `end` date in `YYYY-MM-DD` format, and both dates are included. For a single day, use the same date for both:\n\n```json\n[\n  { \"start\": \"2026-11-26\", \"end\": \"2026-11-27\" },\n  { \"start\": \"2026-12-25\", \"end\": \"2026-12-25\" }\n]\n```\n\n`start` can't be after `end`. A holiday schedule can hold its 5 longest multi-day ranges as ranges, plus up to 40 other days: every other day, including the days in any further multi-day ranges, counts toward the 40. Ranges that have already ended don't count. Over the limit, the request returns `400` (`too_many_dates`).\n"
    },
    {
      "name": "Events",
      "description": "The lock creates an event every time something happens: an unlock, a denied PIN, a battery warning, a sync. Events are read-only.\n\nEvery event has a `type` (the category) and an `action` (what happened). Some types add their own fields; access events, for example, say who opened the lock and how. Open an event type in the response schema to see its actions and fields.\n\n- `access`: Someone locked, unlocked or was denied at the lock.\n- `credential_holder_grant_sync`: A grant was added to or removed from the lock.\n- `battery`: The battery is low or was replaced.\n- `door_state`: The door sensor reported the door open, closed or unknown.\n- `reset`: The lock was reset or had codes erased.\n- `local_pin_sync`: A local PIN was added to or removed from the lock.\n- `reboot`: The lock powered on.\n\n`occurred_at` uses the lock's local UTC offset, so `2026-11-04T08:02:15-07:00` is 08:02 local time at the lock.\n\nTo get events as they arrive instead of polling, use [webhooks](#tag/webhooks). Reading events has a lower rate limit than other reads (30 requests per minute).\n"
    },
    {
      "name": "Webhooks",
      "description": "A webhook tells KoreLock to send events to your server as they happen, so you don't have to poll. When a subscribed event occurs on any lock in your account, KoreLock sends it to your webhook's `url` as an HTTPS `POST`.\n\nThe request body is the event itself, in the same format as [`GET /v1/locks/{lock_id}/events`](#tag/events/GET/v1/locks/{lock_id}/events).\n\n### Which events are sent\n\nWebhooks deliver two event types:\n\n- `access`: locks, unlocks and denied attempts.\n- `credential_holder_grant_sync`: grants added to or removed from a lock.\n\nSet `subscribed_events` to any of these, or to `[\"*\"]` for all of them. Other event types, such as `battery` or `door_state`, aren't sent by webhook; [list events](#tag/events) to read them.\n\n### Verifying deliveries\n\nSet a `secret` on the webhook to sign each delivery. KoreLock then adds an `X-Hub-Signature` header: `sha256=` followed by the HMAC-SHA256 hex digest of the raw request body, keyed with your secret.\n\nCompute the same digest on your server and compare it before you trust the payload:\n\n```js\nimport crypto from \"node:crypto\";\n\nfunction isFromKoreLock(rawBody, signatureHeader, secret) {\n  const expected = \"sha256=\" +\n    crypto.createHmac(\"sha256\", secret).update(rawBody).digest(\"hex\");\n  return signatureHeader.length === expected.length &&\n    crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));\n}\n```\n\n### Disabled webhooks\n\n`disabled_by` says why a webhook stopped: `user` if someone set `enabled` to `false`, or `circuit_breaker` if KoreLock turned it off. Set `enabled` back to `true` to turn it on again.\n\n### Delivery\n\nReply with any `2xx` status within 3 seconds. KoreLock doesn't follow redirects. Any other response, or a timeout, is retried with increasing delays, up to 25 times over about 3 weeks. Retries stop if you disable the webhook.\n\nDeliveries can arrive more than once and out of order, so use the event's `id` to ignore duplicates.\n"
    }
  ],
  "x-tagGroups": [
    {
      "name": "Getting started",
      "tags": [
        "Authentication"
      ]
    },
    {
      "name": "Resources",
      "tags": [
        "Locations",
        "Lock models",
        "Locks",
        "Settings"
      ]
    },
    {
      "name": "Access & credentials",
      "tags": [
        "Credential holders",
        "Grants",
        "SmartPINs"
      ]
    },
    {
      "name": "Schedules",
      "tags": [
        "Action schedules",
        "Auto-lock schedules",
        "Week schedules",
        "Holiday schedules"
      ]
    },
    {
      "name": "Events & webhooks",
      "tags": [
        "Events",
        "Webhooks"
      ]
    }
  ],
  "paths": {
    "/oauth/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "createToken",
        "summary": "Get an access token",
        "description": "Exchanges client credentials, an authorization code or a refresh token for an access token. Access tokens last 7,200 seconds (2 hours).\n",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/ClientCredentialsGrant"
                  },
                  {
                    "$ref": "#/components/schemas/AuthorizationCodeGrant"
                  },
                  {
                    "$ref": "#/components/schemas/RefreshTokenGrant"
                  }
                ]
              },
              "examples": {
                "clientCredentials": {
                  "summary": "Client credentials",
                  "value": {
                    "grant_type": "client_credentials",
                    "client_id": "example_client_id_9f2c4a1e7b3d",
                    "client_secret": "example_client_secret_5e8b1d6a2f4c"
                  }
                },
                "authorizationCode": {
                  "summary": "Authorization code",
                  "value": {
                    "grant_type": "authorization_code",
                    "code": "example_authorization_code_7d2b5f9a",
                    "client_id": "example_client_id_3a7e9c2b5d1f",
                    "client_secret": "example_client_secret_8c4f2a6e1b9d",
                    "redirect_uri": "https://app.example.com/oauth/callback"
                  }
                },
                "authorizationCodePkce": {
                  "summary": "Authorization code with PKCE",
                  "value": {
                    "grant_type": "authorization_code",
                    "code": "example_authorization_code_7d2b5f9a",
                    "client_id": "example_client_id_3a7e9c2b5d1f",
                    "code_verifier": "4f1c2a9e8b7d6c5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f",
                    "redirect_uri": "https://app.example.com/oauth/callback"
                  }
                },
                "refreshToken": {
                  "summary": "Refresh token",
                  "value": {
                    "grant_type": "refresh_token",
                    "refresh_token": "example_refresh_token_6b1e8d3c4a7f",
                    "client_id": "example_client_id_3a7e9c2b5d1f",
                    "client_secret": "example_client_secret_8c4f2a6e1b9d"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Token"
                }
              }
            }
          },
          "400": {
            "description": "The grant, code, refresh token or PKCE verifier is invalid. The body follows OAuth 2.0: `{ \"error\": \"invalid_grant\", \"error_description\": \"…\" }`.\n"
          },
          "401": {
            "description": "The client ID or secret is wrong (`error: \"invalid_client\"`)."
          }
        }
      }
    },
    "/oauth/revoke": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "revokeToken",
        "summary": "Revoke a token",
        "description": "Revokes an access token or refresh token. Do this when a user signs out of your app, especially in mobile and desktop apps. It returns `200` even if the token was already revoked or unknown.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "client_id",
                  "token"
                ],
                "properties": {
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string",
                    "description": "Required for confidential applications."
                  },
                  "token": {
                    "type": "string",
                    "description": "The access token to revoke."
                  }
                }
              },
              "example": {
                "client_id": "example_client_id_3a7e9c2b5d1f",
                "client_secret": "example_client_secret_8c4f2a6e1b9d",
                "token": "example_access_token_2f9a6c1e8b4d"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token revoked."
          }
        }
      }
    },
    "/v1/locations": {
      "get": {
        "tags": [
          "Locations"
        ],
        "operationId": "listLocations",
        "summary": "List locations",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of locations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LocationList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Locations"
        ],
        "operationId": "createLocation",
        "summary": "Create a location",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LocationInput"
              },
              "example": {
                "external_uuid": "08cda068-2a82-4f25-9c61-471a196dfd72",
                "name": "Denver HQ",
                "address": "1801 Wewatta St, Denver, CO",
                "timezone": "America/Denver"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Location created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locations/{location_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/location_id"
        }
      ],
      "get": {
        "tags": [
          "Locations"
        ],
        "operationId": "getLocation",
        "summary": "Get a location",
        "responses": {
          "200": {
            "description": "The location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Locations"
        ],
        "operationId": "updateLocation",
        "summary": "Update a location",
        "description": "Changes the location's details. Changing `timezone` updates every lock at the location, which changes when their schedules run.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LocationUpdate"
              },
              "example": {
                "name": "Denver HQ – Suite 200",
                "address": "1801 Wewatta St, Suite 200, Denver, CO"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Location"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Locations"
        ],
        "operationId": "deleteLocation",
        "summary": "Delete a location",
        "description": "Deletes the location. A location that still has registered locks can't be deleted; that returns `400` with a validation error of type `in_use`.",
        "responses": {
          "200": {
            "description": "Location deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/models": {
      "get": {
        "tags": [
          "Lock models"
        ],
        "operationId": "listLockModels",
        "summary": "List models and capabilities",
        "description": "Lists every lock model and its [capabilities](#tag/lock-models). Match a lock's `model_code` to a model's `name` to see what that lock supports.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of lock models.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LockModel"
                      }
                    },
                    "metadata": {
                      "$ref": "#/components/schemas/PageMetadata"
                    }
                  }
                },
                "example": {
                  "items": [
                    {
                      "name": "K52-3505",
                      "submodel": "3505",
                      "submodel_number": "3505",
                      "pin_credential": true,
                      "smart_card_credential": false,
                      "smart_pin_credential": true,
                      "unikey_credential": true,
                      "entry_app_credential": false,
                      "nfc_reader": false,
                      "prox_reader": false,
                      "unikey_reader_attached": false,
                      "programming_code": true,
                      "no_enter_code": true,
                      "keypad_lockout": true,
                      "manual_auto_lock": true,
                      "wake_wifi_except_manual": true,
                      "alarm": false,
                      "always_on": false,
                      "card_byte_order": false,
                      "nfc_on_keypress": false,
                      "deadbolt_calibration": true,
                      "mobile_credential": false,
                      "prox_nfc_reader": false
                    },
                    {
                      "name": "PDQ-G1-C",
                      "submodel": "G1-C",
                      "submodel_number": "1005",
                      "pin_credential": false,
                      "smart_card_credential": true,
                      "smart_pin_credential": false,
                      "unikey_credential": true,
                      "entry_app_credential": false,
                      "nfc_reader": true,
                      "prox_reader": false,
                      "unikey_reader_attached": false,
                      "programming_code": false,
                      "no_enter_code": false,
                      "keypad_lockout": false,
                      "manual_auto_lock": false,
                      "wake_wifi_except_manual": true,
                      "alarm": false,
                      "always_on": false,
                      "card_byte_order": false,
                      "nfc_on_keypress": false,
                      "deadbolt_calibration": false,
                      "mobile_credential": false,
                      "prox_nfc_reader": false
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": true,
                    "pages": 3,
                    "total_count": 43
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks": {
      "get": {
        "tags": [
          "Locks"
        ],
        "operationId": "listLocks",
        "summary": "List locks",
        "description": "Lists every lock registered to the account.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Sort field. Prefix with `-` for descending order.",
            "schema": {
              "type": "string",
              "default": "name",
              "enum": [
                "name",
                "-name",
                "created_at",
                "-created_at",
                "updated_at",
                "-updated_at"
              ]
            }
          },
          {
            "name": "term",
            "in": "query",
            "description": "Filters by part of the lock's name or serial number, ignoring case. An unrecognised `sort` value sorts by name.",
            "schema": {
              "type": "string"
            },
            "example": "Front door"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of locks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LockList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Locks"
        ],
        "operationId": "registerLock",
        "summary": "Register a lock",
        "description": "Most locks are registered in the KoreLock white-label app, which also connects them to Wi-Fi. Use this endpoint when your own onboarding needs to register locks first.\n\nModels with submodels need `sub_model`:\n\n| Series | Submodels |\n|---|---|\n| K52 | 3505K, 3505, 3510 |\n| K72 | 5200, 5200K |\n| ENA | None |\n| JUN | None |\n| KCM | None |\n| KLB | None |\n| KLM | 8500, WU1DR |\n| PDQ | G1-C, G1-CK, G1-C-P, G1-CK-P, G2-C, G2-CK, G2-C-P, G2-CK-P, EX-C, EX-CK, EX-C-P, EX-CK-P, DB-C, DB-CK, DB-C-P, DB-CK-P, IN-C, IN-CK, IN-C-P, IN-CK-P, M01-C, M01-CK, M01-C-P, M01-CK-P, M03-C, M03-CK, M03-C-P, M03-CK-P |\n| NWT | None |\n| WST | 900JF, 900JQ, E07 |\n\nSome lock brands show their own model names. On those brands, send the submodel as it appears in that brand's [model list](#tag/lock-models/GET/v1/locks/models).\n\nRegistration returns `400` if the serial number isn't recognised or is already registered, if the model and submodel don't match, if the name is taken at that location, or if your plan's lock limit is reached. `location_id` must be the location's `id`, not an `external_uuid`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LockRegistration"
              },
              "example": {
                "name": "Front door",
                "serial_number": "K52489515403",
                "sub_model": "3505",
                "location_id": "e3aec14d-ba7f-4966-99ca-137ef6f87782"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lock registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Lock"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "get": {
        "tags": [
          "Locks"
        ],
        "operationId": "getLock",
        "summary": "Get a lock",
        "description": "Returns the lock's main and state attributes.",
        "responses": {
          "200": {
            "description": "The lock.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Lock"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Locks"
        ],
        "operationId": "updateLock",
        "summary": "Update a lock",
        "description": "Only `name` and `location_id` can be changed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LockUpdate"
              },
              "example": {
                "name": "Back door",
                "location_id": "e3aec14d-ba7f-4966-99ca-137ef6f87782"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated lock.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Lock"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Locks"
        ],
        "operationId": "deregisterLock",
        "summary": "Deregister a lock",
        "description": "Removes the lock from your account.\n\n> **Warning:** This also removes the lock's grants, and deletes any credential holders who have no grants on other locks. That clean-up runs in the background a few moments after the response.\n",
        "responses": {
          "200": {
            "description": "Lock deregistered."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/lock": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "put": {
        "tags": [
          "Locks"
        ],
        "operationId": "lockLock",
        "summary": "Lock",
        "description": "Queues a lock command. `pending_state` becomes `lock` until the lock syncs and runs it, then `state` becomes `locked`.",
        "responses": {
          "200": {
            "description": "Command queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LockCommandResult"
                },
                "example": {
                  "state": "unlocked",
                  "pending_state": "lock"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/unlock": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "put": {
        "tags": [
          "Locks"
        ],
        "operationId": "unlockLock",
        "summary": "Unlock",
        "description": "Queues an unlock command. `pending_state` becomes `unlock` until the lock syncs and runs it, then `state` becomes `unlocked`.",
        "responses": {
          "200": {
            "description": "Command queued. `state` still shows the last reported state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LockCommandResult"
                },
                "example": {
                  "state": "locked",
                  "pending_state": "unlock"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/cancel_pending_action": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "put": {
        "tags": [
          "Locks"
        ],
        "operationId": "cancelPendingAction",
        "summary": "Cancel a pending command",
        "description": "Cancels a queued lock or unlock command and clears `pending_state`, as long as the lock hasn't fetched the command yet. If it already has, nothing changes and `pending_state` stays set, so check it in the response.",
        "responses": {
          "200": {
            "description": "Command cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LockCommandResult"
                },
                "example": {
                  "state": "locked",
                  "pending_state": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/settings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "get": {
        "tags": [
          "Settings"
        ],
        "operationId": "getLockSettings",
        "summary": "Get settings",
        "description": "Returns the lock's current settings. Settings the lock's model doesn't support are left out.",
        "responses": {
          "200": {
            "description": "The lock's settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LockSettings"
                },
                "example": {
                  "action_schedule_id": null,
                  "auto_lock": "enabled",
                  "auto_lock_schedule_id": null,
                  "auto_lock_timeout": 30,
                  "heartbeat_interval": 60,
                  "keypad_lockout_count": 4,
                  "keypad_lockout_time": 30,
                  "manual_auto_lock_timeout": null,
                  "mute": false,
                  "no_enter_code": false,
                  "online_auto_lock": true,
                  "programming_code": "482915",
                  "smart_pin_enabled": false,
                  "smart_pin_seed": "0000",
                  "unikey_read_range_mode": "intermediate",
                  "unikey_reader": "disabled",
                  "wake_wifi": "user_action",
                  "wifi_enabled": true,
                  "created_at": "2026-09-02T18:20:11Z",
                  "updated_at": "2026-10-28T15:01:47Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Settings"
        ],
        "operationId": "updateLockSettings",
        "summary": "Update settings",
        "description": "Changes the lock's settings. Send only the settings you want to change; the rest keep their values. The new values are saved and returned straight away, but the lock applies them only when it next syncs. Until then, the lock's `pending_sync` is `true`.\n\nCheck the lock's [model capabilities](#tag/lock-models) first. Most settings a model doesn't support return `400` with a validation error. A few, such as `programming_code`, `no_enter_code` and the keypad lockout settings, are accepted but have no effect on models without that capability.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LockSettings"
              },
              "example": {
                "auto_lock": "schedule",
                "auto_lock_schedule_id": "3f6d2a1e-7c84-4b59-9e0a-2d5c8b71f4a6",
                "auto_lock_timeout": 15,
                "keypad_lockout_count": 5,
                "keypad_lockout_time": 60,
                "mute": true,
                "no_enter_code": true,
                "smart_pin_enabled": true,
                "wake_wifi": "user_action"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated settings. The lock applies them when it next syncs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LockSettings"
                },
                "example": {
                  "action_schedule_id": null,
                  "auto_lock": "schedule",
                  "auto_lock_schedule_id": "3f6d2a1e-7c84-4b59-9e0a-2d5c8b71f4a6",
                  "auto_lock_timeout": 15,
                  "heartbeat_interval": 60,
                  "keypad_lockout_count": 5,
                  "keypad_lockout_time": 60,
                  "manual_auto_lock_timeout": null,
                  "mute": true,
                  "no_enter_code": true,
                  "online_auto_lock": true,
                  "programming_code": "482915",
                  "smart_pin_enabled": true,
                  "smart_pin_seed": "0000",
                  "unikey_read_range_mode": "intermediate",
                  "unikey_reader": "disabled",
                  "wake_wifi": "user_action",
                  "wifi_enabled": true,
                  "created_at": "2026-09-02T18:20:11Z",
                  "updated_at": "2026-11-04T10:02:51Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/credential_holders": {
      "get": {
        "tags": [
          "Credential holders"
        ],
        "operationId": "listCredentialHolders",
        "summary": "List credential holders",
        "description": "Lists the account's credential holders, sorted by name.\n\nThe results don't include grants. To get them, [list the credential holder's grants](#tag/grants/GET/v1/credential_holders/{credential_holder_id}/grants).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          },
          {
            "name": "term",
            "in": "query",
            "description": "Filters the results. Matches part of a `name` or `email`, ignoring case. If the term is all digits, it also matches a `pin` or `card_number` exactly.\n",
            "schema": {
              "type": "string"
            },
            "example": "Cleaning"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of credential holders.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CredentialHolderList"
                },
                "example": {
                  "items": [
                    {
                      "id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                      "external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                      "name": "Cleaning crew",
                      "email": "cleaning@example.com",
                      "pin": "49820",
                      "card_number": "4100094630",
                      "unikey_enabled": true,
                      "start_time": "2026-11-01T08:30",
                      "end_time": "2026-12-31T22:00",
                      "week_schedule_id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                      "holiday_schedule_id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                      "total_pending": 0,
                      "total_failed": 0,
                      "total_synced": 2,
                      "created_at": "2026-10-30T16:05:12Z",
                      "updated_at": "2026-10-30T16:20:44Z"
                    },
                    {
                      "id": "7e2b5d18-9c43-4f6a-b0e1-3a8d6c9f2b57",
                      "external_uuid": "3acdd5ca-727c-49f4-8a66-f1145d8dc8b5",
                      "name": "Front desk",
                      "email": "frontdesk@example.com",
                      "pin": "94918",
                      "total_pending": 1,
                      "total_failed": 0,
                      "total_synced": 8,
                      "created_at": "2026-09-14T13:41:09Z",
                      "updated_at": "2026-11-02T09:12:30Z"
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Credential holders"
        ],
        "operationId": "createCredentialHolder",
        "summary": "Create a credential holder",
        "description": "Creates a credential holder. To give them access straight away, add a `grants` array with one entry per lock. Each grant starts as `pending` and syncs to its lock the next time that lock syncs.\n\nIf any grant can't be created, nothing is created and the response lists the problem as `Grant N: …`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CredentialHolderCreate"
              },
              "example": {
                "name": "Cleaning crew",
                "external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                "email": "cleaning@example.com",
                "pin": "49820",
                "card_number": "4100094630",
                "unikey_enabled": true,
                "start_time": "2026-11-01T08:30",
                "end_time": "2026-12-31T22:00",
                "week_schedule_id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                "holiday_schedule_id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                "grants": [
                  {
                    "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe"
                  },
                  {
                    "lock_id": "2b7c4e91-6d3a-4f8e-a5b2-9c1d0e7f3a48"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Credential holder created, with their grants.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CredentialHolderWithGrants"
                },
                "example": {
                  "id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                  "external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                  "name": "Cleaning crew",
                  "email": "cleaning@example.com",
                  "pin": "49820",
                  "card_number": "4100094630",
                  "unikey_enabled": true,
                  "start_time": "2026-11-01T08:30",
                  "end_time": "2026-12-31T22:00",
                  "week_schedule_id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                  "holiday_schedule_id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                  "total_pending": 2,
                  "total_failed": 0,
                  "total_synced": 0,
                  "created_at": "2026-10-30T16:05:12Z",
                  "updated_at": "2026-10-30T16:05:12Z",
                  "grants": [
                    {
                      "id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "sync_status": "pending"
                    },
                    {
                      "id": "0cd2cb2a-eced-4ab6-ad6c-30c23305d1e8",
                      "lock_id": "2b7c4e91-6d3a-4f8e-a5b2-9c1d0e7f3a48",
                      "sync_status": "pending"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/credential_holders/{credential_holder_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/credential_holder_id"
        }
      ],
      "get": {
        "tags": [
          "Credential holders"
        ],
        "operationId": "getCredentialHolder",
        "summary": "Get a credential holder",
        "description": "Returns a credential holder. The response doesn't include grants. To get them, [list the credential holder's grants](#tag/grants/GET/v1/credential_holders/{credential_holder_id}/grants).\n",
        "responses": {
          "200": {
            "description": "The credential holder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CredentialHolder"
                },
                "example": {
                  "id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                  "external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                  "name": "Cleaning crew",
                  "email": "cleaning@example.com",
                  "pin": "49820",
                  "card_number": "4100094630",
                  "unikey_enabled": true,
                  "start_time": "2026-11-01T08:30",
                  "end_time": "2026-12-31T22:00",
                  "week_schedule_id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                  "holiday_schedule_id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                  "total_pending": 0,
                  "total_failed": 0,
                  "total_synced": 2,
                  "created_at": "2026-10-30T16:05:12Z",
                  "updated_at": "2026-10-30T16:20:44Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Credential holders"
        ],
        "operationId": "updateCredentialHolder",
        "summary": "Update a credential holder",
        "description": "Changes a credential holder's details and credentials. Changes to credentials and access times are **queued** and reach each lock when it next syncs.\n\nYou can't change grants here. Use the [Grants](#tag/grants) endpoints to add or revoke access.\n\nSetting `unikey_enabled` to `false` revokes the person's UniKey credential on the UniKey servers.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CredentialHolderUpdate"
              },
              "example": {
                "pin": "845811",
                "card_number": "8218744877",
                "unikey_enabled": false,
                "start_time": "2026-11-01T08:30",
                "end_time": "2027-01-31T22:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated credential holder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CredentialHolder"
                },
                "example": {
                  "id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                  "external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                  "name": "Cleaning crew",
                  "email": "cleaning@example.com",
                  "pin": "845811",
                  "card_number": "8218744877",
                  "unikey_enabled": false,
                  "start_time": "2026-11-01T08:30",
                  "end_time": "2027-01-31T22:00",
                  "week_schedule_id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                  "holiday_schedule_id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                  "total_pending": 2,
                  "total_failed": 0,
                  "total_synced": 0,
                  "created_at": "2026-10-30T16:05:12Z",
                  "updated_at": "2026-11-04T10:02:51Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Credential holders"
        ],
        "operationId": "deleteCredentialHolder",
        "summary": "Remove a credential holder",
        "description": "Removes the credential holder and revokes all of their grants. Each lock erases their credentials the next time it syncs.",
        "responses": {
          "200": {
            "description": "Credential holder removed."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/credential_holders/{credential_holder_id}/grants": {
      "parameters": [
        {
          "$ref": "#/components/parameters/credential_holder_id"
        }
      ],
      "get": {
        "tags": [
          "Grants"
        ],
        "operationId": "listCredentialHolderGrants",
        "summary": "List grants for a credential holder",
        "description": "Lists the credential holder's grants. Each grant includes its `lock`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of grants, each with its lock.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CredentialHolderGrantList"
                },
                "example": {
                  "items": [
                    {
                      "id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                      "external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "start_time": "2026-11-01T08:30",
                      "end_time": "2026-12-31T22:00",
                      "week_schedule_id": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62",
                      "holiday_schedule_id": null,
                      "sync_status": "pending",
                      "expired": false,
                      "lock": {
                        "id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                        "name": "Front door",
                        "serial_number": "K52489515403",
                        "location_id": "e3aec14d-ba7f-4966-99ca-137ef6f87782",
                        "model_code": "K52-3505",
                        "firmware_version": "nrf4.1.4+esp1.0.1",
                        "mac_address": "cf:7a:15:94:4c:7d",
                        "battery_voltage": 540,
                        "secondary_wifi_enabled": true,
                        "state": "locked",
                        "pending_state": null,
                        "state_updated_at": "2026-10-28T14:32:08Z",
                        "alive": true,
                        "connected_at": "2026-10-28T15:01:47Z",
                        "pending_sync": false,
                        "power_level": 85,
                        "wifi_level": 4,
                        "wifi_network_name": "Office WiFi",
                        "created_at": "2026-09-02T18:20:11Z",
                        "updated_at": "2026-10-28T15:01:47Z"
                      }
                    },
                    {
                      "id": "0cd2cb2a-eced-4ab6-ad6c-30c23305d1e8",
                      "external_uuid": null,
                      "lock_id": "2b7c4e91-6d3a-4f8e-a5b2-9c1d0e7f3a48",
                      "start_time": null,
                      "end_time": null,
                      "week_schedule_id": null,
                      "holiday_schedule_id": null,
                      "sync_status": "synced",
                      "expired": false,
                      "lock": {
                        "id": "2b7c4e91-6d3a-4f8e-a5b2-9c1d0e7f3a48",
                        "name": "Back door",
                        "serial_number": "K52246414593",
                        "location_id": "e3aec14d-ba7f-4966-99ca-137ef6f87782",
                        "model_code": "K52-3505",
                        "firmware_version": "nrf4.1.4+esp1.0.1",
                        "mac_address": "78:e0:64:fb:5b:a9",
                        "battery_voltage": 540,
                        "secondary_wifi_enabled": true,
                        "state": "unlocked",
                        "pending_state": null,
                        "state_updated_at": "2026-10-28T14:32:08Z",
                        "alive": true,
                        "connected_at": "2026-10-28T15:01:47Z",
                        "pending_sync": false,
                        "power_level": 85,
                        "wifi_level": 4,
                        "wifi_network_name": "Office WiFi",
                        "created_at": "2026-09-02T18:20:11Z",
                        "updated_at": "2026-10-28T15:01:47Z"
                      }
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Grants"
        ],
        "operationId": "createGrant",
        "summary": "Create a grant",
        "description": "Gives the credential holder access to a lock. The grant starts as `pending` and syncs to the lock the next time it syncs.\n\nAn unknown `lock_id` returns `400`, not `404`. An unknown or deleted schedule ID is ignored, so the grant uses the credential holder's schedule.\n\nLeave out the times and schedules to use the credential holder's. Set them to override the credential holder's values for this lock only.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GrantCreate"
              },
              "example": {
                "external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27",
                "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                "start_time": "2026-11-01T08:30",
                "end_time": "2026-12-31T22:00",
                "week_schedule_id": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Grant created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrantWithLock"
                },
                "example": {
                  "id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                  "external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27",
                  "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                  "start_time": "2026-11-01T08:30",
                  "end_time": "2026-12-31T22:00",
                  "week_schedule_id": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62",
                  "holiday_schedule_id": null,
                  "sync_status": "pending",
                  "expired": false,
                  "lock": {
                    "id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                    "name": "Front door",
                    "serial_number": "K52489515403",
                    "location_id": "e3aec14d-ba7f-4966-99ca-137ef6f87782",
                    "model_code": "K52-3505",
                    "firmware_version": "nrf4.1.4+esp1.0.1",
                    "mac_address": "cf:7a:15:94:4c:7d",
                    "battery_voltage": 540,
                    "secondary_wifi_enabled": true,
                    "state": "locked",
                    "pending_state": null,
                    "state_updated_at": "2026-10-28T14:32:08Z",
                    "alive": true,
                    "connected_at": "2026-10-28T15:01:47Z",
                    "pending_sync": false,
                    "power_level": 85,
                    "wifi_level": 4,
                    "wifi_network_name": "Office WiFi",
                    "created_at": "2026-09-02T18:20:11Z",
                    "updated_at": "2026-10-28T15:01:47Z"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/credential_holders/{credential_holder_id}/grants/{grant_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/credential_holder_id"
        },
        {
          "$ref": "#/components/parameters/grant_id"
        }
      ],
      "get": {
        "tags": [
          "Grants"
        ],
        "operationId": "getGrant",
        "summary": "Get a grant",
        "description": "Returns the grant, including its `lock`.",
        "responses": {
          "200": {
            "description": "The grant, with its lock.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GrantWithLock"
                },
                "example": {
                  "id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                  "external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27",
                  "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                  "start_time": "2026-11-01T08:30",
                  "end_time": "2026-12-31T22:00",
                  "week_schedule_id": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62",
                  "holiday_schedule_id": null,
                  "sync_status": "pending",
                  "expired": false,
                  "lock": {
                    "id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                    "name": "Front door",
                    "serial_number": "K52489515403",
                    "location_id": "e3aec14d-ba7f-4966-99ca-137ef6f87782",
                    "model_code": "K52-3505",
                    "firmware_version": "nrf4.1.4+esp1.0.1",
                    "mac_address": "cf:7a:15:94:4c:7d",
                    "battery_voltage": 540,
                    "secondary_wifi_enabled": true,
                    "state": "locked",
                    "pending_state": null,
                    "state_updated_at": "2026-10-28T14:32:08Z",
                    "alive": true,
                    "connected_at": "2026-10-28T15:01:47Z",
                    "pending_sync": false,
                    "power_level": 85,
                    "wifi_level": 4,
                    "wifi_network_name": "Office WiFi",
                    "created_at": "2026-09-02T18:20:11Z",
                    "updated_at": "2026-10-28T15:01:47Z"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Grants"
        ],
        "operationId": "updateGrant",
        "summary": "Update a grant",
        "description": "Changes the grant's access times or schedules. The change is **queued** and reaches the lock when it next syncs.\n\n> **Send every override you want to keep.** `start_time`, `end_time`, `week_schedule_id` and `holiday_schedule_id` you leave out are cleared, so the grant falls back to the credential holder's values.\n\n`lock_id` can't be changed. To move access to another lock, revoke this grant and create a new one.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GrantUpdate"
              },
              "example": {
                "start_time": "2026-11-01T08:30",
                "end_time": "2027-01-31T22:00",
                "week_schedule_id": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62",
                "holiday_schedule_id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated grant. The response doesn't include `lock`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Grant"
                },
                "example": {
                  "id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                  "external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27",
                  "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                  "start_time": "2026-11-01T08:30",
                  "end_time": "2027-01-31T22:00",
                  "week_schedule_id": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62",
                  "holiday_schedule_id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                  "sync_status": "pending",
                  "expired": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Grants"
        ],
        "operationId": "revokeGrant",
        "summary": "Revoke a grant",
        "description": "Removes the credential holder's access to the lock. The revoke is queued: the lock erases the credential when it next syncs.\n\nTo revoke all of a person's grants at once, [remove the credential holder](#tag/credential-holders/DELETE/v1/credential_holders/{credential_holder_id}).\n",
        "responses": {
          "200": {
            "description": "Grant revoked."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/grants": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "get": {
        "tags": [
          "Grants"
        ],
        "operationId": "listLockGrants",
        "summary": "List grants for a lock",
        "description": "Lists every grant on the lock, current and expired. Each grant includes its `credential_holder`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of grants, each with its credential holder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LockGrantList"
                },
                "example": {
                  "items": [
                    {
                      "id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                      "external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "start_time": "2026-11-01T08:30",
                      "end_time": "2026-12-31T22:00",
                      "week_schedule_id": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62",
                      "holiday_schedule_id": null,
                      "sync_status": "pending",
                      "expired": false,
                      "credential_holder": {
                        "id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                        "external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                        "name": "Cleaning crew",
                        "email": "cleaning@example.com",
                        "pin": "49820",
                        "card_number": "4100094630",
                        "unikey_enabled": true,
                        "start_time": "2026-11-01T08:30",
                        "end_time": "2026-12-31T22:00",
                        "week_schedule_id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                        "holiday_schedule_id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                        "total_pending": 1,
                        "total_failed": 0,
                        "total_synced": 1,
                        "created_at": "2026-10-30T16:05:12Z",
                        "updated_at": "2026-10-30T16:20:44Z"
                      }
                    },
                    {
                      "id": "a385ad21-9164-43f4-af33-a4bb279556ef",
                      "external_uuid": null,
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "start_time": null,
                      "end_time": null,
                      "week_schedule_id": null,
                      "holiday_schedule_id": null,
                      "sync_status": "synced",
                      "expired": false,
                      "credential_holder": {
                        "id": "7e2b5d18-9c43-4f6a-b0e1-3a8d6c9f2b57",
                        "external_uuid": "3acdd5ca-727c-49f4-8a66-f1145d8dc8b5",
                        "name": "Front desk",
                        "email": "frontdesk@example.com",
                        "pin": "94918",
                        "card_number": null,
                        "unikey_enabled": false,
                        "start_time": null,
                        "end_time": null,
                        "week_schedule_id": null,
                        "holiday_schedule_id": null,
                        "total_pending": 0,
                        "total_failed": 0,
                        "total_synced": 9,
                        "created_at": "2026-09-14T13:41:09Z",
                        "updated_at": "2026-11-02T09:12:30Z"
                      }
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/grants/current": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "get": {
        "tags": [
          "Grants"
        ],
        "operationId": "listLockCurrentGrants",
        "summary": "List current grants for a lock",
        "description": "Lists the lock's grants that haven't expired (`expired: false`), including ones that haven't started yet. Each grant includes its `credential_holder`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of grants, each with its credential holder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LockGrantList"
                },
                "example": {
                  "items": [
                    {
                      "id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                      "external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "start_time": "2026-11-01T08:30",
                      "end_time": "2026-12-31T22:00",
                      "week_schedule_id": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62",
                      "holiday_schedule_id": null,
                      "sync_status": "pending",
                      "expired": false,
                      "credential_holder": {
                        "id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                        "external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                        "name": "Cleaning crew",
                        "email": "cleaning@example.com",
                        "pin": "49820",
                        "card_number": "4100094630",
                        "unikey_enabled": true,
                        "start_time": "2026-11-01T08:30",
                        "end_time": "2026-12-31T22:00",
                        "week_schedule_id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                        "holiday_schedule_id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                        "total_pending": 1,
                        "total_failed": 0,
                        "total_synced": 1,
                        "created_at": "2026-10-30T16:05:12Z",
                        "updated_at": "2026-10-30T16:20:44Z"
                      }
                    },
                    {
                      "id": "a385ad21-9164-43f4-af33-a4bb279556ef",
                      "external_uuid": null,
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "start_time": null,
                      "end_time": null,
                      "week_schedule_id": null,
                      "holiday_schedule_id": null,
                      "sync_status": "synced",
                      "expired": false,
                      "credential_holder": {
                        "id": "7e2b5d18-9c43-4f6a-b0e1-3a8d6c9f2b57",
                        "external_uuid": "3acdd5ca-727c-49f4-8a66-f1145d8dc8b5",
                        "name": "Front desk",
                        "email": "frontdesk@example.com",
                        "pin": "94918",
                        "card_number": null,
                        "unikey_enabled": false,
                        "start_time": null,
                        "end_time": null,
                        "week_schedule_id": null,
                        "holiday_schedule_id": null,
                        "total_pending": 0,
                        "total_failed": 0,
                        "total_synced": 9,
                        "created_at": "2026-09-14T13:41:09Z",
                        "updated_at": "2026-11-02T09:12:30Z"
                      }
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/grants/expired": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "get": {
        "tags": [
          "Grants"
        ],
        "operationId": "listLockExpiredGrants",
        "summary": "List expired grants for a lock",
        "description": "Lists the lock's expired grants (`expired: true`), worked out in the lock's local time. Each grant includes its `credential_holder`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of grants, each with its credential holder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LockGrantList"
                },
                "example": {
                  "items": [
                    {
                      "id": "5d2e8f71-3c94-4a0b-b6e2-9f1a7c4d8e30",
                      "external_uuid": null,
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "start_time": "2026-09-01T08:00",
                      "end_time": "2026-10-31T18:00",
                      "week_schedule_id": null,
                      "holiday_schedule_id": null,
                      "sync_status": "synced",
                      "expired": true,
                      "credential_holder": {
                        "id": "7e2b5d18-9c43-4f6a-b0e1-3a8d6c9f2b57",
                        "external_uuid": "3acdd5ca-727c-49f4-8a66-f1145d8dc8b5",
                        "name": "Front desk",
                        "email": "frontdesk@example.com",
                        "pin": "94918",
                        "card_number": null,
                        "unikey_enabled": false,
                        "start_time": null,
                        "end_time": null,
                        "week_schedule_id": null,
                        "holiday_schedule_id": null,
                        "total_pending": 0,
                        "total_failed": 0,
                        "total_synced": 9,
                        "created_at": "2026-09-14T13:41:09Z",
                        "updated_at": "2026-11-02T09:12:30Z"
                      }
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 1
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/smart_pins": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "get": {
        "tags": [
          "SmartPINs"
        ],
        "operationId": "listSmartPins",
        "summary": "List SmartPINs",
        "description": "Lists every SmartPIN on the lock, current and expired. The order isn't guaranteed.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of SmartPINs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmartPinList"
                },
                "example": {
                  "items": [
                    {
                      "id": "cc47b6ad-aac6-4e99-8bab-3d448f43e4e8",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "name": "Guest, unit 204",
                      "email": "guest204@example.com",
                      "pin": "4418850246",
                      "start_time": "2026-11-14T15:00",
                      "end_time": "2026-11-21T11:00",
                      "expired": false,
                      "type": "smart_pin_credential",
                      "sync_status": "ready"
                    },
                    {
                      "id": "ee00146f-3f40-4f1f-818b-58187c8bb272",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "name": "HVAC contractor",
                      "email": "hvac@example.com",
                      "pin": "6526592091",
                      "start_time": "2026-11-01T08:00",
                      "end_time": "2027-02-28T20:00",
                      "expired": false,
                      "type": "smart_pin_credential",
                      "sync_status": "ready"
                    },
                    {
                      "id": "7a1d3e5f-9b2c-4d6e-8f0a-1c3e5a7b9d2f",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "name": "Weekend guest",
                      "email": "weekend@example.com",
                      "pin": "8130472265",
                      "start_time": "2026-10-09T16:00",
                      "end_time": "2026-10-12T10:00",
                      "expired": true,
                      "type": "smart_pin_credential",
                      "sync_status": "ready"
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 3
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "SmartPINs"
        ],
        "operationId": "createSmartPin",
        "summary": "Create a SmartPIN",
        "description": "Generates a 10-digit SmartPIN for the lock. It works straight away, even if the lock is offline.\n\nTimes must be whole hours. Short-term SmartPINs last up to 32 days and can use any hour. Long-term SmartPINs last 33 days to a year and must run from 08:00 to 20:00. See [time rules](#tag/smartpins).\n\n> **You can't delete a SmartPIN or change its times after you create it.**\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmartPinCreate"
              },
              "examples": {
                "shortTerm": {
                  "summary": "Short-term (up to 32 days)",
                  "value": {
                    "name": "Guest, unit 204",
                    "email": "guest204@example.com",
                    "start_time": "2026-11-14T15:00",
                    "end_time": "2026-11-21T11:00"
                  }
                },
                "longTerm": {
                  "summary": "Long-term (08:00 to 20:00)",
                  "value": {
                    "name": "HVAC contractor",
                    "email": "hvac@example.com",
                    "start_time": "2026-11-01T08:00",
                    "end_time": "2027-02-28T20:00"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "SmartPIN created. Share `pin` with the guest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmartPin"
                },
                "example": {
                  "id": "cc47b6ad-aac6-4e99-8bab-3d448f43e4e8",
                  "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                  "name": "Guest, unit 204",
                  "email": "guest204@example.com",
                  "pin": "4418850246",
                  "start_time": "2026-11-14T15:00",
                  "end_time": "2026-11-21T11:00",
                  "expired": false,
                  "type": "smart_pin_credential",
                  "sync_status": "ready"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/smart_pins/current": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "get": {
        "tags": [
          "SmartPINs"
        ],
        "operationId": "listCurrentSmartPins",
        "summary": "List current SmartPINs",
        "description": "Lists the lock's SmartPINs that haven't expired (`expired: false`).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of SmartPINs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmartPinList"
                },
                "example": {
                  "items": [
                    {
                      "id": "cc47b6ad-aac6-4e99-8bab-3d448f43e4e8",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "name": "Guest, unit 204",
                      "email": "guest204@example.com",
                      "pin": "4418850246",
                      "start_time": "2026-11-14T15:00",
                      "end_time": "2026-11-21T11:00",
                      "expired": false,
                      "type": "smart_pin_credential",
                      "sync_status": "ready"
                    },
                    {
                      "id": "ee00146f-3f40-4f1f-818b-58187c8bb272",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "name": "HVAC contractor",
                      "email": "hvac@example.com",
                      "pin": "6526592091",
                      "start_time": "2026-11-01T08:00",
                      "end_time": "2027-02-28T20:00",
                      "expired": false,
                      "type": "smart_pin_credential",
                      "sync_status": "ready"
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/smart_pins/expired": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "get": {
        "tags": [
          "SmartPINs"
        ],
        "operationId": "listExpiredSmartPins",
        "summary": "List expired SmartPINs",
        "description": "Lists the lock's expired SmartPINs (`expired: true`), worked out in the lock's local time.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of SmartPINs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmartPinList"
                },
                "example": {
                  "items": [
                    {
                      "id": "7a1d3e5f-9b2c-4d6e-8f0a-1c3e5a7b9d2f",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "name": "Weekend guest",
                      "email": "weekend@example.com",
                      "pin": "8130472265",
                      "start_time": "2026-10-09T16:00",
                      "end_time": "2026-10-12T10:00",
                      "expired": true,
                      "type": "smart_pin_credential",
                      "sync_status": "ready"
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 1
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/smart_pins/{smart_pin_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        },
        {
          "$ref": "#/components/parameters/smart_pin_id"
        }
      ],
      "get": {
        "tags": [
          "SmartPINs"
        ],
        "operationId": "getSmartPin",
        "summary": "Get a SmartPIN",
        "description": "Returns a SmartPIN, including its `pin`.",
        "responses": {
          "200": {
            "description": "The SmartPIN.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmartPin"
                },
                "example": {
                  "id": "cc47b6ad-aac6-4e99-8bab-3d448f43e4e8",
                  "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                  "name": "Guest, unit 204",
                  "email": "guest204@example.com",
                  "pin": "4418850246",
                  "start_time": "2026-11-14T15:00",
                  "end_time": "2026-11-21T11:00",
                  "expired": false,
                  "type": "smart_pin_credential",
                  "sync_status": "ready"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "SmartPINs"
        ],
        "operationId": "updateSmartPin",
        "summary": "Update a SmartPIN",
        "description": "Changes a SmartPIN's `name` or `email`. Nothing else can be changed, because the times are encoded in the PIN.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmartPinUpdate"
              },
              "example": {
                "name": "Guest, unit 204 (extended)",
                "email": "guest204-alt@example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated SmartPIN.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmartPin"
                },
                "example": {
                  "id": "cc47b6ad-aac6-4e99-8bab-3d448f43e4e8",
                  "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                  "name": "Guest, unit 204 (extended)",
                  "email": "guest204-alt@example.com",
                  "pin": "4418850246",
                  "start_time": "2026-11-14T15:00",
                  "end_time": "2026-11-21T11:00",
                  "expired": false,
                  "type": "smart_pin_credential",
                  "sync_status": "ready"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/action_schedules": {
      "get": {
        "tags": [
          "Action schedules"
        ],
        "operationId": "listActionSchedules",
        "summary": "List action schedules",
        "description": "Sorted by name.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of action schedules.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionScheduleList"
                },
                "example": {
                  "items": [
                    {
                      "id": "93d52ee7-6fbe-4432-818f-47846fa0e21a",
                      "external_uuid": "522a9219-d6ea-4f92-ae7a-e1f8e3eae053",
                      "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                      "name": "Lobby open hours",
                      "schedule": {
                        "mon": [
                          {
                            "unlock": "08:00"
                          },
                          {
                            "lock": "18:00"
                          }
                        ],
                        "tue": [
                          {
                            "unlock": "08:00"
                          },
                          {
                            "lock": "18:00"
                          }
                        ],
                        "wed": [
                          {
                            "unlock": "08:00"
                          },
                          {
                            "lock": "18:00"
                          }
                        ],
                        "thu": [
                          {
                            "unlock": "08:00"
                          },
                          {
                            "lock": "18:00"
                          }
                        ],
                        "fri": [
                          {
                            "unlock": "08:00"
                          },
                          {
                            "lock": "16:00"
                          }
                        ]
                      }
                    },
                    {
                      "id": "fe4b6e19-73bb-42e1-b04c-7715f9b99b5b",
                      "external_uuid": "ce452bbc-45e1-4d34-b018-9f2fe76e9afd",
                      "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                      "name": "Saturday deliveries",
                      "schedule": {
                        "sat": [
                          {
                            "unlock": "10:00"
                          },
                          {
                            "lock": "12:00"
                          }
                        ]
                      }
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Action schedules"
        ],
        "operationId": "createActionSchedule",
        "summary": "Create an action schedule",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActionScheduleInput"
              },
              "example": {
                "name": "Lobby open hours",
                "external_uuid": "522a9219-d6ea-4f92-ae7a-e1f8e3eae053",
                "schedule": {
                  "mon": [
                    {
                      "unlock": "08:00"
                    },
                    {
                      "lock": "18:00"
                    }
                  ],
                  "tue": [
                    {
                      "unlock": "08:00"
                    },
                    {
                      "lock": "18:00"
                    }
                  ],
                  "wed": [
                    {
                      "unlock": "08:00"
                    },
                    {
                      "lock": "18:00"
                    }
                  ],
                  "thu": [
                    {
                      "unlock": "08:00"
                    },
                    {
                      "lock": "18:00"
                    }
                  ],
                  "fri": [
                    {
                      "unlock": "08:00"
                    },
                    {
                      "lock": "16:00"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Schedule created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionSchedule"
                },
                "example": {
                  "id": "93d52ee7-6fbe-4432-818f-47846fa0e21a",
                  "external_uuid": "522a9219-d6ea-4f92-ae7a-e1f8e3eae053",
                  "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                  "name": "Lobby open hours",
                  "schedule": {
                    "mon": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "18:00"
                      }
                    ],
                    "tue": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "18:00"
                      }
                    ],
                    "wed": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "18:00"
                      }
                    ],
                    "thu": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "18:00"
                      }
                    ],
                    "fri": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "16:00"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/action_schedules/{action_schedule_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/action_schedule_id"
        }
      ],
      "get": {
        "tags": [
          "Action schedules"
        ],
        "operationId": "getActionSchedule",
        "summary": "Get an action schedule",
        "responses": {
          "200": {
            "description": "The action schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionSchedule"
                },
                "example": {
                  "id": "93d52ee7-6fbe-4432-818f-47846fa0e21a",
                  "external_uuid": "522a9219-d6ea-4f92-ae7a-e1f8e3eae053",
                  "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                  "name": "Lobby open hours",
                  "schedule": {
                    "mon": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "18:00"
                      }
                    ],
                    "tue": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "18:00"
                      }
                    ],
                    "wed": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "18:00"
                      }
                    ],
                    "thu": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "18:00"
                      }
                    ],
                    "fri": [
                      {
                        "unlock": "08:00"
                      },
                      {
                        "lock": "16:00"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Action schedules"
        ],
        "operationId": "updateActionSchedule",
        "summary": "Update an action schedule",
        "description": "Changes the schedule's name or times. Fields you leave out keep their values. If you send `schedule`, it replaces the old one in full, and locks that use the schedule get the new times on their next sync.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ActionScheduleUpdate"
              },
              "example": {
                "schedule": {
                  "mon": [
                    {
                      "unlock": "07:30"
                    },
                    {
                      "lock": "18:30"
                    }
                  ],
                  "fri": [
                    {
                      "unlock": "07:30"
                    },
                    {
                      "lock": "16:00"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated action schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionSchedule"
                },
                "example": {
                  "id": "93d52ee7-6fbe-4432-818f-47846fa0e21a",
                  "external_uuid": "522a9219-d6ea-4f92-ae7a-e1f8e3eae053",
                  "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                  "name": "Lobby open hours",
                  "schedule": {
                    "mon": [
                      {
                        "unlock": "07:30"
                      },
                      {
                        "lock": "18:30"
                      }
                    ],
                    "fri": [
                      {
                        "unlock": "07:30"
                      },
                      {
                        "lock": "16:00"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Action schedules"
        ],
        "operationId": "deleteActionSchedule",
        "summary": "Remove an action schedule",
        "description": "Removes the schedule. It must not be in use by any lock, or the request returns `400` with a validation error of type `in_use`.\n",
        "responses": {
          "200": {
            "description": "Schedule removed."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/auto_lock_schedules": {
      "get": {
        "tags": [
          "Auto-lock schedules"
        ],
        "operationId": "listAutoLockSchedules",
        "summary": "List auto-lock schedules",
        "description": "Sorted by name.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of auto-lock schedules.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AutoLockScheduleList"
                },
                "example": {
                  "items": [
                    {
                      "id": "3f6d2a1e-7c84-4b59-9e0a-2d5c8b71f4a6",
                      "external_uuid": "0b8e4c2a-6f1d-4e93-a7b5-c2d9e0f13a64",
                      "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                      "name": "After hours",
                      "schedule": {
                        "mon": [
                          {
                            "auto-lock-on": "18:00"
                          }
                        ],
                        "tue": [
                          {
                            "auto-lock-off": "08:00"
                          },
                          {
                            "auto-lock-on": "18:00"
                          }
                        ],
                        "wed": [
                          {
                            "auto-lock-off": "08:00"
                          },
                          {
                            "auto-lock-on": "18:00"
                          }
                        ],
                        "thu": [
                          {
                            "auto-lock-off": "08:00"
                          },
                          {
                            "auto-lock-on": "18:00"
                          }
                        ],
                        "fri": [
                          {
                            "auto-lock-off": "08:00"
                          },
                          {
                            "auto-lock-on": "18:00"
                          }
                        ],
                        "sat": [
                          {
                            "auto-lock-off": "08:00"
                          }
                        ]
                      }
                    },
                    {
                      "id": "d4a7f2c9-1e8b-4b36-9c50-7e2f6a8d3b15",
                      "external_uuid": null,
                      "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                      "name": "Weekend events",
                      "schedule": {
                        "sat": [
                          {
                            "auto-lock-off": "09:00"
                          },
                          {
                            "auto-lock-on": "23:00"
                          }
                        ]
                      }
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Auto-lock schedules"
        ],
        "operationId": "createAutoLockSchedule",
        "summary": "Create an auto-lock schedule",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AutoLockScheduleInput"
              },
              "example": {
                "name": "After hours",
                "external_uuid": "0b8e4c2a-6f1d-4e93-a7b5-c2d9e0f13a64",
                "schedule": {
                  "mon": [
                    {
                      "auto-lock-on": "18:00"
                    }
                  ],
                  "tue": [
                    {
                      "auto-lock-off": "08:00"
                    },
                    {
                      "auto-lock-on": "18:00"
                    }
                  ],
                  "wed": [
                    {
                      "auto-lock-off": "08:00"
                    },
                    {
                      "auto-lock-on": "18:00"
                    }
                  ],
                  "thu": [
                    {
                      "auto-lock-off": "08:00"
                    },
                    {
                      "auto-lock-on": "18:00"
                    }
                  ],
                  "fri": [
                    {
                      "auto-lock-off": "08:00"
                    },
                    {
                      "auto-lock-on": "18:00"
                    }
                  ],
                  "sat": [
                    {
                      "auto-lock-off": "08:00"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Schedule created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AutoLockSchedule"
                },
                "example": {
                  "id": "3f6d2a1e-7c84-4b59-9e0a-2d5c8b71f4a6",
                  "external_uuid": "0b8e4c2a-6f1d-4e93-a7b5-c2d9e0f13a64",
                  "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                  "name": "After hours",
                  "schedule": {
                    "mon": [
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "tue": [
                      {
                        "auto-lock-off": "08:00"
                      },
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "wed": [
                      {
                        "auto-lock-off": "08:00"
                      },
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "thu": [
                      {
                        "auto-lock-off": "08:00"
                      },
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "fri": [
                      {
                        "auto-lock-off": "08:00"
                      },
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "sat": [
                      {
                        "auto-lock-off": "08:00"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/auto_lock_schedules/{auto_lock_schedule_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/auto_lock_schedule_id"
        }
      ],
      "get": {
        "tags": [
          "Auto-lock schedules"
        ],
        "operationId": "getAutoLockSchedule",
        "summary": "Get an auto-lock schedule",
        "responses": {
          "200": {
            "description": "The auto-lock schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AutoLockSchedule"
                },
                "example": {
                  "id": "3f6d2a1e-7c84-4b59-9e0a-2d5c8b71f4a6",
                  "external_uuid": "0b8e4c2a-6f1d-4e93-a7b5-c2d9e0f13a64",
                  "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                  "name": "After hours",
                  "schedule": {
                    "mon": [
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "tue": [
                      {
                        "auto-lock-off": "08:00"
                      },
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "wed": [
                      {
                        "auto-lock-off": "08:00"
                      },
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "thu": [
                      {
                        "auto-lock-off": "08:00"
                      },
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "fri": [
                      {
                        "auto-lock-off": "08:00"
                      },
                      {
                        "auto-lock-on": "18:00"
                      }
                    ],
                    "sat": [
                      {
                        "auto-lock-off": "08:00"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Auto-lock schedules"
        ],
        "operationId": "updateAutoLockSchedule",
        "summary": "Update an auto-lock schedule",
        "description": "Changes the schedule's name or times. Fields you leave out keep their values. If you send `schedule`, it replaces the old one in full, and locks that use the schedule get the new times on their next sync.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AutoLockScheduleUpdate"
              },
              "example": {
                "schedule": {
                  "mon": [
                    {
                      "auto-lock-on": "19:00"
                    }
                  ],
                  "tue": [
                    {
                      "auto-lock-off": "07:30"
                    },
                    {
                      "auto-lock-on": "19:00"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated auto-lock schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AutoLockSchedule"
                },
                "example": {
                  "id": "3f6d2a1e-7c84-4b59-9e0a-2d5c8b71f4a6",
                  "external_uuid": "0b8e4c2a-6f1d-4e93-a7b5-c2d9e0f13a64",
                  "account_id": "f7c03f5d-625a-45f4-abe5-d755553bd370",
                  "name": "After hours",
                  "schedule": {
                    "mon": [
                      {
                        "auto-lock-on": "19:00"
                      }
                    ],
                    "tue": [
                      {
                        "auto-lock-off": "07:30"
                      },
                      {
                        "auto-lock-on": "19:00"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Auto-lock schedules"
        ],
        "operationId": "deleteAutoLockSchedule",
        "summary": "Remove an auto-lock schedule",
        "description": "Removes the schedule. It must not be in use by any lock, or the request returns `400` with a validation error of type `in_use`.\n",
        "responses": {
          "200": {
            "description": "Schedule removed."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/week_schedules": {
      "get": {
        "tags": [
          "Week schedules"
        ],
        "operationId": "listWeekSchedules",
        "summary": "List week schedules",
        "description": "Sorted by name.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of week schedules.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeekScheduleList"
                },
                "example": {
                  "items": [
                    {
                      "id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                      "external_uuid": "2d9f6b1e-8a3c-4e57-b0d4-6c1a9e7f2b83",
                      "name": "Office hours",
                      "schedule": {
                        "mon": [
                          {
                            "start": "08:00",
                            "end": "18:00"
                          }
                        ],
                        "tue": [
                          {
                            "start": "08:00",
                            "end": "18:00"
                          }
                        ],
                        "wed": [
                          {
                            "start": "08:00",
                            "end": "18:00"
                          }
                        ],
                        "thu": [
                          {
                            "start": "08:00",
                            "end": "18:00"
                          }
                        ],
                        "fri": [
                          {
                            "start": "08:00",
                            "end": "16:00"
                          }
                        ]
                      }
                    },
                    {
                      "id": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62",
                      "external_uuid": null,
                      "name": "Weekends only",
                      "schedule": {
                        "sat": [
                          {
                            "start": "09:00",
                            "end": "17:00"
                          }
                        ],
                        "sun": [
                          {
                            "start": "09:00",
                            "end": "17:00"
                          }
                        ]
                      }
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Week schedules"
        ],
        "operationId": "createWeekSchedule",
        "summary": "Create a week schedule",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WeekScheduleInput"
              },
              "example": {
                "name": "Office hours",
                "external_uuid": "2d9f6b1e-8a3c-4e57-b0d4-6c1a9e7f2b83",
                "schedule": {
                  "mon": [
                    {
                      "start": "08:00",
                      "end": "18:00"
                    }
                  ],
                  "tue": [
                    {
                      "start": "08:00",
                      "end": "18:00"
                    }
                  ],
                  "wed": [
                    {
                      "start": "08:00",
                      "end": "18:00"
                    }
                  ],
                  "thu": [
                    {
                      "start": "08:00",
                      "end": "18:00"
                    }
                  ],
                  "fri": [
                    {
                      "start": "08:00",
                      "end": "16:00"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Schedule created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeekSchedule"
                },
                "example": {
                  "id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                  "external_uuid": "2d9f6b1e-8a3c-4e57-b0d4-6c1a9e7f2b83",
                  "name": "Office hours",
                  "schedule": {
                    "mon": [
                      {
                        "start": "08:00",
                        "end": "18:00"
                      }
                    ],
                    "tue": [
                      {
                        "start": "08:00",
                        "end": "18:00"
                      }
                    ],
                    "wed": [
                      {
                        "start": "08:00",
                        "end": "18:00"
                      }
                    ],
                    "thu": [
                      {
                        "start": "08:00",
                        "end": "18:00"
                      }
                    ],
                    "fri": [
                      {
                        "start": "08:00",
                        "end": "16:00"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/week_schedules/{week_schedule_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/week_schedule_id"
        }
      ],
      "get": {
        "tags": [
          "Week schedules"
        ],
        "operationId": "getWeekSchedule",
        "summary": "Get a week schedule",
        "responses": {
          "200": {
            "description": "The week schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeekSchedule"
                },
                "example": {
                  "id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                  "external_uuid": "2d9f6b1e-8a3c-4e57-b0d4-6c1a9e7f2b83",
                  "name": "Office hours",
                  "schedule": {
                    "mon": [
                      {
                        "start": "08:00",
                        "end": "18:00"
                      }
                    ],
                    "tue": [
                      {
                        "start": "08:00",
                        "end": "18:00"
                      }
                    ],
                    "wed": [
                      {
                        "start": "08:00",
                        "end": "18:00"
                      }
                    ],
                    "thu": [
                      {
                        "start": "08:00",
                        "end": "18:00"
                      }
                    ],
                    "fri": [
                      {
                        "start": "08:00",
                        "end": "16:00"
                      }
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Week schedules"
        ],
        "operationId": "updateWeekSchedule",
        "summary": "Update a week schedule",
        "description": "Changes the schedule's name or times. Fields you leave out keep their values. If you send `schedule`, it replaces the old one in full, and locks that use the schedule get the new times on their next sync.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WeekScheduleUpdate"
              },
              "example": {
                "schedule": {
                  "tue": [
                    {
                      "start": "08:30",
                      "end": "12:30"
                    },
                    {
                      "start": "14:00",
                      "end": "18:45"
                    }
                  ],
                  "thu": [
                    {
                      "start": "08:30",
                      "end": "12:30"
                    },
                    {
                      "start": "14:00",
                      "end": "18:45"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated week schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeekSchedule"
                },
                "example": {
                  "id": "60c2623b-4577-4743-84af-5cc6407b21b1",
                  "external_uuid": "2d9f6b1e-8a3c-4e57-b0d4-6c1a9e7f2b83",
                  "name": "Office hours",
                  "schedule": {
                    "tue": [
                      {
                        "start": "08:30",
                        "end": "12:30"
                      },
                      {
                        "start": "14:00",
                        "end": "18:45"
                      }
                    ],
                    "thu": [
                      {
                        "start": "08:30",
                        "end": "12:30"
                      },
                      {
                        "start": "14:00",
                        "end": "18:45"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Week schedules"
        ],
        "operationId": "deleteWeekSchedule",
        "summary": "Remove a week schedule",
        "description": "Removes the schedule. It must not be in use by any credential holder or grant, or the request returns `400` with a validation error of type `in_use`.\n",
        "responses": {
          "200": {
            "description": "Schedule removed."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/holiday_schedules": {
      "get": {
        "tags": [
          "Holiday schedules"
        ],
        "operationId": "listHolidaySchedules",
        "summary": "List holiday schedules",
        "description": "Sorted by name.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of holiday schedules.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HolidayScheduleList"
                },
                "example": {
                  "items": [
                    {
                      "id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                      "external_uuid": "9e5c1a7d-3b8f-4d26-a0e4-1f7b3c9d5a62",
                      "name": "Company holidays 2026",
                      "schedule": [
                        {
                          "start": "2026-11-26",
                          "end": "2026-11-27"
                        },
                        {
                          "start": "2026-12-24",
                          "end": "2026-12-25"
                        },
                        {
                          "start": "2026-12-31",
                          "end": "2027-01-01"
                        }
                      ]
                    },
                    {
                      "id": "b2e8d4f6-7a1c-4e39-8d5b-0c6f2a4e8b17",
                      "external_uuid": null,
                      "name": "August shutdown",
                      "schedule": [
                        {
                          "start": "2026-08-01",
                          "end": "2026-08-31"
                        }
                      ]
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Holiday schedules"
        ],
        "operationId": "createHolidaySchedule",
        "summary": "Create a holiday schedule",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HolidayScheduleInput"
              },
              "example": {
                "name": "Company holidays 2026",
                "external_uuid": "9e5c1a7d-3b8f-4d26-a0e4-1f7b3c9d5a62",
                "schedule": [
                  {
                    "start": "2026-11-26",
                    "end": "2026-11-27"
                  },
                  {
                    "start": "2026-12-24",
                    "end": "2026-12-25"
                  },
                  {
                    "start": "2026-12-31",
                    "end": "2027-01-01"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Schedule created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HolidaySchedule"
                },
                "example": {
                  "id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                  "external_uuid": "9e5c1a7d-3b8f-4d26-a0e4-1f7b3c9d5a62",
                  "name": "Company holidays 2026",
                  "schedule": [
                    {
                      "start": "2026-11-26",
                      "end": "2026-11-27"
                    },
                    {
                      "start": "2026-12-24",
                      "end": "2026-12-25"
                    },
                    {
                      "start": "2026-12-31",
                      "end": "2027-01-01"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/holiday_schedules/{holiday_schedule_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/holiday_schedule_id"
        }
      ],
      "get": {
        "tags": [
          "Holiday schedules"
        ],
        "operationId": "getHolidaySchedule",
        "summary": "Get a holiday schedule",
        "responses": {
          "200": {
            "description": "The holiday schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HolidaySchedule"
                },
                "example": {
                  "id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                  "external_uuid": "9e5c1a7d-3b8f-4d26-a0e4-1f7b3c9d5a62",
                  "name": "Company holidays 2026",
                  "schedule": [
                    {
                      "start": "2026-11-26",
                      "end": "2026-11-27"
                    },
                    {
                      "start": "2026-12-24",
                      "end": "2026-12-25"
                    },
                    {
                      "start": "2026-12-31",
                      "end": "2027-01-01"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Holiday schedules"
        ],
        "operationId": "updateHolidaySchedule",
        "summary": "Update a holiday schedule",
        "description": "Changes the schedule's name or times. Fields you leave out keep their values. If you send `schedule`, it replaces the old one in full, and locks that use the schedule get the new times on their next sync.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HolidayScheduleUpdate"
              },
              "example": {
                "schedule": [
                  {
                    "start": "2026-11-26",
                    "end": "2026-11-27"
                  },
                  {
                    "start": "2026-12-24",
                    "end": "2027-01-01"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated holiday schedule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HolidaySchedule"
                },
                "example": {
                  "id": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079",
                  "external_uuid": "9e5c1a7d-3b8f-4d26-a0e4-1f7b3c9d5a62",
                  "name": "Company holidays 2026",
                  "schedule": [
                    {
                      "start": "2026-11-26",
                      "end": "2026-11-27"
                    },
                    {
                      "start": "2026-12-24",
                      "end": "2027-01-01"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Holiday schedules"
        ],
        "operationId": "deleteHolidaySchedule",
        "summary": "Remove a holiday schedule",
        "description": "Removes the schedule. It must not be in use by any credential holder or grant, or the request returns `400` with a validation error of type `in_use`.\n",
        "responses": {
          "200": {
            "description": "Schedule removed."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/events": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "operationId": "listLockEvents",
        "summary": "List events",
        "description": "Lists the lock's events, newest recorded first. Use `has_more_items` to page back through older events. A lock that reports late can add events out of `occurred_at` order.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventList"
                },
                "example": {
                  "items": [
                    {
                      "id": "9729f303-b3e5-43cf-9cc6-7a88b36e9192",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "type": "door_state",
                      "occurred_at": "2026-11-04T08:02:31-07:00",
                      "action": "door_opened"
                    },
                    {
                      "id": "1d4d097f-c75b-420e-828d-504e903bd8f1",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "type": "access",
                      "occurred_at": "2026-11-04T08:02:15-07:00",
                      "action": "unlocked",
                      "method": "keypad",
                      "pin": "49820",
                      "source_type": "credential_holder",
                      "source_name": "Cleaning crew",
                      "credential_holder_id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                      "credential_holder_external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                      "credential_holder_grant_id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                      "credential_holder_grant_external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27"
                    },
                    {
                      "id": "301c32fd-e902-4a86-8552-a5c7ba202ac1",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "type": "access",
                      "occurred_at": "2026-11-03T18:05:44-07:00",
                      "action": "locked",
                      "method": "ble",
                      "source_type": "user",
                      "source_name": "Office admin",
                      "user_id": "5a9c2e7f-1b4d-4f86-a3e0-8d6b2c9f7e14"
                    },
                    {
                      "id": "a8e2c4f6-0b1d-4e3a-9c57-6d8f0a2b4c19",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "type": "battery",
                      "occurred_at": "2026-11-03T06:00:12-07:00",
                      "action": "battery_low"
                    },
                    {
                      "id": "c6d0e2f4-8a1b-4c3d-b5e7-9f1a3c5e7d20",
                      "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                      "type": "credential_holder_grant_sync",
                      "occurred_at": "2026-10-30T09:21:03-06:00",
                      "action": "upsert_success",
                      "credential_holder_id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                      "credential_holder_external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                      "credential_holder_grant_id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                      "credential_holder_grant_external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27"
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": true
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/locks/{lock_id}/events/{event_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/lock_id"
        },
        {
          "$ref": "#/components/parameters/event_id"
        }
      ],
      "get": {
        "tags": [
          "Events"
        ],
        "operationId": "getLockEvent",
        "summary": "Get an event",
        "responses": {
          "200": {
            "description": "The event.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Event"
                },
                "example": {
                  "id": "301c32fd-e902-4a86-8552-a5c7ba202ac1",
                  "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                  "type": "access",
                  "occurred_at": "2026-11-03T18:05:44-07:00",
                  "action": "locked",
                  "method": "ble",
                  "source_type": "user",
                  "source_name": "Office admin",
                  "user_id": "5a9c2e7f-1b4d-4f86-a3e0-8d6b2c9f7e14"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhooks",
        "summary": "List webhooks",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of webhooks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookList"
                },
                "example": {
                  "items": [
                    {
                      "id": "2bd463b9-ef85-4349-bb51-371b32069d78",
                      "url": "https://example.com/korelock/webhooks",
                      "enabled": true,
                      "disabled_by": null,
                      "secret": null,
                      "subscribed_events": [
                        "access"
                      ]
                    },
                    {
                      "id": "853e5864-e3e7-4fff-8a1b-a0dfa549dba4",
                      "url": "https://integrations.example.com/korelock",
                      "enabled": true,
                      "disabled_by": null,
                      "secret": null,
                      "subscribed_events": [
                        "*"
                      ]
                    }
                  ],
                  "metadata": {
                    "page": 1,
                    "per_page": 20,
                    "has_more_items": false,
                    "pages": 1,
                    "total_count": 2
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhook",
        "summary": "Create a webhook",
        "description": "Starts sending the subscribed event types to `url`. The URL must use HTTPS.\n\nSet a `secret` so you can [verify deliveries](#tag/webhooks).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookInput"
              },
              "example": {
                "url": "https://example.com/korelock/webhooks",
                "enabled": true,
                "subscribed_events": [
                  "access"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                },
                "example": {
                  "id": "2bd463b9-ef85-4349-bb51-371b32069d78",
                  "url": "https://example.com/korelock/webhooks",
                  "enabled": true,
                  "disabled_by": null,
                  "secret": null,
                  "subscribed_events": [
                    "access"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhooks/{webhook_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/webhook_id"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "getWebhook",
        "summary": "Get a webhook",
        "responses": {
          "200": {
            "description": "The webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                },
                "example": {
                  "id": "2bd463b9-ef85-4349-bb51-371b32069d78",
                  "url": "https://example.com/korelock/webhooks",
                  "enabled": true,
                  "disabled_by": null,
                  "secret": null,
                  "subscribed_events": [
                    "access"
                  ],
                  "created_at": "2026-10-12T17:40:03Z",
                  "updated_at": "2026-10-12T17:40:03Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "updateWebhook",
        "summary": "Update a webhook",
        "description": "Changes the webhook's URL, secret or subscribed events, or turns it on or off with `enabled`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdate"
              },
              "example": {
                "enabled": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                },
                "example": {
                  "id": "2bd463b9-ef85-4349-bb51-371b32069d78",
                  "url": "https://example.com/korelock/webhooks",
                  "enabled": false,
                  "disabled_by": "user",
                  "secret": null,
                  "subscribed_events": [
                    "access"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook",
        "description": "Removes the webhook and stops new deliveries to it. Deliveries already being retried may still arrive.",
        "responses": {
          "200": {
            "description": "Webhook deleted."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "webhooks": {
    "access": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "accessWebhook",
        "summary": "access event",
        "description": "An access event: a lock, unlock or denied attempt. KoreLock sends this to every enabled webhook subscribed to `access` or `*`.\n",
        "parameters": [
          {
            "name": "X-Hub-Signature",
            "in": "header",
            "required": false,
            "description": "Present when the webhook has a `secret`: `sha256=` plus the HMAC-SHA256 hex digest of the body.",
            "schema": {
              "type": "string"
            },
            "example": "sha256=3f5b0c9e1d7a2b48c6e0f9a1d3b5c7e9f2a4b6c8d0e1f3a5b7c9d1e3f5a7b9c1"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccessEvent"
              },
              "example": {
                "id": "1d4d097f-c75b-420e-828d-504e903bd8f1",
                "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                "type": "access",
                "occurred_at": "2026-11-04T08:02:15-07:00",
                "action": "unlocked",
                "method": "keypad",
                "pin": "49820",
                "source_type": "credential_holder",
                "source_name": "Cleaning crew",
                "credential_holder_id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                "credential_holder_external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                "credential_holder_grant_id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                "credential_holder_grant_external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any `2xx` within 3 seconds. Other responses and timeouts are retried."
          }
        }
      }
    },
    "credential_holder_grant_sync": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "credentialHolderGrantSyncWebhook",
        "summary": "credential_holder_grant_sync event",
        "description": "A grant was added to or removed from a lock. KoreLock sends this to every enabled webhook subscribed to `credential_holder_grant_sync` or `*`.\n",
        "parameters": [
          {
            "name": "X-Hub-Signature",
            "in": "header",
            "required": false,
            "description": "Present when the webhook has a `secret`: `sha256=` plus the HMAC-SHA256 hex digest of the body.",
            "schema": {
              "type": "string"
            },
            "example": "sha256=3f5b0c9e1d7a2b48c6e0f9a1d3b5c7e9f2a4b6c8d0e1f3a5b7c9d1e3f5a7b9c1"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GrantSyncEvent"
              },
              "example": {
                "id": "c6d0e2f4-8a1b-4c3d-b5e7-9f1a3c5e7d20",
                "lock_id": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe",
                "type": "credential_holder_grant_sync",
                "occurred_at": "2026-10-30T09:21:03-06:00",
                "action": "upsert_success",
                "credential_holder_id": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35",
                "credential_holder_external_uuid": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2",
                "credential_holder_grant_id": "e0d0db29-45a0-4a80-a438-9bf44e356f6a",
                "credential_holder_grant_external_uuid": "15c438f9-7554-4bd1-99b3-762234a9bf27"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Return any `2xx` within 3 seconds. Other responses and timeouts are retried."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.0. Use client credentials for your own account, or the authorization code flow (with PKCE for public apps) to act for other customers.",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://api.korelock.com/oauth/token",
            "scopes": {
              "all": "Full access to the account"
            }
          },
          "authorizationCode": {
            "authorizationUrl": "https://korelock.webmanager.cloud/oauth/authorize",
            "tokenUrl": "https://api.korelock.com/oauth/token",
            "refreshUrl": "https://api.korelock.com/oauth/token",
            "scopes": {
              "all": "Full access to the account"
            }
          }
        }
      },
      "bearerToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Paste an access token you already have."
      }
    },
    "parameters": {
      "page": {
        "name": "page",
        "in": "query",
        "description": "Page number. Pages past the end return an empty `items` array.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "per_page": {
        "name": "per_page",
        "in": "query",
        "description": "Results per page.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 250,
          "default": 20
        }
      },
      "lock_id": {
        "name": "lock_id",
        "in": "path",
        "required": true,
        "description": "The lock's `id`.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe"
      },
      "location_id": {
        "name": "location_id",
        "in": "path",
        "required": true,
        "description": "The location's `id`, or `external_uuid:<your uuid>` to look it up by your own identifier.",
        "schema": {
          "type": "string"
        },
        "example": "9db51567-a8a6-4c63-b6a4-4e239b33250f"
      },
      "credential_holder_id": {
        "name": "credential_holder_id",
        "in": "path",
        "required": true,
        "description": "The credential holder's `id`, or `external_uuid:<your uuid>` to look them up by your own identifier.",
        "schema": {
          "type": "string"
        },
        "example": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35"
      },
      "grant_id": {
        "name": "grant_id",
        "in": "path",
        "required": true,
        "description": "The grant's `id`, or `external_uuid:<your uuid>` to look it up by your own identifier.",
        "schema": {
          "type": "string"
        },
        "example": "e0d0db29-45a0-4a80-a438-9bf44e356f6a"
      },
      "smart_pin_id": {
        "name": "smart_pin_id",
        "in": "path",
        "required": true,
        "description": "The SmartPIN's `id`.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "cc47b6ad-aac6-4e99-8bab-3d448f43e4e8"
      },
      "action_schedule_id": {
        "name": "action_schedule_id",
        "in": "path",
        "required": true,
        "description": "The action schedule's `id`, or `external_uuid:<your uuid>` to look it up by your own identifier.",
        "schema": {
          "type": "string"
        },
        "example": "93d52ee7-6fbe-4432-818f-47846fa0e21a"
      },
      "auto_lock_schedule_id": {
        "name": "auto_lock_schedule_id",
        "in": "path",
        "required": true,
        "description": "The auto-lock schedule's `id`, or `external_uuid:<your uuid>` to look it up by your own identifier.",
        "schema": {
          "type": "string"
        },
        "example": "3f6d2a1e-7c84-4b59-9e0a-2d5c8b71f4a6"
      },
      "week_schedule_id": {
        "name": "week_schedule_id",
        "in": "path",
        "required": true,
        "description": "The week schedule's `id`, or `external_uuid:<your uuid>` to look it up by your own identifier.",
        "schema": {
          "type": "string"
        },
        "example": "60c2623b-4577-4743-84af-5cc6407b21b1"
      },
      "holiday_schedule_id": {
        "name": "holiday_schedule_id",
        "in": "path",
        "required": true,
        "description": "The holiday schedule's `id`, or `external_uuid:<your uuid>` to look it up by your own identifier.",
        "schema": {
          "type": "string"
        },
        "example": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079"
      },
      "event_id": {
        "name": "event_id",
        "in": "path",
        "required": true,
        "description": "The event's `id`.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "301c32fd-e902-4a86-8552-a5c7ba202ac1"
      },
      "webhook_id": {
        "name": "webhook_id",
        "in": "path",
        "required": true,
        "description": "The webhook's `id`.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "2bd463b9-ef85-4349-bb51-371b32069d78"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request was invalid. `error_type` says why:\n\n- `validation_failed`: the data didn't pass validation. See `validation_errors`.\n- `unpermitted_params`: the body has a field this endpoint doesn't accept.\n- `invalid_parameter`: `page` or `per_page` is out of range.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationError"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The access token is missing, invalid or expired. For a bad token the body may be empty, with details in the `WWW-Authenticate` header. If the user or account was deleted, or the user's access has expired, the body has an `error_type` of `user_or_account_deleted` or `user_access_expired`.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No resource with that ID exists on this account.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "integer",
                  "example": 404
                },
                "error": {
                  "type": "string",
                  "example": "Not Found"
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. Wait the number of seconds in `Retry-After`, then retry.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying. Always `60`.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RateLimitError"
            }
          }
        }
      }
    },
    "schemas": {
      "ClientCredentialsGrant": {
        "title": "Client credentials",
        "type": "object",
        "required": [
          "grant_type",
          "client_id",
          "client_secret"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "const": "client_credentials"
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string"
          }
        }
      },
      "AuthorizationCodeGrant": {
        "title": "Authorization code",
        "type": "object",
        "required": [
          "grant_type",
          "code",
          "client_id",
          "redirect_uri"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "const": "authorization_code"
          },
          "code": {
            "type": "string",
            "description": "The `code` from your redirect URL."
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string",
            "description": "Confidential apps only."
          },
          "code_verifier": {
            "type": "string",
            "description": "Public apps only (PKCE). The verifier that produced the `code_challenge`."
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri",
            "description": "Must match the redirect URL registered for the app."
          }
        }
      },
      "RefreshTokenGrant": {
        "title": "Refresh token",
        "type": "object",
        "required": [
          "grant_type",
          "refresh_token",
          "client_id"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "const": "refresh_token"
          },
          "refresh_token": {
            "type": "string"
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string",
            "description": "Confidential apps only."
          }
        }
      },
      "Token": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "example": "example_access_token_2f9a6c1e8b4d"
          },
          "refresh_token": {
            "type": "string",
            "description": "Authorization code and refresh flows only.",
            "example": "example_refresh_token_6b1e8d3c4a7f"
          },
          "token_type": {
            "type": "string",
            "example": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Lifetime in seconds.",
            "example": 7200
          },
          "scope": {
            "type": "string",
            "example": "all"
          },
          "created_at": {
            "type": "integer",
            "description": "Unix timestamp. The token expires at `created_at + expires_in`.",
            "example": 1793900000
          }
        }
      },
      "PageMetadata": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer",
            "example": 1
          },
          "per_page": {
            "type": "integer",
            "example": 20
          },
          "has_more_items": {
            "type": "boolean",
            "example": false
          },
          "pages": {
            "type": "integer",
            "example": 1
          },
          "total_count": {
            "type": "integer",
            "example": 2
          }
        }
      },
      "Location": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "9db51567-a8a6-4c63-b6a4-4e239b33250f"
          },
          "external_uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Your own identifier for this location.",
            "example": "08cda068-2a82-4f25-9c61-471a196dfd72"
          },
          "name": {
            "type": "string",
            "maxLength": 50,
            "description": "Unique in the account.",
            "example": "Denver HQ"
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "example": "1801 Wewatta St, Denver, CO"
          },
          "timezone": {
            "type": "string",
            "description": "IANA time zone, such as `America/New_York`. Locks at this location use it for schedules and SmartPIN times.",
            "example": "America/Denver"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-09-02T18:20:11Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-28T15:01:47Z"
          }
        }
      },
      "LocationInput": {
        "type": "object",
        "required": [
          "name",
          "timezone"
        ],
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid",
            "description": "Your own identifier for this location."
          },
          "name": {
            "type": "string",
            "maxLength": 50,
            "description": "Unique in the account."
          },
          "address": {
            "type": "string",
            "maxLength": 100
          },
          "timezone": {
            "type": "string",
            "description": "IANA time zone, such as `America/New_York`."
          }
        }
      },
      "LocationUpdate": {
        "type": "object",
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "timezone": {
            "type": "string"
          }
        }
      },
      "LocationList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Location"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "LockModel": {
        "type": "object",
        "description": "A lock model and its capabilities. See [Models & capabilities](#tag/lock-models).",
        "properties": {
          "name": {
            "type": "string",
            "description": "Model name, including the submodel. Matches a lock's `model_code`.",
            "example": "K52-3505"
          },
          "submodel": {
            "type": [
              "string",
              "null"
            ],
            "description": "The submodel on its own. `null` if the model has no submodels. Send it as `sub_model` when you register a lock.",
            "example": "3505"
          },
          "submodel_number": {
            "type": "string",
            "description": "Numeric code of the submodel. Empty if the model has no submodels.",
            "example": "3505"
          },
          "pin_credential": {
            "type": "boolean",
            "description": "Credential: accepts PINs (`pin`)."
          },
          "smart_card_credential": {
            "type": "boolean",
            "description": "Credential: accepts cards (`card_number`). True when the model has an NFC or proximity reader."
          },
          "smart_pin_credential": {
            "type": "boolean",
            "description": "Credential: accepts [SmartPINs](#tag/smartpins). Also needs the `smart_pin_enabled` setting."
          },
          "unikey_credential": {
            "type": "boolean",
            "description": "Credential: accepts UniKey mobile credentials (`unikey_enabled`). Also supports the `unikey_reader` and `unikey_read_range_mode` settings."
          },
          "entry_app_credential": {
            "type": "boolean",
            "description": "Credential: accepts Entry app credentials (`entry_app_enabled`)."
          },
          "nfc_reader": {
            "type": "boolean",
            "description": "Reader: has an NFC card reader. Supports `nfc_antenna_enabled`."
          },
          "prox_reader": {
            "type": "boolean",
            "description": "Reader: has a proximity card reader. Supports `prox_antenna_enabled`."
          },
          "unikey_reader_attached": {
            "type": "boolean",
            "description": "Reader: has an attached UniKey reader. Supports `edge_controller_reader_type`."
          },
          "programming_code": {
            "type": "boolean",
            "description": "Setting: supports `programming_code`."
          },
          "no_enter_code": {
            "type": "boolean",
            "description": "Setting: supports `no_enter_code`."
          },
          "keypad_lockout": {
            "type": "boolean",
            "description": "Setting: supports `keypad_lockout_count` and `keypad_lockout_time`."
          },
          "manual_auto_lock": {
            "type": "boolean",
            "description": "Setting: supports `manual_auto_lock_timeout`."
          },
          "wake_wifi_except_manual": {
            "type": "boolean",
            "description": "Setting: supports the `user_action_except_manual` value of `wake_wifi`. True for every current model."
          },
          "alarm": {
            "type": "boolean",
            "description": "Setting: has an alarm. Supports `alarm_trigger_time` and `mute_alarm`."
          },
          "always_on": {
            "type": "boolean",
            "description": "Setting: supports `always_on`."
          },
          "card_byte_order": {
            "type": "boolean",
            "description": "Setting: supports `card_byte_order`."
          },
          "nfc_on_keypress": {
            "type": "boolean",
            "description": "Setting: supports `nfc_on_keypress`. False for every current model."
          },
          "deadbolt_calibration": {
            "type": "boolean",
            "description": "The model is a deadbolt that supports calibration. New locks of these models default to `auto_lock: disabled`."
          },
          "mobile_credential": {
            "type": "boolean",
            "deprecated": true,
            "description": "Deprecated. Use `entry_app_credential`."
          },
          "prox_nfc_reader": {
            "type": "boolean",
            "deprecated": true,
            "description": "Deprecated. Use `prox_reader`."
          }
        }
      },
      "Lock": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe"
          },
          "name": {
            "type": "string",
            "example": "Front door"
          },
          "serial_number": {
            "type": "string",
            "example": "K52489515403"
          },
          "location_id": {
            "type": "string",
            "format": "uuid",
            "example": "e3aec14d-ba7f-4966-99ca-137ef6f87782"
          },
          "model_code": {
            "type": "string",
            "readOnly": true,
            "description": "Model including submodel.",
            "example": "K52-3505"
          },
          "firmware_version": {
            "type": [
              "string",
              "null"
            ],
            "readOnly": true,
            "example": "nrf4.1.4+esp1.0.1"
          },
          "mac_address": {
            "type": [
              "string",
              "null"
            ],
            "readOnly": true,
            "description": "Wi-Fi MAC address.",
            "example": "cf:7a:15:94:4c:7d"
          },
          "battery_voltage": {
            "type": [
              "integer",
              "null"
            ],
            "readOnly": true,
            "description": "Battery voltage in hundredths of a volt. `540` means 5.4 V.",
            "example": 540
          },
          "secondary_wifi_enabled": {
            "type": "boolean",
            "readOnly": true,
            "description": "Whether a backup Wi-Fi network is set up. End users set this in the mobile app.",
            "example": true
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "locked",
              "unlocked",
              null
            ],
            "readOnly": true,
            "description": "The last state the lock reported. Fields the lock reports, such as `state`, `firmware_version` and `power_level`, are `null` until it first syncs.",
            "example": "locked"
          },
          "pending_state": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "lock",
              "unlock",
              null
            ],
            "readOnly": true,
            "description": "A queued command waiting for the lock's next sync.",
            "example": null
          },
          "state_updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-28T14:32:08Z"
          },
          "alive": {
            "type": "boolean",
            "readOnly": true,
            "description": "Whether the lock is checking in on its expected heartbeat.",
            "example": true
          },
          "connected_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "readOnly": true,
            "description": "Last time the lock connected.",
            "example": "2026-10-28T15:01:47Z"
          },
          "pending_sync": {
            "type": "boolean",
            "readOnly": true,
            "description": "Whether changes are waiting to sync to the lock.",
            "example": false
          },
          "power_level": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 100,
            "readOnly": true,
            "description": "Remaining battery percentage.",
            "example": 85
          },
          "wifi_level": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 4,
            "readOnly": true,
            "description": "Signal strength from 1 to 4.",
            "example": 4
          },
          "wifi_network_name": {
            "type": [
              "string",
              "null"
            ],
            "readOnly": true,
            "description": "Wi-Fi network the lock last joined.",
            "example": "Office WiFi"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-09-02T18:20:11Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-28T15:01:47Z"
          }
        }
      },
      "LockRegistration": {
        "type": "object",
        "required": [
          "name",
          "serial_number",
          "location_id"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 50,
            "description": "Unique within the location."
          },
          "serial_number": {
            "type": "string"
          },
          "sub_model": {
            "type": "string",
            "description": "Required for model series with submodels. See the table above."
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "LockUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "LockList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Lock"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "LockCommandResult": {
        "type": "object",
        "properties": {
          "state": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "locked",
              "unlocked",
              null
            ],
            "description": "The last state the lock reported."
          },
          "pending_state": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "lock",
              "unlock",
              null
            ],
            "description": "The queued command, if any."
          }
        }
      },
      "LockSettings": {
        "type": "object",
        "description": "A lock's settings. Not every model supports every setting; settings the model doesn't support are left out. Check the [model capabilities](#tag/lock-models) to see which apply.\n",
        "properties": {
          "action_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "An [action schedule](#tag/action-schedules) that limits when access is allowed. `null` means no schedule.",
            "default": null,
            "example": null
          },
          "alarm_trigger_time": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 10,
            "maximum": 120,
            "default": 20,
            "description": "Seconds before the alarm sounds when `mute_alarm` is `false`. Models with the `alarm` capability only.",
            "example": 20
          },
          "always_on": {
            "type": [
              "boolean",
              "null"
            ],
            "default": false,
            "description": "Keeps the lock's connection to the cloud open all the time. Models with the `always_on` capability only.",
            "example": false
          },
          "auto_lock": {
            "type": "string",
            "enum": [
              "enabled",
              "disabled",
              "schedule"
            ],
            "default": "enabled",
            "description": "When the lock locks itself, `auto_lock_timeout` seconds after it unlocks:\n\n- `enabled`: always.\n- `disabled`: never.\n- `schedule`: follows the auto-lock schedule in `auto_lock_schedule_id`.\n\nDefaults to `disabled` on deadbolt models.\n",
            "example": "enabled"
          },
          "auto_lock_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "default": null,
            "description": "The [auto-lock schedule](#tag/auto-lock-schedules) to follow when `auto_lock` is `schedule`.",
            "example": null
          },
          "auto_lock_timeout": {
            "type": "integer",
            "minimum": 5,
            "maximum": 1800,
            "default": 5,
            "description": "Seconds after unlocking before the lock auto-locks.",
            "example": 30
          },
          "card_byte_order": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "least_significant_byte",
              "most_significant_byte",
              null
            ],
            "description": "The byte order used to read cards. Defaults to `least_significant_byte`. Models with the `card_byte_order` capability only.",
            "example": null
          },
          "edge_controller_reader_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "unikey",
              "card_only",
              "card_and_pin",
              null
            ],
            "description": "The type of reader attached to the edge controller. Models with the `unikey_reader_attached` capability only.\n\n- `unikey`: UniKey credential reader.\n- `card_only`: card reader.\n- `card_and_pin`: card and PIN reader.\n",
            "example": null
          },
          "heartbeat_interval": {
            "type": "integer",
            "default": 60,
            "description": "Minutes between heartbeat check-ins. Use a value from 5 to 120, or a whole number of hours from 180 to 1440 (180, 240, 300 and so on, up to 24 hours).",
            "minimum": 5,
            "maximum": 1440,
            "example": 60
          },
          "keypad_lockout_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Failed keypad attempts before the keypad locks out. Not set (`null`) by default, which leaves lockout off. Models with the `keypad_lockout` capability only.",
            "example": 4
          },
          "keypad_lockout_time": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Seconds the keypad stays locked out. Not set (`null`) by default. Needs `keypad_lockout_count` above 0. Models with the `keypad_lockout` capability only.",
            "example": 30
          },
          "manual_auto_lock_timeout": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Seconds before the lock auto-locks after a manual opening, such as turning the knob. Doesn't apply to keypad or remote unlocks. `null` (the default) or `0` turns it off. Models with the `manual_auto_lock` capability only.",
            "example": 0
          },
          "mute": {
            "type": "boolean",
            "default": false,
            "description": "Turns the lock's sounds off.",
            "example": false
          },
          "mute_alarm": {
            "type": [
              "boolean",
              "null"
            ],
            "default": false,
            "description": "Turns the alarm off. Models with the `alarm` capability only.",
            "example": false
          },
          "nfc": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "off",
              "always_on",
              "on_keypress",
              null
            ],
            "deprecated": true,
            "description": "Deprecated. Use `nfc_antenna_enabled` and `nfc_on_keypress` instead. Worked out from those two settings; returned only on NFC models that aren't PDQ locks. Sending a value that disagrees with them in the same request returns `400` (`conflicting_reader_settings`).",
            "example": "on_keypress"
          },
          "nfc_antenna_enabled": {
            "type": [
              "boolean",
              "null"
            ],
            "default": false,
            "description": "Turns the NFC card reader on. Defaults to `true` on PDQ locks. Models with the `nfc_reader` capability only.",
            "example": true
          },
          "nfc_on_keypress": {
            "type": [
              "boolean",
              "null"
            ],
            "default": false,
            "description": "Turns the NFC reader on only after a key press, which saves battery. Models with the `nfc_on_keypress` capability only; no current model has it, so only `false` is accepted.",
            "example": true
          },
          "no_enter_code": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Lets users skip the `#` key after entering a PIN. Not set (`null`) by default. Models with the `no_enter_code` capability only.",
            "example": false
          },
          "online_auto_lock": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Whether the lock auto-locks after a remote unlock from the API, web app or mobile app. Not set (`null`) by default.",
            "example": true
          },
          "programming_code": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9]{4,10}$",
            "default": "123456",
            "description": "Gives admin access to the lock from the keypad. 4 to 10 digits; leading zeros are allowed. Change it from the default. It can't start with a PIN that's already on the lock (`pin_credential_collision`). Models with the `programming_code` capability only.",
            "example": "482915"
          },
          "prox_antenna_enabled": {
            "type": [
              "boolean",
              "null"
            ],
            "default": false,
            "description": "Turns the proximity (prox) card reader on. Models with the `prox_reader` capability only.",
            "example": false
          },
          "reader_frequency_mode": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "both_off",
              "nfc_only",
              "prox_only",
              "both_on",
              null
            ],
            "deprecated": true,
            "description": "Deprecated. Use `nfc_antenna_enabled` and `prox_antenna_enabled` instead. Which card reader frequencies are on. PDQ locks only; ignored on other locks. Sending a value that disagrees with the newer settings in the same request returns `400` (`conflicting_reader_settings`).",
            "example": null
          },
          "smart_pin_enabled": {
            "type": [
              "boolean",
              "null"
            ],
            "default": false,
            "description": "Turns [SmartPINs](#tag/smartpins) on. Only has an effect on models with the `smart_pin_credential` capability.",
            "example": false
          },
          "smart_pin_seed": {
            "type": "string",
            "pattern": "^[0-9]{4,10}$",
            "default": "0000",
            "description": "The seed used to generate SmartPINs. `0000` uses the lock's serial number, so each lock works on its own.\n\nGive several locks the same seed (4 to 10 digits) to let one SmartPIN open all of them during the same time window.\n",
            "example": "0000"
          },
          "unikey_read_range": {
            "type": "integer",
            "deprecated": true,
            "minimum": -80,
            "maximum": -30,
            "description": "Deprecated. Use `unikey_read_range_mode`. The read range as a signal strength. If you send both, `unikey_read_range_mode` wins.",
            "example": -50
          },
          "unikey_read_range_mode": {
            "type": "string",
            "enum": [
              "close",
              "intermediate",
              "far",
              "unknown"
            ],
            "default": "intermediate",
            "description": "How far away the UniKey reader detects credentials. Models with the `unikey_credential` capability only. A response may show `unknown`; sending `unknown` is ignored.",
            "example": null
          },
          "unikey_reader": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "disabled",
              "trigger_button_press",
              "trigger_proximity_sensor",
              "trigger_all"
            ],
            "default": "disabled",
            "description": "What triggers the UniKey reader. Models with the `unikey_credential` capability only; `trigger_proximity_sensor` works on KCM and PDQ models only.",
            "example": "disabled"
          },
          "wake_wifi": {
            "type": "string",
            "enum": [
              "user_action",
              "heartbeat_interval",
              "user_action_except_manual"
            ],
            "default": "user_action",
            "description": "When the lock connects to Wi-Fi to sync:\n\n- `user_action`: when someone uses the lock, such as a key press or unlock.\n- `heartbeat_interval`: only on each heartbeat.\n- `user_action_except_manual`: like `user_action`, but not when the knob is turned by hand. Every current model supports it.\n",
            "example": "user_action"
          },
          "wifi_enabled": {
            "type": "boolean",
            "default": true,
            "description": "Turns the lock's Wi-Fi on or off.",
            "example": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "When the lock was registered.",
            "example": "2026-09-02T18:20:11Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "When the lock record last changed.",
            "example": "2026-10-28T15:01:47Z"
          }
        }
      },
      "CredentialHolder": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35"
          },
          "external_uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Your own identifier for this credential holder. Must be unique in the account.",
            "example": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2"
          },
          "name": {
            "type": "string",
            "description": "Must be unique in the account.",
            "example": "Cleaning crew"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Required when `entry_app_enabled` or `unikey_enabled` is `true`.",
            "example": "cleaning@example.com"
          },
          "pin": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9]{4,10}$",
            "description": "A keypad PIN of 4 to 10 digits. Must be unique in the account. It can't be one digit repeated (such as `4444`) or a run of counting digits up or down (such as `5678` or `8765`).\n",
            "example": "49820"
          },
          "card_number": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9]{4,20}$",
            "description": "A card number of 4 to 20 digits. Must be unique in the account.",
            "example": "4100094630"
          },
          "unikey_card_number": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 65535,
            "description": "A UniKey card number. Must be unique in the account.",
            "example": null
          },
          "entry_app_enabled": {
            "type": "boolean",
            "description": "Emails the person an invitation to the Entry app. The app downloads a credential for each of their locks whose model has `entry_app_credential`, which opens the lock even when it's offline. Needs `email`, and the Entry app feature turned on for your account.\n\nWhen you remove or reduce someone's access, the app drops or replaces their credentials the next time it refreshes, which it does each time it's opened. If it can't refresh, for example because the phone is deliberately kept offline, their current credentials keep working until 7 days after they were downloaded.\n",
            "example": false
          },
          "entry_app_pin_enabled": {
            "type": "boolean",
            "description": "Lets the person set a PIN on each lock from the Entry app, to open it from the keypad without their phone. Can only be `true` when `entry_app_enabled` is `true`.\n",
            "example": false
          },
          "unikey_enabled": {
            "type": "boolean",
            "description": "Creates a UniKey mobile credential and emails the person an invitation to download the UniKey app with the credential built in. Needs:\n\n- `email`.\n- UniKey mobile credentials turned on for your account.\n\nThe lock's [`unikey_reader` setting](#tag/settings) must also be set to something other than `disabled` for the credential to work.\n\nSetting it to `false` revokes the credential on the UniKey servers.\n",
            "example": true
          },
          "week_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [week schedule](#tag/week-schedules) that limits when this person has access.",
            "example": "60c2623b-4577-4743-84af-5cc6407b21b1"
          },
          "holiday_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [holiday schedule](#tag/holiday-schedules) that limits access on holidays.",
            "example": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079"
          },
          "start_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access starts, as `YYYY-MM-DDTHH:MM` local time at each lock, with no time zone. Set it with `end_time`.",
            "example": "2026-11-01T08:30"
          },
          "end_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access ends, as `YYYY-MM-DDTHH:MM` with no time zone. Must be after `start_time`.",
            "example": "2026-12-31T22:00"
          },
          "total_pending": {
            "type": "integer",
            "readOnly": true,
            "description": "Grants waiting to sync to their lock.",
            "example": 0
          },
          "total_failed": {
            "type": "integer",
            "readOnly": true,
            "description": "Grants that failed to sync.",
            "example": 0
          },
          "total_synced": {
            "type": "integer",
            "readOnly": true,
            "description": "Grants synced to their lock.",
            "example": 2
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-30T16:05:12Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-30T16:20:44Z"
          }
        }
      },
      "CredentialHolderWithGrants": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CredentialHolder"
          },
          {
            "type": "object",
            "properties": {
              "grants": {
                "type": "array",
                "description": "The grants created with the credential holder.",
                "items": {
                  "$ref": "#/components/schemas/Grant"
                }
              }
            }
          }
        ]
      },
      "CredentialHolderCreate": {
        "type": "object",
        "description": "Needs `name` and at least one credential: `pin`, `card_number`, `unikey_card_number`, `entry_app_enabled` or `unikey_enabled`.\n",
        "required": [
          "name"
        ],
        "properties": {
          "external_uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Your own identifier for this credential holder. Must be unique in the account.",
            "example": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2"
          },
          "name": {
            "type": "string",
            "description": "Must be unique in the account.",
            "example": "Cleaning crew"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Required when `entry_app_enabled` or `unikey_enabled` is `true`.",
            "example": "cleaning@example.com"
          },
          "pin": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9]{4,10}$",
            "description": "A keypad PIN of 4 to 10 digits. Must be unique in the account. It can't be one digit repeated (such as `4444`) or a run of counting digits up or down (such as `5678` or `8765`).\n",
            "example": "49820"
          },
          "card_number": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9]{4,20}$",
            "description": "A card number of 4 to 20 digits. Must be unique in the account.",
            "example": "4100094630"
          },
          "unikey_card_number": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 65535,
            "description": "A UniKey card number. Must be unique in the account.",
            "example": null
          },
          "entry_app_enabled": {
            "type": "boolean",
            "description": "Emails the person an invitation to the Entry app. The app downloads a credential for each of their locks whose model has `entry_app_credential`, which opens the lock even when it's offline. Needs `email`, and the Entry app feature turned on for your account.\n\nWhen you remove or reduce someone's access, the app drops or replaces their credentials the next time it refreshes, which it does each time it's opened. If it can't refresh, for example because the phone is deliberately kept offline, their current credentials keep working until 7 days after they were downloaded.\n",
            "example": false
          },
          "entry_app_pin_enabled": {
            "type": "boolean",
            "description": "Lets the person set a PIN on each lock from the Entry app, to open it from the keypad without their phone. Can only be `true` when `entry_app_enabled` is `true`.\n",
            "example": false
          },
          "unikey_enabled": {
            "type": "boolean",
            "description": "Creates a UniKey mobile credential and emails the person an invitation to download the UniKey app with the credential built in. Needs:\n\n- `email`.\n- UniKey mobile credentials turned on for your account.\n\nThe lock's [`unikey_reader` setting](#tag/settings) must also be set to something other than `disabled` for the credential to work.\n\nSetting it to `false` revokes the credential on the UniKey servers.\n",
            "example": true
          },
          "week_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [week schedule](#tag/week-schedules) that limits when this person has access.",
            "example": "60c2623b-4577-4743-84af-5cc6407b21b1"
          },
          "holiday_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [holiday schedule](#tag/holiday-schedules) that limits access on holidays.",
            "example": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079"
          },
          "start_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access starts, as `YYYY-MM-DDTHH:MM` local time at each lock, with no time zone. Set it with `end_time`.",
            "example": "2026-11-01T08:30"
          },
          "end_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access ends, as `YYYY-MM-DDTHH:MM` with no time zone. Must be after `start_time`.",
            "example": "2026-12-31T22:00"
          },
          "unikey_installer": {
            "type": "boolean",
            "description": "Makes the UniKey credential an installer credential, which can also pair, configure and update the firmware of UniKey readers. Only accepted when creating a credential holder, and only when `unikey_enabled` is `true`.",
            "example": false
          },
          "grants": {
            "type": "array",
            "description": "Locks to give access to. Each grant starts as `pending` until its lock syncs.",
            "items": {
              "type": "object",
              "required": [
                "lock_id"
              ],
              "properties": {
                "lock_id": {
                  "type": "string",
                  "format": "uuid",
                  "example": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe"
                }
              }
            }
          }
        }
      },
      "CredentialHolderUpdate": {
        "type": "object",
        "description": "Grants can't be changed here, and `unikey_installer` is only accepted on create.",
        "properties": {
          "external_uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Your own identifier for this credential holder. Must be unique in the account.",
            "example": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2"
          },
          "name": {
            "type": "string",
            "description": "Must be unique in the account.",
            "example": "Cleaning crew"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Required when `entry_app_enabled` or `unikey_enabled` is `true`.",
            "example": "cleaning@example.com"
          },
          "pin": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9]{4,10}$",
            "description": "A keypad PIN of 4 to 10 digits. Must be unique in the account. It can't be one digit repeated (such as `4444`) or a run of counting digits up or down (such as `5678` or `8765`).\n",
            "example": "49820"
          },
          "card_number": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[0-9]{4,20}$",
            "description": "A card number of 4 to 20 digits. Must be unique in the account.",
            "example": "4100094630"
          },
          "unikey_card_number": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 65535,
            "description": "A UniKey card number. Must be unique in the account.",
            "example": null
          },
          "entry_app_enabled": {
            "type": "boolean",
            "description": "Emails the person an invitation to the Entry app. The app downloads a credential for each of their locks whose model has `entry_app_credential`, which opens the lock even when it's offline. Needs `email`, and the Entry app feature turned on for your account.\n\nWhen you remove or reduce someone's access, the app drops or replaces their credentials the next time it refreshes, which it does each time it's opened. If it can't refresh, for example because the phone is deliberately kept offline, their current credentials keep working until 7 days after they were downloaded.\n",
            "example": false
          },
          "entry_app_pin_enabled": {
            "type": "boolean",
            "description": "Lets the person set a PIN on each lock from the Entry app, to open it from the keypad without their phone. Can only be `true` when `entry_app_enabled` is `true`.\n",
            "example": false
          },
          "unikey_enabled": {
            "type": "boolean",
            "description": "Creates a UniKey mobile credential and emails the person an invitation to download the UniKey app with the credential built in. Needs:\n\n- `email`.\n- UniKey mobile credentials turned on for your account.\n\nThe lock's [`unikey_reader` setting](#tag/settings) must also be set to something other than `disabled` for the credential to work.\n\nSetting it to `false` revokes the credential on the UniKey servers.\n",
            "example": true
          },
          "week_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [week schedule](#tag/week-schedules) that limits when this person has access.",
            "example": "60c2623b-4577-4743-84af-5cc6407b21b1"
          },
          "holiday_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [holiday schedule](#tag/holiday-schedules) that limits access on holidays.",
            "example": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079"
          },
          "start_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access starts, as `YYYY-MM-DDTHH:MM` local time at each lock, with no time zone. Set it with `end_time`.",
            "example": "2026-11-01T08:30"
          },
          "end_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access ends, as `YYYY-MM-DDTHH:MM` with no time zone. Must be after `start_time`.",
            "example": "2026-12-31T22:00"
          }
        }
      },
      "CredentialHolderList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CredentialHolder"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "Grant": {
        "type": "object",
        "description": "Access for one credential holder to one lock.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "e0d0db29-45a0-4a80-a438-9bf44e356f6a"
          },
          "external_uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Your own identifier for this grant.",
            "example": "15c438f9-7554-4bd1-99b3-762234a9bf27"
          },
          "lock_id": {
            "type": "string",
            "format": "uuid",
            "description": "The lock this grant opens. Can't be changed.",
            "example": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe"
          },
          "week_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [week schedule](#tag/week-schedules) for this lock only. Overrides the credential holder's; `null` uses theirs.",
            "example": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62"
          },
          "holiday_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [holiday schedule](#tag/holiday-schedules) for this lock only. Overrides the credential holder's; `null` uses theirs.",
            "example": null
          },
          "start_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access to this lock starts, as `YYYY-MM-DDTHH:MM`. Overrides the credential holder's. Set it with `end_time`.",
            "example": "2026-11-01T08:30"
          },
          "end_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access to this lock ends, as `YYYY-MM-DDTHH:MM`. Overrides the credential holder's. Must be after `start_time`.",
            "example": "2026-12-31T22:00"
          },
          "sync_status": {
            "type": "string",
            "enum": [
              "pending",
              "synced",
              "failed"
            ],
            "readOnly": true,
            "description": "Whether the grant has reached its lock. `pending` until the lock next syncs.",
            "example": "pending"
          },
          "expired": {
            "type": "boolean",
            "readOnly": true,
            "description": "Whether the grant's `end_time` (or the credential holder's, if the grant has none) has passed, in the lock's local time.",
            "example": false
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-30T16:05:12Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-30T16:05:12Z"
          }
        }
      },
      "GrantWithLock": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Grant"
          },
          {
            "type": "object",
            "properties": {
              "lock": {
                "$ref": "#/components/schemas/Lock",
                "readOnly": true,
                "description": "The lock this grant opens."
              }
            }
          }
        ]
      },
      "GrantWithCredentialHolder": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Grant"
          },
          {
            "type": "object",
            "properties": {
              "credential_holder": {
                "$ref": "#/components/schemas/CredentialHolder",
                "readOnly": true,
                "description": "The credential holder this grant belongs to."
              }
            }
          }
        ]
      },
      "GrantCreate": {
        "type": "object",
        "required": [
          "lock_id"
        ],
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid",
            "description": "Your own identifier for this grant."
          },
          "lock_id": {
            "type": "string",
            "format": "uuid",
            "description": "A lock in your account. A credential holder can have only one grant per lock."
          },
          "week_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [week schedule](#tag/week-schedules) for this lock only. Overrides the credential holder's; `null` uses theirs.",
            "example": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62"
          },
          "holiday_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [holiday schedule](#tag/holiday-schedules) for this lock only. Overrides the credential holder's; `null` uses theirs.",
            "example": null
          },
          "start_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access to this lock starts, as `YYYY-MM-DDTHH:MM`. Overrides the credential holder's. Set it with `end_time`.",
            "example": "2026-11-01T08:30"
          },
          "end_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access to this lock ends, as `YYYY-MM-DDTHH:MM`. Overrides the credential holder's. Must be after `start_time`.",
            "example": "2026-12-31T22:00"
          }
        }
      },
      "GrantUpdate": {
        "type": "object",
        "description": "Send every override you want to keep: fields you leave out are cleared. `lock_id` and `external_uuid` can't be changed.",
        "properties": {
          "week_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [week schedule](#tag/week-schedules) for this lock only. Overrides the credential holder's; `null` uses theirs.",
            "example": "8d1f3c6a-2e7b-4a95-b0c4-5f9e1d7a3b62"
          },
          "holiday_schedule_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A [holiday schedule](#tag/holiday-schedules) for this lock only. Overrides the credential holder's; `null` uses theirs.",
            "example": null
          },
          "start_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access to this lock starts, as `YYYY-MM-DDTHH:MM`. Overrides the credential holder's. Set it with `end_time`.",
            "example": "2026-11-01T08:30"
          },
          "end_time": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$",
            "description": "When access to this lock ends, as `YYYY-MM-DDTHH:MM`. Overrides the credential holder's. Must be after `start_time`.",
            "example": "2026-12-31T22:00"
          }
        }
      },
      "CredentialHolderGrantList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GrantWithLock"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "LockGrantList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GrantWithCredentialHolder"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "SmartPin": {
        "type": "object",
        "description": "A time-limited guest PIN that works without a network connection.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "cc47b6ad-aac6-4e99-8bab-3d448f43e4e8"
          },
          "lock_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe"
          },
          "name": {
            "type": "string",
            "description": "A label for the guest or purpose.",
            "example": "Guest, unit 204"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "example": "guest204@example.com"
          },
          "pin": {
            "type": "string",
            "readOnly": true,
            "pattern": "^[0-9]{10}$",
            "description": "The generated 10-digit PIN to give the guest.",
            "example": "4418850246"
          },
          "start_time": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:00$",
            "description": "When the SmartPIN starts working, as `YYYY-MM-DDTHH:00`. Can't be changed.",
            "example": "2026-11-14T15:00"
          },
          "end_time": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:00$",
            "description": "When the SmartPIN stops working, as `YYYY-MM-DDTHH:00`. Can't be changed.",
            "example": "2026-11-21T11:00"
          },
          "expired": {
            "type": "boolean",
            "readOnly": true,
            "description": "Whether the SmartPIN has expired, in the lock's local time.",
            "example": false
          },
          "type": {
            "type": "string",
            "const": "smart_pin_credential",
            "readOnly": true,
            "example": "smart_pin_credential"
          },
          "sync_status": {
            "type": "string",
            "const": "ready",
            "readOnly": true,
            "description": "Always `ready`. SmartPINs don't need to sync to the lock.",
            "example": "ready"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-11-05T09:14:22Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-11-05T09:14:22Z"
          }
        }
      },
      "SmartPinCreate": {
        "type": "object",
        "required": [
          "name",
          "start_time",
          "end_time"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "A label for the guest or purpose."
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "start_time": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:00$",
            "description": "A whole hour, as `YYYY-MM-DDTHH:00`. Must be `08:00` for long-term SmartPINs."
          },
          "end_time": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:00$",
            "description": "A whole hour, as `YYYY-MM-DDTHH:00`. Up to 32 days after `start_time` (any hour), or up to a year if it ends at `20:00`."
          }
        }
      },
      "SmartPinUpdate": {
        "type": "object",
        "description": "Only `name` and `email` can be changed.",
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "SmartPinList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SmartPin"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "ActionSchedule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "93d52ee7-6fbe-4432-818f-47846fa0e21a"
          },
          "external_uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Your own identifier for this schedule.",
            "example": "522a9219-d6ea-4f92-ae7a-e1f8e3eae053"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "description": "The account that owns the schedule.",
            "example": "f7c03f5d-625a-45f4-abe5-d755553bd370"
          },
          "name": {
            "type": "string",
            "description": "Must be unique in the account.",
            "example": "Lobby open hours"
          },
          "schedule": {
            "description": "Day-by-day list of lock and unlock times. See [schedule format](#tag/action-schedules).",
            "allOf": [
              {
                "$ref": "#/components/schemas/ActionScheduleDays"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-20T15:12:40Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-20T15:12:40Z"
          }
        }
      },
      "ActionScheduleInput": {
        "type": "object",
        "required": [
          "name",
          "schedule"
        ],
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid",
            "description": "Your own identifier for this schedule."
          },
          "name": {
            "type": "string",
            "description": "Must be unique among the account's schedules of this kind."
          },
          "schedule": {
            "$ref": "#/components/schemas/ActionScheduleDays"
          }
        }
      },
      "ActionScheduleUpdate": {
        "type": "object",
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "description": "Must be unique among the account's schedules of this kind."
          },
          "schedule": {
            "$ref": "#/components/schemas/ActionScheduleDays"
          }
        }
      },
      "ActionScheduleList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionSchedule"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "ClockTime": {
        "type": "string",
        "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
        "description": "24-hour time, `HH:MM`, in the lock's local time (its location's time zone).",
        "example": "09:00"
      },
      "ActionScheduleEntry": {
        "description": "One timed action. An object with a single key.",
        "oneOf": [
          {
            "title": "unlock",
            "type": "object",
            "required": [
              "unlock"
            ],
            "additionalProperties": false,
            "properties": {
              "unlock": {
                "$ref": "#/components/schemas/ClockTime",
                "description": "Unlock at this time."
              }
            }
          },
          {
            "title": "lock",
            "type": "object",
            "required": [
              "lock"
            ],
            "additionalProperties": false,
            "properties": {
              "lock": {
                "$ref": "#/components/schemas/ClockTime",
                "description": "Lock at this time."
              }
            }
          }
        ]
      },
      "ActionScheduleDays": {
        "type": "object",
        "description": "Up to 10 entries per day, in any order. Leave out days with nothing scheduled. Don't send a day with an empty list.",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "mon": {
            "type": "array",
            "description": "Monday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/ActionScheduleEntry"
            }
          },
          "tue": {
            "type": "array",
            "description": "Tuesday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/ActionScheduleEntry"
            }
          },
          "wed": {
            "type": "array",
            "description": "Wednesday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/ActionScheduleEntry"
            }
          },
          "thu": {
            "type": "array",
            "description": "Thursday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/ActionScheduleEntry"
            }
          },
          "fri": {
            "type": "array",
            "description": "Friday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/ActionScheduleEntry"
            }
          },
          "sat": {
            "type": "array",
            "description": "Saturday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/ActionScheduleEntry"
            }
          },
          "sun": {
            "type": "array",
            "description": "Sunday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/ActionScheduleEntry"
            }
          }
        }
      },
      "AutoLockSchedule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "3f6d2a1e-7c84-4b59-9e0a-2d5c8b71f4a6"
          },
          "external_uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Your own identifier for this schedule.",
            "example": "0b8e4c2a-6f1d-4e93-a7b5-c2d9e0f13a64"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "description": "The account that owns the schedule.",
            "example": "f7c03f5d-625a-45f4-abe5-d755553bd370"
          },
          "name": {
            "type": "string",
            "description": "Must be unique in the account.",
            "example": "After hours"
          },
          "schedule": {
            "description": "Day-by-day list of auto-lock on and off times. See [schedule format](#tag/action-schedules).",
            "allOf": [
              {
                "$ref": "#/components/schemas/AutoLockScheduleDays"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-20T15:12:40Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-20T15:12:40Z"
          }
        }
      },
      "AutoLockScheduleInput": {
        "type": "object",
        "required": [
          "name",
          "schedule"
        ],
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid",
            "description": "Your own identifier for this schedule."
          },
          "name": {
            "type": "string",
            "description": "Must be unique among the account's schedules of this kind."
          },
          "schedule": {
            "$ref": "#/components/schemas/AutoLockScheduleDays"
          }
        }
      },
      "AutoLockScheduleUpdate": {
        "type": "object",
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "description": "Must be unique among the account's schedules of this kind."
          },
          "schedule": {
            "$ref": "#/components/schemas/AutoLockScheduleDays"
          }
        }
      },
      "AutoLockScheduleList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AutoLockSchedule"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "AutoLockScheduleEntry": {
        "description": "One timed action. An object with a single key.",
        "oneOf": [
          {
            "title": "auto-lock-on",
            "type": "object",
            "required": [
              "auto-lock-on"
            ],
            "additionalProperties": false,
            "properties": {
              "auto-lock-on": {
                "$ref": "#/components/schemas/ClockTime",
                "description": "Turn auto-lock on at this time."
              }
            }
          },
          {
            "title": "auto-lock-off",
            "type": "object",
            "required": [
              "auto-lock-off"
            ],
            "additionalProperties": false,
            "properties": {
              "auto-lock-off": {
                "$ref": "#/components/schemas/ClockTime",
                "description": "Turn auto-lock off at this time."
              }
            }
          }
        ]
      },
      "AutoLockScheduleDays": {
        "type": "object",
        "description": "Up to 10 entries per day, in any order. Leave out days with nothing scheduled. Don't send a day with an empty list.",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "mon": {
            "type": "array",
            "description": "Monday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/AutoLockScheduleEntry"
            }
          },
          "tue": {
            "type": "array",
            "description": "Tuesday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/AutoLockScheduleEntry"
            }
          },
          "wed": {
            "type": "array",
            "description": "Wednesday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/AutoLockScheduleEntry"
            }
          },
          "thu": {
            "type": "array",
            "description": "Thursday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/AutoLockScheduleEntry"
            }
          },
          "fri": {
            "type": "array",
            "description": "Friday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/AutoLockScheduleEntry"
            }
          },
          "sat": {
            "type": "array",
            "description": "Saturday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/AutoLockScheduleEntry"
            }
          },
          "sun": {
            "type": "array",
            "description": "Sunday.",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/AutoLockScheduleEntry"
            }
          }
        }
      },
      "WeekSchedule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "60c2623b-4577-4743-84af-5cc6407b21b1"
          },
          "external_uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Your own identifier for this schedule.",
            "example": "2d9f6b1e-8a3c-4e57-b0d4-6c1a9e7f2b83"
          },
          "name": {
            "type": "string",
            "description": "Must be unique in the account.",
            "example": "Office hours"
          },
          "schedule": {
            "description": "Day-by-day list of time intervals when access is allowed. See [schedule format](#tag/action-schedules).",
            "allOf": [
              {
                "$ref": "#/components/schemas/WeekScheduleDays"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-20T15:12:40Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-20T15:12:40Z"
          }
        }
      },
      "WeekScheduleInput": {
        "type": "object",
        "required": [
          "name",
          "schedule"
        ],
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid",
            "description": "Your own identifier for this schedule."
          },
          "name": {
            "type": "string",
            "description": "Must be unique among the account's schedules of this kind."
          },
          "schedule": {
            "$ref": "#/components/schemas/WeekScheduleDays"
          }
        }
      },
      "WeekScheduleUpdate": {
        "type": "object",
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "description": "Must be unique among the account's schedules of this kind."
          },
          "schedule": {
            "$ref": "#/components/schemas/WeekScheduleDays"
          }
        }
      },
      "WeekScheduleList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WeekSchedule"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "TimeInterval": {
        "type": "object",
        "required": [
          "start",
          "end"
        ],
        "properties": {
          "start": {
            "$ref": "#/components/schemas/ClockTime"
          },
          "end": {
            "$ref": "#/components/schemas/ClockTime"
          }
        }
      },
      "WeekScheduleDays": {
        "type": "object",
        "description": "Time intervals when access is allowed, by day. Leave out days with no access. Don't send a day with an empty list.",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "mon": {
            "type": "array",
            "description": "Monday.",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/TimeInterval"
            }
          },
          "tue": {
            "type": "array",
            "description": "Tuesday.",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/TimeInterval"
            }
          },
          "wed": {
            "type": "array",
            "description": "Wednesday.",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/TimeInterval"
            }
          },
          "thu": {
            "type": "array",
            "description": "Thursday.",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/TimeInterval"
            }
          },
          "fri": {
            "type": "array",
            "description": "Friday.",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/TimeInterval"
            }
          },
          "sat": {
            "type": "array",
            "description": "Saturday.",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/TimeInterval"
            }
          },
          "sun": {
            "type": "array",
            "description": "Sunday.",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/TimeInterval"
            }
          }
        }
      },
      "HolidaySchedule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "4b3aa724-e570-4e7d-9d75-e7cfd5a5e079"
          },
          "external_uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Your own identifier for this schedule.",
            "example": "9e5c1a7d-3b8f-4d26-a0e4-1f7b3c9d5a62"
          },
          "name": {
            "type": "string",
            "description": "Must be unique in the account.",
            "example": "Company holidays 2026"
          },
          "schedule": {
            "description": "List of date ranges when access is blocked.",
            "allOf": [
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "$ref": "#/components/schemas/DateRange"
                }
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-20T15:12:40Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-20T15:12:40Z"
          }
        }
      },
      "HolidayScheduleInput": {
        "type": "object",
        "required": [
          "name",
          "schedule"
        ],
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid",
            "description": "Your own identifier for this schedule."
          },
          "name": {
            "type": "string",
            "description": "Must be unique among the account's schedules of this kind."
          },
          "schedule": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/DateRange"
            }
          }
        }
      },
      "HolidayScheduleUpdate": {
        "type": "object",
        "properties": {
          "external_uuid": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "description": "Must be unique among the account's schedules of this kind."
          },
          "schedule": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/DateRange"
            }
          }
        }
      },
      "HolidayScheduleList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HolidaySchedule"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "DateRange": {
        "type": "object",
        "required": [
          "start",
          "end"
        ],
        "description": "A range of dates. Both `start` and `end` are included, and `start` can't be after `end`.",
        "properties": {
          "start": {
            "type": "string",
            "format": "date",
            "example": "2026-12-24"
          },
          "end": {
            "type": "string",
            "format": "date",
            "example": "2026-12-25"
          }
        }
      },
      "EventBase": {
        "type": "object",
        "required": [
          "id",
          "lock_id",
          "type",
          "occurred_at",
          "action"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "1d4d097f-c75b-420e-828d-504e903bd8f1"
          },
          "lock_id": {
            "type": "string",
            "format": "uuid",
            "description": "The lock where the event happened.",
            "example": "fbae92f9-3bb9-4ad1-b86e-6b802200f1fe"
          },
          "type": {
            "type": "string",
            "enum": [
              "access",
              "credential_holder_grant_sync",
              "battery",
              "door_state",
              "reset",
              "local_pin_sync",
              "reboot"
            ],
            "description": "The event's category.",
            "example": "access"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "When it happened, with the lock's local UTC offset.",
            "example": "2026-11-04T08:02:15-07:00"
          },
          "action": {
            "type": "string",
            "description": "What happened. The values depend on `type`.",
            "example": "unlocked"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-11-04T15:02:20Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-11-04T15:02:20Z"
          }
        }
      },
      "LegacyEventFields": {
        "type": "object",
        "description": "Fields returned on every event type except `access` and `credential_holder_grant_sync`.",
        "properties": {
          "method": {
            "type": [
              "string",
              "null"
            ],
            "description": "How the event was triggered, when known. `keypad` for local PIN changes, and `keypad` or `ble` for resets. Otherwise `null`.",
            "example": null
          },
          "source_entity_type": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Deprecated. Who or what caused the event, when known.",
            "example": null
          },
          "source_entity_name": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Deprecated. Who or what caused the event, when known.",
            "example": null
          }
        }
      },
      "Event": {
        "description": "A lock event. The fields depend on `type`.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/AccessEvent"
          },
          {
            "$ref": "#/components/schemas/GrantSyncEvent"
          },
          {
            "$ref": "#/components/schemas/BatteryEvent"
          },
          {
            "$ref": "#/components/schemas/DoorStateEvent"
          },
          {
            "$ref": "#/components/schemas/ResetEvent"
          },
          {
            "$ref": "#/components/schemas/LocalPinSyncEvent"
          },
          {
            "$ref": "#/components/schemas/RebootEvent"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "access": "#/components/schemas/AccessEvent",
            "credential_holder_grant_sync": "#/components/schemas/GrantSyncEvent",
            "battery": "#/components/schemas/BatteryEvent",
            "door_state": "#/components/schemas/DoorStateEvent",
            "reset": "#/components/schemas/ResetEvent",
            "local_pin_sync": "#/components/schemas/LocalPinSyncEvent",
            "reboot": "#/components/schemas/RebootEvent"
          }
        }
      },
      "AccessEvent": {
        "title": "access",
        "description": "Someone locked, unlocked or was denied at the lock.",
        "allOf": [
          {
            "$ref": "#/components/schemas/EventBase"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "access"
              },
              "action": {
                "type": "string",
                "enum": [
                  "access_denied",
                  "locked",
                  "unlocked"
                ],
                "example": "unlocked"
              },
              "method": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "keypad",
                  "local_pin",
                  "smart_pin",
                  "auto_lock",
                  "nfc",
                  "wifi",
                  "button_relay",
                  "ble",
                  "unikey",
                  "schedule",
                  "manual_knob",
                  null
                ],
                "description": "How the lock was operated.",
                "example": "keypad"
              },
              "source_type": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "user",
                  "credential_holder",
                  null
                ],
                "description": "Who caused the event, when known. `null` for SmartPIN, local PIN, schedule, auto-lock and manual knob events.",
                "example": "credential_holder"
              },
              "source_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The name of the user or credential holder, when known.",
                "example": "Cleaning crew"
              },
              "credential_holder_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "Set when `source_type` is `credential_holder`.",
                "example": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35"
              },
              "credential_holder_external_uuid": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "Set when `source_type` is `credential_holder`.",
                "example": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2"
              },
              "credential_holder_grant_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The grant used. Set when `source_type` is `credential_holder`.",
                "example": "e0d0db29-45a0-4a80-a438-9bf44e356f6a"
              },
              "credential_holder_grant_external_uuid": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "Set when `source_type` is `credential_holder`.",
                "example": "15c438f9-7554-4bd1-99b3-762234a9bf27"
              },
              "user_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The KoreLock user. Set when `source_type` is `user`.",
                "example": null
              },
              "pin": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The PIN used, for the `keypad`, `local_pin` and `auto_lock` methods.",
                "example": "49820"
              },
              "card_number": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The card used, for the `nfc` method.",
                "example": null
              },
              "smart_pin": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The SmartPIN used, for the `smart_pin` method.",
                "example": null
              },
              "source_external_uuid": {
                "type": [
                  "string",
                  "null"
                ],
                "deprecated": true,
                "description": "Deprecated. Same as `credential_holder_external_uuid`."
              },
              "source_entity_type": {
                "type": [
                  "string",
                  "null"
                ],
                "deprecated": true,
                "description": "Deprecated. Same as `source_type`."
              },
              "source_entity_name": {
                "type": [
                  "string",
                  "null"
                ],
                "deprecated": true,
                "description": "Deprecated. Same as `source_name`."
              }
            }
          }
        ]
      },
      "GrantSyncEvent": {
        "title": "credential_holder_grant_sync",
        "description": "A grant was added to or removed from the lock.",
        "allOf": [
          {
            "$ref": "#/components/schemas/EventBase"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "credential_holder_grant_sync"
              },
              "action": {
                "type": "string",
                "enum": [
                  "upsert_success",
                  "delete_success"
                ],
                "example": "upsert_success"
              },
              "credential_holder_id": {
                "type": "string",
                "format": "uuid",
                "example": "c3f1a9d2-5b7e-4c08-9a61-e2d4b8f07c35"
              },
              "credential_holder_external_uuid": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "example": "b695376d-5fe4-4e6a-b4f3-e96fd39fd6a2"
              },
              "credential_holder_grant_id": {
                "type": "string",
                "format": "uuid",
                "description": "The grant that synced.",
                "example": "e0d0db29-45a0-4a80-a438-9bf44e356f6a"
              },
              "credential_holder_grant_external_uuid": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "example": "15c438f9-7554-4bd1-99b3-762234a9bf27"
              }
            }
          }
        ]
      },
      "BatteryEvent": {
        "title": "battery",
        "description": "The battery is low or was replaced.",
        "allOf": [
          {
            "$ref": "#/components/schemas/EventBase"
          },
          {
            "$ref": "#/components/schemas/LegacyEventFields"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "battery"
              },
              "action": {
                "type": "string",
                "enum": [
                  "battery_low",
                  "battery_replaced"
                ],
                "example": "battery_low"
              }
            }
          }
        ]
      },
      "DoorStateEvent": {
        "title": "door_state",
        "description": "The door sensor reported the door open, closed or unknown.",
        "allOf": [
          {
            "$ref": "#/components/schemas/EventBase"
          },
          {
            "$ref": "#/components/schemas/LegacyEventFields"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "door_state"
              },
              "action": {
                "type": "string",
                "enum": [
                  "door_opened",
                  "door_closed",
                  "door_state_unknown"
                ],
                "example": "door_opened"
              }
            }
          }
        ]
      },
      "ResetEvent": {
        "title": "reset",
        "description": "The lock was reset or had codes erased.",
        "allOf": [
          {
            "$ref": "#/components/schemas/EventBase"
          },
          {
            "$ref": "#/components/schemas/LegacyEventFields"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "reset"
              },
              "action": {
                "type": "string",
                "enum": [
                  "factory_reset",
                  "erased_local_pins",
                  "erased_access_codes"
                ],
                "example": "factory_reset"
              }
            }
          }
        ]
      },
      "LocalPinSyncEvent": {
        "title": "local_pin_sync",
        "description": "A local PIN was added to or removed from the lock.",
        "allOf": [
          {
            "$ref": "#/components/schemas/EventBase"
          },
          {
            "$ref": "#/components/schemas/LegacyEventFields"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "local_pin_sync"
              },
              "action": {
                "type": "string",
                "enum": [
                  "added_local_pin",
                  "removed_local_pin"
                ],
                "example": "added_local_pin"
              }
            }
          }
        ]
      },
      "RebootEvent": {
        "title": "reboot",
        "description": "The lock powered on.",
        "allOf": [
          {
            "$ref": "#/components/schemas/EventBase"
          },
          {
            "$ref": "#/components/schemas/LegacyEventFields"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "const": "reboot"
              },
              "action": {
                "type": "string",
                "enum": [
                  "power_on"
                ],
                "example": "power_on"
              }
            }
          }
        ]
      },
      "EventPageMetadata": {
        "type": "object",
        "description": "Event lists don't include `pages` or `total_count`.",
        "properties": {
          "page": {
            "type": "integer",
            "example": 1
          },
          "per_page": {
            "type": "integer",
            "example": 20
          },
          "has_more_items": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "EventList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Event"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/EventPageMetadata"
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "access",
          "credential_holder_grant_sync",
          "*"
        ],
        "description": "An event type to send. `*` means every type and must be used on its own."
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "readOnly": true,
            "example": "2bd463b9-ef85-4349-bb51-371b32069d78"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "Where KoreLock sends events. Must use HTTPS.",
            "example": "https://example.com/korelock/webhooks"
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether deliveries are on.",
            "example": true
          },
          "disabled_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "user",
              "circuit_breaker",
              null
            ],
            "readOnly": true,
            "description": "Why the webhook is off. `user` if `enabled` was set to `false`, `circuit_breaker` if KoreLock turned it off. `null` while it's on.",
            "example": null
          },
          "secret": {
            "type": [
              "string",
              "null"
            ],
            "description": "Signs each delivery. When set, deliveries include an `X-Hub-Signature` header: `sha256=` plus the HMAC-SHA256 hex digest of the body. Returned as set, so keep responses private.\n",
            "example": null
          },
          "subscribed_events": {
            "type": "array",
            "minItems": 1,
            "description": "The event types to send. Use `[\"*\"]` for all of them.",
            "uniqueItems": true,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            },
            "example": [
              "access"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-12T17:40:03Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "example": "2026-10-12T17:40:03Z"
          }
        }
      },
      "WebhookInput": {
        "type": "object",
        "required": [
          "url",
          "subscribed_events"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "Must use HTTPS and be unique in the account."
          },
          "enabled": {
            "type": "boolean",
            "default": true
          },
          "secret": {
            "type": "string",
            "description": "Signs each delivery with `X-Hub-Signature`."
          },
          "subscribed_events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          }
        }
      },
      "WebhookUpdate": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://",
            "description": "Must use HTTPS."
          },
          "enabled": {
            "type": "boolean"
          },
          "secret": {
            "type": "string"
          },
          "subscribed_events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          }
        }
      },
      "WebhookList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          },
          "metadata": {
            "$ref": "#/components/schemas/PageMetadata"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error_type": {
            "type": "string",
            "description": "A stable code for the kind of error.",
            "example": "user_access_expired"
          },
          "error_message": {
            "type": "string",
            "description": "A readable description of the error.",
            "example": "User access expired"
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "description": "The body of a `400`. Only `validation_failed` errors include `validation_errors`.",
        "properties": {
          "error_type": {
            "type": "string",
            "enum": [
              "validation_failed",
              "unpermitted_params",
              "invalid_parameter"
            ],
            "example": "validation_failed"
          },
          "error_message": {
            "type": "string",
            "example": "Validation failed"
          },
          "validation_errors": {
            "type": "array",
            "description": "One entry per problem.",
            "items": {
              "type": "object",
              "properties": {
                "attribute": {
                  "type": "string",
                  "description": "The field with the problem, or `self` for the record as a whole.",
                  "example": "name"
                },
                "type": {
                  "type": "string",
                  "description": "A stable code for the problem, such as `blank`, `taken` or `in_use`.",
                  "example": "blank"
                },
                "message": {
                  "type": "string",
                  "example": "Name can't be blank"
                }
              }
            }
          },
          "parameter": {
            "type": "string",
            "description": "For `invalid_parameter`, the query parameter that was out of range.",
            "example": "per_page"
          },
          "errors": {
            "type": "array",
            "deprecated": true,
            "description": "Deprecated. The same problems as plain messages; use `validation_errors`.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "error_message": {
            "type": "string",
            "example": "Rate limit exceeded, please slow down and retry later"
          },
          "error_type": {
            "type": "string",
            "example": "rate_limit_exceeded"
          }
        }
      }
    }
  }
}