Authentication
KISS uses two authentication methods depending on your integration type:
| Integration | Auth method | Who authenticates |
|---|---|---|
| PMS Push | API token (Bearer) | Your server |
| White-Label App | Phone + OTP | Your app's end user (tenant) |
API Tokens (PMS Integrators)
API tokens authenticate server-to-server requests from your PMS to KISS. Each token is scoped to a single company.
Generate a token
- Log in to the KISS Dashboard
- Navigate to your Company page from the sidebar
- Click the API tab
- Enter a name for your token (e.g.,
production-sync,staging) - Click Create a new token
- Copy the token immediately — it won't be shown again
API tokens grant full access to your company's data. Store them securely and never expose them in client-side code, public repos, or logs.
Use the token
Include the token in the Authorization header of every request:
# Generate one key per logical operation. Reuse the same value when retrying.
IDEMPOTENCY_KEY=$(uuidgen)
curl -X PATCH https://api.keepitsimplestorage.com/api/v2/units \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{"units": [...]}'
Every PMS write endpoint accepts an optional Idempotency-Key header. The value is any client-chosen opaque string (max 255 characters) — UUIDs are a common choice but any unique identifier within your namespace works. When present, the server stores the request hash + response for 24 hours — retrying the same key with the same payload returns the cached response without a second write. Retrying the same key with a different payload returns 409 Conflict. Use a new unique value per logical operation; reuse the same value when retrying that operation.
Manage tokens
You can view all active tokens in the API tab of your Company page. Each token shows its name and creation date. To revoke a token, click the delete icon next to it — this takes effect immediately.
You can create multiple tokens (e.g., separate tokens for production and staging environments).
Error responses
| Status | Meaning |
|---|---|
401 Unauthorized | Missing or invalid token |
{
"message": "Unauthorized."
}
Tenant OTP Flow (White-Label Apps)
White-label app integrators authenticate tenants using a phone number + OTP (one-time password) flow. The tenant receives an SMS, verifies the code, and your app receives a Bearer token.
The V2 endpoints for the tenant OTP flow and /tenant/access are still being scoped under the V2: Tenant & Mobile Access initiative. Today's production white-label clients use the predecessor (unversioned) endpoints — see the White-Label Quickstart for the current paths. The shape below describes the v2 contract this surface is moving to; treat the URL prefixes as illustrative until V2 white-label ships.
How it works
- App calls
POST /auth/phonewith tenant's phone number - Tenant receives a 6-digit OTP via SMS
- App calls
POST /auth/verify-otpwith the OTP - API returns a Bearer token
- App uses the token for all subsequent requests
Step 1: Request OTP
curl -X POST https://api.keepitsimplestorage.com/api/v2/auth/phone \
-H "Content-Type: application/json" \
-d '{
"country_code": "1",
"phone_number": "5551234567"
}'
| Field | Type | Required | Description |
|---|---|---|---|
country_code | string | Yes | Country calling code without + (1-3 digits) |
phone_number | string | Yes | Phone number without country code (7-15 digits) |
Success — 200 OK:
{
"message": "OTP sent successfully."
}
Error — 422: The phone number is not associated with any tenant in KISS.
{
"message": "A tenant with the submitted phone number does not exist."
}
OTPs expire after 5 minutes. A new OTP cannot be requested until the resend cooldown (30 seconds) has passed.
Step 2: Verify OTP
curl -X POST https://api.keepitsimplestorage.com/api/v2/auth/verify-otp \
-H "Content-Type: application/json" \
-d '{
"country_code": "1",
"phone_number": "5551234567",
"otp": "482910"
}'
| Field | Type | Required | Description |
|---|---|---|---|
country_code | string | Yes | Country calling code without + |
phone_number | string | Yes | Phone number |
otp | string | Yes | 6-digit code received via SMS |
tenant_id | string | No | Required on second call when multiple accounts exist |
Success — 200 OK (single account):
{
"message": "Login successful.",
"data": {
"token": "1|abc123def456...",
"user": {
"id": "usr_abc123",
"name": "Jane Smith",
"email": null,
"phone": "+15551234567"
}
}
}
Handling multiple accounts
If a phone number is linked to multiple tenant accounts (e.g., a tenant renting at two locations), the API returns token: null and a list of accounts:
{
"message": "Multiple accounts found.",
"data": {
"token": null,
"accounts": [
{
"tenant_id": "01HQA123456789ABCDEFGHJKMNPQRS",
"name": "Jane Smith",
"location": "Downtown Storage",
"company": "ABC Storage Co."
},
{
"tenant_id": "01HQB234567890BCDEFGHJKMNPQRST",
"name": "Jane Smith",
"location": "Uptown Storage",
"company": "ABC Storage Co."
}
]
}
}
Prompt the tenant to select an account, then call the same endpoint again with the selected tenant_id:
curl -X POST https://api.keepitsimplestorage.com/api/v2/auth/verify-otp \
-H "Content-Type: application/json" \
-d '{
"country_code": "1",
"phone_number": "5551234567",
"otp": "482910",
"tenant_id": "01HQA123456789ABCDEFGHJKMNPQRS"
}'
This returns the standard success response with a token.
The OTP is not consumed on the first call when multiple accounts are returned — it stays valid so your app can call again with the selected tenant_id.
Step 3: Use the token
Include the Bearer token in all subsequent API requests:
curl https://api.keepitsimplestorage.com/api/v2/tenant/access \
-H "Authorization: Bearer 1|abc123def456..."
Token expiration
Tenant Bearer tokens have a limited lifetime. When a token expires, the API returns 401 Unauthorized. There is no refresh token mechanism. The tenant must re-authenticate via the OTP flow to get a new token.
Cache the token for the duration of the session and handle 401 responses by redirecting the tenant back to the OTP login screen.
Error responses
| Status | Meaning |
|---|---|
401 Unauthorized | Missing, invalid, or expired token |
403 Forbidden | Token is valid but lacks permission for this resource (e.g., accessing a unit that doesn't belong to this tenant) |
422 Unprocessable | Invalid or expired OTP |
429 Too Many Requests | Rate limited. Retry after 60 seconds. |
{
"message": "The OTP code is invalid or has expired."
}
Rate Limits
White-Label Auth
| Endpoint | Limit | Window | Notes |
|---|---|---|---|
POST /auth/phone | 5 requests | 60 seconds | Per phone number |
POST /auth/verify-otp | 5 attempts | 60 seconds | Per phone number |
PMS Push
| Endpoint group | Limit | Window | Notes |
|---|---|---|---|
PATCH /units (bulk) | TBD — see note below | 60 seconds | Per company |
PUT/DELETE /units/{crm_unit_id}/tenancy, PATCH /units/{crm_unit_id} | TBD — see note below | 60 seconds | Per company |
Final numbers are a product decision in progress. Partners should design for retries and treat 429 Too Many Requests with a Retry-After header as a recoverable state. Exponential backoff with jitter is the expected client behavior.
When rate limited, the API returns 429 Too Many Requests with a Retry-After header:
{
"message": "Too many attempts. Please try again in 60 seconds."
}
Best Practices
- Store tokens securely. API tokens belong in environment variables or a secrets manager, never in source code.
- Use separate tokens per environment. Create distinct tokens for production, staging, and development.
- Cache the tenant Bearer token. After OTP verification, cache the token in the app for the session. Don't re-authenticate on every API call.
- Handle 401 gracefully. If a request returns 401, the token has expired or been revoked. Prompt the tenant to re-authenticate via OTP.
- Never send tokens to your backend. Tenant Bearer tokens should stay on the device. Your server should use API tokens for server-side operations.