---
title: Implementation Guide
slug: frameworks/kmi-implementation-guide
createdAt: 2026-11-03T06:49:31.405Z
---

# Key Management Lifecycle

This guide defines the endpoint contract for **KMI version 1.0**. Read [Getting Started](docId\:nU3QcpZMjH8LsZjqklZEL) for the architecture and terminology.

## Lifecycle Summary

| Operation    | Trigger in Sine                                     | Integration responsibility                                                     |
| ------------ | --------------------------------------------------- | ------------------------------------------------------------------------------ |
| Verify       | Connection check                                    | Validate authentication and cabinet connectivity without creating a user       |
| Resources    | Key-group selection                                 | Return the available key groups                                                |
| Keys         | Inventory synchronisation                           | Return a complete snapshot of keys in the selected groups                      |
| Provision    | Pass creation with requested keys                   | Prepare the cabinet user and credentials without enabling requested key access |
| Activate     | Check-in or pass acceptance                         | Enable the requested key access                                                |
| Check-return | Check-out and follow-up return check                | Report outstanding requested keys from cabinet custody records                 |
| Deprovision  | Pass rejection or end of the check-out grace period | Remove the visit's cabinet access                                              |

## Access and Custody Boundaries

Provision and Activate are separate operations. A provisioned cabinet user must not have access to the requested keys until Activate is received. If your cabinet cannot create an inactive user, withhold the requested key permissions until activation.

Sine waits for Provision during pass creation, but a cabinet failure handled by Sine does not, by itself, reject check-in. Activation failure does not roll back a completed check-in.

Check-return reports physical custody independently of access permissions. On check-out, Sine checks return status and waits at least 15 minutes before a follow-up check and Deprovision. Failed return checks or outstanding keys do not prevent the deprovision attempt. Pass rejection triggers Deprovision without that grace period.

# Endpoint Implementation Reference

Implement seven HTTPS endpoints that Sine can call. Your service authenticates each request, maps the KMI operation to your key management system (KMS), and returns the specified response.

Sine configuration uses an endpoint URL for each operation. Paths in the examples are illustrative - your URLs do not have to use these paths. A Sine administrator configures the URLs, an API key, an `externalId` and the selected key groups without requiring a bespoke Sine integration.

| Operation    | Example endpoint         | Purpose                             | Success status |
| ------------ | ------------------------ | ----------------------------------- | -------------- |
| Verify       | `POST /kmi/verify`       | Validate the connection             | `200`          |
| Resources    | `POST /kmi/resources`    | List key groups                     | `200`          |
| Keys         | `POST /kmi/keys`         | Return the selected groups' keys    | `200`          |
| Provision    | `POST /kmi/provision`    | Prepare a cabinet user              | `201`          |
| Activate     | `POST /kmi/activate`     | Enable requested key access         | `200`          |
| Check-return | `POST /kmi/check-return` | Report outstanding requested keys   | `200`          |
| Deprovision  | `POST /kmi/deprovision`  | Remove the visitor's cabinet access | `200`          |

## Transport and Authentication

For each request, KMI sends:

```http
POST /kmi/verify HTTP/1.1
Host: integration.example.com
Authorization: <REPLACE_ME>
Content-Type: application/json
Accept: application/json
```

The `Authorization` value is the KMI API key configured in Sine. Sine sends the configured value as-is; it does not add a `Bearer` prefix unless that prefix is part of the configured value. Authenticate every endpoint, reject invalid credentials, and use HTTPS with a valid certificate.

Return successful and error responses as JSON with `Content-Type: application/json`.

Keep the KMI API key and your cabinet API credentials separate. Your service owns and securely stores the cabinet credentials; KMI payloads do not require Sine to send them.

Issue a separate KMI API key for each customer integration and bind it to the authorised `externalId` values. On every request, authenticate the key and check that it is authorised for the supplied `config.externalId`. Reject an invalid key with `401` and a valid key used with an unauthorised `externalId` with `403`. Do not trust `externalId` alone to establish the customer.

Validate request types, lengths, identifiers and required fields. Use high-entropy API keys, support key rotation and apply rate limiting to protect your service. Do not log API keys, visitor passwords, complete request bodies or unredacted personal data.

## Common Request Fields

Every example contains a `config` object:

| Field                | Requirement                                                                        | Purpose                                                          |
| -------------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `config`             | Required object                                                                    | Contains the configuration fields below                          |
| `config.externalId`  | Required non-empty string                                                          | Identifies the customer or cabinet configuration in your service |
| `config.keyGroupIds` | Non-empty array of non-empty strings for Keys; may be supplied on other operations | Selected cabinet key-group IDs                                   |

The KMI request envelope does not require the cabinet API base URL or vendor-specific settings. Those belong in your service's configuration. Treat `externalId` and all key and user IDs as strings; preserve leading zeroes.

## Endpoint Specifications

