Before you begin
What you’ll need
- Permission to manage connected-system keys
- Connected services available for your organization
- A recent secure identity check to manage keys
- A server or trusted backend that can keep credentials private
Create a key for a trusted system
The Organization API is a server-to-server HTTP interface for one organization. A system key determines the organization and allowed operations; requests do not choose an organization ID.
- Open Organization settings → Security & API → Keys for trusted systems and select Create system key.
- Give the key a recognizable name, choose only the access the system needs, and optionally set an expiration date.
- Select Create and reveal key once. Store the full key in your backend's secret store, then select I stored this key.
- Use Replace key when rotating credentials, or Disable to stop a system's access. Replacement immediately disables the previous key.
Make your first request
Use the configured Supabase project URL followed by /functions/v1/organization-api. An owner can find the project's host in the endpoint shown under Organization agents; replace the final organization-mcp segment with organization-api. The portal website URL is not the API base URL.
- Every request needs Authorization: Bearer <system-key> and X-Request-ID containing a UUID. Generate a fresh request ID for each HTTP attempt and retain it for troubleshooting.
- Writes also need Content-Type: application/json and Idempotency-Key with 8–200 printable characters. The idempotency key identifies the intended change and stays the same on retries.
- The examples use shell variables. Set API_BASE to your real API base URL and load SYSTEM_KEY securely before running them. Replace example record IDs with IDs from your organization's responses.
API_BASE="https://<project>.supabase.co/functions/v1/organization-api"
# SYSTEM_KEY is supplied by your secret store.
curl --fail-with-body "$API_BASE/members?limit=50" \
-H "Authorization: Bearer $SYSTEM_KEY" \
-H "X-Request-ID: $(uuidgen)"Routes and required scopes
Paths below are relative to the API base URL. Read and write scopes are independent: a write scope does not automatically provide read access. Only these routes are currently supported.
| Method and path | Required scope | Result |
|---|---|---|
| GET /members | members:read | Member profiles, primary email and phone, preferences, and custom fields. |
| GET /memberships | memberships:read | Memberships, option names, status, dates, and paid-through dates. |
| GET /payments | payments:read | Payment history, status, currency, amounts, and receipt status. |
| GET /publications | publications:read | Publication issues, release information, and asset metadata; no protected download URLs. |
| GET /entitlements | entitlements:read | Stored benefit-access records, effects, resources, and validity dates. |
| PATCH /members/{member_record_id} | members:write | Update the supported fields on an existing member profile. |
| POST /entitlements/overrides | entitlements:write | Create a documented one-member access override. |
Read collections and follow cursors
All five GET routes accept limit (1–200; default 100) and an optional UUID cursor. Results are ordered by record ID. Other query parameters, duplicate parameters, and GET request bodies are rejected.
- Process items, then send next_cursor as cursor on the next request. Stop when next_cursor is null. A full final page can be followed by one empty page.
- There is no search, date filter, updated-since filter, or individual-record GET route in this API. Select records from collection results in your integration.
- Read the currency alongside every amount ending in _minor. Those values are integer minor units, not formatted decimal amounts.
- Publication results describe files but do not provide authorization to download protected content. Keep member and payment data within the systems approved by your organization.
{
"collection": "members",
"items": [],
"next_cursor": null,
"request_id": "7f90ee4c-4fb5-4b64-a7d1-a7fc72d6a280"
}Update a member profile
PATCH /members/{member_record_id} accepts a nonempty JSON object containing only the fields below. Send the changes directly in the body, without a changes wrapper. Omitted fields stay unchanged.
| Field | Accepted value |
|---|---|
| display_name | Nonblank string, up to 200 characters. |
| legal_name | Nonblank string, up to 300 characters; null clears it. |
| member_number | Nonblank string, up to 100 characters; null clears it. |
| status | prospective, pending, active, inactive, deceased, rejected, or archived. This is the member profile's status, not a membership's status. |
| delivery_preferences · communication_preferences · directory_preferences · custom_fields | JSON objects. Each supplied object replaces that field's current object; nested values are not merged. |
MEMBER_ID="<member-record-uuid>"
OPERATION_KEY="<unique-key-for-this-change>"
curl --fail-with-body -X PATCH "$API_BASE/members/$MEMBER_ID" \
-H "Authorization: Bearer $SYSTEM_KEY" \
-H "X-Request-ID: $(uuidgen)" \
-H "Idempotency-Key: $OPERATION_KEY" \
-H "Content-Type: application/json" \
--data '{"display_name":"Alex Morgan"}'{
"result": {
"command": "member.update",
"member_record_id": "b6a4e468-f514-4f5c-934e-2cf50724a490",
"updated": true,
"replayed": false
},
"request_id": "7f90ee4c-4fb5-4b64-a7d1-a7fc72d6a280"
}Create a one-member access override
POST /entitlements/overrides requires member_record_id and resource_id UUIDs, effect (grant or deny), and a reason of 3–2,000 characters. Both records must belong to the key's organization, and the resource must be active.
- valid_from is optional and defaults to the current time. valid_until is optional or null for no end date; if supplied, it must be later than the start.
- Supply timestamps in ISO 8601 format with Z or a numeric timezone offset. Use a resource_id from an existing access record or the organization's authorized benefit configuration.
- The body limit is 16 KiB. The response uses the result and request_id envelope, with command set to entitlement.override.
{
"member_record_id": "b6a4e468-f514-4f5c-934e-2cf50724a490",
"resource_id": "6e16ce34-cff4-42f9-a47e-7079af6b75ab",
"effect": "grant",
"reason": "Approved access exception for this member",
"valid_until": null
}Handle errors, limits, and retries
Successful reads and writes return HTTP 200. Errors contain error.code, error.message, and request_id. Record the status and request ID for support without logging the Authorization header or private request bodies.
- The current budget is 120 requests per minute per key. Authorized responses include RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset (an ISO timestamp); throttled responses include Retry-After in seconds.
- On a timeout or uncertain write result, retry with the same credential, Idempotency-Key, and identical body. A completed replay returns result.replayed: true without applying the change again. Use a new X-Request-ID for the HTTP attempt.
- Write retry keys are scoped to the credential. Do not assume a retry after replacing a credential is protected by the old key's history; inspect the result before submitting through a new credential.
| Status | What to check |
|---|---|
| 400 | Required UUID X-Request-ID, write Idempotency-Key, valid JSON, supported fields, and pagination parameters. |
| 401 | Missing, malformed, replaced, disabled, or expired system key. |
| 403 | The key's scope and current authorization for the requested operation. |
| 404 | Unsupported route/method or a record unavailable to this organization. |
| 409 | Request conflict, including reuse of a write key with different arguments. Resolve the conflict before retrying. |
| 413 / 415 | Request body too large / missing or unsupported Content-Type. Writes require application/json. |
| 429 | Rate limit reached. Wait for the Retry-After interval before retrying. |
| 503 | Temporarily unavailable. Retry with bounded backoff; preserve the write's original key and body. |
{
"error": {
"code": "invalid_request_id",
"message": "X-Request-ID must be a UUID."
},
"request_id": "7f90ee4c-4fb5-4b64-a7d1-a7fc72d6a280"
}