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
EnvironmentBase URLUse for
Developmenthttps://api.keyspace-dev.comEarly build + smoke tests
UAThttps://api.keyspace-qat.comPre-production sign-off
Productionhttps://api.keyspace.techLive

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 (duration days 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 status active.
  • Threshold — violationsThreshold (default 3) active logs within the reset window. Reaching threshold − 1 sends 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 }. type is temporary (lifts itself) or permanent (staff must remove); status is active or removed. Auto-blacklists are temporary for blacklistDurationInDays (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 /reservations answers 409 KS019 with "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:

  1. disables violation logs older than violationsResetInDays;
  2. lifts temporary blacklists whose blacklistedAt + durationInDays has passed;
  3. for every active locker reservation, computes days left; sends the warning at warningBeforeInDays; at 0 or below creates a violation log (once per reservation — a locker kept 10 days over counts one violation, not ten);
  4. counts the member's active logs; at threshold − 1 schedules the blacklist warning; at ≥ threshold creates 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=50

Returns 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=50

Filters: 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.

typeFiresKey data fields
LOCKER_VIOLATION_WARNINGwarningBeforeInDays before the hold limitlockerTitle, zoneName, floorName?, locationName?, expiryDate, daysUntilExpiry, userName, userEmail?
LOCKER_VIOLATION_CREATEthe hold limit was passed; a violation log was createdreservationId, lockerTitle, zoneName, expiryDate, daysOverdue, violationThreshold, resetPeriodDays, currentViolations, userName, userEmail?
LOCKER_BLACKLIST_WARNINGthe member is one violation away from the thresholdviolationCount, violationThreshold, resetPeriodDays, lastViolationDate, blacklistDuration, userName?, userEmail?
LOCKER_BLACKLIST_CREATEthe member was blacklisted (auto or by staff)reason, blacklistedAt, blacklistUntil, blacklistDuration, violationCount, violationThreshold, resetPeriodDays, userName?, userEmail?
LOCKER_COMPENSATION_ISSUEDstaff 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

HTTPcodeWhenDo
401KS002token missing / expired / revokedre-authenticate
503KS234credential check temporarily unavailableretry with backoff
403KS003your role lacks USER_VIEW / PROJECT_MANAGE_USERSask your Keyspace contact
409KS019on 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
409KS204blacklist entry does not existre-read the list
409KS208violation does not existre-read the list
409KS211 / KS213the property has no locker / quota configurationask 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}/violations and …/blacklist return 200 with paging meta
  • A test member with an active violation gets 409 KS019 "You have violated the policy" on POST /reservations
  • Returning that member's locker lets them reserve again
  • PUT /projects/{projectId}/blacklist action: "create" on a test member → LOCKER_BLACKLIST_CREATE received; POST /reservations for them → "You are in the blacklist"
  • PUT …/blacklist action: "remove" → the member can reserve again; a second remove → 409 KS204
  • Your listener dedupes a redelivered LOCKER_VIOLATION_CREATE by X-Keyspace-Delivery
  • Clean up: remove every blacklist entry and violation you created on test members

9. Go-live checklist

  • Your UI explains the two KS019 refusals in the user's words and shows the lift date for a temporary blacklist
  • You never request PROJECT_MANAGE_USERS unless 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.

On this page