# Register an identity provider (OIDC or SAML)

POST

/v1/auth/providers

```sh
curl --request POST \
  --url https://example.com/v1/auth/providers \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{ "providerId": "example", "displayName": "example", "domains": [ "example" ], "join": "invite", "signIn": { "redirectUri": "https://example.com" }, "metadata": {}, "kind": "oidc", "issuer": "https://example.com", "clientId": "example", "clientSecretRef": "example", "scopes": [ "example" ], "authorizationEndpoint": "https://example.com", "tokenEndpoint": "https://example.com", "userinfoEndpoint": "https://example.com", "jwksEndpoint": "https://example.com", "claimMapping": { "userId": "example", "email": "example", "displayName": "example", "scopes": { "claim": "example", "delimiter": "example" }, "metadata": [ "example" ] } }'
```

Unique per tenant on `providerId`: re-registering a known provider returns `409 identity-provider-already-registered`; change it with `PATCH /v1/auth/providers/{providerId}`, which keeps its sign-in URLs. Secrets are given by reference (`clientSecretRef`, `spSigningKeyRef`…); a `clientSecret` (or a raw key) is refused with `400 invalid-provider-config`, as is `allowedRedirectUris`, which nothing would enforce (sign-in runs in the deployment, at its own callback URL). The deployment may check the configuration (OIDC discovery, SAML metadata): `422 identity-provider-invalid` says what failed. The answer carries the stored provider when the deployment returns it, with `signIn`: what to give the identity provider.

## Authorizations

- **[bearerAuth](https://docs.kindgi.com/v0.1.10/reference/api.md#bearerauth)**

## Parameters

### Header Parameters

**Idempotency-Key**

string

\>= 1 characters

Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the tenant is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1). A retry sent while the first request still runs is answered `409 idempotency-key-in-flight` with `Retry-After`, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request’s answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn’t kept: a retry gets `409 idempotency-key-replay-withheld`, with the first request’s status and when it succeeded (from 0.1.5).

## Request Bodyrequired

application/json

One of: discriminator: kind

- **object**

  An OpenID Connect identity provider people sign in with. The endpoints come from the issuer’s discovery document when absent, and are returned once the deployment has them.

  object

  **providerId**

  required

  string

  \>= 1 characters

  **displayName**

  The name a sign-in page shows (“Sign in with …”). Default: `providerId`.

  string

  \>= 1 characters

  **domains**

  The email domains whose people sign in with this provider (lowercase, e.g. `acme.com`): how an email-first sign-in page finds it.

  Array\<string>

  **join**

  Who may sign in the first time: `invite` (default) only people a tenant admin added; `domain` also anyone from one of `domains`, once the deployment has verified them.

  string

  Allowed values: invite domain

  **signIn**

  One of:

  - **object**

    object

    **redirectUri**

    required

    string format: uri

  - **object**

    object

    **spEntityId**

    required

    string

    \>= 1 characters

    **acsUrl**

    required

    string format: uri

    **spMetadataUrl**

    required

    string format: uri

  **metadata**

  object

  **_key_**

  additional properties

  any

  **kind**

  required

  string

  Allowed value: oidc

  **issuer**

  required

  string format: uri

  **clientId**

  required

  string

  \>= 1 characters

  **clientSecretRef**

  required

  Opaque reference resolved server-side. Never a plaintext secret.

  string

  \>= 1 characters

  **scopes**

  Default `openid email profile`.

  Array\<string>

  **authorizationEndpoint**

  string format: uri

  **tokenEndpoint**

  string format: uri

  **userinfoEndpoint**

  string format: uri

  **jwksEndpoint**

  string format: uri

  **claimMapping**

  Per-provider claim mapping. All fields optional; closer to OIDC defaults (sub/email/name) → fewer overrides needed.

  object

  **userId**

  string

  \>= 1 characters

  **email**

  string

  \>= 1 characters

  **displayName**

  string

  \>= 1 characters

  **scopes**

  Claim path + optional delimiter for extracting Kindgi OAuth scopes from provider claims. When absent, scopes come from the token response `scope` field instead.

  object

  **claim**

  required

  string

  \>= 1 characters

  **delimiter**

  string

  **metadata**

  Array\<string>

