Keyspace Locker Violations & Blacklist — Integration Guide
Version 1.15.0 · The narrative guide for third-party apps whose users hold Keyspace lockers: what a violation is, how repeated violations become a blacklist, how both lift, how to read and (for property staff) manage them, and which events to listen for. Platform basics are on the getting-started page.
The limits that produce violations (how many lockers, for how many days) are configured per property — see the quota guide. Holding a locker is a reservation.
1. Before you start
Keyspace issues you, per environment:
app_id+app_secret— your OAuth client credentials (server-side only)- a default
projectId— the property whose members you serve
| Environment | Base URL | Use for |
|---|---|---|
| Development | https://api.keyspace-dev.com | Early build + smoke tests |
| UAT | https://api.keyspace-qat.com | Pre-production sign-off |
| Production | https://api.keyspace.tech | Live |
Permissions. Reading violations and the blacklist needs USER_VIEW on the
project; lifting a violation or creating/removing a blacklist entry needs
PROJECT_MANAGE_USERS — a staff permission. Most partner apps only read
and react to events; ask your Keyspace contact before requesting the manage
permission.
2. The five-minute mental model
- Violation — a locker kept past the allowed hold (
durationdays of the holder's quota row). Keyspace records one violation log per offending reservation (reservationId,violatedAt,enabled: true,domain: "locker") and marks the member's violation statusactive. - Threshold —
violationsThreshold(default 3) active logs within the reset window. Reachingthreshold − 1sends a blacklist warning; reaching the threshold auto-blacklists the member. - Reset window —
violationsResetInDays(default 30). Logs older than that are disabled by the daily job and stop counting. - Blacklist — stored on the member's project record:
{ type, status, reason, blacklistedAt, durationInDays }.typeistemporary(lifts itself) orpermanent(staff must remove);statusisactiveorremoved. Auto-blacklists aretemporaryforblacklistDurationInDays(default 30) with reason"Auto-blacklisted due to violation threshold". - Effect — a member with an active violation or an active blacklist
cannot create a new locker reservation:
POST /reservationsanswers409KS019with"You have violated the policy"or"You are in the blacklist". The locker they already hold stays theirs until returned. - Per role. Both the violation rule and the quota row are picked by the holder's project role, falling back to the role-less default row.
3. How a violation becomes a blacklist
All of this runs in the property's daily check at 00:45 local time (quota guide §4). In one pass it:
- disables violation logs older than
violationsResetInDays; - lifts
temporaryblacklists whoseblacklistedAt + durationInDayshas passed; - for every active locker reservation, computes days left; sends the warning
at
warningBeforeInDays; at0or below creates a violation log (once per reservation — a locker kept 10 days over counts one violation, not ten); - counts the member's active logs; at
threshold − 1schedules the blacklist warning; at≥ thresholdcreates the temporary blacklist.
Notifications (email / LINE / webhook) that are scheduled rather than immediate
go out at the property's defaultNotificationCronTime (default 08:00).
What lifts a violation. Returning or cancelling the offending reservation clears the member's violation status for that reservation, so they can reserve again — but the log stays counted toward the threshold until the reset window passes or staff remove it (§5).
4. Reading the state — GET
4.1 Violations
GET /projects/{projectId}/violations?userId={memberId}&roleId={roleId}&page=1&limit=50Returns the project's members with their violation record, filterable by member
and role. Use it to show "you have N of 3 violations; the oldest resets on …"
— LOCKER_VIOLATION_CREATE carries the same counters (currentViolations,
violationThreshold, resetPeriodDays) if you prefer to track from events.
4.2 Blacklist
GET /projects/{projectId}/blacklist?status=active&blacklistType=temporary&roleId=…&from=…&to=…&page=1&limit=50Filters: status (active | removed), blacklistType
(temporary | permanent), roleId, from / to (blacklisted-at window).
Each row carries the member and their blacklist object; compute the lift
date as blacklistedAt + durationInDays for temporary entries.
4.3 From the member's point of view
Before offering "reserve a locker", you can pre-check cheaply: call
GET /projects/{projectId}/blacklist?status=active filtered to your members,
or simply attempt the reservation and render the KS019 message. The second
is always correct; the first lets you grey the button out.
5. Managing — PUT (staff permission)
Both routes need PROJECT_MANAGE_USERS.
Lift a member's violation status (does not delete logs):
PUT /projects/{projectId}/violation
{ "violationAction": "remove", "userId": "<memberId>" }Create or remove a blacklist entry:
PUT /projects/{projectId}/blacklist
{ "userId": "<memberId>", "action": "create", "type": "temporary",
"durationInDays": 14, "reason": "Repeated late returns" }PUT /projects/{projectId}/blacklist
{ "userId": "<memberId>", "action": "remove", "type": "temporary" }type is required (temporary | permanent); durationInDays applies to
temporary; reason is free text shown to staff and sent in
LOCKER_BLACKLIST_CREATE. Removing an entry that does not exist answers
409 KS204; lifting a violation that does not exist, 409 KS208.
6. Receiving events on your server (webhooks)
Subscribe to these types on your registered listener
(getting started §5).
Every payload carries projectId, userId, timestamp.
type | Fires | Key data fields |
|---|---|---|
LOCKER_VIOLATION_WARNING | warningBeforeInDays before the hold limit | lockerTitle, zoneName, floorName?, locationName?, expiryDate, daysUntilExpiry, userName, userEmail? |
LOCKER_VIOLATION_CREATE | the hold limit was passed; a violation log was created | reservationId, lockerTitle, zoneName, expiryDate, daysOverdue, violationThreshold, resetPeriodDays, currentViolations, userName, userEmail? |
LOCKER_BLACKLIST_WARNING | the member is one violation away from the threshold | violationCount, violationThreshold, resetPeriodDays, lastViolationDate, blacklistDuration, userName?, userEmail? |
LOCKER_BLACKLIST_CREATE | the member was blacklisted (auto or by staff) | reason, blacklistedAt, blacklistUntil, blacklistDuration, violationCount, violationThreshold, resetPeriodDays, userName?, userEmail? |
LOCKER_COMPENSATION_ISSUED | staff issued a compensation on a reservation (POST /reservations/{id}/compensate) | reservationId, lockerTitle, zoneName?, discountCode, amount, reason, userName? |
There is no "blacklist removed" event today — re-read
GET /projects/{projectId}/blacklist (or compute the lift date from
blacklistUntil) to re-enable the reserve button.
7. Error handling strategy
| HTTP | code | When | Do |
|---|---|---|---|
| 401 | KS002 | token missing / expired / revoked | re-authenticate |
| 503 | KS234 | credential check temporarily unavailable | retry with backoff |
| 403 | KS003 | your role lacks USER_VIEW / PROJECT_MANAGE_USERS | ask your Keyspace contact |
| 409 | KS019 | on POST /reservations: "You are in the blacklist" / "You have violated the policy" | explain and point the user to return their locker / wait for the lift date |
| 409 | KS204 | blacklist entry does not exist | re-read the list |
| 409 | KS208 | violation does not exist | re-read the list |
| 409 | KS211 / KS213 | the property has no locker / quota configuration | ask your Keyspace contact to configure the property |
8. Sandbox testing checklist
Ask your Keyspace contact for a development property whose quota row has a
short duration (e.g. 1 day) so the daily check produces a violation quickly,
or have them create a violation for a test member by hand.
-
GET /projects/{projectId}/violationsand…/blacklistreturn200with pagingmeta - A test member with an active violation gets
409KS019"You have violated the policy"onPOST /reservations - Returning that member's locker lets them reserve again
-
PUT /projects/{projectId}/blacklistaction: "create"on a test member →LOCKER_BLACKLIST_CREATEreceived;POST /reservationsfor them →"You are in the blacklist" -
PUT …/blacklistaction: "remove"→ the member can reserve again; a second remove →409KS204 - Your listener dedupes a redelivered
LOCKER_VIOLATION_CREATEbyX-Keyspace-Delivery - Clean up: remove every blacklist entry and violation you created on test members
9. Go-live checklist
- Your UI explains the two
KS019refusals in the user's words and shows the lift date for a temporary blacklist - You never request
PROJECT_MANAGE_USERSunless staff will act through your app - Event handlers are idempotent and keyed on
X-Keyspace-Delivery - You re-read the blacklist after the property's 00:45 daily check rather than caching it for the day
- Production webhook registered and signature verified
10. Getting help & reporting discrepancies
Send your Keyspace contact the environment, projectId, the member id, the
request and the full response envelope. The machine-readable contract is
/docs/openapi.json; the platform reference is /llms-full.txt.