### Verify

Validate the KMI API key and confirm that the selected customer configuration can reach the cabinet system. Do not create a cabinet user.

**Request:**

```json
{
  "config": {
    "externalId": "site-123"
  }
}
```

**Response - `200`:**

```json
{ "code": "SUCCESS" }
```

Return an error if authentication or the required connection check fails. A successful Verify response does not prove that user creation, selected groups or permissions work.

### Resources

Return the key groups available for the configured customer.

**Request:**

```json
{
  "config": {
    "externalId": "site-123"
  }
}
```

**Response - `200`:**

```json
{
  "keyGroups": [
    { "id": "group-01", "name": "Site keys" },
    { "id": "group-02", "name": "Vehicle keys" }
  ]
}
```

The response contains a top-level `keyGroups` array, without a `code` wrapper. Each group needs a stable string `id` and a display `name`. If the cabinet does not have native groups, expose an equivalent grouping that lets the administrator select the intended inventory.

### Keys

Return the complete set of keys in the selected groups.

**Request:**

```json
{
  "config": {
    "externalId": "site-123",
    "keyGroupIds": ["group-01", "group-02"]
  }
}
```

**Response - `200`:**

```json
{
  "code": "SUCCESS",
  "data": [
    { "id": "001", "name": "Plant room" },
    { "id": "002", "name": "Store room" }
  ]
}
```

Each key needs a stable string `id` and a display `name`. The ID can be the cabinet ID or a stable ID that your service maps to the cabinet. Sine uses it as the key's reference and sends it back in `requestedKeyIds`.

Retrieve every required cabinet API page and deduplicate keys that belong to multiple selected groups. A successful response represents a complete inventory snapshot. Sine may remove previously synchronised keys that are absent from it, so return an error if the inventory is incomplete or cannot be retrieved. Return an empty `data` array only when the complete selected inventory is genuinely empty.

The generated Sine asset identifier has a maximum length of 100 UTF-16 code units, including the asset-type prefix, the `::` separator and the key ID. An ID that exceeds the remaining space is skipped rather than truncated. Check the site's asset-type prefix to determine how much space is available. If a cabinet ID does not fit, maintain a stable, collision-safe mapping in your service rather than shortening it blindly.

### Provision

Create or prepare a cabinet user for a visitor, without granting access to the requested keys until Activate.

**Request:**

```json
{
  "config": {
    "externalId": "site-123",
    "keyGroupIds": ["group-01"]
  },
  "visitId": "1e851635-4b94-4a97-9b68-15be6bda1c3f",
  "visitor": {
    "firstName": "Example",
    "lastName": "Visitor",
    "email": "visitor@example.com",
    "mobile": "+61400000000"
  },
  "validFrom": "2026-11-03T00:00:00.000Z",
  "validTo": "2026-11-03T08:00:00.000Z",
  "timezone": "Australia/Adelaide",
  "requestedKeyIds": ["001", "002"]
}
```

| Field                                   | Requirement                                      | Meaning                                                       |
| --------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------- |
| `visitor`                               | Required object                                  | Contains the visitor fields below                             |
| `visitId`                               | Required non-empty string                        | Sine pass ID; use it with `externalId` to correlate the visit |
| `visitor.firstName`, `visitor.lastName` | Required non-empty strings                       | Visitor name                                                  |
| `visitor.email`, `visitor.mobile`       | Optional strings; at least one must be non-empty | Available contact details                                     |
| `validFrom`, `validTo`                  | Required strings                                 | Visit validity timestamps in ISO UTC format                   |
| `timezone`                              | Required non-empty string                        | Site timezone for cabinet-specific date handling              |
| `requestedKeyIds`                       | Required non-empty array of non-empty strings    | Selected key IDs returned by your service                     |

**Response - `201`:**

```json
{
  "code": "PROVISIONED",
  "data": {
    "keyUserId": "042381",
    "password": "<REPLACE_ME>"
  }
}
```

Your Provision endpoint must return the KMS-issued cabinet user ID in `data.keyUserId` and the visitor's cabinet password or PIN in `data.password`, both as non-empty strings. These are response fields, not credentials supplied by Sine in the request. The user ID above is synthetic; replace the password placeholder only with a test credential in a secure environment. Sine uses the returned user ID in later lifecycle requests and may email the returned credentials to the visitor after activation.

Store the association between `externalId`, `visitId` and `keyUserId` so that you can reconcile repeated or uncertain requests. Retain the validity information needed to apply the visit's access window when Activate arrives. Later operations include `externalId` and `keyUserId`, but not `visitId`, `validFrom`, `validTo` or `timezone`.

Do not grant the requested key permissions at this stage. Return `PROVISIONED` only after the cabinet user and credentials are available.

### Activate

Enable the provisioned user for the requested keys after the visitor is checked in or approved.

**Request:**