- **object**

  A SAML 2.0 identity provider people sign in with: its metadata XML, or its entity ID, single sign-on URL and signing certificates. Keys are given as references, never as keys.

  object

  **providerId**

  required

  string

  \>= 1 characters

  **displayName**

  The name a sign-in page shows (“Sign in with …”). Default: `providerId`.

  string

  \>= 1 characters

  **domains**

  The email domains whose people sign in with this provider (lowercase, e.g. `acme.com`): how an email-first sign-in page finds it.

  Array\<string>

  **join**

  Who may sign in the first time: `invite` (default) only people a tenant admin added; `domain` also anyone from one of `domains`, once the deployment has verified them.

  string

  Allowed values: invite domain

  **signIn**

  One of:

  - **object**

    object

    **redirectUri**

    required

    string format: uri

  - **object**

    object

    **spEntityId**

    required

    string

    \>= 1 characters

    **acsUrl**

    required

    string format: uri

    **spMetadataUrl**

    required

    string format: uri

  **metadata**

  object

  **_key_**

  additional properties

  any

  **kind**

  required

  string

  Allowed value: saml

  **idpMetadataXml**

  string

  \>= 1 characters

  **idpEntityId**

  string

  \>= 1 characters

  **idpSsoUrl**

  The IdP’s single sign-on URL (HTTP-Redirect binding).

  string format: uri

  **idpCertificates**

  The IdP’s signing certificates (PEM); several during a rollover.

  Array\<string>

  **spSigningKeyRef**

  Opaque reference to the service provider’s signing key, for IdPs that require signed AuthnRequests. Never a plaintext key.

  string

  \>= 1 characters

  **spDecryptionKeyRef**

  Opaque reference to the key that decrypts encrypted assertions. Never a plaintext key.

  string

  \>= 1 characters

  **wantAssertionsSigned**

  Require signed assertions. Default `true`.

  boolean

  **attributeMapping**

  Assertion attribute names. Defaults: `userId` = the NameID, `email` = `email`.

  object

  **userId**

  string

  \>= 1 characters

  **email**

  string

  \>= 1 characters

  **displayName**

  string

  \>= 1 characters

## Responses

### 201

Provider registered.

application/json

object

**providerId**

required

string

\>= 1 characters

**provider**

One of: discriminator: kind

- **object**

  An OpenID Connect identity provider people sign in with. The endpoints come from the issuer’s discovery document when absent, and are returned once the deployment has them.

  object

  **providerId**

  required

  string

  \>= 1 characters

  **displayName**

  The name a sign-in page shows (“Sign in with …”). Default: `providerId`.

  string

  \>= 1 characters

  **domains**

  The email domains whose people sign in with this provider (lowercase, e.g. `acme.com`): how an email-first sign-in page finds it.

  Array\<string>

  **join**

  Who may sign in the first time: `invite` (default) only people a tenant admin added; `domain` also anyone from one of `domains`, once the deployment has verified them.

  string

  Allowed values: invite domain

  **signIn**

  One of:

  - **object**

    object

    **redirectUri**

    required

    string format: uri

  - **object**

    object

    **spEntityId**

    required

    string

    \>= 1 characters

    **acsUrl**

    required

    string format: uri

    **spMetadataUrl**

    required

    string format: uri

  **metadata**

  object

  **_key_**

  additional properties

  any

  **kind**

  required

  string

  Allowed value: oidc

  **issuer**

  required

  string format: uri

  **clientId**

  required

  string

  \>= 1 characters

  **clientSecretRef**

  required

  Opaque reference resolved server-side. Never a plaintext secret.

  string

  \>= 1 characters

  **scopes**

  Default `openid email profile`.

  Array\<string>

  **authorizationEndpoint**

  string format: uri

  **tokenEndpoint**

  string format: uri

  **userinfoEndpoint**

  string format: uri

  **jwksEndpoint**

  string format: uri

  **claimMapping**

  Per-provider claim mapping. All fields optional; closer to OIDC defaults (sub/email/name) → fewer overrides needed.

  object

  **userId**

  string

  \>= 1 characters

  **email**

  string

  \>= 1 characters

  **displayName**

  string

  \>= 1 characters

  **scopes**

  Claim path + optional delimiter for extracting Kindgi OAuth scopes from provider claims. When absent, scopes come from the token response `scope` field instead.

  object

  **claim**

  required

  string

  \>= 1 characters

  **delimiter**

  string

  **metadata**

  Array\<string>

