The Golden Record API provides a centralized, authoritative source for managing organizations, users, roles, and permissions. It is designed for enterprise use cases where multiple applications need consistent access to organizational data.
https://your-domain.com/api/v1The API uses URL path versioning (e.g., /api/v1/). All endpoints are prefixed with the version number. When breaking changes are introduced, a new version will be released while maintaining backwards compatibility for existing versions.
All API requests require authentication using a Bearer token. Include your API key in the Authorization header:
Authorization: Bearer gr_live_your_api_key_heregr_live_*Production keys - Use these for live applications. All data operations affect real records.
gr_test_*Test keys - For development and staging environments. (Coming soon)
@gloo.us emailSecurity Note: Never expose your API keys in client-side code, public repositories, or logs. Use environment variables and server-side requests only.
API keys are granted specific scopes that determine their access level. Following the principle of least privilege, only grant the scopes necessary for your integration.
By default, the Organizations API only returns verified organizations. To access non-verified (staging) organizations, your API key must have the staging:read scope. Without this scope, the includeStaging parameter is ignored, and direct access to non-verified organization IDs returns a 404 error.
| Scope | Description |
|---|---|
organizations:read | List and view verified organizations |
organizations:create | Create new organizations |
organizations:update | Update existing organizations, toggle verification status |
organizations:delete | Delete organizations |
staging:read | Access non-verified (staging) organizations. Required for includeStaging parameter and direct access to non-verified org IDs. |
users:read | List and view users |
users:create | Create new users |
users:update | Update existing users |
users:delete | Delete users |
roles:read | List and view roles |
roles:create | Create new roles |
roles:update | Update existing roles |
roles:delete | Delete roles |
permissions:read | List all available permissions |
permissions:assign | Assign permissions to roles |
memberships:read | List organization memberships |
memberships:create | Add users to organizations |
api_keys:read | List API keys (does not expose actual key values) |
api_keys:create | Create and rotate API keys |
api_keys:revoke | Revoke API keys |
audit_logs:read | View audit logs |
applications:read | List and view applications, roles, permissions, and user app assignments |
applications:write | Create applications, roles, permissions; assign permissions to roles; manage org app access |
applications:delete | Delete applications, roles, and permissions |
* | Full access to all resources. Use with caution - grants all permissions. |
The * wildcard scope grants full access to all resources. Use this scope sparingly and only for administrative integrations. Consider using specific scopes for better security.
API keys can be granted specific scopes that limit what operations they can perform. Always follow the principle of least privilege - only grant scopes that are necessary. See the API Scopes section for a complete reference.
| Scope | Description | Endpoints |
|---|---|---|
organizations:read | List and view verified organizations | |
organizations:create | Create new organizations | |
organizations:update | Update existing organizations, toggle verification status | |
organizations:delete | Delete organizations | |
staging:read | Access non-verified (staging) organizations. Required for includeStaging parameter and direct access to non-verified org IDs. | |
users:read | List and view users | |
users:create | Create new users | |
users:update | Update existing users | |
users:delete | Delete users | |
roles:read | List and view roles | |
roles:create | Create new roles | |
roles:update | Update existing roles | |
roles:delete | Delete roles | |
permissions:read | List all available permissions | |
permissions:assign | Assign permissions to roles | |
memberships:read | List organization memberships | |
memberships:create | Add users to organizations | |
api_keys:read | List API keys (does not expose actual key values) | |
api_keys:create | Create and rotate API keys | |
api_keys:revoke | Revoke API keys | |
audit_logs:read | View audit logs | |
applications:read | List and view applications, roles, permissions, and user app assignments | |
applications:write | Create applications, roles, permissions; assign permissions to roles; manage org app access | |
applications:delete | Delete applications, roles, and permissions | |
* | Full access to all resources. Use with caution - grants all permissions. |
API keys can be scoped to a specific organization, providing strict data isolation:
GR_ORG_SCOPE_VIOLATIONRestrict API key usage to specific IP addresses or CIDR ranges for additional security:
{
"name": "Production Key",
"scopes": ["organizations:read"],
"allowedIps": [
"203.0.113.50", // Single IP
"192.168.1.0/24", // CIDR range (256 IPs)
"10.0.0.0/8" // Large internal range
]
}GR_IP_NOT_ALLOWEDRotate API keys with zero downtime using the 7-day grace period:
POST /api/v1/keys/:id?action=rotateSet expiration dates on API keys for temporary access:
{
"name": "Temporary Integration Key",
"scopes": ["organizations:read"],
"expiresAt": "2024-12-31T23:59:59Z"
}Expired keys return GR_INVALID_API_KEY. Keys without an expiration date never expire.
All responses include headers with rate limit information:
When rate limited, you will receive a 429 response with GR_RATE_LIMITED:
{
"success": false,
"errors": [{
"code": "GR_RATE_LIMITED",
"message": "Rate limit exceeded. Try again in 45 seconds."
}],
"requestId": "req_abc123"
}
// Response Headers:
// Retry-After: 45
// X-RateLimit-Reset: 1706745645Implement exponential backoff and respect the Retry-After header value.
The API uses cursor-based pagination for list endpoints, which is more efficient and reliable than offset-based pagination for large datasets.
limitmeta.hasMore to see if more pages existmeta.nextCursor value in next request as cursor parameterhasMore is falseGET /api/v1/organizations?limit=50limit(integer, default: 20, max: 100)- Number of results per pagecursor(string, optional)- Opaque cursor from previous responseNote: Cursors are opaque strings based on createdAt timestamp and id for deterministic ordering. Do not attempt to construct or modify cursor values manually.
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"slug": "acme-corp",
...
},
"meta": {
"limit": 20,
"total": 150,
"hasMore": true,
"nextCursor": "eyJjcmVhdGVkQXQiOi4uLn0"
},
"requestId": "req_abc123def456"
}{
"success": false,
"data": null,
"errors": [
{
"code": "GR_VALIDATION_ERROR",
"message": "Validation failed",
"field": "slug",
"details": {
"pattern": "Slug must contain only lowercase letters, numbers, and hyphens"
}
}
],
"requestId": "req_abc123def456"
}success - Boolean indicating if the request succeededdata - The response payload (single object or array)meta - Pagination info and other metadataerrors - Array of error objects (only on failure)requestId - Unique identifier for debugging and support| Code | Status | Description | Resolution |
|---|---|---|---|
GR_UNAUTHORIZED | 401 | Authentication required - no API key provided or invalid Authorization header format | Include a valid Bearer token in the Authorization header |
GR_INVALID_API_KEY | 401 | The API key is invalid, expired, or has been revoked | Check the key is correct and active. Generate a new key if needed |
GR_FORBIDDEN | 403 | The API key lacks the required scope for this operation | Use a key with the appropriate scopes or request additional permissions |
GR_IP_NOT_ALLOWED | 403 | Request IP address is not in the key's allowed IP list | Add your IP to the key's allowedIps list or remove IP restrictions |
GR_ORG_SCOPE_VIOLATION | 403 | Org-scoped key attempted to access a different organization's data | Use a global key or the key scoped to the target organization |
GR_NOT_FOUND | 404 | The requested resource was not found | Verify the resource ID is correct |
GR_ORG_NOT_FOUND | 404 | Organization with the specified ID does not exist | Verify the organization ID or check if it was deleted |
GR_USER_NOT_FOUND | 404 | User with the specified ID does not exist | Verify the user ID or check if they were deleted |
GR_KEY_NOT_FOUND | 404 | API key with the specified ID does not exist | Verify the key ID or check if it was revoked |
GR_VALIDATION_ERROR | 400 | Request body or parameters failed validation | Check the error details for specific field issues |
GR_DUPLICATE_SLUG | 409 | An organization with this slug already exists | Use a different slug value |
GR_DUPLICATE_EMAIL | 409 | A user with this email already exists | Use a different email or update the existing user |
GR_RATE_LIMITED | 429 | Too many requests - rate limit exceeded | Wait until the rate limit resets (see Retry-After header) |
GR_INTERNAL_ERROR | 500 | An unexpected server error occurred | Retry the request. If persistent, contact support with the requestId |
Organizations are the primary entities in the Golden Record. They can be verified (visible in public API results) or staging (hidden by default, useful for testing or pending approval).
{
"id": "550e8400-e29b-41d4-a716-446655440000", // UUID
"name": "Acme Corporation", // Display name
"slug": "acme-corp", // URL-friendly identifier
"domain": "acme.com", // Primary domain (optional)
"logoUrl": "https://example.com/logo.png", // Logo URL (optional)
"workosOrgId": "org_01ABC123", // WorkOS organization ID (if synced)
"isVerified": true, // Verified = public, false = staging
"isActive": true, // Soft-delete flag
"metadata": { "industry": "technology" }, // Custom data (optional)
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}/api/v1/organizationsRetrieve a paginated list of organizations. By default, only verified organizations are returned. To include staging organizations, you need the `staging:read` scope.
limit(integer)default: 20- Number of results per page (max 100)cursor(string)- Cursor for pagination (from previous response)search(string)- Search by name or slugincludeStaging(boolean)default: false- Include non-verified (staging) organizations. Requires `staging:read` scope; without this scope, the parameter is ignored.{
"success": true,
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"slug": "acme-corp",
"domain": "acme.com",
"logoUrl": "https://example.com/logo.png",
"workosOrgId": "org_01ABC123",
"isVerified": true,
"isActive": true,
"metadata": { "industry": "technology" },
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
],
"meta": {
"limit": 20,
"total": 150,
"hasMore": true,
"nextCursor": "eyJjcmVhdGVkQXQiOiIyMDI0LTAxLTE1VDEwOjMwOjAwWiIsImlkIjoiNTUwZTg0MDAtZTI5Yi00MWQ0LWE3MTYtNDQ2NjU1NDQwMDAwIn0"
},
"requestId": "req_abc123def456"
}/api/v1/organizationsCreate a new organization. New organizations are created as staging (unverified) by default.
name(string)slug(string)domain(string)- Primary domain (e.g., acme.com)logoUrl(string)- URL to organization logometadata(object)- Custom key-value data{
"name": "Acme Corporation",
"slug": "acme-corp",
"domain": "acme.com",
"metadata": {
"industry": "technology",
"size": "enterprise"
}
}{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"slug": "acme-corp",
"domain": "acme.com",
"logoUrl": null,
"workosOrgId": null,
"isVerified": false,
"isActive": true,
"metadata": { "industry": "technology", "size": "enterprise" },
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
},
"requestId": "req_abc123def456"
}/api/v1/organizations/:idRetrieve a single organization by ID. Only verified organizations are accessible by default. Non-verified (staging) organizations require the `staging:read` scope. Org-scoped API keys can only access their own organization.
id(uuid)/api/v1/organizations/:idUpdate an existing organization. Only provided fields are updated.
name(string)- Organization display nameslug(string)- URL-friendly identifierdomain(string)- Primary domainlogoUrl(string)- URL to organization logoisActive(boolean)- Whether the organization is activemetadata(object)- Custom key-value data (replaces existing)/api/v1/organizations/:idPermanently delete an organization and all associated data including memberships.
/api/v1/organizations/:id/verifyToggle organization verification status. Verified organizations appear in public API results.
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"isVerified": true,
"message": "Organization verified successfully"
},
"requestId": "req_abc123def456"
}Users represent individual accounts in the system. They can be synced from WorkOS (SSO users) or created directly via the API.
{
"id": "user_01ABC123", // UUID
"email": "john.doe@acme.com", // Email address (unique)
"firstName": "John", // First name (optional)
"lastName": "Doe", // Last name (optional)
"avatarUrl": "https://example.com/avatar.jpg", // Avatar URL (optional)
"workosUserId": "user_01WORKOS123", // WorkOS user ID (if synced via SSO)
"isActive": true, // Active status
"metadata": { "department": "Engineering" }, // Custom data (optional)
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}/api/v1/usersRetrieve a paginated list of users.
limit(integer)default: 20- Number of results per page (max 100)cursor(string)- Cursor for paginationsearch(string)- Search by email, first name, or last nameorganizationId(uuid)- Filter by organization membership{
"success": true,
"data": [
{
"id": "user_01ABC123",
"email": "john.doe@acme.com",
"firstName": "John",
"lastName": "Doe",
"avatarUrl": "https://example.com/avatar.jpg",
"workosUserId": "user_01WORKOS123",
"isActive": true,
"metadata": { "department": "Engineering" },
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
],
"meta": {
"limit": 20,
"total": 50,
"hasMore": false,
"nextCursor": null
},
"requestId": "req_abc123def456"
}/api/v1/usersCreate a new user account.
email(string)firstName(string)- First namelastName(string)- Last nameavatarUrl(string)- URL to user avatarmetadata(object)- Custom key-value data/api/v1/users/:idRetrieve a single user by ID.
/api/v1/users/:idUpdate an existing user.
email(string)- User email addressfirstName(string)- First namelastName(string)- Last nameavatarUrl(string)- URL to user avatarisActive(boolean)- Whether the user is activemetadata(object)- Custom key-value data/api/v1/users/:idPermanently delete a user and all associated memberships.
Memberships define the relationship between users and organizations, including their assigned role.
/api/v1/membershipsList organization memberships. Returns users with their roles in organizations.
organizationId(uuid)- Filter by organizationuserId(uuid)- Filter by userlimit(integer)default: 20- Number of results per pagecursor(string)- Cursor for pagination{
"success": true,
"data": [
{
"id": "mem_01ABC123",
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"userId": "user_01ABC123",
"roleId": "role_01ABC123",
"isOwner": true,
"createdAt": "2024-01-15T10:30:00Z",
"user": {
"id": "user_01ABC123",
"email": "john.doe@acme.com",
"firstName": "John",
"lastName": "Doe"
},
"organization": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"slug": "acme-corp"
},
"role": {
"id": "role_01ABC123",
"name": "Admin",
"slug": "admin"
}
}
],
"meta": { "limit": 20, "total": 5, "hasMore": false },
"requestId": "req_abc123def456"
}/api/v1/membershipsAdd a user to an organization with a specific role.
organizationId(uuid)userId(uuid)roleId(uuid)isOwner(boolean)default: false- Whether user is organization ownerThe RBAC system provides flexible permission management. Roles contain sets of permissions that define what actions users can perform.
/api/v1/rolesList all roles with their associated permissions.
{
"success": true,
"data": [
{
"id": "role_01ABC123",
"name": "Admin",
"slug": "admin",
"description": "Full administrative access",
"isSystem": true,
"permissions": [
{ "id": "perm_01", "name": "Read Organizations", "slug": "organizations:read", "resource": "organizations", "action": "read" },
{ "id": "perm_02", "name": "Create Organizations", "slug": "organizations:create", "resource": "organizations", "action": "create" }
],
"createdAt": "2024-01-15T10:30:00Z"
},
{
"id": "role_02ABC123",
"name": "Member",
"slug": "member",
"description": "Basic member access",
"isSystem": true,
"permissions": [
{ "id": "perm_01", "name": "Read Organizations", "slug": "organizations:read", "resource": "organizations", "action": "read" }
],
"createdAt": "2024-01-15T10:30:00Z"
}
],
"meta": { "total": 2 },
"requestId": "req_abc123def456"
}/api/v1/permissionsList all available permissions.
{
"success": true,
"data": [
{ "id": "perm_01", "name": "Read Organizations", "slug": "organizations:read", "resource": "organizations", "action": "read" },
{ "id": "perm_02", "name": "Create Organizations", "slug": "organizations:create", "resource": "organizations", "action": "create" },
{ "id": "perm_03", "name": "Update Organizations", "slug": "organizations:update", "resource": "organizations", "action": "update" },
{ "id": "perm_04", "name": "Delete Organizations", "slug": "organizations:delete", "resource": "organizations", "action": "delete" },
{ "id": "perm_05", "name": "Read Users", "slug": "users:read", "resource": "users", "action": "read" },
{ "id": "perm_06", "name": "Create Users", "slug": "users:create", "resource": "users", "action": "create" },
{ "id": "perm_07", "name": "Update Users", "slug": "users:update", "resource": "users", "action": "update" },
{ "id": "perm_08", "name": "Delete Users", "slug": "users:delete", "resource": "users", "action": "delete" }
],
"meta": { "total": 8 },
"requestId": "req_abc123def456"
}API keys provide secure, scoped access to the Golden Record API. Keys can be organization-scoped, IP-restricted, and time-limited.
/api/v1/keysList API keys. Only returns key metadata, not the actual key values.
organizationId(uuid)- Filter by organization scope{
"success": true,
"data": [
{
"id": "key_01ABC123",
"name": "Production API Key",
"keyPrefix": "gr_live_abc123",
"organizationId": null,
"scopes": ["organizations:read", "users:read"],
"tier": "pro",
"allowedIps": ["192.168.1.0/24", "10.0.0.1"],
"expiresAt": "2025-01-15T10:30:00Z",
"lastUsedAt": "2024-01-20T15:45:00Z",
"isActive": true,
"createdAt": "2024-01-15T10:30:00Z"
}
],
"meta": { "total": 3 },
"requestId": "req_abc123def456"
}/api/v1/keysCreate a new API key. The full key is only returned once at creation time - store it securely!
name(string)scopes(array)organizationId(uuid)- Scope key to specific organizationtier(string)default: free- Rate limit tier: free, basic, pro, enterpriseallowedIps(array)- IP addresses/CIDR ranges allowed to use this keyexpiresAt(datetime)- Key expiration date (ISO 8601){
"name": "Production API Key",
"scopes": ["organizations:read", "organizations:create", "users:read"],
"tier": "pro",
"allowedIps": ["192.168.1.0/24", "10.0.0.1"],
"expiresAt": "2025-12-31T23:59:59Z"
}{
"success": true,
"data": {
"id": "key_01ABC123",
"name": "Production API Key",
"key": "gr_live_sk_1234567890abcdefghijklmnopqrstuvwxyz",
"keyPrefix": "gr_live_sk_12345",
"organizationId": null,
"scopes": ["organizations:read", "organizations:create", "users:read"],
"tier": "pro",
"allowedIps": ["192.168.1.0/24", "10.0.0.1"],
"expiresAt": "2025-12-31T23:59:59Z",
"isActive": true,
"createdAt": "2024-01-15T10:30:00Z"
},
"requestId": "req_abc123def456"
}/api/v1/keys/:id?action=rotateRotate an API key. Creates a new key and schedules the old one to expire after a 7-day grace period for seamless migration.
{
"success": true,
"data": {
"oldKey": {
"id": "key_01ABC123",
"keyPrefix": "gr_live_sk_12345",
"expiresAt": "2024-01-22T10:30:00Z",
"message": "Old key will expire in 7 days"
},
"newKey": {
"id": "key_02DEF456",
"key": "gr_live_sk_newkey1234567890abcdefghij",
"keyPrefix": "gr_live_sk_newke",
"isActive": true,
"createdAt": "2024-01-15T10:30:00Z"
}
},
"requestId": "req_abc123def456"
}/api/v1/keys/:idImmediately revoke an API key. The key will no longer work for authentication.
/api/v1/keys/:id/access-tokenGenerate a short-lived JWT access token (valid for 1 hour). The token inherits the same scopes as the parent API key and is useful for time-limited operations or frontend applications.
{
"success": true,
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresIn": 3600,
"expiresAt": "2024-01-15T11:30:00Z",
"scope": "organizations:read users:read"
},
"requestId": "req_xyz789"
}When you rotate an API key, the old key enters a 7-day grace period. During this time, responses include deprecation headers to notify you that the key will soon expire:
For enhanced security, generate short-lived JWT tokens (valid for 1 hour) using thePOST /api/v1/keys/:id/access-token endpoint. This is useful for:
The analytics endpoint provides insights into API usage patterns, response times, and error rates.
/api/v1/analyticsRetrieve API usage analytics for the authenticated key or organization.
startDate(datetime)endDate(datetime)endpoint(string)- Filter by specific endpointgroupBy(string)- Group results: day, hour, endpoint{
"success": true,
"data": {
"summary": {
"totalRequests": 15420,
"successfulRequests": 15100,
"failedRequests": 320,
"avgResponseTimeMs": 45,
"successRate": 97.9
},
"byEndpoint": [
{ "endpoint": "/api/v1/organizations", "method": "GET", "count": 8500, "avgMs": 35 },
{ "endpoint": "/api/v1/users", "method": "GET", "count": 4200, "avgMs": 42 },
{ "endpoint": "/api/v1/organizations", "method": "POST", "count": 2720, "avgMs": 68 }
],
"byDay": [
{ "date": "2024-01-15", "count": 2200 },
{ "date": "2024-01-16", "count": 2450 },
{ "date": "2024-01-17", "count": 2100 }
]
},
"meta": {
"startDate": "2024-01-15T00:00:00Z",
"endDate": "2024-01-22T00:00:00Z"
},
"requestId": "req_abc123def456"
}Webhooks allow your application to receive real-time HTTP notifications when events occur in the Golden Record. You can programmatically manage webhook subscriptions using the same API key that gives you access to other resources.
Use the Webhooks API to create, list, update, and delete webhook subscriptions. The same API key works for both managing webhooks and accessing other Golden Record resources.
/api/v1/webhooksList all webhook subscriptions for your account.
limit(integer)default: 20- Number of results per page (max 100)cursor(string)- Cursor for pagination{
"success": true,
"data": [
{
"id": "wh_01ABC123",
"name": "Production Webhook",
"url": "https://example.com/webhooks/golden-record",
"events": ["organization.created", "organization.updated", "user.created"],
"isActive": true,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
],
"meta": { "total": 1 },
"requestId": "req_abc123def456"
}/api/v1/webhooksCreate a new webhook subscription. The signing secret is only returned once at creation - store it securely!
name(string)url(string)events(array)metadata(object)- Custom key-value data{
"name": "Production Webhook",
"url": "https://example.com/webhooks/golden-record",
"events": ["organization.created", "organization.updated", "user.created"]
}{
"success": true,
"data": {
"id": "wh_01ABC123",
"name": "Production Webhook",
"url": "https://example.com/webhooks/golden-record",
"secret": "whsec_abc123def456ghijklmnopqrstuvwxyz1234567890",
"events": ["organization.created", "organization.updated", "user.created"],
"isActive": true,
"createdAt": "2024-01-15T10:30:00Z"
},
"requestId": "req_abc123def456"
}/api/v1/webhooks/:idRetrieve a single webhook subscription by ID.
id(uuid)/api/v1/webhooks/:idUpdate a webhook subscription. You can change the URL, events, or active status.
name(string)- Updated nameurl(string)- Updated endpoint URLevents(array)- Updated event types arrayisActive(boolean)- Enable or disable the webhook{
"events": ["*"],
"isActive": true
}/api/v1/webhooks/:idDelete a webhook subscription. The subscription will immediately stop receiving events.
Subscribe to specific events or use * to receive all events. Events are delivered in real-time as changes occur in the system.
organization.created - New organization createdorganization.updated - Organization details changedorganization.deleted - Organization deleteduser.created - New user createduser.updated - User details changeduser.deleted - User deletedmembership.created - User added to organizationmembership.deleted - User removed from organization* - Subscribe to all events{
"event": "organization.created",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"slug": "acme-corp",
"domain": "acme.com",
"isVerified": false,
"createdAt": "2024-01-15T10:30:00Z"
}
}{
"event": "user.created",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"id": "user_01ABC123",
"email": "john.doe@acme.com",
"firstName": "John",
"lastName": "Doe",
"createdAt": "2024-01-15T10:30:00Z"
}
}{
"event": "membership.created",
"timestamp": "2024-01-15T10:30:00Z",
"data": {
"id": "mem_01ABC123",
"organizationId": "550e8400-e29b-41d4-a716-446655440000",
"userId": "user_01ABC123",
"roleId": "role_01ABC123",
"createdAt": "2024-01-15T10:30:00Z"
}
}If your endpoint fails to respond with a 2xx status code, we automatically retry delivery with exponential backoff:
After 5 failed attempts, the delivery is marked as failed.
All webhook payloads are signed using HMAC-SHA256. Always verify signatures to ensure requests are authentic and prevent replay attacks.
{timestamp}.{raw_body}import crypto from 'crypto';
function verifyWebhook(rawBody: string, headers: Headers, secret: string): boolean {
const timestamp = headers.get('X-Webhook-Timestamp');
const signature = headers.get('X-Webhook-Signature');
if (!timestamp || !signature) {
return false;
}
// Check timestamp is within 5 minutes
const timestampAge = Math.floor(Date.now() / 1000) - parseInt(timestamp);
if (timestampAge > 300) {
return false; // Replay attack protection
}
// Compute expected signature
const signedPayload = `${timestamp}.${rawBody}`;
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}import hmac
import hashlib
import time
def verify_webhook(raw_body: str, headers: dict, secret: str) -> bool:
timestamp = headers.get('X-Webhook-Timestamp')
signature = headers.get('X-Webhook-Signature')
if not timestamp or not signature:
return False
# Check timestamp is within 5 minutes
timestamp_age = int(time.time()) - int(timestamp)
if timestamp_age > 300:
return False
# Compute expected signature
signed_payload = f"{timestamp}.{raw_body}"
expected_sig = 'sha256=' + hmac.new(
secret.encode(),
signed_payload.encode(),
hashlib.sha256
).hexdigest()
# Constant-time comparison
return hmac.compare_digest(signature, expected_sig)The Multi-App RBAC system allows third-party applications to self-manage their authorization logic through the Golden Record API. Each application can define its own roles and permissions, assign users to roles within organizations, and check effective permissions at runtime.
Each app you build (e.g., NPS Surveys, CRM, Helpdesk) is registered as an Application. Applications are containers for roles and permissions.
Roles group permissions together (e.g., "Survey Admin", "Viewer"). Users are assigned to roles within a specific organization.
Granular actions users can perform (e.g., "surveys:create", "reports:export"). Use the resource:action format for consistency.
The flattened list of all permissions a user has within an org, calculated from their assigned roles.
Follow these steps to integrate your application with the Golden Record RBAC system:
Create your application in Golden Record with a unique slug.
curl -X POST https://your-domain.com/api/v1/applications \
-H "Authorization: Bearer gr_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"name": "NPS Surveys",
"slug": "nps-surveys",
"description": "Customer satisfaction survey tool"
}'Create the granular permissions your app needs. Use resource:action format.
// Create permissions for your app
const permissions = [
{ name: "Create Survey", slug: "surveys:create" },
{ name: "Read Surveys", slug: "surveys:read" },
{ name: "Update Surveys", slug: "surveys:update" },
{ name: "Delete Surveys", slug: "surveys:delete" },
{ name: "Export Reports", slug: "reports:export" }
];
for (const perm of permissions) {
await fetch(`https://your-domain.com/api/v1/applications/${appId}/permissions`, {
method: "POST",
headers: {
"Authorization": "Bearer gr_live_your_key",
"Content-Type": "application/json"
},
body: JSON.stringify(perm)
});
}Group permissions into roles that make sense for your app.
curl -X POST https://your-domain.com/api/v1/applications/${appId}/roles \
-H "Authorization: Bearer gr_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Survey Admin",
"slug": "survey-admin",
"level": "admin",
"permissionIds": ["perm-uuid-1", "perm-uuid-2", "perm-uuid-3"]
}'Grant organizations access to your application.
curl -X POST https://your-domain.com/api/v1/organizations/${orgId}/apps \
-H "Authorization: Bearer gr_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "your-app-id",
"isEnabled": true
}'Grant users roles within their organization.
curl -X POST https://your-domain.com/api/v1/organizations/${orgId}/members/${userId}/apps \
-H "Authorization: Bearer gr_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"applicationId": "your-app-id",
"appRoleId": "role-uuid"
}'Query effective permissions before allowing actions in your app.
// In your application's middleware or authorization layer
async function checkPermission(userId, orgId, requiredPermission) {
const response = await fetch(
`https://your-domain.com/api/v1/organizations/${orgId}/members/${userId}/effective-permissions?applicationId=your-app-id`,
{ headers: { "Authorization": "Bearer gr_live_your_key" } }
);
const { data } = await response.json();
const app = data.applications.find(a => a.applicationSlug === "nps-surveys");
return app?.permissions.includes(requiredPermission) ?? false;
}
// Usage in your app
if (await checkPermission(userId, orgId, "surveys:create")) {
// Show create button
} else {
// Hide or disable create button
}/api/v1/applicationsList all applications registered in the system. Applications are containers for roles and permissions in a Multi-App RBAC setup.
{
"success": true,
"data": [
{
"id": "app_01ABC123",
"name": "NPS Surveys",
"slug": "nps-surveys",
"description": "Net Promoter Score survey application",
"logoUrl": "https://example.com/nps-logo.png",
"baseUrl": "https://nps.example.com",
"isActive": true,
"isSystem": false,
"metadata": { "category": "feedback" },
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
],
"meta": { "total": 3 },
"requestId": "req_abc123def456"
}/api/v1/applicationsRegister a new application for Multi-App RBAC. After creating an application, you can define roles and permissions for it.
name(string)slug(string)description(string)- Application descriptionlogoUrl(string)- URL to application logobaseUrl(string)- Base URL of the applicationmetadata(object)- Custom key-value data{
"name": "NPS Surveys",
"slug": "nps-surveys",
"description": "Net Promoter Score survey application",
"baseUrl": "https://nps.example.com"
}{
"success": true,
"data": {
"id": "app_01ABC123",
"name": "NPS Surveys",
"slug": "nps-surveys",
"description": "Net Promoter Score survey application",
"isActive": true,
"isSystem": false,
"createdAt": "2024-01-15T10:30:00Z"
},
"requestId": "req_abc123def456"
}/api/v1/applications/:idRetrieve application details. Use query parameters to include roles and/or permissions.
id(uuid)includeRoles(boolean)default: false- Include application roles in responseincludePermissions(boolean)default: false- Include application permissions in response/api/v1/applications/:idUpdate application details. System applications cannot be modified.
name(string)- Updated namedescription(string)- Updated descriptionlogoUrl(string)- Updated logo URLbaseUrl(string)- Updated base URLisActive(boolean)- Enable or disable the applicationmetadata(object)- Updated metadata/api/v1/applications/:idDelete an application and all associated roles, permissions, and user assignments. System applications cannot be deleted.
/api/v1/applications/:id/rolesList all roles defined for an application. Roles group permissions together and can be assigned to users.
id(uuid)includePermissions(boolean)default: false- Include permissions for each role{
"success": true,
"data": [
{
"id": "role_01ABC123",
"applicationId": "app_01ABC123",
"name": "Survey Admin",
"slug": "survey-admin",
"description": "Full access to manage surveys",
"level": "admin",
"isDefault": false,
"isSystem": false,
"permissions": [
{ "id": "perm_01", "slug": "surveys:create", "name": "Create Survey" },
{ "id": "perm_02", "slug": "surveys:read", "name": "Read Surveys" },
{ "id": "perm_03", "slug": "surveys:update", "name": "Update Surveys" },
{ "id": "perm_04", "slug": "surveys:delete", "name": "Delete Surveys" }
],
"createdAt": "2024-01-15T10:30:00Z"
},
{
"id": "role_02DEF456",
"applicationId": "app_01ABC123",
"name": "Survey Viewer",
"slug": "survey-viewer",
"description": "Read-only access to surveys",
"level": "viewer",
"isDefault": true,
"isSystem": false,
"permissions": [
{ "id": "perm_02", "slug": "surveys:read", "name": "Read Surveys" }
],
"createdAt": "2024-01-15T10:30:00Z"
}
],
"meta": { "total": 2 },
"requestId": "req_abc123def456"
}/api/v1/applications/:id/rolesCreate a new role for an application. You can optionally assign permissions during creation.
name(string)slug(string)description(string)- Role descriptionlevel(string)default: member- Role level: admin, editor, viewer, member, customisDefault(boolean)default: false- If true, new users are automatically assigned this rolemetadata(object)- Custom key-value datapermissionIds(array)- Array of permission UUIDs to assign to this role{
"name": "Survey Admin",
"slug": "survey-admin",
"description": "Full access to manage surveys",
"level": "admin",
"permissionIds": ["perm-uuid-1", "perm-uuid-2", "perm-uuid-3"]
}/api/v1/applications/:id/roles/:roleIdRetrieve a specific role. Optionally include its permissions.
id(uuid)roleId(uuid)includePermissions(boolean)default: false- Include role permissions/api/v1/applications/:id/roles/:roleIdUpdate a role. Include permissionIds to replace all role permissions. System roles cannot be modified.
name(string)- Updated nameslug(string)- Updated slugdescription(string)- Updated descriptionlevel(string)- Updated levelisDefault(boolean)- Updated default flagmetadata(object)- Updated metadatapermissionIds(array)- If provided, replaces all role permissions/api/v1/applications/:id/roles/:roleIdDelete a role. System roles cannot be deleted. Users assigned to this role will lose their assignment.
/api/v1/applications/:id/roles/:roleId/permissionsAdd permissions to a role. Existing assignments are preserved; duplicates are skipped.
permissionIds(array){
"permissionIds": ["perm-uuid-1", "perm-uuid-2"]
}{
"success": true,
"data": {
"assigned": 2,
"skipped": 0,
"permissionIds": ["perm-uuid-1", "perm-uuid-2"]
},
"requestId": "req_abc123def456"
}/api/v1/applications/:id/roles/:roleId/permissionsRemove specific permissions from a role.
permissionIds(array)/api/v1/applications/:id/permissionsList all permissions defined for an application. Permissions use the resource:action slug format (e.g., surveys:create).
id(uuid){
"success": true,
"data": [
{ "id": "perm_01", "applicationId": "app_01ABC123", "name": "Create Survey", "slug": "surveys:create", "description": "Allows creating new surveys", "resource": "surveys", "action": "create", "createdAt": "2024-01-15T10:30:00Z" },
{ "id": "perm_02", "applicationId": "app_01ABC123", "name": "Read Surveys", "slug": "surveys:read", "description": "Allows viewing surveys", "resource": "surveys", "action": "read", "createdAt": "2024-01-15T10:30:00Z" },
{ "id": "perm_03", "applicationId": "app_01ABC123", "name": "Update Surveys", "slug": "surveys:update", "description": "Allows editing surveys", "resource": "surveys", "action": "update", "createdAt": "2024-01-15T10:30:00Z" },
{ "id": "perm_04", "applicationId": "app_01ABC123", "name": "Delete Surveys", "slug": "surveys:delete", "description": "Allows deleting surveys", "resource": "surveys", "action": "delete", "createdAt": "2024-01-15T10:30:00Z" }
],
"meta": { "total": 4 },
"requestId": "req_abc123def456"
}/api/v1/applications/:id/permissionsCreate a new permission for an application. The slug should follow the resource:action format for consistency.
name(string)slug(string)description(string)- Permission description{
"name": "Create Survey",
"slug": "surveys:create",
"description": "Allows creating new surveys"
}{
"success": true,
"data": {
"id": "perm_01ABC123",
"applicationId": "app_01ABC123",
"name": "Create Survey",
"slug": "surveys:create",
"description": "Allows creating new surveys",
"resource": "surveys",
"action": "create",
"createdAt": "2024-01-15T10:30:00Z"
},
"requestId": "req_abc123def456"
}/api/v1/applications/:id/permissions/:permissionIdRetrieve a specific permission by ID.
id(uuid)permissionId(uuid)/api/v1/applications/:id/permissions/:permissionIdUpdate a permission. If the slug is changed, it must still follow the resource:action format.
name(string)- Updated nameslug(string)- Updated slug (resource:action format)description(string)- Updated description/api/v1/applications/:id/permissions/:permissionIdDelete a permission. This will also remove it from all roles that have it assigned.
/api/v1/organizations/:orgId/appsGet all applications that an organization has access to.
orgId(uuid){
"success": true,
"data": [
{
"id": "access_01ABC123",
"organizationId": "org_01ABC123",
"applicationId": "app_01ABC123",
"isEnabled": true,
"createdAt": "2024-01-15T10:30:00Z",
"application": {
"id": "app_01ABC123",
"name": "NPS Surveys",
"slug": "nps-surveys"
}
}
],
"meta": { "total": 1 },
"requestId": "req_abc123def456"
}/api/v1/organizations/:orgId/appsGrant or revoke an organization's access to an application.
applicationId(uuid)isEnabled(boolean){
"applicationId": "app_01ABC123",
"isEnabled": true
}/api/v1/organizations/:orgId/members/:userId/appsGet all application role assignments for a user within an organization.
orgId(uuid)userId(uuid){
"success": true,
"data": [
{
"id": "uar_01ABC123",
"userId": "user_01ABC123",
"organizationId": "org_01ABC123",
"applicationId": "app_01ABC123",
"appRoleId": "role_01ABC123",
"source": "manual",
"createdAt": "2024-01-15T10:30:00Z",
"role": {
"id": "role_01ABC123",
"name": "Survey Admin",
"slug": "survey-admin"
},
"application": {
"id": "app_01ABC123",
"name": "NPS Surveys",
"slug": "nps-surveys"
}
}
],
"meta": { "total": 1 },
"requestId": "req_abc123def456"
}/api/v1/organizations/:orgId/members/:userId/appsAssign a user to an application role within an organization. The organization must have access to the application.
applicationId(uuid)appRoleId(uuid){
"applicationId": "app_01ABC123",
"appRoleId": "role_01ABC123"
}/api/v1/organizations/:orgId/members/:userId/appsRemove a user's role assignment for an application within an organization.
orgId(uuid)userId(uuid)applicationId(uuid)/api/v1/organizations/:orgId/members/:userId/effective-permissionsCalculate and return all effective permissions for a user across all applications they have access to within an organization. This flattens role-based permissions into a simple list per application.
orgId(uuid)userId(uuid)applicationId(uuid)- Filter to a specific application{
"success": true,
"data": {
"userId": "user_01ABC123",
"organizationId": "org_01ABC123",
"applications": [
{
"applicationId": "app_01ABC123",
"applicationName": "NPS Surveys",
"applicationSlug": "nps-surveys",
"roles": [
{ "id": "role_01ABC123", "name": "Survey Admin", "slug": "survey-admin" }
],
"permissions": [
"surveys:create",
"surveys:read",
"surveys:update",
"surveys:delete"
]
}
]
},
"requestId": "req_abc123def456"
}You can automatically assign users to application roles based on their WorkOS directory group membership. Configure group mappings in the Admin UI or via the /api/v1/workos-group-mappings endpoint. When a user's group membership changes in your identity provider, their Golden Record roles are updated automatically.
The Golden Record integrates with WorkOS User Management to sync users who authenticate via SSO. This provides a unified view of all users across your applications.
workosUserId first, then falls back to emailWebhook events automatically sync user changes as they happen. Endpoint: POST /api/webhooks/workos
Admin-only endpoint to sync all WorkOS users at once. Endpoint: POST /api/workos/sync-users
workosUserId - WorkOS user IDemail - User email addressfirstName - First namelastName - Last nameavatarUrl - Profile picture URLNote: This integration uses WorkOS User Management APIs (for SSO users), not Directory Sync APIs. It syncs users who have logged in via SSO authentication.
The Golden Record uses Typesense for blazing-fast, typo-tolerant search. Currently supports organization search with autocomplete functionality.
Use the search query parameter on the organizations list endpoint:
GET /api/v1/organizations?search=acme&limit=10
// Returns organizations matching "acme" with typo tolerance
// Also matches "acne", "acma", etc.Admin Dashboard: The admin interface includes a search dropdown with real-time autocomplete powered by Typesense for quick organization lookup.
curl -X GET "https://your-domain.com/api/v1/organizations?limit=20" \
-H "Authorization: Bearer gr_live_your_api_key"curl -X POST "https://your-domain.com/api/v1/organizations" \
-H "Authorization: Bearer gr_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corporation",
"slug": "acme-corp",
"domain": "acme.com",
"metadata": {"industry": "technology"}
}'curl -X PUT "https://your-domain.com/api/v1/organizations/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer gr_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Corp (Updated)"
}'curl -X POST "https://your-domain.com/api/v1/keys/key_01ABC123?action=rotate" \
-H "Authorization: Bearer gr_live_your_api_key"