Skip to Content

Brands API

Brands live under organizations. All routes require Authorization: Bearer <access_token> with the appropriate IdentityOrgPermission granted on the parent org.

:orgSlug/:brandSlug identifies the brand. Both slugs are stable.

Create a brand

POST /identity/organizations/:orgSlug/brands

Requires org.brands.edit.

{ slug: string // [a-z0-9-]{3,30}, unique within the org name: string // 1–80 chars, shown on consent screen primaryColor?: string // hex, e.g. '#5FD8CA' supportEmail?: string privacyPolicyUrl?: string termsUrl?: string logoFile?: string // FileTracker id providerOrder?: string[] // default order in sign-in modal }

Response (201): the created Brand.

oauthCallbackId is generated by Synq and returned in the response. It never changes after creation; the callback URL pattern it appears in (https://synq.id/oauth/<provider>/<oauthCallbackId>/callback) is what you register with each OAuth provider.

Read / list / update / delete

List brands

GET /identity/organizations/:orgSlug/brands — requires org.brands.view.

Get one

GET /identity/organizations/:orgSlug/brands/:brandSlug

interface Brand { id: string org: string slug: string name: string oauthCallbackId: string // immutable logoFile?: FileTracker primaryColor?: string supportEmail?: string privacyPolicyUrl?: string termsUrl?: string providerOrder: string[] createdAt: string updatedAt: string }

Update

PATCH /identity/organizations/:orgSlug/brands/:brandSlug — requires org.brands.edit.

{ name?: string primaryColor?: string | null supportEmail?: string | null privacyPolicyUrl?: string | null termsUrl?: string | null logoFile?: string | null providerOrder?: string[] }

slug and oauthCallbackId cannot be changed.

Delete

DELETE /identity/organizations/:orgSlug/brands/:brandSlug — requires org.brands.edit.

Soft-deletes. Apps and connections under the brand are disabled.

Apps under a brand

List apps

GET /identity/organizations/:orgSlug/brands/:brandSlug/apps

Requires org.apps.view.

Returns { items: App[], total: number }.

Create an app

POST /identity/organizations/:orgSlug/brands/:brandSlug/apps

Requires org.apps.edit.

{ name: string // 1–80 chars type: 'web' | 'spa' | 'native' | 'm2m' | 'agent' isConfidential?: boolean // default: true for web/m2m, false otherwise redirectUris: string[] // required for browser flows allowedGrantTypes: GrantType[] allowedScopes: string[] description?: string } type GrantType = | 'authorization_code' | 'refresh_token' | 'client_credentials' | 'urn:ietf:params:oauth:grant-type:device_code'

Response (201): the new App, including clientSecret for confidential clients. clientSecret is returned only on create and on /rotate-secret.

interface App { id: string brand: string org: string clientId: string clientSecret?: string // only on create + rotate name: string type: 'web' | 'spa' | 'native' | 'm2m' | 'agent' isConfidential: boolean redirectUris: string[] allowedGrantTypes: GrantType[] allowedScopes: string[] enabled: boolean description?: string | null createdAt: string updatedAt: string }

Get an app

GET /identity/organizations/:orgSlug/brands/:brandSlug/apps/:clientId

Requires org.apps.view. No clientSecret in the response.

Update an app

PATCH /identity/organizations/:orgSlug/brands/:brandSlug/apps/:clientId

Requires org.apps.edit.

{ name?: string description?: string | null redirectUris?: string[] allowedGrantTypes?: GrantType[] allowedScopes?: string[] enabled?: boolean }

Rotate the client_secret

POST /identity/organizations/:orgSlug/brands/:brandSlug/apps/:clientId/rotate-secret

Requires org.apps.edit. Issues a new clientSecret and invalidates the previous one. The previous secret stops working immediately.

Response: { clientSecret: string } — returned once.

Delete an app

DELETE /identity/organizations/:orgSlug/brands/:brandSlug/apps/:clientId

Soft-disables. Outstanding access tokens issued by this App stop validating on their next request to the issuer.

Connections (BYO credentials)

Upsert a connection

PUT /identity/organizations/:orgSlug/brands/:brandSlug/connections

Requires org.connections.edit.

{ type: 'google' | 'apple' | 'microsoft' | 'discord' | 'facebook' | 'x' | 'telegram' | 'matrica' enabled?: boolean // default: true config: ProviderConfig // shape varies per provider }

ProviderConfig shapes — see BYO credentials for full details.

Response: connection metadata (no plaintext config — encrypted at rest).

{ id: string brand: string type: string enabled: boolean callbackUrl: string // the URL to register with the provider createdAt: string updatedAt: string }

Toggle enabled

PATCH /identity/organizations/:orgSlug/brands/:brandSlug/connections/:type/enabled

{ enabled: boolean }

Delete a connection

DELETE /identity/organizations/:orgSlug/brands/:brandSlug/connections/:type

Reverts the brand to Synq default credentials for that provider (if any).

List connections

GET /identity/organizations/:orgSlug/brands/:brandSlug/connections

Requires org.connections.view. Returns metadata only — never the encrypted config or plaintext fields.

Scopes vocabulary

List

GET /identity/organizations/:orgSlug/brands/:brandSlug/scopes

Returns the brand-specific scope vocabulary. Does not include the OIDC standard scopes (those are universal).

{ items: Array<{ name: string // e.g. 'wallets:read' description: string // shown on consent screen createdAt: string }> }

Add

POST /identity/organizations/:orgSlug/brands/:brandSlug/scopes

Requires org.scopes.edit.

{ name: string; description: string }

name must be [a-z0-9:_-]{2,80}. Convention: resource:action.

Update description

PATCH /identity/organizations/:orgSlug/brands/:brandSlug/scopes/:name

{ description: string }

The name itself cannot be edited (it appears in token claims).

Delete

DELETE /identity/organizations/:orgSlug/brands/:brandSlug/scopes/:name

Returns conflict if any App under the brand currently lists this scope in its allowedScopes. Remove from those Apps first.

Errors

Standard REST envelope. See Errors.

  • forbidden — missing org.brands.* / org.apps.* / org.connections.* / org.scopes.* permission.
  • conflict — slug, scope name, or callback URL collision.
  • validation_failed — body validation.
  • not_found — brand or app slug does not exist (or you cannot see it).
Last updated on