- **object**

  A SAML 2.0 identity provider people sign in with: its metadata XML, or its entity ID, single sign-on URL and signing certificates. Keys are given as references, never as keys.

  object

  **providerId**

  required

  string

  \>= 1 characters

  **displayName**

  The name a sign-in page shows (“Sign in with …”). Default: `providerId`.

  string

  \>= 1 characters

  **domains**

  The email domains whose people sign in with this provider (lowercase, e.g. `acme.com`): how an email-first sign-in page finds it.

  Array\<string>

  **join**

  Who may sign in the first time: `invite` (default) only people a tenant admin added; `domain` also anyone from one of `domains`, once the deployment has verified them.

  string

  Allowed values: invite domain

  **signIn**

  One of:

  - **object**

    object

    **redirectUri**

    required

    string format: uri

  - **object**

    object

    **spEntityId**

    required

    string

    \>= 1 characters

    **acsUrl**

    required

    string format: uri

    **spMetadataUrl**

    required

    string format: uri

  **metadata**

  object

  **_key_**

  additional properties

  any

  **kind**

  required

  string

  Allowed value: saml

  **idpMetadataXml**

  string

  \>= 1 characters

  **idpEntityId**

  string

  \>= 1 characters

  **idpSsoUrl**

  The IdP’s single sign-on URL (HTTP-Redirect binding).

  string format: uri

  **idpCertificates**

  The IdP’s signing certificates (PEM); several during a rollover.

  Array\<string>

  **spSigningKeyRef**

  Opaque reference to the service provider’s signing key, for IdPs that require signed AuthnRequests. Never a plaintext key.

  string

  \>= 1 characters

  **spDecryptionKeyRef**

  Opaque reference to the key that decrypts encrypted assertions. Never a plaintext key.

  string

  \>= 1 characters

  **wantAssertionsSigned**

  Require signed assertions. Default `true`.

  boolean

  **attributeMapping**

  Assertion attribute names. Defaults: `userId` = the NameID, `email` = `email`.

  object

  **userId**

  string

  \>= 1 characters

  **email**

  string

  \>= 1 characters

  **displayName**

  string

  \>= 1 characters

##### Example

```json
{
  "provider": {
    "join": "invite",
    "kind": "oidc"
  }
}
```

### 400

Malformed request body.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 403

Not a tenant admin (`permission-denied`), or this deployment’s operator manages sign-in (`identity-providers-operator-managed`, `KINDGI_AUTH_TENANT_PROVIDERS=off`): only the deployment’s own token can change its providers.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 409

Idempotency-Key was reused with a different body, or resource-state conflict (`identity-provider-already-registered`: a provider with this id is registered). Or this deployment has no secrets store (`identity-providers-need-secrets-store`, `KINDGI_SECRETS_BACKEND`): a provider’s client secret and keys are references into one, so no provider is added or changed until it has one.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 413

The request body is over the API’s limit (`request-too-large`; `details.maxBytes` says how much).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 422

The deployment could not use the configuration, or `kind` is `oauth2`, a plain OAuth 2.0 provider, which sign-in does not use (`identity-provider-invalid`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 500

Server error (unmapped domain code or framework crash).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [`GET /v1/auth/providers`: List identity providers configured for the tenant](https://docs.kindgi.com/v0.1.10/reference/api/operations/authproviderslist.md)
- [`GET /v1/auth/providers/{providerId}`: Get one identity provider](https://docs.kindgi.com/v0.1.10/reference/api/operations/authprovidersget.md)
- [`PATCH /v1/auth/providers/{providerId}`: Change an identity provider, keeping its sign-in URLs](https://docs.kindgi.com/v0.1.10/reference/api/operations/authprovidersupdate.md)
- [`GET /v1/auth/providers/{providerId}/sign-in`: What to give the identity provider, before or after registering](https://docs.kindgi.com/v0.1.10/reference/api/operations/authproviderssignin.md)
- [Error code `identity-provider-already-registered`](https://docs.kindgi.com/v0.1.10/reference/errors.md#identity-provider-already-registered)
- [Error code `identity-provider-invalid`](https://docs.kindgi.com/v0.1.10/reference/errors.md#identity-provider-invalid)
- [Error code `identity-providers-need-secrets-store`](https://docs.kindgi.com/v0.1.10/reference/errors.md#identity-providers-need-secrets-store)
- [Error code `identity-providers-operator-managed`](https://docs.kindgi.com/v0.1.10/reference/errors.md#identity-providers-operator-managed)
- [Error code `permission-denied`](https://docs.kindgi.com/v0.1.10/reference/errors.md#permission-denied)
- [Error code `request-too-large`](https://docs.kindgi.com/v0.1.10/reference/errors.md#request-too-large)
- [Every error code](https://docs.kindgi.com/v0.1.10/reference/errors.md)
