Skip to content

Onboarding a tenant

For real tenants, use the admin API (or the dashboard, which calls the same endpoints) instead of dev seed.

The admin API takes a JWT with a roles claim (owner ⊇ operator ⊇ viewer) and an optional tenant claim. An owner without a tenant claim is a system-scope owner who picks the tenant per request with ?tenantId=. In Development, stayfn dev token --role owner mints such a token. In production the token comes from your identity provider (see Security model).

Terminal window
BASE=http://localhost:5080
AUTH="Authorization: Bearer $TOKEN"
# 1. Tenant (system-scope owner)
curl -s -X POST "$BASE/api/admin/tenants" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"name":"Acme Hotels"}'
TENANT=<id from the response>
# 2. OHIP environment: secret *references* only, never values
curl -s -X POST "$BASE/api/admin/environments?tenantId=$TENANT" -H "$AUTH" -H 'Content-Type: application/json' -d '{
"name": "production",
"kind": "Ohip",
"gatewayBaseUrl": "https://<gateway host>/",
"oAuthFlow": "ClientCredentials",
"oAuthScope": "urn:opc:hgbu:ws:__myscopes__",
"enterpriseId": "<enterprise id>",
"appKeySecretRef": "aws:stayfn/acme#appKey",
"clientIdSecretRef": "aws:stayfn/acme#clientId",
"clientSecretSecretRef": "aws:stayfn/acme#clientSecret"
}'
ENVIRONMENT=<id from the response>
# 3. Hotel (codes are stored upper-case; one default hotel per environment)
curl -s -X POST "$BASE/api/admin/hotels?tenantId=$TENANT" -H "$AUTH" -H 'Content-Type: application/json' -d "{
\"environmentId\": \"$ENVIRONMENT\", \"hotelCode\": \"<HOTEL>\", \"chainCode\": \"<CHAIN>\",
\"displayName\": \"Acme Downtown\", \"timezone\": \"America/Denver\", \"isDefault\": true
}"
# 4. Tenant API key (shown exactly once; store it in the caller's secret store)
curl -s -X POST "$BASE/api/admin/api-keys?tenantId=$TENANT" -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"name":"pms-integration","scopes":["functions:invoke","functions:read"]}'

For password (“SSD”) environments, use "oAuthFlow": "Password" with integrationUserSecretRef and integrationPasswordSecretRef instead of enterpriseId/oAuthScope.

Secret references have the form <provider>:<reference>:

Prefix Store Example
env: host environment variable, only names starting OHIP_ or STAYFN_SECRET_ env:OHIP_ACME_APP_KEY
aws: AWS Secrets Manager (AWSCURRENT), optional JSON key aws:stayfn/acme#appKey
vault: HashiCorp Vault KV v2, optional field vault:stayfn/acme#appKey
kv: Azure Key Vault, optional version kv:acme-app-key

Values are cached for 5 minutes and re-read every 5 minutes. A new version counts as a rotation: the OHIP tokens that used it are dropped and an audit row is written. After rotating credentials by hand, POST /api/admin/environments/{id}/rotate-credentials applies the change on every replica immediately.

Per-tenant settings are versioned variables (/api/admin/variables) at tenant, environment or hotel scope, resolved hotel → environment → tenant. Functions read them with ctx.Vars. Keys under quota.* can only be set by a system-scope owner:

Variable Effect
quota.invocationsPerDay invocations per UTC day (429 quota_exceeded beyond)
quota.concurrentInvocations invocations running at once
quota.webhookRequestsPerMinute webhook requests per minute per replica
webhook.<source>.secret HMAC secret of a webhook source (masked in every API response)

Every admin mutation writes an audit row (GET /api/admin/audit). Usage is rolled up hourly and exposed at GET /api/admin/usage?groupBy=function|hotel, as JSON or &format=csv.