Docs

Create or revise an identity provider connection

POST
/idps

Creates or revises a connection document. If the slug does not already exist, a new connection is created. If a connection with that slug already exists, a revision is created.

A revision gets a new revision_id and does not modify the connection id, so identity links that reference the connection keep resolving while releases and auth attempts stay pinned to the revision they captured.

Identity fields are fixed for the life of the connection and a revision that changes one is rejected: protocol, subject_claim, and issuer, the field that names the authority. Their values decide which provider account a stored subject belongs to, so changing one would silently repoint existing identities at a different provider.

subject_claim is optional, and sub is used as the default value. If a revision updates subject_claim to a different value than the one set during creation, the request is rejected.

OIDC is the only protocol.

The document is validated against the idp-connection.json schema before anything is stored. The rules the engine applies when it starts a sign-in then run on the document, so a connection that could never start one is refused here instead with idp.endpoints_partial: the OIDC block sets an endpoint without the others. It either sets no endpoint, and discovery fills them, or sets authorization_endpoint, token_endpoint, jwks_uri, and userinfo_endpoint. userinfo_endpoint may be left out when id_token_mapping is on, but setting it alone still counts as setting an endpoint. The details list the missing endpoints.

A refused document stores nothing, and on a revise the current revision stays unchanged.

Authorization

oauth2 idp.write
headerAuthorizationBearer <token>

Scope: idp.write

Query Parameters

project_id*string

The unique identifier of the project

Match^[a-zA-Z0-9_-]+$

Request Body

application/json
  1. body

Creates or revises an identity provider connection document.

idp*

The connection document, in the shape of a .zitadel/idps/<slug>.json file. If its slug does not already exist, a new connection is created. If a connection with that slug already exists, this becomes a new revision of it. The document is validated against the idp-connection.json schema, and returned as the definition in the response.

Response Body

The IdP connection document after revision.

application/json
  1. response

An identity provider connection.

id*string

A unique connection id, generated when the connection is created, and shared by every revision. This id is referenced by the identity links.

revision_id*string

The id of the revision this row holds, pinned by auth attempts and releases. GET /idps/{id} and the query return the newest one; GET /idps/revisions/{revision_id} returns the requested revision.

slug*string

The identifier by which user schemas and flow definitions reference the connection. Fixed for the life of the connection.

created_at*string

When the connection was created.

Formatdate-time
updated_at*string

When this revision was created. On the newest revision, that is when the connection was last revised.

Formatdate-time
definition*

The stored connection document.

import { createZitadelClient } from "@zitadel/api/client";const client = createZitadelClient({  baseUrl: process.env.ZITADEL_URL ?? "http://localhost:8080",  token: process.env.ZITADEL_PROJECT_SECRET!, // server-side only});const idp = await client.createIdp(  {    idp: {      slug: "google",      protocol: "oidc",      template: "google",      display_name: "Google",      subject_claim: "sub",      claim_mapping: {        email: "email",        givenName: "given_name",        familyName: "family_name",      },      verified_claims: { email: "email_verified" },      provisioning: { creation: "auto" },      oidc: {        issuer: "https://accounts.google.com",        client_id: "${{ GOOGLE_CLIENT_ID }}",        client_secret: "${{ GOOGLE_CLIENT_SECRET }}",        scopes: ["openid", "profile", "email"],        static_authorize_parameters: { prompt: "select_account" },      },    },  },  { project_id: "proj_01KWH1P4MYS7F0N9WD3P2E4YM5" },);
Example Response

The scope list grew. id is the one allocated when the connection was created, revision_id is new, and updated_at has moved past created_at.

{  "id": "idp_01KWH3B72K7M7F0N9WD3P2E4YM",  "revision_id": "idprev_01KWHF2XY6RN7F0N9WD3P2E4YM",  "slug": "google",  "created_at": "2026-07-14T09:11:00Z",  "updated_at": "2026-07-16T14:20:00Z",  "definition": {    "slug": "google",    "protocol": "oidc",    "template": "google",    "display_name": "Google",    "subject_claim": "sub",    "claim_mapping": {      "email": "email",      "givenName": "given_name",      "familyName": "family_name"    },    "verified_claims": {      "email": "email_verified"    },    "provisioning": {      "creation": "auto"    },    "oidc": {      "issuer": "https://accounts.google.com",      "id_token_mapping": false,      "client_id": "${{ GOOGLE_CLIENT_ID }}",      "client_secret": "${{ GOOGLE_CLIENT_SECRET }}",      "token_endpoint_auth_method": "client_secret_basic",      "scopes": [        "openid",        "profile",        "email",        "https://www.googleapis.com/auth/user.phonenumbers.read"      ],      "pkce_enabled": true,      "static_authorize_parameters": {        "prompt": "select_account"      }    }  }}

Was this helpful?