BUZZER API / REST REFERENCE

Every endpoint.
Every field.

Implementation reference

This documents the connector implementation at backend commit a85cadaf34b9. The backend release and live-provider verification are still pending. The website being live does not make these routes available. Use your configured backend origin; api.buzzerapi.com remains planned.

Coverage: 32 public /v1 operations. This excludes Lowkey app-internal routes and the website's private session bridge. Download the OpenAPI 3.1 contract for agents and tools, or start with the workflow guide and CLI reference. Schemas preserve the API's actual casing, including apiKey, requestId, and webhook timestamps.

HTTP, authentication, and limits

Send Authorization: Bearer YOUR_API_KEY and Content-Type: application/json for bodies. Never send a key as a query parameter. Use a server-side client or the CLI; the backend is not configured for arbitrary browser origins. The website uses its own server-side session bridge. JSON requests use the server's default 100 KB body limit; do not send files or card details.

Responses use X-Request-ID and normally Cache-Control: no-store; public plans explicitly allow caching. Selected-building endpoints return X-Buzzer-Building-Id. Treat identifiers as opaque, timestamps as ISO 8601 unless explicitly Unix seconds, and money as minor currency units.

Rate-limit bucketCurrent implementation
Public plans and identity-authenticated API operations100 requests per 15 minutes, keyed by API key or IP for anonymous calls. The limiter runs before subscription lookup on these routes, so do not assume a higher plan increases this allowance.
Webhook routesSubscription lookup precedes the limiter: Lite 100, Standard 500, Premium 2,000, Business/Multi Building 5,000 per 15 minutes. The authenticated routes share the same limiter bucket for a key; this is not an independent allowance per endpoint.
All /v1/auth/* routes30 requests per 15 minutes per IP, including key management. /auth/otp additionally allows 5 per 15 minutes per IP; at most 3 unexpired challenges per email. Each challenge lasts 15 minutes and permits at most 5 verification attempts.

Honor rate-limit headers and Retry-After when present. The per-email challenge limit does not currently attach Retry-After; wait for existing challenges to expire. Limits are enforced by the current process and are not a guaranteed distributed quota. Back off with jitter after 429; inspect mutation state before repeating anything with side effects.

Account/setup/access/log reads and access revocation remain available with a valid key after subscription expiry. All webhook operations require an active subscription, including list and delete. Linked-building writes require Multi Building. The exact-body {"active":false} access update bypasses grant plan checks for the primary building; linked-building PATCH still requires Multi Building, so use DELETE for downgrade cleanup.

REST examples

Set BUZZER_BASE_URL to the backend service origin, without /v1. These examples assume authentication and physical building setup are complete. Read onboarding for email authentication and hosted payment.

# Create a one-use, four-digit visitor code. Save the returned grant ID.
curl -sS "$BUZZER_BASE_URL/v1/unlock" \
  -H "Authorization: Bearer $BUZZER_API_KEY" \
  -H "X-Buzzer-Building-Id: $BUILDING_ID" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: delivery-example-001' \
  --data '{"type":"passcode","label":"Delivery","max_uses":1,"expires_in_minutes":60}'

# Read the current grant and ETag before an update.
curl -i "$BUZZER_BASE_URL/v1/unlock/$UNLOCK_ID" \
  -H "Authorization: Bearer $BUZZER_API_KEY" \
  -H "X-Buzzer-Building-Id: $BUILDING_ID"

# Replace "3" with the exact quoted ETag from that GET.
curl -sS -X PATCH "$BUZZER_BASE_URL/v1/unlock/$UNLOCK_ID" \
  -H "Authorization: Bearer $BUZZER_API_KEY" \
  -H "X-Buzzer-Building-Id: $BUILDING_ID" \
  -H 'Content-Type: application/json' -H 'If-Match: "3"' \
  --data '{"active":false}'

# REST requires events even though the CLI supplies a default.
curl -sS "$BUZZER_BASE_URL/v1/webhooks" \
  -H "Authorization: Bearer $BUZZER_API_KEY" \
  -H "X-Buzzer-Building-Id: $BUILDING_ID" \
  -H 'Content-Type: application/json' \
  --data '{"url":"https://agent.example/buzzer","events":["unlock.completed"]}'

# Fetch an activity page; repeat with the returned pagination.cursor.
curl -sS -G "$BUZZER_BASE_URL/v1/logs" \
  -H "Authorization: Bearer $BUZZER_API_KEY" \
  -H "X-Buzzer-Building-Id: $BUILDING_ID" \
  --data-urlencode 'since=2026-09-19T00:00:00Z' --data-urlencode 'limit=100'
# Add --data-urlencode "cursor=$CURSOR" for subsequent pages.

Creation returns a grant like this (illustrative values, optional fields omitted):

{
  "building_id": "507f1f77bcf86cd799439011",
  "id": "507f1f77bcf86cd799439012",
  "type": "passcode", "label": "Delivery", "active": true,
  "version": 0, "status": "active", "request_id": "delivery-example-001",
  "created_at": "2026-09-19T12:00:00Z", "expires_at": "2026-09-19T13:00:00Z",
  "code": "0427", "max_uses": 1, "remaining_uses": 1, "voice_enabled": false
}

To page safely, retain the same building and filters while pagination.has_more is true. Unlock lists use an ID cursor; logs use an opaque encoded cursor. For ongoing activity recovery, persist a timestamp checkpoint, overlap the next polling window, and deduplicate by log ID. The CLI's logs --follow is polling, not a separate streaming REST endpoint.

Callback payloads and signature verification

Subscribe separately for each building. Production destinations must use HTTPS and resolve to a supported public IPv4 address; private networks, URL credentials, IPv6-only endpoints, and redirect-based receivers are unsupported. Registrations are capped at five per building, including inactive ones. Secret rotation requires creating a replacement registration and deliberately removing the old one.

EventActual payload and meaning
unlock.completeddata: routine_id, unlock_id, activity_id, occurred_at, name, numeric unlock_type (1 timer, 2 passcode, 3 routine). Emitted when Lowkey accepted a rule and prepared the release tone. No passcode in this event. This does not prove the door opened or anyone entered.
unlock.revokeddata: routine_id, name, reason:"uses_exhausted". Emitted when a use-limited passcode is exhausted. It does not include unlock_id/activity_id; use routine_id. Manual API deletion does not currently emit this event.
access.denieddata.attempted_passcode may contain attempted digits or recognized speech. Treat this as sensitive. This callback does not supply a correlated activity ID.
unlock.created, unlock.expiredAccepted subscription values but currently not emitted. Do not depend on them for lifecycle tracking; read grant state.
webhook.testdata.message only; no created_at in the test envelope. Sent by the test endpoint even for an inactive registration. Cannot be selected in events. Never use it to trigger entry actions.
{
  "event": "unlock.completed",
  "eventId": "29b4a1b5-69b0-4e40-b9d7-4850a6fcf521",
  "building_id": "507f1f77bcf86cd799439011",
  "created_at": "2026-09-19T12:05:00Z",
  "data": {
    "routine_id": "507f1f77bcf86cd799439012",
    "unlock_id": "507f1f77bcf86cd799439012",
    "activity_id": "507f1f77bcf86cd799439013",
    "occurred_at": "2026-09-19T12:05:00Z",
    "name": "Delivery", "unlock_type": 2
  }
}

Every callback is JSON POST with X-Buzzer-Event, X-Buzzer-Delivery-Id (same as eventId), and X-Buzzer-Signature: t=UNIX_SECONDS,v1=HEX_DIGEST. Verify HMAC-SHA256 over timestamp + "." + raw body bytes using the returned secret as a string. Do not hex-decode the secret or reserialize parsed JSON.

import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody must be the untouched Buffer, captured before JSON parsing.
export function verifyBuzzerSignature(rawBody, header, secret, now = Date.now()) {
  if (!Buffer.isBuffer(rawBody) || typeof header !== 'string') return false;
  const match = /^t=([0-9]+),v1=([a-f0-9]{64})$/.exec(header);
  if (!match) return false;
  const [, timestamp, digest] = match;
  if (Math.abs(Math.floor(now / 1000) - Number(timestamp)) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(timestamp + '.').update(rawBody).digest();
  return timingSafeEqual(expected, Buffer.from(digest, 'hex'));
}

After verification, parse the event, deduplicate by eventId, durably queue processing, and return 2xx promptly. The five-minute tolerance above is a receiver recommendation. Callbacks time out after 10 seconds; network failures, 429, and 5xx retry up to three total attempts, with approximately 5–7.5 and 30–45 seconds between attempts. Other HTTP errors are not retried, and redirects are not followed. Retry signatures use a fresh timestamp but retain the event ID and body.

Ten consecutive failed attempts auto-disable a registration; success resets the counter. Inspect the list, fix delivery, then PATCH active=true. Test deliveries make one attempt and return success, optional statusCode/errorMessage, and latencyMs; HTTP 200 alone does not mean the callback succeeded. Test attempts do not reset failure counters. Delivery is best effort with in-process retries; there is no durable delivery queue, public delivery-history endpoint, or replay API. Recover missed use events through logs; not all callback events have a log equivalent.

Errors and recovery

{
  "error": {
    "type": "invalid_request_error",
    "message": "Access changed since it was read...",
    "code": "VERSION_CONFLICT", "retryable": false,
    "next_action": "buzzer unlock get <id>"
  },
  "requestId": "req_example"
}

Preserve requestId for support. retryable is true for 429 and 5xx; it is not permission to duplicate a mutation. Retry access creation, checkout, and building execution with the original idempotency key and identical body. Access updates use If-Match; after a timeout reread the grant. For webhook creation, inspect registrations before retrying. Building reconciliation requires support instead of another mutation.

HTTP / codesRecovery
400: INVALID_JSON, INVALID_EMAIL, INVALID_KEY_OPTIONS, INVALID_PLAN, INVALID_TONE, INVALID_ID, INVALID_BUILDING, INVALID_BUILDING_CONTEXT, INVALID_BUILDING_INPUT, INVALID_PHONE_NUMBER, INVALID_QUOTE, INVALID_UNLOCK, INVALID_TYPE, INVALID_ACTIVE, INVALID_LIMIT, INVALID_CURSOR, INVALID_DATE, IDEMPOTENCY_KEY_REQUIRED, MISSING_URL, MISSING_EVENTS, INVALID_EVENTS, INVALID_URL, MAX_WEBHOOKS_EXCEEDEDCorrect the named input. Follow returned messages and documented bounds. Use a returned cursor and supported HTTPS destination. Delete an unwanted webhook registration before exceeding the five-registration limit.
401: AUTH_REQUIRED, INVALID_KEY, INVALID_OTPAuthenticate again or request a valid delegated key. OTP challenges are single-use, expiring, and attempt-limited.
403: INSUFFICIENT_SCOPE, OWNER_KEY_REQUIRED, PARENT_BILLING_REQUIRED, PARENT_BUILDING_KEY_REQUIRED, SUBSCRIPTION_REQUIRED, PLAN_UPGRADE_REQUIRED, MULTI_BUILDING_REQUIREDUse the correct owner/parent credential and scopes, or manage the subscription. Do not rotate credentials to bypass a plan requirement.
404: NOT_FOUND, ACCOUNT_NOT_FOUND, NUMBER_NOT_FOUND, BUILDING_NOT_FOUND, CUSTOMER_NOT_FOUNDVerify resource ownership and building selection. A number may still need provisioning. An unknown API path or unreleased service can return a non-JSON 404 instead.
409: KEY_LIMIT, PLAN_UNAVAILABLE, SUBSCRIPTION_EXISTS, CHECKOUT_EXISTS, CHECKOUT_PENDING, CHECKOUT_RECONCILIATION_REQUIRED, BUILDING_REQUIRED, BUILDING_BILLING_MISMATCH, BUILDING_LIMIT, INVALID_QUOTE, IDEMPOTENCY_CONFLICT, QUOTE_EXPIRED, QUOTE_STALE, BUILDING_BUSY, GRANT_REVOKED, VERSION_CONFLICT, SETUP_REQUIREDInspect existing state. Expired/stale unexecuted quotes need a new preview. Pending checkout needs recovery, not another purchase. Reconciliation/billing mismatch needs support. Revoke an old credential for key limits. A revoked grant requires a deliberately new grant.
428: VERSION_REQUIREDGET the grant and send its quoted ETag as If-Match.
429: AUTH_RATE_LIMIT, OTP_RATE_LIMIT, RATE_LIMIT_EXCEEDEDRespect Retry-After where returned and reduce polling/request frequency.
500: INTERNAL_ERROR, FETCH_FAILED, CREATE_FAILED, LIST_FAILED, UPDATE_FAILED, DELETE_FAILED, TEST_FAILED, SECRET_DECRYPT_FAILED; 503: CHECKOUT_UNAVAILABLERetry reads with backoff. Reconcile mutations using their receipt/resource first. Persist requestId for support. Provider/validation failures not explicitly mapped by a route can surface as a generic 500.

Some legacy error paths and hosting/proxy failures may return text or HTML rather than this envelope. Check HTTP status and content type before parsing. Unknown fields are rejected on access, setup, key creation, checkout, and building mutation bodies; other routes may ignore them. Send only documented fields. A 202 building receipt uses the normal operation schema, not the error envelope; inspect status before treating it as success.

Every REST operation

GET /v1/health

Check service health

Health is public; success does not establish account readiness.

Authorization: Public; no key required.

No request body.

Responses

HTTP statusContract
200Success. Health
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/plans

Read live public prices

Only Standard/Premium monthly/yearly are listed. Multi Building is managed in the Lowkey app. Public cache max-age=60, shared cache max-age=300.

Authorization: Public; no key required.

No request body.

Responses

HTTP statusContract
200Success. Plans
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/auth/otp

Request email sign-in code

Expires in 15 minutes, five verification attempts, single use. At most three unexpired challenges per email. Delivery is not proof that the account already exists.

Authorization: Public; no key required.

JSON request body

OtpRequest

Responses

HTTP statusContract
200Success. OtpChallenge
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/auth/verify

Verify email and obtain owner credential

Creates the account if needed. At most five active non-web owner credentials. Never disclose apiKey. purpose=web is for server-side website sessions; it is not a browser-storage recommendation.

Authorization: Public; no key required.

JSON request body

VerifyRequest

Responses

HTTP statusContract
200Success. OwnerCredential
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

DELETE /v1/auth/key

Revoke the current credential

No extra scope required. Subsequent use fails authentication. CLI child credentials are invalidated when their parent expires or is revoked. Website account-agent keys survive website logout.

Authorization: Bearer key; no additional scope.

No request body.

Responses

HTTP statusContract
200Success. Revoked
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/auth/keys

Create an agent credential

One delegation level; a parentKeyId credential cannot delegate again. Requested scopes must be held by the issuer. Website sessions require an active subscription and may issue only default access scopes plus optional buildings:write, with a limit of 20 active account-agent keys. Website-issued keys are independent of session expiry; CLI-delegated keys depend on their parent.

Authorization: Bearer key; keys:write.

JSON request body

KeyRequest

Responses

HTTP statusContract
201Created. AgentCredential
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/auth/keys

List manageable agent credentials

Website sessions list account-agent keys. Other credentials list their own children plus account-agent keys. Excludes revoked keys, but can include expired ones. No secrets or pagination.

Authorization: Bearer key; keys:write.

No request body.

Responses

HTTP statusContract
200Success. KeyList
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

DELETE /v1/auth/keys/{id}

Revoke a manageable agent key

Same ownership rules as list. Repeated revocation of a known managed key succeeds.

Authorization: Bearer key; keys:write.

Parameters

ParameterContract
id
path · required
string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

No request body.

Responses

HTTP statusContract
200Success. KeyRevoked
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/account

Inspect account readiness

Account-level response; building header does not select another account. Subscription active and a recorded buzz do not prove physical entry.

Authorization: Bearer key; account:read.

No request body.

Responses

HTTP statusContract
200Success. Account
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/billing/plans

Read authenticated price catalog

Same catalog as public plans; no active subscription required.

Authorization: Bearer key; billing:write.

No request body.

Responses

HTTP statusContract
200Success. Plans
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/billing/checkout

Recover latest checkout

Read after a timeout. complete means hosted checkout completed; recheck account.subscription.status and setup. preparing means retry original plan and request ID.

Authorization: Bearer key; billing:write.

No request body.

Responses

HTTP statusContract
200Success. Checkout
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/billing/checkout

Start or recover hosted checkout

Parent account only. Existing subscriptions use the portal. One pending checkout: same plan reuses it, another plan conflicts until the unpaid session is expired. Same request ID with another plan conflicts. After an unresolved provider outcome older than 23 hours, reconciliation is required instead of another purchase. No card details accepted by this API.

Authorization: Bearer key; billing:write.

Parameters

ParameterContract
Idempotency-Key
header · required
Stable identifier for this action. Reuse the identical body and key after a timeout.string

pattern: ^[a-zA-Z0-9_-]{16,100}$

JSON request body

CheckoutRequest

Responses

HTTP statusContract
200Success. Checkout
201Created. Checkout
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/billing/checkout/{id}/expire

Expire unpaid checkout

Open sessions can be expired; already-expired sessions succeed. Completed checkout returns 409 SUBSCRIPTION_EXISTS. This does not cancel a subscription.

Authorization: Bearer key; billing:write.

Parameters

ParameterContract
id
path · required
string

Stripe checkout session ID.

No request body.

Responses

HTTP statusContract
200Success. CheckoutExpired
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/billing/portal

Open hosted billing management

Parent account with an existing billing customer. No request body.

Authorization: Bearer key; billing:write.

No request body.

Responses

HTTP statusContract
200Success. Portal
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/setup

Inspect selected building setup

No subscription required for inspection. A previous successful buzz is not proof of physical door release.

Authorization: Bearer key; account:read.

Parameters

ParameterContract
X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

No request body.

Responses

HTTP statusContract
200Success. Setup
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

PATCH /v1/setup

Configure selected building release tone

Must own a provisioned number. Linked-building writes require active Multi Building. The API does not program the building call box; its manager must change the destination.

Authorization: Bearer key; setup:write.

Parameters

ParameterContract
X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

JSON request body

SetupRequest

Responses

HTTP statusContract
200Success. SetupUpdated
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/buildings

List accessible buildings

No pagination. Ordered by creation time then ID. Parent sees primary and children; child sees only itself.

Authorization: Bearer key; account:read.

No request body.

Responses

HTTP statusContract
200Success. Buildings
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/buildings

Execute quoted building addition

Parent account only; do not send building header. Supply the matching unexpired quote and stable request ID. Stale inventory/billing requires a fresh preview before execution. Replay returns stored operation. HTTP 202 is not completion. running: poll the operation; reconciliation_required: contact support with its ID and do not create another request. Account mutations are serialized; uncertain outcomes retain the lock.

Authorization: Bearer key; account:read + buildings:write.

Parameters

ParameterContract
Idempotency-Key
header · required
Stable identifier for this action. Reuse the identical body and key after a timeout.string

pattern: ^[a-zA-Z0-9_-]{16,100}$

JSON request body

QuoteRequest

Responses

HTTP statusContract
200Success. BuildingOperation
201Created. BuildingOperation
202Pending or uncertain: inspect status and next_action. BuildingOperation
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/buildings/preview

Preview addition or removal

Parent account only; do not send building header. Requires exactly one active/trialing Multi Building subscription, matching billed quantity, maximum 20 buildings including primary. Preview purchases nothing, creates a 15-minute quote and invoice snapshot. Only US addresses supported for additions. Removal cannot target primary; review release/deletion effects.

Authorization: Bearer key; account:read + buildings:write.

JSON request body

BuildingPreview

Responses

HTTP statusContract
201Created. BuildingOperation
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

DELETE /v1/buildings/{id}

Execute quoted building removal

Parent account only; do not send building header. Supply the matching unexpired quote and stable request ID. Stale inventory/billing requires a fresh preview before execution. Replay returns stored operation. HTTP 202 is not completion. running: poll the operation; reconciliation_required: contact support with its ID and do not create another request. Account mutations are serialized; uncertain outcomes retain the lock. The JSON DELETE body is required. Permanently releases the number, deletes child rules/activity, revokes child keys, disables child webhooks, and reduces subscription quantity.

Authorization: Bearer key; account:read + buildings:write.

Parameters

ParameterContract
id
path · required
string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

Idempotency-Key
header · required
Stable identifier for this action. Reuse the identical body and key after a timeout.string

pattern: ^[a-zA-Z0-9_-]{16,100}$

JSON request body

QuoteRequest

Responses

HTTP statusContract
200Success. BuildingOperation
202Pending or uncertain: inspect status and next_action. BuildingOperation
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/buildings/operations/{id}

Read building operation receipt

Parent-owned operation. Recovery remains available without an active subscription. A running operation older than five minutes is reported as reconciliation_required. Quotes/receipts are not erased when the quote expires.

Authorization: Bearer key; account:read + buildings:write.

Parameters

ParameterContract
id
path · required
string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

No request body.

Responses

HTTP statusContract
200Success. BuildingOperation
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/unlock

Create an access grant

Requires active subscription and provisioned selected number. Lite: timer; Standard: timer/passcode; Premium, Business, Multi Building: all three. Linked buildings require Multi Building. New grants start enabled. A repeated idempotency key returns the current grant (including revoked state), never reopens it; a changed body conflicts.

Authorization: Bearer key; access:write.

Parameters

ParameterContract
X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

Idempotency-Key
header · required
Stable identifier for this action. Reuse the identical body and key after a timeout.string

pattern: ^[a-zA-Z0-9_-]{16,100}$

JSON request body

UnlockCreate

Responses

HTTP statusContract
200Success. Unlock

Location: On new 201: relative /v1/unlock/{id}. string

Idempotency-Replayed: Present on replay. string

constant: "true"

201Created. Unlock

Location: On new 201: relative /v1/unlock/{id}. string

Idempotency-Replayed: Present on replay. string

constant: "true"

defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/unlock

List access grants

No active subscription required. Excludes API-revoked grants. Ordered by descending ID. active=true means timer within window, nonexpired/nonexhausted enabled passcode, or enabled routine (not necessarily inside its schedule).

Authorization: Bearer key; access:read.

Parameters

ParameterContract
X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

active
query
string

one of: true, false

type
query
string

one of: timer, passcode, routine

limit
query
integer

minimum: 1
maximum: 100
default: 50

cursor
query
Use pagination.cursor; this endpoint uses a grant ID cursor.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

No request body.

Responses

HTTP statusContract
200Success. UnlockPage
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/unlock/{id}

Read one access grant

Includes revoked grants; ownership is scoped to selected building.

Authorization: Bearer key; access:read.

Parameters

ParameterContract
id
path · required
string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

No request body.

Responses

HTTP statusContract
200Success. Unlock

ETag: Supply this exact quoted value as If-Match when updating. string

Quoted decimal version, e.g. "3".

defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

PATCH /v1/unlock/{id}

Update an access grant

Requires If-Match from GET. Missing/malformed version: 428 VERSION_REQUIRED; changed version: 409 VERSION_CONFLICT. Reread before retrying. Revoked grants cannot be reopened. Changing max_uses resets remaining uses. Creation plan/setup checks apply except body exactly {"active":false}; linked-building PATCH still requires active Multi Building even for deactivation. DELETE remains the downgrade cleanup path.

Authorization: Bearer key; access:write.

Parameters

ParameterContract
id
path · required
string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

If-Match
header · required
string

pattern: ^"[0-9]+"$

JSON request body

UnlockUpdate

Responses

HTTP statusContract
200Success. Unlock

ETag: Supply this exact quoted value as If-Match when updating. string

Quoted decimal version, e.g. "3".

defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

DELETE /v1/unlock/{id}

Revoke access grant

No active subscription required. Soft revocation is repeatable; never erases create idempotency receipt. Missing grant is 404. Other grants may still allow access. No manual-revocation callback is currently dispatched.

Authorization: Bearer key; access:write.

Parameters

ParameterContract
id
path · required
string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

No request body.

Responses

HTTP statusContract
200Success. UnlockRevoked
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/logs

Read persisted access activity

No active subscription required. Descending created_at then ID. since/until inclusive. Follow every cursor with unchanged filters; overlap timestamp checkpoints and deduplicate by ID to recover use events. Failed entries and sensitive passcodes may be included. Older records may lack unlock_id. No server streaming endpoint.

Authorization: Bearer key; logs:read.

Parameters

ParameterContract
X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

limit
query
integer

minimum: 1
maximum: 100
default: 50

since
query
string

ISO 8601 timestamp.

format: date-time

until
query
string

ISO 8601 timestamp.

format: date-time

type
query
string

constant: "unlock"

cursor
query
Opaque base64url cursor; do not construct or decode as part of client logic.string

No request body.

Responses

HTTP statusContract
200Success. LogPage
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/webhooks

Register callback

Requires active subscription for every webhook operation, including list, delete, and test. Linked building requires Multi Building. Maximum five registrations per selected building, including inactive registrations. events is required in REST. Save the secret once. No idempotency support: after uncertain creation, inspect list before repeating; if the secret was lost, delete/recreate deliberately.

Authorization: Bearer key; webhooks:write.

Parameters

ParameterContract
X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

JSON request body

WebhookCreate

Responses

HTTP statusContract
201Created. WebhookCreated
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

GET /v1/webhooks

List callback registrations

Requires active subscription for every webhook operation, including list, delete, and test. Linked building requires Multi Building. Newest first, no pagination. Secret is never returned; optional status timestamps are omitted until set.

Authorization: Bearer key; webhooks:write.

Parameters

ParameterContract
X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

No request body.

Responses

HTTP statusContract
200Success. Webhooks
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

PATCH /v1/webhooks/{id}

Update callback registration

Requires active subscription for every webhook operation, including list, delete, and test. Linked building requires Multi Building. Returns updated fields, failure counters, last success/failure and created/updated timestamps. disabledAt/disabledReason are list-only. No signing-secret rotation endpoint; create a replacement registration.

Authorization: Bearer key; webhooks:write.

Parameters

ParameterContract
id
path · required
string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

JSON request body

WebhookUpdate

Responses

HTTP statusContract
200Success. Webhook
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

DELETE /v1/webhooks/{id}

Delete callback registration

Requires active subscription for every webhook operation, including list, delete, and test. Linked building requires Multi Building. Hard delete; repeating returns 404.

Authorization: Bearer key; webhooks:write.

Parameters

ParameterContract
id
path · required
string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

No request body.

Responses

HTTP statusContract
200Success. Deleted
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

POST /v1/webhooks/{id}/test

Send a test callback

Requires active subscription for every webhook operation, including list, delete, and test. Linked building requires Multi Building. One attempt, ten-second timeout; no automatic retries for test. Sends even to inactive registrations. HTTP 200 describes the attempt: inspect success and statusCode. Test does not create access or reset automatic failure state.

Authorization: Bearer key; webhooks:write.

Parameters

ParameterContract
id
path · required
string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

X-Buzzer-Building-Id
header
Required when a parent account has linked buildings, even for its primary building. Omit only for a single-building account. Child keys cannot select siblings.string

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

No request body.

Responses

HTTP statusContract
200Success. WebhookTest
defaultStructured API error; see error handling and the error code catalog. Hosting/proxy failures and unknown paths can return non-JSON bodies. Error

Request and response schemas

Optional response fields can be omitted; null is documented separately. Type-specific grant fields appear only for that type; older app-created records may omit optional fields. Required means required in that schema, not that every supported request variant uses that schema.

Error

object
FieldContract
error requiredobject
error.type requiredstring

one of: permission_error, authentication_error, invalid_request_error, not_found_error, rate_limit_error, internal_error

error.message requiredstring
error.code requiredstring
error.retryable requiredboolean

True for 429 and 5xx; does not authorize blindly repeating mutations.

error.next_action requiredstring
requestId requiredstring

Also returned as X-Request-ID.

Health

object
FieldContract
status requiredstring

one of: ok

version requiredstring

Backend package version.

uptime requirednumber

Process uptime in seconds.

Plans

object
FieldContract
data requiredarray of object
data[].id requiredstring

one of: standard_monthly, standard_yearly, premium_monthly, premium_yearly

data[].available requiredboolean
data[].amount requiredinteger

Minor currency units; do not hardcode prices.

| null
data[].currency requiredstring
data[].interval optionalstring
data[].interval_count optionalinteger
data[].access requiredarray of string

one of: timer, passcode, routine

OtpRequest

object
FieldContract
email requiredstring

Trimmed and lowercased.

format: email
maxLength: 254

OtpChallenge

object
FieldContract
challenge requiredstring

One-use challenge; expires after 15 minutes.

message requiredstring

VerifyRequest

object
FieldContract
email requiredstring

format: email

challenge requiredstring
otp requiredstring

Six-digit email sign-in code, not a visitor code.

pattern: ^[0-9]{6}$

purpose optionalstring

one of: web

OwnerCredential

object
FieldContract
apiKey requiredstring

Secret returned once. Omit purpose for CLI owner key; purpose=web produces a 12-hour website session key.

keyPrefix requiredstring
name requiredstring

KeyRequest

object

Unknown fields rejected.

FieldContract
name optionalstring

Nonblank; trimmed before storage.

maxLength: 120
default: "Delivery agent"

scopes optionalarray of string

one of: account:read, access:read, access:write, logs:read, setup:write, billing:write, webhooks:write, keys:write, buildings:write

minItems: 1
default: ["account:read","access:read","access:write","logs:read","webhooks:write"]

expires_in_days optionalinteger

CLI delegation is also bounded by parent expiry.

minimum: 1
maximum: 90
default: 30

AgentCredential

object
FieldContract
id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

api_key requiredstring

Returned once; store in secret settings.

scopes requiredarray of string

one of: account:read, access:read, access:write, logs:read, setup:write, billing:write, webhooks:write, keys:write, buildings:write

expires_at requiredstring

ISO 8601 timestamp.

format: date-time

message requiredstring

KeyList

object
FieldContract
data requiredarray of object
data[].id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

data[].name requiredstring
data[].scopes optionalarray of string

one of: account:read, access:read, access:write, logs:read, setup:write, billing:write, webhooks:write, keys:write, buildings:write

data[].expires_at optionalstring

ISO 8601 timestamp.

format: date-time

| null
data[].last_used_at optionalstring

ISO 8601 timestamp.

format: date-time

| null

Revoked

object
FieldContract
revoked requiredboolean

constant: true

KeyRevoked

object
FieldContract
id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

revoked requiredboolean

constant: true

Account

object
FieldContract
id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

email requiredstring
virtual_number requiredstring | null
subscription requiredobject
subscription.type requiredstring

one of: NONE, LITE, STANDARD, PREMIUM, BUSINESS, MULTI_BUILDING

subscription.status requiredstring

one of: none, active, past_due

usage requiredobject
usage.total_unlocks requiredinteger

Recorded activity count, including failed attempts; not a physical-entry count.

usage.active_routines requiredinteger

Stored activated rules; not necessarily currently effective.

usage.shared_users requiredinteger

Number of users associated with the number.

created_at requiredstring

ISO 8601 timestamp.

format: date-time

buildings requiredobject
buildings.count requiredinteger
buildings.selection_required requiredboolean
buildings.list_command requiredstring
capabilities requiredobject
capabilities.multi_building requiredboolean
capabilities.access_types requiredarray of string

one of: timer, passcode, routine

capabilities.scopes requiredarray of string

May contain owner for legacy unscoped keys.

readiness requiredobject
readiness.can_create_access requiredboolean

Readiness hint; creation still validates plan, scope, setup, and selected building.

readiness.building_connection requiredstring

one of: not_provisioned, previous_buzz_recorded, unverified

readiness.physical_entry_verified requiredboolean

constant: false

next_actions requiredarray of object
next_actions[].command requiredstring
next_actions[].reason requiredstring

CheckoutRequest

object

Unknown fields rejected.

FieldContract
plan requiredstring

one of: standard_monthly, standard_yearly, premium_monthly, premium_yearly

Checkout

object
FieldContract
status requiredstring

constant: "none"

payment_required requiredboolean

constant: false

next_action requiredstring
| object
FieldContract
id requiredstring

Stripe checkout session ID.

| null
request_id requiredstring
plan requiredstring

one of: standard_monthly, standard_yearly, premium_monthly, premium_yearly

url requiredstring

format: uri

| null
status requiredstring

one of: preparing, open, complete, expired, failed

payment_required requiredboolean
next_action requiredstring
message requiredstring

CheckoutExpired

object
FieldContract
id requiredstring
status requiredstring

constant: "expired"

payment_required requiredboolean

constant: false

Portal

object
FieldContract
url requiredstring

Hosted Stripe billing-management URL.

format: uri

Setup

object
FieldContract
building_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

virtual_number requiredstring | null
unlock_tone requiredstring | null
last_successful_buzz_at requiredstring

ISO 8601 timestamp.

format: date-time

| null
status requiredstring

one of: awaiting_subscription_or_provisioning, previous_buzz_recorded, building_setup_required

instructions requiredstring

SetupRequest

object

Unknown fields rejected.

FieldContract
unlock_tone requiredstring

Exact building release keypress; test at the entrance.

pattern: ^[0-9#*]{1,3}$

SetupUpdated

object
FieldContract
building_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

virtual_number requiredstring
unlock_tone requiredstring
physical_test_required requiredboolean

constant: true

Address

object
FieldContract
street optionalstring
city optionalstring
state optionalstring
zip optionalstring
country optionalstring

Buildings

object
FieldContract
data requiredarray of object
data[].id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

data[].label requiredstring
data[].address requiredAddress | null
data[].virtual_number requiredstring | null
data[].is_primary requiredboolean
data[].provisioned requiredboolean
selection_required requiredboolean
next_action requiredstring

BuildingAddPreview

object

Unknown fields rejected.

FieldContract
action requiredstring

constant: "add"

label requiredstring

Nonblank; trimmed.

maxLength: 120

address requiredobject

Unknown fields rejected.

address.street optionalstring

maxLength: 200

address.city requiredstring

Nonblank; trimmed.

maxLength: 120

address.state requiredstring

Normalized uppercase.

pattern: ^[A-Za-z]{2}$

address.zip requiredstring

pattern: ^[0-9]{5}(-[0-9]{4})?$

address.country optionalstring

constant: "US"
default: "US"

unlock_tone requiredstring

Exact building release keypress; test at the entrance.

pattern: ^[0-9#*]{1,3}$

phone_number optionalstring

Resident forwarding number. Defaults to parent phone if omitted; a valid number is still required.

pattern: ^\+[1-9][0-9]{6,14}$

BuildingRemovePreview

object

Unknown fields rejected.

FieldContract
action requiredstring

constant: "remove"

building_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

QuoteRequest

object

Unknown fields rejected.

FieldContract
quote_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

BuildingOperation

object
FieldContract
id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

action requiredstring

one of: add, remove

status requiredstring

one of: quoted, expired, running, succeeded, reconciliation_required

input requiredobject

Add: normalized label/address/unlock_tone/phone_number. Remove: building_id/label/virtual_number/effects. Inspect the exact snapshot before execution.

input.label optionalstring
input.address optionalAddress
input.unlock_tone optionalstring
input.phone_number optionalstring
input.building_id optionalstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

input.virtual_number optionalstring | null
input.effects optionalarray of string
billing requiredobject
billing.subscription_id requiredstring
billing.price_id requiredstring
billing.current_quantity requiredinteger
billing.new_quantity requiredinteger
billing.currency requiredstring
billing.next_invoice_amount_due requiredinteger

Entire upcoming invoice estimate in minor currency units, not an immediate charge or refund.

billing.per_building_amount requiredinteger

Can be null for tiered pricing. Do not multiply this to reconstruct the invoice.

| null
billing.interval optionalstring
billing.proration_date requiredinteger

Unix seconds used for the preview and execution.

billing.note requiredstring
expires_at requiredstring

ISO 8601 timestamp.

format: date-time

building_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

| null
request_id requiredstring | null
result requiredobject

Add result: building_id, label, virtual_number, physical_test_required. Remove result: building_id, deleted, released_number. Null until succeeded.

| null
result.building_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

result.label optionalstring
result.virtual_number optionalstring
result.physical_test_required optionalboolean

constant: true

result.deleted optionalboolean

constant: true

result.released_number optionalstring | null
next_action requiredstring

TimerCreate

object

Unknown fields rejected.

FieldContract
type requiredstring

constant: "timer"

label optionalstring

Defaults to the grant type when omitted or empty on creation.

maxLength: 120

duration_minutes requiredinteger

Starts/restarts the timer immediately.

minimum: 1
maximum: 1440

PasscodeCreate

object

Unknown fields rejected.

FieldContract
type requiredstring

constant: "passcode"

label optionalstring

Defaults to the grant type when omitted or empty on creation.

maxLength: 120

code optionalstring

Omit to generate exactly four digits. Custom codes retain app compatibility: 1–4 digits except 1 alone. Preserve leading zeroes. Six-digit visitor codes are rejected.

pattern: ^(?!1$)[0-9]{1,4}$

max_uses optionalinteger

On update, resets both maximum and remaining uses.

minimum: 1
maximum: 100
default: 1

voice_enabled optionalboolean

Allow spoken-code recognition.

default: false

expires_at optionalstring

Future ISO timestamp with Z or numeric timezone offset, no more than seven days ahead. Cannot combine with expires_in_minutes.

format: date-time

expires_in_minutes optionalinteger

Default expiry is 60 minutes. Do not combine with expires_at.

minimum: 1
maximum: 10080
default: 60

RoutineCreate

object

Unknown fields rejected.

FieldContract
type requiredstring

constant: "routine"

label optionalstring

Defaults to the grant type when omitted or empty on creation.

maxLength: 120

days requiredarray of string

Day name, case-insensitive: sunday, monday, tuesday, wednesday, thursday, friday, saturday.

Case-insensitive input. Only one day, all five weekdays, both weekend days, or all seven days are supported. Arbitrary subsets and duplicates are not supported.

minItems: 1
maxItems: 7

start requiredstring

24-hour HH:MM. End must be later than start on the same day; no overnight window.

pattern: ^([01][0-9]|2[0-3]):[0-5][0-9]$

end requiredstring

24-hour HH:MM. End must be later than start on the same day; no overnight window.

pattern: ^([01][0-9]|2[0-3]):[0-5][0-9]$

timezone requiredstring

Valid IANA timezone, such as America/Los_Angeles.

TimerUpdate

object

minProperties: 1

Unknown fields rejected.

FieldContract
label optionalstring

Defaults to the grant type when omitted or empty on creation.

maxLength: 120

active optionalboolean

False ends timer now; true restarts with stored duration unless a new duration is supplied.

duration_minutes optionalinteger

Starts/restarts the timer immediately.

minimum: 1
maximum: 1440

PasscodeUpdate

object

minProperties: 1

Unknown fields rejected.

FieldContract
label optionalstring

Defaults to the grant type when omitted or empty on creation.

maxLength: 120

active optionalboolean
code optionalstring

Optional replacement code; omission leaves the current code unchanged. Same 1–4 digit app-compatible validation as creation.

pattern: ^(?!1$)[0-9]{1,4}$

max_uses optionalinteger

Optional; supplying it resets both maximum and remaining uses.

minimum: 1
maximum: 100

voice_enabled optionalboolean
expires_at optionalstring

Future ISO timestamp with Z or numeric timezone offset, no more than seven days ahead. Cannot combine with expires_in_minutes.

format: date-time

RoutineUpdate

object

minProperties: 1

Unknown fields rejected.

FieldContract
label optionalstring

Defaults to the grant type when omitted or empty on creation.

maxLength: 120

active optionalboolean
days optionalarray of string

Day name, case-insensitive: sunday, monday, tuesday, wednesday, thursday, friday, saturday.

Case-insensitive input. Only one day, all five weekdays, both weekend days, or all seven days are supported. Arbitrary subsets and duplicates are not supported.

minItems: 1
maxItems: 7

start optionalstring

24-hour HH:MM. End must be later than start on the same day; no overnight window.

pattern: ^([01][0-9]|2[0-3]):[0-5][0-9]$

end optionalstring

24-hour HH:MM. End must be later than start on the same day; no overnight window.

pattern: ^([01][0-9]|2[0-3]):[0-5][0-9]$

timezone optionalstring

IANA timezone.

UnlockUpdate

TimerUpdate | PasscodeUpdate | RoutineUpdate

Nonempty body; fields must match existing grant type. Type cannot change. Include start and end together if changing either. expires_in_minutes is creation-only.

Unlock

object
FieldContract
building_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

type requiredstring

one of: timer, passcode, routine

label requiredstring
active requiredboolean

For routines, enabled does not mean currently inside the schedule.

version requiredinteger
status requiredstring

one of: active, inactive, expired, exhausted, revoked

request_id optionalstring

Original create idempotency key, absent on older app-created rules.

created_at requiredstring

ISO 8601 timestamp.

format: date-time

updated_at optionalstring

ISO 8601 timestamp.

format: date-time

duration_minutes optionalinteger
expires_at optionalstring

ISO 8601 timestamp.

format: date-time

code optionalstring

Sensitive visitor code, present for passcodes.

max_uses optionalinteger | null
remaining_uses optionalinteger | null
voice_enabled optionalboolean
days optionalarray of string
start optionalstring
end optionalstring
timezone optionalstring

Pagination

object
FieldContract
cursor requiredstring

Pass unchanged into the next request with the same filters and building.

| null
has_more requiredboolean

UnlockPage

object
FieldContract
data requiredarray of Unlock
pagination requiredPagination

UnlockRevoked

object
FieldContract
id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

deleted requiredboolean

constant: true

status requiredstring

constant: "revoked"

Log

object
FieldContract
building_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

unlock_id optionalstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

type requiredstring

constant: "unlock"

unlock_type optionalstring

timer, passcode, routine, or unknown(N) for legacy values.

name requiredstring
succeeded requiredboolean
created_at requiredstring

ISO 8601 timestamp.

format: date-time

passcode optionalstring

May contain an attempted or accepted code. Treat activity data as sensitive.

LogPage

object
FieldContract
building_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

data requiredarray of Log
pagination requiredPagination

WebhookCreate

object
FieldContract
url requiredstring

Production: HTTPS, no URL credentials, DNS must resolve to supported public IPv4. Private/reserved destinations and IPv6-only endpoints are unsupported. Redirects are not followed.

format: uri

events requiredarray of string

one of: unlock.created, unlock.completed, unlock.expired, unlock.revoked, access.denied

Required in REST. The CLI alone defaults to unlock.completed.

minItems: 1

description optionalstring

WebhookUpdate

object
FieldContract
url optionalstring

format: uri

events optionalarray of string

one of: unlock.created, unlock.completed, unlock.expired, unlock.revoked, access.denied

minItems: 1

description optionalstring
active optionalboolean

Setting true resets consecutive failures and clears disabled metadata.

Webhook

object
FieldContract
id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

url requiredstring
events requiredarray of string

one of: unlock.created, unlock.completed, unlock.expired, unlock.revoked, access.denied

description optionalstring
active requiredboolean
consecutiveFailures optionalinteger
lastSuccessAt optionalstring

ISO 8601 timestamp.

format: date-time

lastFailureAt optionalstring

ISO 8601 timestamp.

format: date-time

disabledAt optionalstring

ISO 8601 timestamp.

format: date-time

disabledReason optionalstring
createdAt requiredstring

ISO 8601 timestamp.

format: date-time

updatedAt optionalstring

ISO 8601 timestamp.

format: date-time

WebhookCreated

object
FieldContract
id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

url requiredstring
events requiredarray of string

one of: unlock.created, unlock.completed, unlock.expired, unlock.revoked, access.denied

description optionalstring
active requiredboolean
secret requiredstring

64 hex characters, returned only here. Use the string itself as the HMAC key, not hex-decoded bytes.

createdAt requiredstring

ISO 8601 timestamp.

format: date-time

Webhooks

object
FieldContract
data requiredarray of Webhook

Deleted

object
FieldContract
id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

deleted requiredboolean

constant: true

WebhookTest

object
FieldContract
success requiredboolean

Inspect this even when the API returns HTTP 200.

statusCode optionalinteger

Present when endpoint responded.

latencyMs requiredinteger
errorMessage optionalstring

May be present for a transport failure.

WebhookEvent

object
FieldContract
event requiredstring

one of: unlock.completed, unlock.revoked, access.denied, webhook.test

eventId requiredstring

Deduplicate this ID; retries retain it.

building_id requiredstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

created_at optionalstring

ISO 8601 timestamp.

format: date-time

data requiredobject

unlock.completed: routine_id, unlock_id, activity_id, occurred_at, name, numeric unlock_type. unlock.revoked: routine_id, name, reason. access.denied: attempted_passcode (may be omitted). webhook.test: message only.

data.routine_id optionalstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

data.unlock_id optionalstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

data.activity_id optionalstring

Opaque resource ID, normally 24 hexadecimal characters.

pattern: ^[a-fA-F0-9]{24}$

data.occurred_at optionalstring

ISO 8601 timestamp.

format: date-time

data.name optionalstring
data.unlock_type optionalinteger

Numeric callback values: 1 timer, 2 passcode, 3 routine. Log values are strings.

data.reason optionalstring

constant: "uses_exhausted"

data.attempted_passcode optionalstring

Denied events can contain the attempted digits or speech. Do not log indiscriminately.

data.message optionalstring