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).
BASE=http://localhost:5080AUTH="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 valuescurl -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.