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.
| Operation | Request | Permission |
|---|---|---|
listCodes | GET /lock-codes | view_lock_code |
getCode | GET /lock-codes/{id} | view_lock_code |
createCode | POST /lock-codes | manage_lock_code |
updateCode | PATCH /lock-codes/{id} | manage_lock_code |
deleteCode | DELETE /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.
The code lifecycle
Section titled “The code lifecycle”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:
statusis what SuiteOp has stored, for examplepending,scheduled,active,installed,failed,pending_deletionorfailed_deletion.effectiveStatusis what the time window makes of it now. Anactiveorinstalledcode reads asexpiredafterendTime, asinstalledbeforestartTime, and asactivein between.pending,awaiting_code,failed,pending_deletion,failed_deletionandunknownpass through unchanged.messageis 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.
Listing codes
Section titled “Listing codes”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.
curl "https://api-us.suiteop.com/api/v1/lock-codes?deviceId=c1f0a6d2-…" \ -H "Authorization: Bearer sk_live_your_key_here"statusfilters on the stored status, not oneffectiveStatus.sourceis one ofmanual(whatcreateCodemakes),reservation,staff,user,master,backup,synced_backup_code,checkout, orunknownfor a code found on the lock during a sync. Staff codes are returned withsourceuser.- 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.
Creating a code
Section titled “Creating a code”deviceId and name are required.
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:
codeis 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 withgetCode.startTimeandendTimeare ISO 8601 instants ending inZ; without theZthey’re read in the server’s timezone.endTimeneedsstartTimeand 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: trueasks for an offline code, one the lock accepts with no network connection. It defaults tofalse.
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
isProvisionedandprovisioningAtfromlistDevices, and link the lock withassignDeviceToPropertyto provision it) - the provider doesn’t support the kind of code you asked for (
hasOnlineCodesorhasOfflineCodesisfalse) - the same PIN is already on that lock, including a code still
pendingorscheduled(409 CONFLICT) - the lock is a Dormakaba lock, where creating standalone codes isn’t supported
Changing a code
Section titled “Changing a code”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.
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"}}'nullclears a time. The exception is a code backed by a provider Access Grant that holds that time: clearing it is refused with400.- 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.
Removing a code
Section titled “Removing a code”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
409unless you add?confirmSharedGrantDeletion=true. The409names 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
sourceunknown(found on the lock, not issued by SuiteOp) is refused. Those are cleared when the lock is provisioned again withdeleteUnknownCodes: true. - To limit access in time rather than remove it, change
endTimewithupdateCode.
When the lock’s integration refuses
Section titled “When the lock’s integration refuses”createCode, updateCode and deleteCode check the lock’s integration account before saving, and return 409 when it can’t take writes:
error.code | Meaning |
|---|---|
CONFLICT | The lock has no integration account to push through |
INTEGRATION_PUSH_DISABLED | Outbound 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_REQUIRED | The 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.
Withheld codes
Section titled “Withheld codes”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.nullwhen 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.