# Keyspace Third-Party API — Getting Started

**Version 1.12.0** · The partner "start here" for every Keyspace integration:
credentials, authentication, the response envelope, webhooks — and the index
of **per-module guides**. For field-by-field detail see the
[full API reference](/llms-full.txt); for the machine-readable contract see the
[OpenAPI spec](/docs/openapi.json).

This page is served by each environment — read it from the environment you're
integrating against. Base URLs:

| Environment | Base URL |
| ----------- | ------------------------------ |
| Development | `https://api.keyspace-dev.com` |
| UAT | `https://api.keyspace-qat.com` |
| Production | `https://api.keyspace.tech` |

---

## 1. Pick your modules

Every integration authenticates the same way (§3) and reads the same envelope.
What you build on top depends on what your app does — each module has its own
narrative guide with a sandbox and a go-live checklist:

| You want to… | Guide | Reference |
| --- | --- | --- |
| Book **meeting rooms and desks** from your app — find → book → pay → check-in — and receive booking events | [Meeting rooms & desks](/docs/guides/meeting.md) | [§Meeting Rooms](/llms-full.txt#meeting-rooms) … [§Meeting Lifecycle](/llms-full.txt#meeting-lifecycle) |
| Your app is the **system of record for its users** — push profiles, access-card numbers and face photos; Keyspace enrols the doors | [Members & credentials](/docs/guides/members.md) | [§5 Provision members](/llms-full.txt#5-provision-members-and-credentials-partner-managed) |
| Let users find, open and return **smart lockers** — types, states, the door command and its tracking | [Lockers](/docs/guides/locker.md) | [§Receiving Events](/llms-full.txt#receiving-events--webhooks) · [§Error codes](/llms-full.txt#error-code-reference) |
| **Hold a locker** for a user — create, read, edit, cancel or finish the reservation; handle the create-time gates | [Reservations](/docs/guides/reservation.md) | [§Error codes](/llms-full.txt#error-code-reference) |
| React when a locker is **kept too long** — violations, blacklist, auto-lift; read or (staff) manage them | [Violations & blacklist](/docs/guides/violation.md) | [§Receiving Events](/llms-full.txt#receiving-events--webhooks) |
| Read the property's **locker limits** — how many lockers, for how many days, per role — and pre-check before reserving | [Locker quotas](/docs/guides/quota.md) | [§Error codes](/llms-full.txt#error-code-reference) |

Modules combine: a condo app typically provisions its residents (members guide)
and then books facilities *as* those residents (`act_as`, §4 → meeting guide).
New modules are added to this table as they open to partners — it is the
authoritative list; each guide lives at `/docs/guides/<module>.md`.

---

## 2. Get your credentials

Your Keyspace contact issues you, **per environment**:

- `app_id` + `app_secret` — your OAuth client credentials
- a `projectId` — the property your app is scoped to

Keep `app_secret` **server-side only** — never in a mobile app, browser bundle,
or repository. Credentials do not cross environments; you get a fresh set for
UAT and production.

Confirm they work:

```bash
curl -X POST <baseUrl>/auth/accessToken \
  -H "Content-Type: application/json" \
  -d '{"app_id":"<app_id>","app_secret":"<app_secret>","grant_type":"app_credentials"}'
# → { access_token, expires_in, refresh_token, refresh_token_expires_in }
```

---

## 3. Authenticate and read responses

Send every request as `Authorization: Bearer <access_token>`. Refresh
proactively (~5 min before `expires_in`); on a `401` (`KS002`), refresh → retry
once → re-authenticate. Authenticate once per server process, not per request.
A `503` with `KS234` means authentication is temporarily unavailable — your token
was not rejected: retry with backoff and keep your tokens (never re-authenticate
on it).

Every response except the token endpoint is wrapped:

```json
{ "code": "KS000", "success": true, "message": "…", "data": { … }, "meta": { … } }
```

`KS000` is success; errors carry a `KSxxx` code next to the HTTP status
(`409 KS012` booking conflict, `409 KS006` duplicate / identity conflict,
`429 KS036` rate limited …). Full list and envelope rules:
[reference §Error Handling](/llms-full.txt#error-handling) ·
[§API Conventions](/llms-full.txt#api-conventions).

To scaffold a typed client fast, hand your coding agent this environment's
[OpenAPI spec](/docs/openapi.json) and the [full reference](/llms-full.txt).

---

## 4. Acting as your users (`act_as`) — optional

By default your app acts **as itself**: bookings belong to the app, people on
them are attendees. If your users should get Keyspace's **per-user** scope
(member quotas, member-owned bookings, per-member roles), exchange your app
credentials for a **member-scoped token** — same endpoint,
`grant_type: "act_as"`, `subject: "<your user id>"`. No Keyspace password or
login for the user; the token is short-lived, has no refresh token, and can
never exceed your app's own scope.

The `subject` is the same id you use as `externalUserId` in the
[members guide](/docs/guides/members.md), so a member you provisioned and a
member you act as are one person. Coordinate the scope with your Keyspace
contact first. Full detail + errors (`KS221` / `KS223`):
[reference §act_as](/llms-full.txt#4-act-on-behalf-of-a-member-act_as--advanced).

---

## 5. (Optional) Receive events via webhooks

Instead of polling, Keyspace can **POST lifecycle events to your server** as
they happen — booking create / cancel / reminders / no-show / payment-lapse —
and, for projects using the Visitor Management System, **visitor events**
(`VMS_INVITE_SENT`, `VMS_VISITOR_CHECKED_IN`, `VMS_HOST_CANCELLED`) on the same
channel. Note `VMS_INVITE_SENT` carries the visitor's access QR (`qrCardCode`)
for you to relay to the invitee — treat it as a credential.

**Give your Keyspace contact:**

- an **HTTPS** callback URL (a tunnel is fine for development)
- a **shared secret** you generate (32–256 characters)
- which **events** you want (catalog in the
  [reference](/llms-full.txt#receiving-events--webhooks))
- your endpoint's **auth scheme** + its material:
  - `signature-only` — nothing (you verify our HMAC signature)
  - `bearer` — a static token
  - `custom-header` — one or more `{ name, value }` headers (e.g. `X-Api-Key`,
    `X-Client-Id`)
  - `jwt` — nothing; Keyspace mints a short-lived HS256 JWT signed with the
    shared secret

> Every delivery is **HMAC-signed regardless** — that proves it's from
> Keyspace. The auth scheme is only the credential *your* gateway needs. Pick
> `signature-only` unless your endpoint genuinely rejects unauthenticated POSTs.

**Implement the receiver** (details + code in the
[reference](/llms-full.txt#receiving-events--webhooks)):

- **Verify the signature over the raw body**
  (`X-Keyspace-Signature = sha256=HMAC-SHA256(secret, timestamp + "." + rawBody)`),
  and reject a stale `X-Keyspace-Timestamp` (~5 min).
- **Ack `2xx` within 10 seconds**; process async. Failures retry; 20 consecutive
  dead deliveries auto-disable your webhook.
- **De-duplicate on `X-Keyspace-Delivery`**; the payload is a change *signal* —
  re-fetch the resource (`GET /meetings/:id`) for authoritative state (it never
  contains `pinCode`).

---

## 6. Test, then go live

**Development first.** Each guide ends with a sandbox checklist — run the ones
for the modules you use against `https://api.keyspace-dev.com`, and make your
test clean up after itself:
[meeting sandbox](/docs/guides/meeting.md#8-sandbox-testing-checklist) ·
[members sandbox](/docs/guides/members.md#7-sandbox-checklist) ·
[lockers sandbox](/docs/guides/locker.md#8-sandbox-testing-checklist) ·
[reservations sandbox](/docs/guides/reservation.md#9-sandbox-testing-checklist) ·
[violations sandbox](/docs/guides/violation.md#8-sandbox-testing-checklist) ·
[quotas sandbox](/docs/guides/quota.md#8-sandbox-testing-checklist).

**Go live.** Run the matching go-live checklist
([meeting](/docs/guides/meeting.md#9-go-live-checklist) ·
[members](/docs/guides/members.md#8-go-live-checklist) ·
[lockers](/docs/guides/locker.md#9-go-live-checklist) ·
[reservations](/docs/guides/reservation.md#10-go-live-checklist) ·
[violations](/docs/guides/violation.md#9-go-live-checklist) ·
[quotas](/docs/guides/quota.md#9-go-live-checklist)), then ask your Keyspace
contact to issue production credentials and register your production webhook —
credentials and webhooks are per-environment.

Found the API behaving differently from these docs? Send your Keyspace contact
the endpoint, what you expected, and what you saw — the docs are served from the
API and we fix drift at the source.
