Tenant API keys
Tenant API keys are long-lived secrets scoped to one tenant. They are intended for devices, scripts, or backends that cannot use a user session — for example, posting automated meter readings through the ingest-meter-reading Edge Function.
What they are (and are not)
They are not a replacement for the Supabase anon key or JWT: PostgREST and the SDK’s normal calls still expect Supabase Auth (or the service role on the server). A tenant API key does not let you call arbitrary RPCs as that tenant from the public API.
They are opaque strings stored only as a SHA-256 hash in the database. When you create a key, the raw value is returned once (wosk_… prefix); you cannot retrieve it later — only metadata (name, prefix, timestamps).
Managing keys requires the tenant.admin permission (same pattern as other tenant-scoped admin RPCs).
SDK: create, list, revoke
Use a signed-in Supabase client (user with tenant admin). Pass the tenant UUID explicitly on each call.
client.tenantApiKeys
// Create — raw key returned only in this response
const created = await client.tenantApiKeys.create(tenantId, 'Building B IoT gateway')
// { id, key, keyPrefix, name, createdAt }
const keys = await client.tenantApiKeys.list(tenantId)
// Metadata only: id, name, keyPrefix, createdAt, lastUsedAt, expiresAt
await client.tenantApiKeys.revoke(tenantId, keyId)
Rate limits
Server-side rate limits apply per user per tenant (rolling windows):
- Create: 10 per minute
- List: 30 per minute
- Revoke: 20 per minute
Meter ingest Edge Function
The repo includes functions/ingest-meter-reading. When deployed, it validates the tenant API key with the service role, then records a reading with reading_type = 'automated' via rpc_record_meter_reading_automated.
Endpoint (hosted Supabase): https://<project-ref>.supabase.co/functions/v1/ingest-meter-reading
Headers (either form):
Authorization: Bearer <tenant_api_key>- or
X-API-Key: <tenant_api_key>
Body (JSON):
{
"meterId": "uuid",
"readingValue": 123.4,
"readingDate": "2025-03-22T12:00:00.000Z",
"notes": "optional"
}
Responses: 201 with { "readingId": "uuid" } on success; 401 for missing/invalid/expired keys; 4xx for validation or tenant/meter mismatches. The function needs SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in its environment (set automatically when deployed in Supabase).
Local testing: see apps/supabase/README.md and the root script that serves functions (e.g. supabase:functions).