```json
{
  "config": {
    "externalId": "site-123",
    "keyGroupIds": ["group-01"]
  },
  "keyUserId": "042381",
  "requestedKeyIds": ["001", "002"]
}
```

**Response - `200`:**

```json
{ "code": "ACTIVATED" }
```

Require a non-empty `keyUserId` string and a non-empty `requestedKeyIds` array of non-empty strings. Validate that the user and keys belong to the customer, then apply the appropriate cabinet permissions. Handle repeat activation safely. Sine attempts credential email after successful activation when the visitor has an email address.

### Check-return

Report which requested keys remain outstanding for the provisioned user.

**Request:**

```json
{
  "config": {
    "externalId": "site-123",
    "keyGroupIds": ["group-01"]
  },
  "keyUserId": "042381",
  "requestedKeyIds": ["001", "002"]
}
```

**Response - `200`:**

```json
{
  "code": "SUCCESS",
  "unreturnedKeyIds": ["002"]
}
```

Require a non-empty `keyUserId` string and a `requestedKeyIds` array of non-empty strings; the array may be empty. `unreturnedKeyIds` is a top-level array containing only IDs from the request. Return an empty array only when your service can determine from the cabinet's custody data that none of the requested keys are outstanding.

Map the cabinet's custody records to the requested key IDs, including records for missing keys or keys held by another user. If those records do not establish whether a requested key is outstanding, return an error rather than reporting a confirmed return. Check-return does not itself update a Sine asset assignment.

### Deprovision

Remove the visit's cabinet access.

**Request:**

```json
{
  "config": {
    "externalId": "site-123",
    "keyGroupIds": ["group-01"]
  },
  "keyUserId": "042381",
  "requestedKeyIds": ["001", "002"]
}
```

**Response - `200`:**

```json
{ "code": "DEPROVISIONED" }
```

Require a non-empty `keyUserId` string and a `requestedKeyIds` array of non-empty strings; the array may be empty. Remove or disable the visit's access according to your cabinet's capabilities, even when no key IDs are supplied. Handle an already-removed identity safely and do not remove another visitor's access. Sine calls Deprovision on pass rejection and after the return grace period following check-out.

Return `DEPROVISIONED` only when the visit's access has been removed or is already absent. Deleting a cabinet user is not required if disabling the visit's access achieves that result.

## Response Field Reference

| Operation    | Required field                       | Type              | Value or meaning                                    |
| ------------ | ------------------------------------ | ----------------- | --------------------------------------------------- |
| Verify       | `code`                               | string            | `SUCCESS`                                           |
| Resources    | `keyGroups`                          | array of objects  | Available key groups; may be empty                  |
| Resources    | `keyGroups[].id`, `keyGroups[].name` | strings           | Stable group identifier and display name            |
| Keys         | `code`                               | string            | `SUCCESS`                                           |
| Keys         | `data`                               | array of objects  | Complete selected inventory; may be empty           |
| Keys         | `data[].id`, `data[].name`           | strings           | Stable key identifier and display name              |
| Provision    | `code`                               | string            | `PROVISIONED`                                       |
| Provision    | `data`                               | object            | Cabinet credentials                                 |
| Provision    | `data.keyUserId`, `data.password`    | non-empty strings | Cabinet user identifier and visitor PIN or password |
| Activate     | `code`                               | string            | `ACTIVATED`                                         |
| Check-return | `code`                               | string            | `SUCCESS`                                           |
| Check-return | `unreturnedKeyIds`                   | array of strings  | Outstanding IDs from the request; may be empty      |
| Deprovision  | `code`                               | string            | `DEPROVISIONED`                                     |

Resources has no `code` wrapper. Check-return places `unreturnedKeyIds` at the top level, not inside `data`. Return the documented HTTP status as well as the required fields.

# Errors and Recovery

## Error Responses

Return a non-success HTTP status if your service cannot authenticate, validate or complete a request. The following is an example error response:

```json
{
  "code": "REQUEST_FAILED_ERROR",
  "message": "Unable to complete the requested operation."
}
```

Use an appropriate status such as `400` for invalid input, `401` for invalid credentials, `403` for forbidden access or `409` for a conflict. Use a server-error status for an unavailable cabinet or an unexpected downstream failure.

Do not expose credentials, PINs, personal data or cabinet stack traces in error responses. In particular, do not report failed or incomplete inventory and custody checks as successful empty arrays.

## Timeouts, Retries and Duplicate Requests

Design your service for repeated requests and uncertain outcomes:

- Correlate Provision using `externalId` and `visitId`, and retain the mapping to `keyUserId` for later operations.
- Make Activate and Deprovision safe to repeat.
- On a repeated or timed-out Provision, reconcile with the cabinet before creating another user.
- Record enough information to recover partial failures without logging secrets.
- Do not assume a timed-out request had no effect or that requests are delivered exactly once.
