ONSCREEN · Developer

Senior Living Canonical API

One stable REST contract. Any vendor on the other side. No data or credentials stored on our platform.

How it works — in one request

GET /v1/communities/<communityId>/residents
Authorization:          Bearer osl_prd_<platform-api-key>
X-Source:               speak2_family                        ← which vendor
X-Source-Authorization: <base64 of vendor credentials JSON>  ← we never store this

Customers send a platform API key (identifies the caller) plus per-request vendor credentials. The platform translates live vendor calls into a canonical shape and returns them. No storage, no sync, no shared-secret liability.

Try a real request in the Playground →

Connectors

SourceStatusWhat it exposesCredentials JSON
speak2_family Live communities, residents, events, menu days { apiKey, username, password }
go_icon Live residents, events, contacts (staff) { clientId, clientSecret } or { accessToken }
worxhub Live tickets (list, get, create) { host, instance, clientId, clientSecret } or { host, instance, accessToken }
tels Stub Pending doc review
point_click_care Future TBD

Endpoints

Key validation (no vendor call)

MethodPathNotes
GET/v1/_meEchoes the caller's tenant + key metadata. Useful for CI smoke tests and SDK "is this key live?" checks. Requires the platform API key only — no X-Source headers needed.

Communities / discovery

MethodPathSupported by
GET/v1/communitiesspeak2_family

Residents

MethodPathSupported by
GET/v1/communities/{communityId}/residentsspeak2_family, go_icon
GET/v1/communities/{communityId}/residents/{residentId}go_icon

Events

MethodPathSupported by
GET/v1/communities/{communityId}/eventsspeak2_family, go_icon

Menu days

MethodPathSupported by
GET/v1/communities/{communityId}/menu-daysspeak2_family
GET/v1/communities/{communityId}/menu-days/{YYYY-MM-DD}speak2_family

Contacts

MethodPathSupported by
GET/v1/communities/{communityId}/contactsgo_icon

Tickets

MethodPathSupported by
GET/v1/communities/{communityId}/ticketsworxhub (requires ?resident_id=)
GET/v1/communities/{communityId}/tickets/{ticketId}worxhub
POST/v1/communities/{communityId}/ticketsworxhub
PATCH/v1/communities/{communityId}/tickets/{ticketId}— (WorxHub doesn't expose update)

Writes require an API key with scope write:tickets. Pass Idempotency-Key: <your-id> on POST — we cache the canonical response for 24h so retries don't duplicate in the source system.

Canonical response envelope

Every canonical record includes these fields at minimum:

{
  "id":        "vendor-native-id",
  "source":    "speak2_family" | "go_icon" | "worxhub" | ...,
  "createdAt": "optional, vendor-reported ISO 8601",
  "updatedAt": "optional, vendor-reported ISO 8601",
  ...canonical fields for the specific resource...
}

Errors

Clients should branch on error.code (stable). error.message is human-readable and can change.

{
  "error": {
    "code": "unauthorized",
    "message": "Vendor auth rejected (HTTP 401).",
    "requestId": "req_abcdef1234567890",
    "details": { "detail": "..." }
  }
}
codeHTTPTypical cause
bad_request400Malformed input, invalid dates, unsupported source header.
unauthorized401Platform API key invalid or vendor credentials rejected at source.
forbidden403API key missing required scope.
resource_not_found404Record or endpoint not found.
unprocessable422Source doesn't support this operation, or malformed body.
rate_limited429Vendor-side rate limit exceeded. Retry with backoff.
upstream_unavailable502Vendor timed out or returned 5xx. Already retried 3×.

Customer onboarding (operator steps)

Each customer is a tenant. Tenants own zero-or-more API keys. Every key carries a tenantId, so every request traces back to its customer.

# 1. Create the tenant
POST /admin/organizations
{ "name": "Joy Living", "primaryContactEmail": "ops@joyliving.com" }
→ { "id": "org_…", "status": "active", … }

# 2. Issue the customer's platform API key
POST /admin/api-keys
{
  "tenantId":    "org_…",
  "label":       "Joy prod — ops backend",
  "environment": "prod",
  "scopes":      ["read", "write:tickets"]
}
→ { "key": {…meta, no plaintext…}, "plaintext": "osl_prd_Fg3aH7q2X9mK…" }
# — plaintext is returned ONCE. Hand off to the customer, never shown again.

# 3. Customer validates the key landed correctly
GET /v1/_me
Authorization: Bearer osl_prd_…
→ { "apiKey": { "id": "key_…", "scopes": [...], "environment": "prod", … },
    "tenant": { "id": "org_…", "name": "Joy Living", "status": "active" } }

Every request is logged

The requestAudit middleware records one entry per request in auditLogs/*, indexed by actorId (= key id). Entries include method, path, status, durationMs, source (when sent), external scope id, and request id. Never bodies, never credentials. Query via GET /admin/audit with filters for tenantId, apiKeyId, action, or source.

curl "$API/admin/audit?apiKeyId=key_…&limit=100" \
  -H "Authorization: Bearer $ADMIN_KEY"

Security posture


Built with pass-through architecture: ONSCREEN never stores your residents, events, or work orders. Your vendor is the system of record; we just translate the shape.