Company sign-in
Company sign-in: the organization's own identity provider (Okta, Microsoft Entra ID or any OpenID Connect provider), the e-mail domains whose people sign in there, and the sign-in itself, which the passkey step follows.
| Operation | Summary | Scope | Idempotency-Key |
|---|---|---|---|
POST /v1/auth/company/finish |
Finish a company sign-in | ||
POST /v1/auth/company/start |
Start a company sign-in | ||
GET /v1/company-sign-in |
Get the company sign-in | admin |
|
PUT /v1/company-sign-in |
Set up the company sign-in | admin |
|
DELETE /v1/company-sign-in |
Remove the company sign-in | admin |
POST /v1/auth/company/finish
Finish a company sign-in
Exchange the code the identity provider sent back for its ID token, check it (signature,
issuer, audience, expiry, nonce, a verified address of one of the connection's domains) and
sign the person in to that organization only. The answer is that of POST /v1/auth/google:
passkey (then POST /v1/auth/passkey). Refused with 403 for a state that is too old or not
this server's (company_sign_in_state), a token that does not hold
(company_sign_in_refused) or an address that does not fit (company_sign_in_email).
| Required scope | none (public) |
| Idempotency-Key | not used |
| Operation id | finish_v1_auth_company_finish_post |
Request body
application/json (required): CompanyFinish
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes | The code the provider sent the browser back with. (min length 1; max length 4000) |
state |
string | yes | The state the provider sent the browser back with. (min length 1; max length 4000) |
Example request
curl -X POST "$ERP_URL/v1/auth/company/finish" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "c0d3-from-the-provider",
"state": "eyJjIjoi.c2lnbmF0dXJl"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | LoginResult |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"status": "choose_organization",
"organizations": [
{
"org_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"org_name": "string",
"test_organization": false,
"user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"copy_of": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
],
"expires_at": "2026-09-21T09:30:00Z",
"passkey": {
"setup": true,
"options": {}
}
}
POST /v1/auth/company/start
Start a company sign-in
The identity provider's sign-in page for an address whose organization signs in with its
own provider. It needs no credential and serves the web UI's sign-in page. The browser goes to
url and comes back to the redirect URI with code and state for
POST /v1/auth/company/finish. Refused with 403 (company_sign_in_none) when no organization
that invited the address covers its domain.
| Required scope | none (public) |
| Idempotency-Key | not used |
| Operation id | start_v1_auth_company_start_post |
Request body
application/json (required): CompanyStart
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | The person's address: its domain names their organization's provider. (min length 3; max length 320) |
org_id |
string (uuid) or null | The organization to sign in to, when the address may sign in to several through their company's provider. |
Example request
curl -X POST "$ERP_URL/v1/auth/company/start" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "lena.meier@acme.com"
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | CompanyStartOut |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"url": "string"
}
GET /v1/company-sign-in
Get the company sign-in
Whether company sign-in can be set up here, the redirect URI to register at the identity provider, and the organization's connection (its secret is never shown).
| Required scope | admin (admin implies every scope) |
| Operation id | get_company_sign_in_v1_company_sign_in_get |
Example request
curl "$ERP_URL/v1/company-sign-in" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | CompanySignInSetup |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"available": true,
"redirect_uri": "string",
"connection": {
"provider": "entra",
"issuer": "https://acme.okta.com",
"client_id": "string",
"has_secret": true,
"domains": [
"string"
],
"required": true,
"last_used_at": "2026-09-21T09:30:00Z",
"updated_at": "2026-09-21T09:30:00Z"
}
}
PUT /v1/company-sign-in
Set up the company sign-in
Connect the organization's identity provider, or change the connection: the provider, its
issuer (an Okta domain, an Entra tenant or an issuer URL), the app's client id and secret, the
e-mail domains whose people sign in through it and whether they may sign in only through it.
The provider's discovery document is read first: refused with 502
(company_sign_in_unreachable) when it does not answer for the issuer. Making it the only way
in (required) is refused (company_sign_in_untried, 409) until somebody signed in through
it. Every sign-in through it still ends with the person's passkey.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | put_company_sign_in_v1_company_sign_in_put |
Request body
application/json (required): ConnectionInput
| Field | Type | Required | Description |
|---|---|---|---|
provider |
"entra" | "oidc" | "okta" |
yes | The identity provider: okta (Okta), entra (Microsoft Entra ID, the former Azure AD) or oidc (any other OpenID Connect provider). |
issuer |
string | yes | Where the provider is: for Okta the Okta domain (acme.okta.com) or an authorization server's issuer; for Microsoft Entra ID the tenant id or the tenant's primary domain (acme.onmicrosoft.com); otherwise the issuer URL (https://...). (min length 3; max length 300) |
client_id |
string | yes | The client id (Entra: application id) of the app registered at the provider for this sign-in. (min length 1; max length 200) |
client_secret |
string or null | The app's client secret. Left out on a change: the one kept stays; an empty string removes it (an app without a secret, which signs in with PKCE alone). (max length 500) | |
domains |
array of string or null | The e-mail domains whose people sign in through it. Left out: the domain of the administrator's own address. (max items 20) | |
required |
boolean | Whether those people may sign in only through it: Google and a password no longer open the organization for them. (default false) |
Example request
curl -X PUT "$ERP_URL/v1/company-sign-in" \
-H "Authorization: Bearer $ERP_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_id": "0oa1b2c3d4e5f6g7h8i9",
"client_secret": "s3cr3t-of-the-okta-app",
"domains": [
"acme.com"
],
"issuer": "acme.okta.com",
"provider": "okta",
"required": false
}'
Responses
| Status | Meaning | Body |
|---|---|---|
| 200 | OK | ConnectionOut |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Example response (shape)
{
"provider": "entra",
"issuer": "https://acme.okta.com",
"client_id": "string",
"has_secret": true,
"domains": [
"string"
],
"required": true,
"last_used_at": "2026-09-21T09:30:00Z",
"updated_at": "2026-09-21T09:30:00Z"
}
DELETE /v1/company-sign-in
Remove the company sign-in
Remove the connection and its sealed secret: its people sign in with Google or a password again.
| Required scope | admin (admin implies every scope) |
| Idempotency-Key | not used |
| Operation id | delete_company_sign_in_v1_company_sign_in_delete |
Example request
curl -X DELETE "$ERP_URL/v1/company-sign-in" \
-H "Authorization: Bearer $ERP_KEY"
Responses
| Status | Meaning | Body |
|---|---|---|
| 204 | No content | |
| 401 | Missing or invalid API key (unauthorized). |
ErrorEnvelope |
| 403 | The caller lacks the required scope (missing_right, with scope), the key belongs to no person where a person is needed (forbidden), or the approval policy denies the action (policy_denied, with action and rule_id). |
ErrorEnvelope |
| 409 | The request conflicts with the data: an Idempotency-Key reused with a different request, a closed period, or an integrity rule of the database (conflict, integrity_violation). |
ErrorEnvelope |
| 422 | Validation failed: the error envelope (validation_failed). For a malformed body or parameter its message is in sentences ("Line 2: the quantity must be above zero") and fields lists each error with pydantic's loc, type and msg beside its sentence (message) and its parts, to word it in another language: items (name and number from 1: line 2), field (its name as sent) and the rule it breaks with its limit, count and choices (none for a validator's own sentence); FastAPI's own detail list stays beside the envelope. |
HTTPValidationError |
Schemas
CompanyFinish
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | yes | The code the provider sent the browser back with. (min length 1; max length 4000) |
state |
string | yes | The state the provider sent the browser back with. (min length 1; max length 4000) |
CompanySignInSetup
| Field | Type | Required | Description |
|---|---|---|---|
available |
boolean | yes | Whether company sign-in can be set up on this server (it needs the key that seals client secrets and the address people reach the app at). |
redirect_uri |
string or null | yes | The sign-in redirect URI to register in the app at the identity provider. |
connection |
ConnectionOut or null |
yes | The connection; null: none yet. |
CompanyStart
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | The person's address: its domain names their organization's provider. (min length 3; max length 320) |
org_id |
string (uuid) or null | The organization to sign in to, when the address may sign in to several through their company's provider. |
CompanyStartOut
| Field | Type | Required | Description |
|---|---|---|---|
url |
string | yes | The identity provider's sign-in page: the browser goes there, and comes back to the redirect URI with code and state. |
ConnectionInput
| Field | Type | Required | Description |
|---|---|---|---|
provider |
"entra" | "oidc" | "okta" |
yes | The identity provider: okta (Okta), entra (Microsoft Entra ID, the former Azure AD) or oidc (any other OpenID Connect provider). |
issuer |
string | yes | Where the provider is: for Okta the Okta domain (acme.okta.com) or an authorization server's issuer; for Microsoft Entra ID the tenant id or the tenant's primary domain (acme.onmicrosoft.com); otherwise the issuer URL (https://...). (min length 3; max length 300) |
client_id |
string | yes | The client id (Entra: application id) of the app registered at the provider for this sign-in. (min length 1; max length 200) |
client_secret |
string or null | The app's client secret. Left out on a change: the one kept stays; an empty string removes it (an app without a secret, which signs in with PKCE alone). (max length 500) | |
domains |
array of string or null | The e-mail domains whose people sign in through it. Left out: the domain of the administrator's own address. (max items 20) | |
required |
boolean | Whether those people may sign in only through it: Google and a password no longer open the organization for them. (default false) |
ConnectionOut
| Field | Type | Required | Description |
|---|---|---|---|
provider |
"entra" | "oidc" | "okta" |
yes | The identity provider: okta (Okta), entra (Microsoft Entra ID, the former Azure AD) or oidc (any other OpenID Connect provider). |
issuer |
string | yes | The provider's issuer, which its ID tokens name and its discovery document is read from. |
client_id |
string | yes | The client id of the app registered at the provider. |
has_secret |
boolean | yes | Whether the app's client secret is kept (sealed; it is never shown). |
domains |
array of string | yes | The e-mail domains whose people sign in through it. |
required |
boolean | yes | Whether those people may sign in only through it, not with Google or a password. |
last_used_at |
string (date-time) or null | yes | The last sign-in through it (UTC). |
updated_at |
string (date-time) | yes | When it was last changed (UTC). |