Skip to content

Reference

Organizations

An organization is your customer: the tenant that owns a connection, a directory, a role set and, in your own database, a pile of rows. Almost every other object in this API carries an organization_id, and almost every authorization mistake in a multi-tenant application comes from forgetting to filter on it.

The organization object

Create one organization per customer, and create it early. An organization with no connection is free and carries no behaviour; an organization created three months after the customer, once they finally ask for SSO, means backfilling a tenant id across every row you have already written.

FieldTypeDescription
idstringUnique identifier, prefixed org_. Use it as the tenant key in your own schema.
objectstringAlways "organization".
namestringDisplay name. Shown in the Admin Portal and in dashboard search; not unique.
domainsarrayEmail domains claimed by this organization. Each entry is an organization_domain with its own id and verification state.
allow_profiles_outside_organizationbooleanWhen false, a sign-in whose email domain is not claimed here is refused. Leave it false unless you knowingly support guest accounts.
external_idstring | nullYour own tenant identifier, if you already had one before the migration.
metadataobjectUp to 40 string key/value pairs of your own. Never used for routing or authorization.
created_attimestampRFC 3339.
updated_attimestampRFC 3339.

The domain object

Domains are how an email address becomes a tenant. Only a verified domain participates in routing, which is deliberate: if claiming a domain were enough, anyone who could create an organization could intercept sign-ins for a company they do not own.

FieldTypeDescription
idstringPrefixed org_domain_.
objectstringAlways "organization_domain".
domainstringThe bare domain, without a scheme or a leading @.
statestringpending, verified or failed. Only a verified domain routes a sign-in; a pending one is recorded but ignored.
verification_strategystringdns or manual. DNS verification asks for a TXT record; manual is set by an operator.
verification_tokenstring | nullThe TXT record value to publish, present while the state is pending.

Free mail domains are refused

Public domains such as the large consumer mail providers cannot be claimed. If a customer genuinely signs in with consumer addresses, route them with a workspace slug or an explicit organization picker instead of by domain.

Endpoints

Paths are relative to https://api.paycux.com. See the reference overview for authentication and environments.

MethodPathWhat it does
GET/organizationsList organizations, optionally filtered by claimed email domain.
POST/organizationsCreate an organization and claim the email domains it owns.
GET/organizations/:idRetrieve a single organization with its current domain states.
PUT/organizations/:idRename an organization, or replace the set of domains that route to it.
DELETE/organizations/:idDelete an organization along with its connections, directories and memberships.
GET/organizations/:id/membershipsList the people in an organization, with the role each one holds.

List organizations

The domains filter is the one that matters at sign-in time: it turns an email address into the tenant it belongs to, which is how you decide whether to show a password field or redirect to an identity provider.

GET /organizations
1curl "https://api.paycux.com/organizations?domains[]=foo-corp.example" \
2 -H "Authorization: Bearer sk_example_123456789"
response
1HTTP 200
2{
3 "object": "list",
4 "data": [
5 {
6 "object": "organization",
7 "id": "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
8 "name": "Foo Corp",
9 "allow_profiles_outside_organization": false,
10 "domains": [
11 {
12 "object": "organization_domain",
13 "id": "org_domain_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
14 "domain": "foo-corp.example",
15 "state": "verified"
16 }
17 ],
18 "external_id": null,
19 "metadata": {},
20 "created_at": "2026-04-11T09:24:02.881Z",
21 "updated_at": "2026-04-11T09:24:02.881Z"
22 }
23 ],
24 "list_metadata": { "before": null, "after": null }
25}

Create an organization

Create one organization per customer, at the moment the customer is created in your own system rather than when they first ask about SSO. An organization with no connection costs nothing and saves a migration later, because every other record hangs off this id.

POST /organizations
1curl -X POST https://api.paycux.com/organizations \
2 -H "Authorization: Bearer sk_example_123456789" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "name": "Foo Corp",
6 "domain_data": [{ "domain": "foo-corp.example", "state": "verified" }],
7 "external_id": "tenant-4812"
8 }'
response
1HTTP 201
2{
3 "object": "organization",
4 "id": "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
5 "name": "Foo Corp",
6 "allow_profiles_outside_organization": false,
7 "domains": [
8 {
9 "object": "organization_domain",
10 "id": "org_domain_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
11 "domain": "foo-corp.example",
12 "state": "verified"
13 }
14 ],
15 "external_id": "tenant-4812",
16 "metadata": {},
17 "created_at": "2026-04-11T09:24:02.881Z",
18 "updated_at": "2026-04-11T09:24:02.881Z"
19}

