Skip to content
Dashboard

Lock Codes

An access code is a PIN that opens one smart lock, optionally only within a time window. Codes come from reservations, staff, provider syncs, or you. This guide covers managing codes on a lock. Every field is listed on the operation’s page in the API Reference. To find locks, see Devices.

OperationRequestPermission
listCodesGET /lock-codesview_lock_code
getCodeGET /lock-codes/{id}view_lock_code
createCodePOST /lock-codesmanage_lock_code
updateCodePATCH /lock-codes/{id}manage_lock_code
deleteCodeDELETE /lock-codes/{id}manage_lock_code

Paths are relative to your region’s base URL, for example https://api-us.suiteop.com/api/v1. See Authentication for permissions.

Every change to a code is saved in SuiteOp first and reaches the lock afterwards, through a background job. createCode, updateCode and deleteCode all return before the lock is touched. Use getCode to see where a code stands:

  • status is what SuiteOp has stored, for example pending, scheduled, active, installed, failed, pending_deletion or failed_deletion.
  • effectiveStatus is what the time window makes of it now. An active or installed code reads as expired after endTime, as installed before startTime, and as active in between. pending, awaiting_code, failed, pending_deletion, failed_deletion and unknown pass through unchanged.
  • message is the result of the last provider attempt, for example why a code failed.

Each code also carries id, code (the PIN), name, source, startTime, endTime, isOfflineAccessCode, deviceId, createdAt and updatedAt. name is null when the owner can’t be resolved or you aren’t allowed to see who it is. code can also be null when the PIN is withheld from an OAuth caller.

GET /lock-codes lists the codes on one lock, so deviceId is required. There is no listing across locks, properties or reservations. It is paginated (default limit 20, maximum 100), newest first, and each item includes the PIN unless it is withheld.

Terminal window
curl "https://api-us.suiteop.com/api/v1/lock-codes?deviceId=c1f0a6d2-…" \
-H "Authorization: Bearer sk_live_your_key_here"
  • status filters on the stored status, not on effectiveStatus.
  • source is one of manual (what createCode makes), reservation, staff, user, master, backup, synced_backup_code, checkout, or unknown for a code found on the lock during a sync. Staff codes are returned with source user.
  • Codes SuiteOp has removed are left out, so a code missing here can still be live on the lock. A removal you queued stays listed as pending_deletion.

deviceId and name are required.

Terminal window
curl -X POST https://api-us.suiteop.com/api/v1/lock-codes \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-H "Content-Type: application/json" \
-d '{
"deviceId": "c1f0a6d2-…",
"name": "Cleaning crew",
"code": "048291",
"startTime": "2026-07-05T15:00:00Z",
"endTime": "2026-07-05T18:00:00Z"
}'
{
"data": {
"id": "5e7a1b93-…",
"createdAt": "2026-07-04T10:00:00.000Z",
"updatedAt": "2026-07-04T10:00:00.000Z",
"code": "048291",
"name": "Cleaning crew",
"source": "manual",
"status": "scheduled",
"effectiveStatus": "scheduled",
"message": null,
"startTime": "2026-07-05T15:00:00.000Z",
"endTime": "2026-07-05T18:00:00.000Z",
"isOfflineAccessCode": false,
"deviceId": "c1f0a6d2-…"
},
"meta": { "requestId": "…" }
}

The response is 201 with status pending or scheduled. The door doesn’t open with the code yet: poll getCode until it reads active, or failed with a message.

Field notes:

  • code is the PIN, 4 to 8 digits, subject to the lock’s policy. Leave it out for Salto, KeyNest and offline Igloohome codes, where the provider generates the PIN; a supplied one is rejected. Read the result with getCode.
  • startTime and endTime are ISO 8601 instants ending in Z; without the Z they’re read in the server’s timezone. endTime needs startTime and must follow it. Leave both out for a code with no time limit, where the provider supports that. A start without an end works only on online SmartThings and RemoteLock codes.
  • isOffline: true asks for an offline code, one the lock accepts with no network connection. It defaults to false.

createCode is refused when:

  • the lock isn’t provisioned yet, or a provisioning run started on it less than 15 minutes ago and is still reading the codes already on the lock (check isProvisioned and provisioningAt from listDevices, and link the lock with assignDeviceToProperty to provision it)
  • the provider doesn’t support the kind of code you asked for (hasOnlineCodes or hasOfflineCodes is false)
  • the same PIN is already on that lock, including a code still pending or scheduled (409 CONFLICT)
  • the lock is a Dormakaba lock, where creating standalone codes isn’t supported

PATCH /lock-codes/{id} takes the fields to change inside data: code, name, startTime and endTime. The lock a code sits on can’t be changed.

Terminal window
curl -X PATCH https://api-us.suiteop.com/api/v1/lock-codes/5e7a1b93-… \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"data": {"endTime": "2026-07-05T20:00:00Z"}}'
  • null clears a time. The exception is a code backed by a provider Access Grant that holds that time: clearing it is refused with 400.
  • A code that belongs to a shared Access Grant (one guest’s code across several doors) fans out: the same change is applied to every code in the grant, but only the code you named is returned.
  • Installed codes get the matching change on the lock after the response. Confirm with getCode.
Terminal window
curl -X DELETE "https://api-us.suiteop.com/api/v1/lock-codes/5e7a1b93-…" \
-H "Authorization: Bearer sk_live_your_key_here"

The response is 200 with {"success": true}. A code that is already on the lock is removed in the background: it lists as pending_deletion meanwhile and can end as failed_deletion, so check with getCode.

  • A shared code is refused with 409 unless you add ?confirmSharedGrantDeletion=true. The 409 names what would go with it: every code in the same Access Grant, and the door for any other reservation still staying on that PIN.
  • A code with source unknown (found on the lock, not issued by SuiteOp) is refused. Those are cleared when the lock is provisioned again with deleteUnknownCodes: true.
  • To limit access in time rather than remove it, change endTime with updateCode.

createCode, updateCode and deleteCode check the lock’s integration account before saving, and return 409 when it can’t take writes:

error.codeMeaning
CONFLICTThe lock has no integration account to push through
INTEGRATION_PUSH_DISABLEDOutbound writes are off. error.details.reason is org_muted (the organization’s outbound communications are paused), migration_held (the organization’s migration into SuiteOp has paused it, until it finishes) or push_disabled (the integration account’s own switch)
RECONNECT_REQUIREDThe integration account is expired, disconnected or needs re-authorization. Reconnect it in SuiteOp before any write

Retrying unchanged gets the same answer until the account or organization is fixed.

The same three operations also return 400 VALIDATION_ERROR while the lock has not been provisioned by SuiteOp yet, or when a provisioning run started on it less than 15 minutes ago and is still reading the codes already on the lock. Wait for provisioning to finish, then retry.

An sk_ API key always reads the PIN. An OAuth token acts as its member, and unless the token carries manage_lock_code or modify_properties (granted to the token and held by the member), it sees a code only from 00:00 property-local time on the due day of an open task of theirs at that property (as assignee or collaborator), and only for a property in their property access. A task with no due date counts only while in_progress, and an overdue task keeps counting for up to 7 days. Their own PIN is always visible; any other code with no property never is.

A withheld code comes back from listCodes and getCode with code: null and two extra fields:

  • codeWithheld: true.
  • codeRevealAt: when the code becomes visible, as an ISO 8601 instant: 00:00 property-local on the due day of the caller’s next task there. null when no task of theirs will reveal it in the next 14 days, or none ever can (for example, a code with no property).

Both fields are absent when the code is returned, so test for codeWithheld rather than for a null code.