Retrieve an organization

Read this when you need the domain list rather than the name — for example to show an IT admin which domains are still pending verification, and what TXT record is outstanding.

GET /organizations/:id
1curl https://api.paycux.com/organizations/org_01HQ8ZK3M4N5P6R7S8T9V0W1X2 \
2 -H "Authorization: Bearer sk_example_123456789"
response
1HTTP 200
2{
3 "object": "organization",
4 "id": "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
5 "name": "Foo Corp",
6 "domains": [
7 {
8 "object": "organization_domain",
9 "id": "org_domain_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
10 "domain": "foo-corp.example",
11 "state": "verified",
12 "verification_strategy": "dns"
13 },
14 {
15 "object": "organization_domain",
16 "id": "org_domain_01HQ8ZK3M4N5P6R7S8T9V0W1X3",
17 "domain": "foo-corp-eu.example",
18 "state": "pending",
19 "verification_strategy": "dns",
20 "verification_token": "paycux-domain-verification=8f2c41ab"
21 }
22 ],
23 "created_at": "2026-04-11T09:24:02.881Z"
24}

Update an organization

domain_data replaces the whole list rather than merging into it, so send every domain the customer should keep. Removing a domain does not sign anybody out, but the next sign-in from that domain no longer resolves to this organization.

PUT /organizations/:id
1curl -X PUT https://api.paycux.com/organizations/org_01HQ8ZK3M4N5P6R7S8T9V0W1X2 \
2 -H "Authorization: Bearer sk_example_123456789" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "name": "Foo Corp International",
6 "domain_data": [
7 { "domain": "foo-corp.example", "state": "verified" },
8 { "domain": "foo-corp-eu.example", "state": "verified" }
9 ]
10 }'
response
1HTTP 200
2{
3 "object": "organization",
4 "id": "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
5 "name": "Foo Corp International",
6 "domains": [
7 { "domain": "foo-corp.example", "state": "verified" },
8 { "domain": "foo-corp-eu.example", "state": "verified" }
9 ],
10 "updated_at": "2026-07-02T13:18:44.502Z"
11}

Delete an organization

This is the widest destructive call in the API. Connections stop accepting assertions, directories stop syncing, and memberships disappear — while the users themselves survive, because they are account-level. Audit log events are retained.

DELETE /organizations/:id
1curl -X DELETE https://api.paycux.com/organizations/org_01HQ8ZK3M4N5P6R7S8T9V0W1X2 \
2 -H "Authorization: Bearer sk_example_123456789"
response
1HTTP 204
2(no body)

List memberships

Use this to render a customer's own user list. Filter by role slug when you need to answer the perennial support question of who the admins are before a plan change or an ownership transfer.

GET /organizations/:id/memberships
1curl "https://api.paycux.com/organizations/org_01HQ8ZK3M4N5P6R7S8T9V0W1X2/memberships?limit=1" \
2 -H "Authorization: Bearer sk_example_123456789"
response
1HTTP 200
2{
3 "object": "list",
4 "data": [
5 {
6 "object": "organization_membership",
7 "id": "om_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
8 "user_id": "user_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
9 "organization_id": "org_01HQ8ZK3M4N5P6R7S8T9V0W1X2",
10 "role": { "slug": "admin" },
11 "status": "active",
12 "created_at": "2026-04-11T09:23:04.918Z"
13 }
14 ],
15 "list_metadata": { "before": null, "after": "om_01HQ8ZK3M4N5P6R7S8T9V0W1X3" }
16}

Errors

Claiming a domain that another organization has already verified returns 422 with the conflicting organization id, rather than moving the domain silently. Deleting an organization that does not exist returns 404. Every error response carries a request id.

Pagination

Both list endpoints on this page are cursor paginated with limit, before and after. An environment with a few thousand organizations is ordinary, so paginate rather than reading the first page and hoping.

lookup.ts
1const page = await paycux.organizations.listOrganizations({
2 domains: ['foo-corp.example'],
3 limit: 100,
4});
5
6const organization = page.data[0];