From 0179ac26f3c46564cee06c0d9b53676aafc413c4 Mon Sep 17 00:00:00 2001 From: Flavius Mardare Date: Tue, 22 Sep 2026 12:09:13 +0200 Subject: [PATCH 1/4] identity api client --- .changeset/identity-client.md | 5 + README.md | 1 + clients/identity-client/.gitignore | 2 + clients/identity-client/CHANGELOG.md | 1 + clients/identity-client/LICENSE | 21 + clients/identity-client/README.md | 34 + clients/identity-client/package.json | 73 + clients/identity-client/src/client.test.ts | 25 + clients/identity-client/src/client.ts | 30 + clients/identity-client/src/definition.ts | 5 + clients/identity-client/src/index.ts | 5 + .../identity-client/src/openapi-runtime.json | 322 ++++ clients/identity-client/src/openapi.d.ts | 1154 ++++++++++++ clients/identity-client/src/openapi.json | 1546 +++++++++++++++++ clients/identity-client/tsconfig.json | 23 + clients/identity-client/webpack.config.js | 31 + packages/cli/README.md | 2 + packages/cli/definitions/identity.json | 1546 +++++++++++++++++ packages/cli/docs/identity.md | 1277 ++++++++++++++ packages/cli/docs/index.md | 3 +- packages/cli/src/commands/apis/identity.ts | 48 + packages/cli/src/generated/api-list.ts | 34 +- packages/cli/src/index.ts | 3 +- packages/epilot-sdk-v2/README.md | 1 + packages/epilot-sdk-v2/docs/identity.md | 1279 ++++++++++++++ packages/epilot-sdk-v2/package.json | 9 + packages/epilot-sdk-v2/src/apis/_registry.ts | 5 + packages/epilot-sdk-v2/src/apis/identity.ts | 46 + packages/epilot-sdk-v2/src/client-map.ts | 2 + .../src/definitions/identity-runtime.json | 1 + packages/epilot-sdk-v2/src/docs/_index.json | 1 + packages/epilot-sdk-v2/src/docs/identity.json | 1 + .../epilot-sdk-v2/src/types/identity.d.ts | 1155 ++++++++++++ pnpm-lock.yaml | 52 + publish-clients.sh | 1 + 35 files changed, 8736 insertions(+), 8 deletions(-) create mode 100644 .changeset/identity-client.md create mode 100644 clients/identity-client/.gitignore create mode 100644 clients/identity-client/CHANGELOG.md create mode 100644 clients/identity-client/LICENSE create mode 100644 clients/identity-client/README.md create mode 100644 clients/identity-client/package.json create mode 100644 clients/identity-client/src/client.test.ts create mode 100644 clients/identity-client/src/client.ts create mode 100644 clients/identity-client/src/definition.ts create mode 100644 clients/identity-client/src/index.ts create mode 100644 clients/identity-client/src/openapi-runtime.json create mode 100644 clients/identity-client/src/openapi.d.ts create mode 100644 clients/identity-client/src/openapi.json create mode 100644 clients/identity-client/tsconfig.json create mode 100644 clients/identity-client/webpack.config.js create mode 100644 packages/cli/definitions/identity.json create mode 100644 packages/cli/docs/identity.md create mode 100644 packages/cli/src/commands/apis/identity.ts create mode 100644 packages/epilot-sdk-v2/docs/identity.md create mode 100644 packages/epilot-sdk-v2/src/apis/identity.ts create mode 100644 packages/epilot-sdk-v2/src/definitions/identity-runtime.json create mode 100644 packages/epilot-sdk-v2/src/docs/identity.json create mode 100644 packages/epilot-sdk-v2/src/types/identity.d.ts diff --git a/.changeset/identity-client.md b/.changeset/identity-client.md new file mode 100644 index 000000000..ddc03c7bf --- /dev/null +++ b/.changeset/identity-client.md @@ -0,0 +1,5 @@ +--- +"@epilot/identity-client": minor +--- + +Add `@epilot/identity-client` for the epilot Identity API ("Sign in with epilot"): operator client registry and 360 session endpoints. diff --git a/README.md b/README.md index 4b27bd8b5..a96174cca 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Full API documentation: [https://docs.epilot.io/api](https://docs.epilot.io/api) | `epilot.eventCatalog` | `@epilot/sdk/event-catalog` | [docs](./packages/epilot-sdk-v2/docs/event-catalog.md) | | `epilot.file` | `@epilot/sdk/file` | [docs](./packages/epilot-sdk-v2/docs/file.md) | | `epilot.iban` | `@epilot/sdk/iban` | [docs](./packages/epilot-sdk-v2/docs/iban.md) | +| `epilot.identity` | `@epilot/sdk/identity` | [docs](./packages/epilot-sdk-v2/docs/identity.md) | | `epilot.integrationToolkit` | `@epilot/sdk/integration-toolkit` | [docs](./packages/epilot-sdk-v2/docs/integration-toolkit.md) | | `epilot.journey` | `@epilot/sdk/journey` | [docs](./packages/epilot-sdk-v2/docs/journey.md) | | `epilot.kanban` | `@epilot/sdk/kanban` | [docs](./packages/epilot-sdk-v2/docs/kanban.md) | diff --git a/clients/identity-client/.gitignore b/clients/identity-client/.gitignore new file mode 100644 index 000000000..3f34f6a15 --- /dev/null +++ b/clients/identity-client/.gitignore @@ -0,0 +1,2 @@ +*.js +!webpack.config.js diff --git a/clients/identity-client/CHANGELOG.md b/clients/identity-client/CHANGELOG.md new file mode 100644 index 000000000..6697a864d --- /dev/null +++ b/clients/identity-client/CHANGELOG.md @@ -0,0 +1 @@ +# @epilot/identity-client diff --git a/clients/identity-client/LICENSE b/clients/identity-client/LICENSE new file mode 100644 index 000000000..9169ae203 --- /dev/null +++ b/clients/identity-client/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2026 epilot GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/clients/identity-client/README.md b/clients/identity-client/README.md new file mode 100644 index 000000000..1c36abdeb --- /dev/null +++ b/clients/identity-client/README.md @@ -0,0 +1,34 @@ +# @epilot/identity-client + +[![CI](https://github.com/epilot-dev/sdk-js/workflows/CI/badge.svg)](https://github.com/epilot-dev/sdk-js/actions?query=workflow%3ACI) +[![npm version](https://img.shields.io/npm/v/@epilot/identity-client.svg)](https://www.npmjs.com/package/@epilot/identity-client) +[![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/epilot-dev/sdk-js/blob/main/LICENSE) + +Client library for the epilot [Identity API](https://docs.epilot.io/api/identity): epilot as an OpenID Connect +provider ("Sign in with epilot"). + +The client covers the epilot-specific endpoints: the platform-operator client registry (partner applications and +the organizations enabled on them) and the 360 session endpoint used by the portal. The standard OpenID Connect +endpoints are described by the discovery document at `https://id.epilot.cloud/.well-known/openid-configuration`. + +Uses [`openapi-client-axios`](https://github.com/openapistack/openapi-client-axios) + +## Installation + +```bash +npm install --save @epilot/identity-client +``` + +## Usage + +```typescript +import { getClient } from "@epilot/identity-client"; + +const identityClient = getClient(); +// operator API: admin-portal internal-auth bearer required +const { data: clients } = await identityClient.listClients({ partner_key: "babelforce" }); +``` + +## Documentation: + +https://docs.epilot.io/api/identity diff --git a/clients/identity-client/package.json b/clients/identity-client/package.json new file mode 100644 index 000000000..0f9191427 --- /dev/null +++ b/clients/identity-client/package.json @@ -0,0 +1,73 @@ +{ + "name": "@epilot/identity-client", + "version": "0.0.0", + "description": "Client for epilot Identity API (Sign in with epilot: operator client registry and 360 session hop)", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "author": "epilot GmbH", + "license": "MIT", + "private": false, + "repository": { + "type": "git", + "url": "git+https://github.com/epilot-dev/sdk-js.git", + "directory": "clients/identity-client" + }, + "bugs": { + "url": "https://github.com/epilot-dev/sdk-js/issues" + }, + "homepage": "https://github.com/epilot-dev/sdk-js/tree/main/clients/identity-client#readme", + "keywords": [ + "identity", + "oidc", + "openid-connect", + "sso" + ], + "scripts": { + "test": "vitest", + "bundle-definition": "webpack", + "openapi": "node ../../scripts/update-openapi.js https://docs.api.epilot.io/identity.yaml", + "openapi:local": "node ../../scripts/update-openapi.js ../../../auth/identity-api/openapi.yml", + "typegen": "openapi typegen src/openapi.json --client > src/openapi.d.ts", + "build": "tsc && npm run build:patch && npm run bundle-definition", + "build:patch": "sed -i'' -e '/^__exportStar.*openapi.*$/d' dist/index.js", + "build:watch": "npm run build && tsc -w", + "prepublishOnly": "npm run typegen && npm run build", + "lint": "biome check src" + }, + "files": [ + "*.js", + "*.d.ts", + "**/*.js", + "**/*.d.ts", + "**/*.json", + "!*.test.*", + "!**/*.test.*", + "!node_modules", + "!src", + "!src/**", + "!*.config.js" + ], + "peerDependencies": { + "axios": "^1.0.0 || >=0.25.0 <1.0.0" + }, + "dependencies": { + "@dazn/lambda-powertools-correlation-ids": "^1.28.1", + "buffer": "^6.0.3", + "https-browserify": "^1.0.0", + "openapi-client-axios": "^7.8.0", + "stream-http": "^3.1.1", + "url": "^0.11.0", + "util": "^0.12.3" + }, + "devDependencies": { + "axios": "^1.11.0", + "copy-webpack-plugin": "^7.0.0", + "json-loader": "^0.5.7", + "openapicmd": "^2.9.2", + "ts-loader": "^8.0.14", + "ts-node": "^10.9.1", + "typescript": "^4.1.3", + "webpack": "^5.18.0", + "webpack-cli": "^4.4.0" + } +} diff --git a/clients/identity-client/src/client.test.ts b/clients/identity-client/src/client.test.ts new file mode 100644 index 000000000..6581358ec --- /dev/null +++ b/clients/identity-client/src/client.test.ts @@ -0,0 +1,25 @@ +import { getClient } from './client'; + +describe('client', () => { + describe('getClient', () => { + it('should inititalise and return client', async () => { + const client = getClient(); + + expect(client.api.initialized).toBe(true); + }); + + it('should have operations', async () => { + const client = getClient(); + + const operations = client.api.getOperations(); + + expect(operations.length).toBeGreaterThan(0); + }); + + it('should have a default server defined', async () => { + const client = getClient(); + + expect(client.defaults.baseURL).toBeDefined(); + }); + }); +}); diff --git a/clients/identity-client/src/client.ts b/clients/identity-client/src/client.ts new file mode 100644 index 000000000..e43d7c194 --- /dev/null +++ b/clients/identity-client/src/client.ts @@ -0,0 +1,30 @@ +import CorrelationIds from '@dazn/lambda-powertools-correlation-ids'; +import OpenAPIClientAxios from 'openapi-client-axios'; + +import definition from './definition'; +import type { Client } from './openapi'; + +let client: Client; +export const getClient = () => { + if (!client) { + client = createClient(); + } + + return client; +}; + +export const createClient = () => { + const api = new OpenAPIClientAxios({ + definition, + quick: true, + }); + + const apiClient = api.initSync(); + + apiClient.defaults.headers.common = { + ...(apiClient.defaults.headers.common ?? {}), + ...(CorrelationIds.get() || {}), + }; + + return apiClient; +}; diff --git a/clients/identity-client/src/definition.ts b/clients/identity-client/src/definition.ts new file mode 100644 index 000000000..40fd23c38 --- /dev/null +++ b/clients/identity-client/src/definition.ts @@ -0,0 +1,5 @@ +import type { Document } from 'openapi-client-axios'; + +import definition from './openapi-runtime.json'; + +export default definition as unknown as Document; diff --git a/clients/identity-client/src/index.ts b/clients/identity-client/src/index.ts new file mode 100644 index 000000000..3d2b15e81 --- /dev/null +++ b/clients/identity-client/src/index.ts @@ -0,0 +1,5 @@ +export type { Document, OpenAPIClient, OpenAPIClientAxios } from 'openapi-client-axios'; +export type { Client, Components, OperationMethods, Paths, PathsDictionary } from './openapi'; + +export * from './client'; +export * from './openapi'; diff --git a/clients/identity-client/src/openapi-runtime.json b/clients/identity-client/src/openapi-runtime.json new file mode 100644 index 000000000..5f53cf969 --- /dev/null +++ b/clients/identity-client/src/openapi-runtime.json @@ -0,0 +1,322 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "", + "version": "" + }, + "servers": [ + { + "url": "https://id.epilot.cloud" + } + ], + "paths": { + "/v1/identity/operator/clients": { + "get": { + "operationId": "listClients", + "parameters": [ + { + "$ref": "#/components/parameters/PartnerKeyQuery" + }, + { + "$ref": "#/components/parameters/ClientStatusQuery" + }, + { + "$ref": "#/components/parameters/SizeQuery" + }, + { + "$ref": "#/components/parameters/CursorQuery" + } + ], + "responses": {} + }, + "post": { + "operationId": "createClient", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/identity/operator/clients/{client_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "get": { + "operationId": "getClient", + "responses": {} + }, + "put": { + "operationId": "updateClient", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + }, + "delete": { + "operationId": "retireClient", + "responses": {} + } + }, + "/v1/identity/operator/clients/{client_id}:activate": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "post": { + "operationId": "activateClient", + "responses": {} + } + }, + "/v1/identity/operator/clients/{client_id}:suspend": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "post": { + "operationId": "suspendClient", + "requestBody": { + "required": false, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/identity/operator/clients/{client_id}:rotateSecret": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "post": { + "operationId": "rotateClientSecret", + "requestBody": { + "required": false, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/identity/operator/clients/{client_id}/organizations": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "get": { + "operationId": "listClientOrganizations", + "parameters": [ + { + "$ref": "#/components/parameters/OrgEnablementStatusQuery" + } + ], + "responses": {} + }, + "post": { + "operationId": "enableClientOrganization", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/identity/operator/clients/{client_id}/organizations/{org_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + }, + { + "$ref": "#/components/parameters/OrgIdPath" + } + ], + "get": { + "operationId": "getClientOrganization", + "responses": {} + }, + "put": { + "operationId": "updateClientOrganization", + "requestBody": { + "required": true, + "content": { + "application/json": {} + } + }, + "responses": {} + }, + "delete": { + "operationId": "revokeClientOrganization", + "requestBody": { + "required": false, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + }, + { + "$ref": "#/components/parameters/OrgIdPath" + } + ], + "post": { + "operationId": "suspendClientOrganization", + "requestBody": { + "required": false, + "content": { + "application/json": {} + } + }, + "responses": {} + } + }, + "/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + }, + { + "$ref": "#/components/parameters/OrgIdPath" + } + ], + "post": { + "operationId": "activateClientOrganization", + "responses": {} + } + }, + "/v1/identity/session": { + "get": { + "operationId": "createIdentitySession", + "responses": {} + }, + "delete": { + "operationId": "deleteIdentitySession", + "responses": {} + } + }, + "/v1/identity/interaction/{interaction_id}": { + "parameters": [ + { + "name": "interaction_id", + "in": "path", + "required": true, + "description": "Opaque identifier of the parked authorization request" + } + ] + }, + "/v1/identity/launch/{client_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "get": { + "operationId": "launchClient", + "parameters": [ + { + "name": "target", + "in": "query", + "required": false + }, + { + "$ref": "#/components/parameters/AnyOtherQueryParameters" + } + ], + "responses": {} + } + } + }, + "components": { + "parameters": { + "AnyOtherQueryParameters": { + "name": "extra", + "in": "query", + "required": false, + "style": "form", + "explode": true + }, + "ClientIdPath": { + "name": "client_id", + "in": "path", + "required": true + }, + "OrgIdPath": { + "name": "org_id", + "in": "path", + "required": true + }, + "PartnerKeyQuery": { + "name": "partner_key", + "in": "query", + "required": false + }, + "ClientStatusQuery": { + "name": "status", + "in": "query", + "required": false + }, + "OrgEnablementStatusQuery": { + "name": "status", + "in": "query", + "required": false + }, + "SizeQuery": { + "name": "size", + "in": "query", + "required": false + }, + "CursorQuery": { + "name": "cursor", + "in": "query", + "required": false + } + }, + "responses": { + "BadRequest": { + "content": { + "application/json": {} + } + }, + "Unauthorized": { + "content": { + "application/json": {} + } + }, + "Forbidden": { + "content": { + "application/json": {} + } + }, + "NotFound": { + "content": { + "application/json": {} + } + }, + "Conflict": { + "content": { + "application/json": {} + } + } + } + } +} diff --git a/clients/identity-client/src/openapi.d.ts b/clients/identity-client/src/openapi.d.ts new file mode 100644 index 000000000..f1706c484 --- /dev/null +++ b/clients/identity-client/src/openapi.d.ts @@ -0,0 +1,1154 @@ +import type { + OpenAPIClient, + Parameters, + UnknownParamsObject, + OperationResponse, + AxiosRequestConfig, +} from 'openapi-client-axios'; + +declare namespace Components { + namespace Parameters { + export interface AnyOtherQueryParameters { + [name: string]: any; + } + export type ClientIdPath = /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + Schemas.PartnerClientId /* ^[A-Za-z0-9._~-]+$ */; + export type ClientStatusQuery = Schemas.PartnerClientStatus; + export type CursorQuery = string; + export type OrgEnablementStatusQuery = Schemas.OrgEnablementStatus; + export type OrgIdPath = /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + Schemas.OrgId /* ^[A-Za-z0-9_-]+$ */; + export type PartnerKeyQuery = string; + export type SizeQuery = number; + } + export interface PathParameters { + ClientIdPath?: Parameters.ClientIdPath; + OrgIdPath?: Parameters.OrgIdPath; + } + export interface QueryParameters { + AnyOtherQueryParameters?: Parameters.AnyOtherQueryParameters; + PartnerKeyQuery?: Parameters.PartnerKeyQuery; + ClientStatusQuery?: Parameters.ClientStatusQuery; + OrgEnablementStatusQuery?: Parameters.OrgEnablementStatusQuery; + SizeQuery?: Parameters.SizeQuery; + CursorQuery?: Parameters.CursorQuery; + } + namespace Responses { + export type BadRequest = Schemas.Error; + export type Conflict = Schemas.Error; + export type Forbidden = Schemas.Error; + export type NotFound = Schemas.Error; + export type Unauthorized = Schemas.Error; + } + namespace Schemas { + export interface AuthorizationPolicy { + mode?: "preauthorized"; + population?: "all_current_org_members"; + /** + * Must contain `openid` and be a subset of the client's `scopes_allowed` + */ + allowed_scopes: [ + Scope, + ...Scope[] + ]; + /** + * Reference to the customer's approval evidence (ticket, signed order, contract clause) + */ + customer_approval_ref: string; + approved_at: string; // date-time + version: number; + mandatory_claims: ("org_id")[]; + recorded_by: string; + recorded_at: string; // date-time + } + export interface AuthorizationPolicyInput { + mode?: "preauthorized"; + population?: "all_current_org_members"; + /** + * Must contain `openid` and be a subset of the client's `scopes_allowed` + */ + allowed_scopes: [ + Scope, + ...Scope[] + ]; + /** + * Reference to the customer's approval evidence (ticket, signed order, contract clause) + */ + customer_approval_ref: string; + approved_at: string; // date-time + } + /** + * Partner-side environment this client belongs to + */ + export type Environment = "dev" | "staging" | "production"; + export interface Error { + /** + * example: + * 404 + */ + status: number; + /** + * example: + * Client not found + */ + error: string; + /** + * Request validation problems (only on `400` from schema validation) + */ + details?: { + [name: string]: any; + }[]; + } + /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + export type HttpsUri = string; // uri + export interface IdentitySession { + org_id: /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + OrgId /* ^[A-Za-z0-9_-]+$ */; + /** + * epilot user id of the session owner + */ + user_id: string; + /** + * When the mirrored credential, and therefore the cookie, expires + */ + expires_at: string; // date-time + } + export interface OrgEnablement { + client_id: /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + PartnerClientId /* ^[A-Za-z0-9._~-]+$ */; + org_id: /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + OrgId /* ^[A-Za-z0-9_-]+$ */; + status: OrgEnablementStatus; + status_reason?: string; + authorization_policy: AuthorizationPolicy; + partner_account?: /* Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping. */ PartnerAccount; + created_at: string; // date-time + updated_at: string; // date-time + created_by?: string; + updated_by?: string; + } + export interface OrgEnablementCreate { + org_id: /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + OrgId /* ^[A-Za-z0-9_-]+$ */; + authorization_policy: AuthorizationPolicyInput; + partner_account?: /* Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping. */ PartnerAccount; + } + export interface OrgEnablementList { + results: OrgEnablement[]; + } + export type OrgEnablementStatus = "active" | "suspended" | "revoked"; + export interface OrgEnablementUpdate { + /** + * Current policy version, for compare-and-swap + */ + expected_policy_version: number; + authorization_policy: AuthorizationPolicyInput; + partner_account?: /* Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping. */ PartnerAccount; + } + /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + export type OrgId = string; // ^[A-Za-z0-9_-]+$ + /** + * Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping. + */ + export interface PartnerAccount { + id: string; + label?: string; + } + export interface PartnerClient { + client_id: /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + PartnerClientId /* ^[A-Za-z0-9._~-]+$ */; + partner_key: string; + display_name: string; + description?: string; + environment: /* Partner-side environment this client belongs to */ Environment; + redirect_uris: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */[]; + sector_identifier_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + initiate_login_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + launch_target_prefixes?: string[]; + token_endpoint_auth_method: "client_secret_basic"; + grant_types: ("authorization_code")[]; + scopes_allowed: Scope[]; + status: PartnerClientStatus; + /** + * Operator-supplied reason for the last suspension or retirement + */ + status_reason?: string; + config_version: number; + secret_rotated_at?: string; // date-time + /** + * Until when the previous secret still authenticates after a rotation + */ + previous_secret_expires_at?: string; // date-time + created_at: string; // date-time + updated_at: string; // date-time + /** + * Admin-portal identity of the operator who created the client + */ + created_by?: string; + updated_by?: string; + } + export interface PartnerClientCreate { + /** + * Stable key of the partner application, shared by its environments + * example: + * babelforce + */ + partner_key: string; // ^[a-z0-9][a-z0-9-]*$ + /** + * example: + * Babelforce + */ + display_name: string; + description?: string; + environment: /* Partner-side environment this client belongs to */ Environment; + redirect_uris: [ + /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */, + .../** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */[] + ]; + /** + * Optional. Pairwise sector for `sub` derivation. Required when the redirect URIs span + * more than one host, so one person keeps one `sub` at this partner. + * + * example: + * https://partner.example.com/auth/callback + */ + sector_identifier_uri?: string; // uri + /** + * Where a login started from epilot should begin at the partner + * example: + * https://partner.example.com/auth/callback + */ + initiate_login_uri?: string; // uri + /** + * Allowed prefixes for deep-link targets when a login is started from epilot + */ + launch_target_prefixes?: string[]; + /** + * Upper bound for every organization enablement's `allowed_scopes`. Defaults to all scopes. + */ + scopes_allowed?: [ + Scope, + ...Scope[] + ]; + } + /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + export type PartnerClientId = string; // ^[A-Za-z0-9._~-]+$ + export interface PartnerClientList { + results: PartnerClient[]; + next_cursor?: string | null; + } + export type PartnerClientStatus = "draft" | "active" | "suspended" | "retired"; + export interface PartnerClientUpdate { + /** + * Current configuration version, for compare-and-swap + */ + config_version: number; + /** + * Required (true) when the update changes the pairwise sector of an **active** client, i.e. the + * `sector_identifier_uri` host or, without one, the redirect host. Changing the sector changes the + * `sub` of every person at this partner; without this flag such an update is rejected with `409`. + * + */ + allow_sector_change?: boolean; + display_name: string; + description?: string; + redirect_uris: [ + /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */, + .../** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */[] + ]; + sector_identifier_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + initiate_login_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + launch_target_prefixes?: string[]; + scopes_allowed: [ + Scope, + ...Scope[] + ]; + } + export interface PartnerClientWithSecret { + client_id: /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + PartnerClientId /* ^[A-Za-z0-9._~-]+$ */; + partner_key: string; + display_name: string; + description?: string; + environment: /* Partner-side environment this client belongs to */ Environment; + redirect_uris: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */[]; + sector_identifier_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + initiate_login_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + launch_target_prefixes?: string[]; + token_endpoint_auth_method: "client_secret_basic"; + grant_types: ("authorization_code")[]; + scopes_allowed: Scope[]; + status: PartnerClientStatus; + /** + * Operator-supplied reason for the last suspension or retirement + */ + status_reason?: string; + config_version: number; + secret_rotated_at?: string; // date-time + /** + * Until when the previous secret still authenticates after a rotation + */ + previous_secret_expires_at?: string; // date-time + created_at: string; // date-time + updated_at: string; // date-time + /** + * Admin-portal identity of the operator who created the client + */ + created_by?: string; + updated_by?: string; + /** + * Returned exactly once. Store it securely; it cannot be retrieved again. + */ + client_secret: string; + } + export interface RotateSecretRequest { + /** + * When true the previous secret stops working immediately + */ + compromise_mode?: boolean; + /** + * How long the previous secret keeps working (ignored in compromise mode) + */ + migration_window_seconds?: number; + } + export type Scope = "openid" | "email" | "profile"; + export interface StatusChangeRequest { + reason?: string; + } + } +} +declare namespace Paths { + namespace ActivateClient { + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace ActivateClientOrganization { + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace CreateClient { + export type RequestBody = Components.Schemas.PartnerClientCreate; + namespace Responses { + export type $201 = Components.Schemas.PartnerClientWithSecret; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + } + } + namespace CreateIdentitySession { + namespace Responses { + export type $200 = Components.Schemas.IdentitySession; + export type $401 = Components.Responses.Unauthorized; + } + } + namespace DeleteIdentitySession { + namespace Responses { + export interface $204 { + } + } + } + namespace EnableClientOrganization { + export type RequestBody = Components.Schemas.OrgEnablementCreate; + namespace Responses { + export type $201 = Components.Schemas.OrgEnablement; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace GetClient { + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace GetClientOrganization { + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace LaunchClient { + namespace Parameters { + export interface Extra { + [name: string]: any; + } + export type Target = string; // uri + } + export interface QueryParameters { + target?: Parameters.Target /* uri */; + extra?: Parameters.Extra; + } + namespace Responses { + export interface $302 { + } + export type $400 = Components.Responses.BadRequest; + export type $404 = Components.Responses.NotFound; + } + } + namespace ListClientOrganizations { + namespace Parameters { + export type Status = Components.Schemas.OrgEnablementStatus; + } + export interface QueryParameters { + status?: Parameters.Status; + } + namespace Responses { + export type $200 = Components.Schemas.OrgEnablementList; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace ListClients { + namespace Parameters { + export type Cursor = string; + export type PartnerKey = string; + export type Size = number; + export type Status = Components.Schemas.PartnerClientStatus; + } + export interface QueryParameters { + partner_key?: Parameters.PartnerKey; + status?: Parameters.Status; + size?: Parameters.Size; + cursor?: Parameters.Cursor; + } + namespace Responses { + export type $200 = Components.Schemas.PartnerClientList; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + } + } + namespace RetireClient { + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace RevokeClientOrganization { + export type RequestBody = Components.Schemas.StatusChangeRequest; + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace RotateClientSecret { + export type RequestBody = Components.Schemas.RotateSecretRequest; + namespace Responses { + export type $200 = Components.Schemas.PartnerClientWithSecret; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace SuspendClient { + export type RequestBody = Components.Schemas.StatusChangeRequest; + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace SuspendClientOrganization { + export type RequestBody = Components.Schemas.StatusChangeRequest; + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace UpdateClient { + export type RequestBody = Components.Schemas.PartnerClientUpdate; + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace UpdateClientOrganization { + export type RequestBody = Components.Schemas.OrgEnablementUpdate; + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace V1IdentityInteraction$InteractionId { + namespace Parameters { + export type InteractionId = string; + } + export interface PathParameters { + interaction_id: Parameters.InteractionId; + } + } + namespace V1IdentityLaunch$ClientId { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientId { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdActivate { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdOrganizations { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdOrganizations$OrgId { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + export type $1 = Components.Parameters.OrgIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdOrganizations$OrgIdActivate { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + export type $1 = Components.Parameters.OrgIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdOrganizations$OrgIdSuspend { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + export type $1 = Components.Parameters.OrgIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdRotateSecret { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdSuspend { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } +} + + +export interface OperationMethods { + /** + * listClients - listClients + * + * List registered partner clients. The client secret is never returned. + */ + 'listClients'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createClient - createClient + * + * Create a partner client in `draft` status. The client secret is returned exactly once in this + * response and can never be read again; use `rotateClientSecret` to obtain a new one. + * + */ + 'createClient'( + parameters?: Parameters | null, + data?: Paths.CreateClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getClient - getClient + * + * Read a partner client. The client secret is never returned. + */ + 'getClient'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * updateClient - updateClient + * + * Replace the mutable configuration of a client. `config_version` must equal the current + * version (compare-and-swap); a mismatch is rejected with `409`. + * + */ + 'updateClient'( + parameters?: Parameters | null, + data?: Paths.UpdateClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * retireClient - retireClient + * + * Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every + * organization enablement is revoked and all provider-side artefacts are revoked. + * + */ + 'retireClient'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * activateClient - activateClient + * + * Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute + * HTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the + * authentication method is `client_secret_basic`. + * + */ + 'activateClient'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * suspendClient - suspendClient + * + * Immediately block the client for every organization. Provider-side artefacts (codes, tokens, + * grants) are revoked; the audit trail is kept. Reversible with `activateClient`. + * + */ + 'suspendClient'( + parameters?: Parameters | null, + data?: Paths.SuspendClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * rotateClientSecret - rotateClientSecret + * + * Issue a new client secret. The new secret is returned exactly once. The previous secret + * keeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which + * case it stops working immediately. + * + */ + 'rotateClientSecret'( + parameters?: Parameters | null, + data?: Paths.RotateClientSecret.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * listClientOrganizations - listClientOrganizations + * + * List the organizations enabled on a client, including suspended and revoked ones. + */ + 'listClientOrganizations'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * enableClientOrganization - enableClientOrganization + * + * Enable an epilot organization on a client, recording the customer's authorization policy + * (version 1). `allowed_scopes` must contain `openid` and be a subset of the client's + * `scopes_allowed`. + * + */ + 'enableClientOrganization'( + parameters?: Parameters | null, + data?: Paths.EnableClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getClientOrganization - getClientOrganization + * + * Read one organization enablement. + */ + 'getClientOrganization'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * updateClientOrganization - updateClientOrganization + * + * Record a new version of the customer's authorization policy. `expected_policy_version` must + * equal the current version (compare-and-swap); a mismatch is rejected with `409`. + * + */ + 'updateClientOrganization'( + parameters?: Parameters | null, + data?: Paths.UpdateClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * revokeClientOrganization - revokeClientOrganization + * + * Revoke an organization's enablement. The record is kept as an audited tombstone; future + * authorizations for this organization fail with `access_denied` and provider-side artefacts + * bound to it are revoked. + * + */ + 'revokeClientOrganization'( + parameters?: Parameters | null, + data?: Paths.RevokeClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * suspendClientOrganization - suspendClientOrganization + * + * Temporarily block one organization on this client. Reversible with `activateClientOrganization`. + */ + 'suspendClientOrganization'( + parameters?: Parameters | null, + data?: Paths.SuspendClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * activateClientOrganization - activateClientOrganization + * + * Resume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy version. + */ + 'activateClientOrganization'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createIdentitySession - createIdentitySession + * + * Called by the epilot portal through the portal-host proxy after every persisted 360 session. + * Validates the 360 bearer token itself and mirrors it into the `identity_session` cookie + * (`HttpOnly; Secure; Path=/v1/identity`) whose lifetime equals the remaining token lifetime. + * The cookie is what proves the live 360 session during `completeInteraction`. + * + */ + 'createIdentitySession'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteIdentitySession - deleteIdentitySession + * + * Clears the `identity_session` cookie. Called by the portal on logout. + */ + 'deleteIdentitySession'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * launchClient - launchClient + * + * One validated place for partner start links (manager UI, hosted apps). Redirects the browser to the + * client's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep link as + * `target_link_uri` (OpenID Connect Core §4, third-party initiated login). Authenticates nobody: the partner + * then starts a normal "Sign in with epilot" authorization. + * + * `target` must be an absolute HTTPS URL starting with one of the client's registered + * `launch_target_prefixes`; anything else is rejected. Retired, suspended or draft clients, and clients + * without an `initiate_login_uri`, answer `404`. + * + */ + 'launchClient'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse +} + +export interface PathsDictionary { + ['/v1/identity/operator/clients']: { + /** + * listClients - listClients + * + * List registered partner clients. The client secret is never returned. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createClient - createClient + * + * Create a partner client in `draft` status. The client secret is returned exactly once in this + * response and can never be read again; use `rotateClientSecret` to obtain a new one. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.CreateClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}']: { + /** + * getClient - getClient + * + * Read a partner client. The client secret is never returned. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * updateClient - updateClient + * + * Replace the mutable configuration of a client. `config_version` must equal the current + * version (compare-and-swap); a mismatch is rejected with `409`. + * + */ + 'put'( + parameters?: Parameters | null, + data?: Paths.UpdateClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * retireClient - retireClient + * + * Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every + * organization enablement is revoked and all provider-side artefacts are revoked. + * + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}:activate']: { + /** + * activateClient - activateClient + * + * Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute + * HTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the + * authentication method is `client_secret_basic`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}:suspend']: { + /** + * suspendClient - suspendClient + * + * Immediately block the client for every organization. Provider-side artefacts (codes, tokens, + * grants) are revoked; the audit trail is kept. Reversible with `activateClient`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.SuspendClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}:rotateSecret']: { + /** + * rotateClientSecret - rotateClientSecret + * + * Issue a new client secret. The new secret is returned exactly once. The previous secret + * keeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which + * case it stops working immediately. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.RotateClientSecret.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}/organizations']: { + /** + * listClientOrganizations - listClientOrganizations + * + * List the organizations enabled on a client, including suspended and revoked ones. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * enableClientOrganization - enableClientOrganization + * + * Enable an epilot organization on a client, recording the customer's authorization policy + * (version 1). `allowed_scopes` must contain `openid` and be a subset of the client's + * `scopes_allowed`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.EnableClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}/organizations/{org_id}']: { + /** + * getClientOrganization - getClientOrganization + * + * Read one organization enablement. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * updateClientOrganization - updateClientOrganization + * + * Record a new version of the customer's authorization policy. `expected_policy_version` must + * equal the current version (compare-and-swap); a mismatch is rejected with `409`. + * + */ + 'put'( + parameters?: Parameters | null, + data?: Paths.UpdateClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * revokeClientOrganization - revokeClientOrganization + * + * Revoke an organization's enablement. The record is kept as an audited tombstone; future + * authorizations for this organization fail with `access_denied` and provider-side artefacts + * bound to it are revoked. + * + */ + 'delete'( + parameters?: Parameters | null, + data?: Paths.RevokeClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend']: { + /** + * suspendClientOrganization - suspendClientOrganization + * + * Temporarily block one organization on this client. Reversible with `activateClientOrganization`. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.SuspendClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate']: { + /** + * activateClientOrganization - activateClientOrganization + * + * Resume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy version. + */ + 'post'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/session']: { + /** + * createIdentitySession - createIdentitySession + * + * Called by the epilot portal through the portal-host proxy after every persisted 360 session. + * Validates the 360 bearer token itself and mirrors it into the `identity_session` cookie + * (`HttpOnly; Secure; Path=/v1/identity`) whose lifetime equals the remaining token lifetime. + * The cookie is what proves the live 360 session during `completeInteraction`. + * + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteIdentitySession - deleteIdentitySession + * + * Clears the `identity_session` cookie. Called by the portal on logout. + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/interaction/{interaction_id}']: { + } + ['/v1/identity/launch/{client_id}']: { + /** + * launchClient - launchClient + * + * One validated place for partner start links (manager UI, hosted apps). Redirects the browser to the + * client's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep link as + * `target_link_uri` (OpenID Connect Core §4, third-party initiated login). Authenticates nobody: the partner + * then starts a normal "Sign in with epilot" authorization. + * + * `target` must be an absolute HTTPS URL starting with one of the client's registered + * `launch_target_prefixes`; anything else is rejected. Retired, suspended or draft clients, and clients + * without an `initiate_login_uri`, answer `404`. + * + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } +} + +export type Client = OpenAPIClient + + +export type AuthorizationPolicy = Components.Schemas.AuthorizationPolicy; +export type AuthorizationPolicyInput = Components.Schemas.AuthorizationPolicyInput; +export type Environment = Components.Schemas.Environment; +export type Error = Components.Schemas.Error; +export type HttpsUri = Components.Schemas.HttpsUri; +export type IdentitySession = Components.Schemas.IdentitySession; +export type OrgEnablement = Components.Schemas.OrgEnablement; +export type OrgEnablementCreate = Components.Schemas.OrgEnablementCreate; +export type OrgEnablementList = Components.Schemas.OrgEnablementList; +export type OrgEnablementStatus = Components.Schemas.OrgEnablementStatus; +export type OrgEnablementUpdate = Components.Schemas.OrgEnablementUpdate; +export type OrgId = Components.Schemas.OrgId; +export type PartnerAccount = Components.Schemas.PartnerAccount; +export type PartnerClient = Components.Schemas.PartnerClient; +export type PartnerClientCreate = Components.Schemas.PartnerClientCreate; +export type PartnerClientId = Components.Schemas.PartnerClientId; +export type PartnerClientList = Components.Schemas.PartnerClientList; +export type PartnerClientStatus = Components.Schemas.PartnerClientStatus; +export type PartnerClientUpdate = Components.Schemas.PartnerClientUpdate; +export type PartnerClientWithSecret = Components.Schemas.PartnerClientWithSecret; +export type RotateSecretRequest = Components.Schemas.RotateSecretRequest; +export type Scope = Components.Schemas.Scope; +export type StatusChangeRequest = Components.Schemas.StatusChangeRequest; diff --git a/clients/identity-client/src/openapi.json b/clients/identity-client/src/openapi.json new file mode 100644 index 000000000..e650d0583 --- /dev/null +++ b/clients/identity-client/src/openapi.json @@ -0,0 +1,1546 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Identity API", + "version": "1.0.0", + "description": "epilot as an OpenID Connect provider (\"Sign in with epilot\").\n\nThe standard OpenID Connect endpoints (discovery, JWKS, authorize, token, UserInfo, revocation)\nare described by the discovery document at `/.well-known/openid-configuration` and are not\nrepeated here. Partners integrate against that document with any certified OpenID Connect\nrelying-party library.\n\nThis specification covers the epilot-specific parts of the service:\n\n- the **operator API** used by epilot platform staff to register partner applications\n (clients) and to enable organizations on them,\n- the **360 session endpoints** that let the portal prove a live 360 session during an\n authorization (the session hop).\n\nTenant model: one confidential client per partner application and environment; the organization\na person is acting in travels in every ID token as the claim `https://id.epilot.cloud/claims/org_id`.\nA relying party must key everything on `(iss, sub, org_id)`.\n" + }, + "tags": [ + { + "name": "Operator Clients", + "description": "Register and manage partner applications (OIDC clients). Platform operators only." + }, + { + "name": "Operator Organizations", + "description": "Enable, suspend and revoke organizations on a partner client. Platform operators only." + }, + { + "name": "Session", + "description": "360 session hop used by the epilot portal during an authorization." + }, + { + "name": "Launch", + "description": "Start a partner login from an epilot link (OpenID Connect third-party initiated login)." + } + ], + "servers": [ + { + "url": "https://id.epilot.cloud" + } + ], + "security": [ + { + "OperatorAuth": [] + } + ], + "paths": { + "/v1/identity/operator/clients": { + "get": { + "operationId": "listClients", + "summary": "listClients", + "description": "List registered partner clients. The client secret is never returned.", + "tags": [ + "Operator Clients" + ], + "parameters": [ + { + "$ref": "#/components/parameters/PartnerKeyQuery" + }, + { + "$ref": "#/components/parameters/ClientStatusQuery" + }, + { + "$ref": "#/components/parameters/SizeQuery" + }, + { + "$ref": "#/components/parameters/CursorQuery" + } + ], + "responses": { + "200": { + "description": "Clients", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientList" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + }, + "post": { + "operationId": "createClient", + "summary": "createClient", + "description": "Create a partner client in `draft` status. The client secret is returned exactly once in this\nresponse and can never be read again; use `rotateClientSecret` to obtain a new one.\n", + "tags": [ + "Operator Clients" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Created client including its one-time secret", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientWithSecret" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "get": { + "operationId": "getClient", + "summary": "getClient", + "description": "Read a partner client. The client secret is never returned.", + "tags": [ + "Operator Clients" + ], + "responses": { + "200": { + "description": "Client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "operationId": "updateClient", + "summary": "updateClient", + "description": "Replace the mutable configuration of a client. `config_version` must equal the current\nversion (compare-and-swap); a mismatch is rejected with `409`.\n", + "tags": [ + "Operator Clients" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Updated client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + }, + "delete": { + "operationId": "retireClient", + "summary": "retireClient", + "description": "Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every\norganization enablement is revoked and all provider-side artefacts are revoked.\n", + "tags": [ + "Operator Clients" + ], + "responses": { + "200": { + "description": "Retired client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}:activate": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "post": { + "operationId": "activateClient", + "summary": "activateClient", + "description": "Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute\nHTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the\nauthentication method is `client_secret_basic`.\n", + "tags": [ + "Operator Clients" + ], + "responses": { + "200": { + "description": "Activated client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}:suspend": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "post": { + "operationId": "suspendClient", + "summary": "suspendClient", + "description": "Immediately block the client for every organization. Provider-side artefacts (codes, tokens,\ngrants) are revoked; the audit trail is kept. Reversible with `activateClient`.\n", + "tags": [ + "Operator Clients" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusChangeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Suspended client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}:rotateSecret": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "post": { + "operationId": "rotateClientSecret", + "summary": "rotateClientSecret", + "description": "Issue a new client secret. The new secret is returned exactly once. The previous secret\nkeeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which\ncase it stops working immediately.\n", + "tags": [ + "Operator Clients" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RotateSecretRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Client including its new one-time secret", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientWithSecret" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}/organizations": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "get": { + "operationId": "listClientOrganizations", + "summary": "listClientOrganizations", + "description": "List the organizations enabled on a client, including suspended and revoked ones.", + "tags": [ + "Operator Organizations" + ], + "parameters": [ + { + "$ref": "#/components/parameters/OrgEnablementStatusQuery" + } + ], + "responses": { + "200": { + "description": "Organization enablements", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablementList" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "post": { + "operationId": "enableClientOrganization", + "summary": "enableClientOrganization", + "description": "Enable an epilot organization on a client, recording the customer's authorization policy\n(version 1). `allowed_scopes` must contain `openid` and be a subset of the client's\n`scopes_allowed`.\n", + "tags": [ + "Operator Organizations" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablementCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Created enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}/organizations/{org_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + }, + { + "$ref": "#/components/parameters/OrgIdPath" + } + ], + "get": { + "operationId": "getClientOrganization", + "summary": "getClientOrganization", + "description": "Read one organization enablement.", + "tags": [ + "Operator Organizations" + ], + "responses": { + "200": { + "description": "Enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "operationId": "updateClientOrganization", + "summary": "updateClientOrganization", + "description": "Record a new version of the customer's authorization policy. `expected_policy_version` must\nequal the current version (compare-and-swap); a mismatch is rejected with `409`.\n", + "tags": [ + "Operator Organizations" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablementUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Updated enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + }, + "delete": { + "operationId": "revokeClientOrganization", + "summary": "revokeClientOrganization", + "description": "Revoke an organization's enablement. The record is kept as an audited tombstone; future\nauthorizations for this organization fail with `access_denied` and provider-side artefacts\nbound to it are revoked.\n", + "tags": [ + "Operator Organizations" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusChangeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Revoked enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + }, + { + "$ref": "#/components/parameters/OrgIdPath" + } + ], + "post": { + "operationId": "suspendClientOrganization", + "summary": "suspendClientOrganization", + "description": "Temporarily block one organization on this client. Reversible with `activateClientOrganization`.", + "tags": [ + "Operator Organizations" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusChangeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Suspended enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + }, + { + "$ref": "#/components/parameters/OrgIdPath" + } + ], + "post": { + "operationId": "activateClientOrganization", + "summary": "activateClientOrganization", + "description": "Resume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy version.", + "tags": [ + "Operator Organizations" + ], + "responses": { + "200": { + "description": "Active enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/session": { + "get": { + "operationId": "createIdentitySession", + "summary": "createIdentitySession", + "description": "Called by the epilot portal through the portal-host proxy after every persisted 360 session.\nValidates the 360 bearer token itself and mirrors it into the `identity_session` cookie\n(`HttpOnly; Secure; Path=/v1/identity`) whose lifetime equals the remaining token lifetime.\nThe cookie is what proves the live 360 session during `completeInteraction`.\n", + "tags": [ + "Session" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "responses": { + "200": { + "description": "Session mirrored into the cookie", + "headers": { + "Set-Cookie": { + "description": "`identity_session=<360 ID token>; HttpOnly; Secure; SameSite=None; Path=/v1/identity; Max-Age=`", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdentitySession" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "delete": { + "operationId": "deleteIdentitySession", + "summary": "deleteIdentitySession", + "description": "Clears the `identity_session` cookie. Called by the portal on logout.", + "tags": [ + "Session" + ], + "security": [], + "responses": { + "204": { + "description": "Cookie cleared", + "headers": { + "Set-Cookie": { + "description": "Expired `identity_session` cookie", + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v1/identity/interaction/{interaction_id}": { + "parameters": [ + { + "name": "interaction_id", + "in": "path", + "required": true, + "description": "Opaque identifier of the parked authorization request", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + ] + }, + "/v1/identity/launch/{client_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "get": { + "operationId": "launchClient", + "summary": "launchClient", + "description": "One validated place for partner start links (manager UI, hosted apps). Redirects the browser to the\nclient's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep link as\n`target_link_uri` (OpenID Connect Core §4, third-party initiated login). Authenticates nobody: the partner\nthen starts a normal \"Sign in with epilot\" authorization.\n\n`target` must be an absolute HTTPS URL starting with one of the client's registered\n`launch_target_prefixes`; anything else is rejected. Retired, suspended or draft clients, and clients\nwithout an `initiate_login_uri`, answer `404`.\n", + "tags": [ + "Launch" + ], + "security": [], + "parameters": [ + { + "name": "target", + "in": "query", + "required": false, + "description": "Deep link inside the partner application to continue to after login", + "schema": { + "type": "string", + "format": "uri", + "maxLength": 2048 + } + }, + { + "$ref": "#/components/parameters/AnyOtherQueryParameters" + } + ], + "responses": { + "302": { + "description": "Redirect to the partner's initiate_login_uri", + "headers": { + "Location": { + "schema": { + "type": "string", + "format": "uri" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + } + }, + "components": { + "securitySchemes": { + "OperatorAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "Internal-auth bearer token minted for an epilot admin-portal user. Customer organization\nbearers are never accepted; the caller must belong to an allowed admin-portal group.\n" + }, + "EpilotAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "Authorization header with the caller's epilot 360 bearer token." + }, + "IdentitySessionCookie": { + "type": "apiKey", + "in": "cookie", + "name": "identity_session", + "description": "Cookie set by `createIdentitySession`, carrying the live 360 credential." + } + }, + "parameters": { + "AnyOtherQueryParameters": { + "name": "extra", + "in": "query", + "required": false, + "description": "Any other query parameters (marketing tags on a start link, parameters a proxy appends) are accepted\nand ignored. Start links and the session hop must never fail on an unknown parameter.\n", + "style": "form", + "explode": true, + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "ClientIdPath": { + "name": "client_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/PartnerClientId" + } + }, + "OrgIdPath": { + "name": "org_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/OrgId" + } + }, + "PartnerKeyQuery": { + "name": "partner_key", + "in": "query", + "required": false, + "description": "Filter by partner key", + "schema": { + "type": "string" + } + }, + "ClientStatusQuery": { + "name": "status", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/PartnerClientStatus" + } + }, + "OrgEnablementStatusQuery": { + "name": "status", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/OrgEnablementStatus" + } + }, + "SizeQuery": { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + } + }, + "CursorQuery": { + "name": "cursor", + "in": "query", + "required": false, + "description": "Opaque cursor from a previous response", + "schema": { + "type": "string" + } + } + }, + "responses": { + "BadRequest": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "Unauthorized": { + "description": "Missing or invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "Forbidden": { + "description": "Caller is not a platform operator", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "NotFound": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "Conflict": { + "description": "Version mismatch or invalid state transition", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "schemas": { + "Error": { + "type": "object", + "required": [ + "status", + "error" + ], + "properties": { + "status": { + "type": "integer", + "example": 404 + }, + "error": { + "type": "string", + "example": "Client not found" + }, + "details": { + "type": "array", + "description": "Request validation problems (only on `400` from schema validation)", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "PartnerClientId": { + "type": "string", + "description": "Opaque client identifier, unique within the issuer, never reused", + "example": "bf-prod-x7k2", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9._~-]+$" + }, + "OrgId": { + "type": "string", + "description": "epilot organization id, an opaque case-sensitive string", + "example": "739224", + "minLength": 1, + "maxLength": 64, + "pattern": "^[A-Za-z0-9_-]+$" + }, + "Environment": { + "type": "string", + "description": "Partner-side environment this client belongs to", + "enum": [ + "dev", + "staging", + "production" + ] + }, + "PartnerClientStatus": { + "type": "string", + "enum": [ + "draft", + "active", + "suspended", + "retired" + ] + }, + "Scope": { + "type": "string", + "enum": [ + "openid", + "email", + "profile" + ] + }, + "HttpsUri": { + "type": "string", + "format": "uri", + "description": "Absolute HTTPS URL without fragment", + "example": "https://partner.example.com/auth/callback" + }, + "PartnerClientCreate": { + "type": "object", + "required": [ + "partner_key", + "display_name", + "environment", + "redirect_uris" + ], + "properties": { + "partner_key": { + "type": "string", + "description": "Stable key of the partner application, shared by its environments", + "example": "babelforce", + "minLength": 1, + "maxLength": 64, + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "display_name": { + "type": "string", + "example": "Babelforce", + "minLength": 1, + "maxLength": 128 + }, + "description": { + "type": "string", + "maxLength": 1024 + }, + "environment": { + "$ref": "#/components/schemas/Environment" + }, + "redirect_uris": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/HttpsUri" + } + }, + "sector_identifier_uri": { + "allOf": [ + { + "$ref": "#/components/schemas/HttpsUri" + } + ], + "description": "Optional. Pairwise sector for `sub` derivation. Required when the redirect URIs span\nmore than one host, so one person keeps one `sub` at this partner.\n" + }, + "initiate_login_uri": { + "allOf": [ + { + "$ref": "#/components/schemas/HttpsUri" + } + ], + "description": "Where a login started from epilot should begin at the partner" + }, + "launch_target_prefixes": { + "type": "array", + "description": "Allowed prefixes for deep-link targets when a login is started from epilot", + "items": { + "type": "string" + } + }, + "scopes_allowed": { + "type": "array", + "description": "Upper bound for every organization enablement's `allowed_scopes`. Defaults to all scopes.", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "PartnerClientUpdate": { + "type": "object", + "required": [ + "config_version", + "display_name", + "redirect_uris", + "scopes_allowed" + ], + "properties": { + "config_version": { + "type": "integer", + "description": "Current configuration version, for compare-and-swap", + "minimum": 1 + }, + "allow_sector_change": { + "type": "boolean", + "default": false, + "description": "Required (true) when the update changes the pairwise sector of an **active** client, i.e. the\n`sector_identifier_uri` host or, without one, the redirect host. Changing the sector changes the\n`sub` of every person at this partner; without this flag such an update is rejected with `409`.\n" + }, + "display_name": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "description": { + "type": "string", + "maxLength": 1024 + }, + "redirect_uris": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/HttpsUri" + } + }, + "sector_identifier_uri": { + "$ref": "#/components/schemas/HttpsUri" + }, + "initiate_login_uri": { + "$ref": "#/components/schemas/HttpsUri" + }, + "launch_target_prefixes": { + "type": "array", + "items": { + "type": "string" + } + }, + "scopes_allowed": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "PartnerClient": { + "type": "object", + "required": [ + "client_id", + "partner_key", + "display_name", + "environment", + "redirect_uris", + "token_endpoint_auth_method", + "grant_types", + "scopes_allowed", + "status", + "config_version", + "created_at", + "updated_at" + ], + "properties": { + "client_id": { + "$ref": "#/components/schemas/PartnerClientId" + }, + "partner_key": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "environment": { + "$ref": "#/components/schemas/Environment" + }, + "redirect_uris": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HttpsUri" + } + }, + "sector_identifier_uri": { + "$ref": "#/components/schemas/HttpsUri" + }, + "initiate_login_uri": { + "$ref": "#/components/schemas/HttpsUri" + }, + "launch_target_prefixes": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_endpoint_auth_method": { + "type": "string", + "enum": [ + "client_secret_basic" + ] + }, + "grant_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "authorization_code" + ] + } + }, + "scopes_allowed": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Scope" + } + }, + "status": { + "$ref": "#/components/schemas/PartnerClientStatus" + }, + "status_reason": { + "type": "string", + "description": "Operator-supplied reason for the last suspension or retirement" + }, + "config_version": { + "type": "integer", + "minimum": 1 + }, + "secret_rotated_at": { + "type": "string", + "format": "date-time" + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "description": "Until when the previous secret still authenticates after a rotation" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "created_by": { + "type": "string", + "description": "Admin-portal identity of the operator who created the client" + }, + "updated_by": { + "type": "string" + } + } + }, + "PartnerClientWithSecret": { + "allOf": [ + { + "$ref": "#/components/schemas/PartnerClient" + }, + { + "type": "object", + "required": [ + "client_secret" + ], + "properties": { + "client_secret": { + "type": "string", + "description": "Returned exactly once. Store it securely; it cannot be retrieved again." + } + } + } + ] + }, + "PartnerClientList": { + "type": "object", + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PartnerClient" + } + }, + "next_cursor": { + "type": "string", + "nullable": true + } + } + }, + "StatusChangeRequest": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "maxLength": 1024 + } + } + }, + "RotateSecretRequest": { + "type": "object", + "properties": { + "compromise_mode": { + "type": "boolean", + "default": false, + "description": "When true the previous secret stops working immediately" + }, + "migration_window_seconds": { + "type": "integer", + "minimum": 0, + "maximum": 2592000, + "default": 86400, + "description": "How long the previous secret keeps working (ignored in compromise mode)" + } + } + }, + "AuthorizationPolicyInput": { + "type": "object", + "required": [ + "allowed_scopes", + "customer_approval_ref", + "approved_at" + ], + "properties": { + "mode": { + "type": "string", + "enum": [ + "preauthorized" + ], + "default": "preauthorized" + }, + "population": { + "type": "string", + "enum": [ + "all_current_org_members" + ], + "default": "all_current_org_members" + }, + "allowed_scopes": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "description": "Must contain `openid` and be a subset of the client's `scopes_allowed`", + "items": { + "$ref": "#/components/schemas/Scope" + } + }, + "customer_approval_ref": { + "type": "string", + "description": "Reference to the customer's approval evidence (ticket, signed order, contract clause)", + "minLength": 1, + "maxLength": 512 + }, + "approved_at": { + "type": "string", + "format": "date-time" + } + } + }, + "AuthorizationPolicy": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthorizationPolicyInput" + }, + { + "type": "object", + "required": [ + "version", + "mandatory_claims", + "recorded_by", + "recorded_at" + ], + "properties": { + "version": { + "type": "integer", + "minimum": 1 + }, + "mandatory_claims": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "org_id" + ] + } + }, + "recorded_by": { + "type": "string" + }, + "recorded_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "PartnerAccount": { + "type": "object", + "description": "Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping.", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "maxLength": 256 + }, + "label": { + "type": "string", + "maxLength": 256 + } + } + }, + "OrgEnablementStatus": { + "type": "string", + "enum": [ + "active", + "suspended", + "revoked" + ] + }, + "OrgEnablementCreate": { + "type": "object", + "required": [ + "org_id", + "authorization_policy" + ], + "properties": { + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "authorization_policy": { + "$ref": "#/components/schemas/AuthorizationPolicyInput" + }, + "partner_account": { + "$ref": "#/components/schemas/PartnerAccount" + } + } + }, + "OrgEnablementUpdate": { + "type": "object", + "required": [ + "expected_policy_version", + "authorization_policy" + ], + "properties": { + "expected_policy_version": { + "type": "integer", + "minimum": 1, + "description": "Current policy version, for compare-and-swap" + }, + "authorization_policy": { + "$ref": "#/components/schemas/AuthorizationPolicyInput" + }, + "partner_account": { + "$ref": "#/components/schemas/PartnerAccount" + } + } + }, + "OrgEnablement": { + "type": "object", + "required": [ + "client_id", + "org_id", + "status", + "authorization_policy", + "created_at", + "updated_at" + ], + "properties": { + "client_id": { + "$ref": "#/components/schemas/PartnerClientId" + }, + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "status": { + "$ref": "#/components/schemas/OrgEnablementStatus" + }, + "status_reason": { + "type": "string" + }, + "authorization_policy": { + "$ref": "#/components/schemas/AuthorizationPolicy" + }, + "partner_account": { + "$ref": "#/components/schemas/PartnerAccount" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "created_by": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, + "OrgEnablementList": { + "type": "object", + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "IdentitySession": { + "type": "object", + "required": [ + "org_id", + "user_id", + "expires_at" + ], + "properties": { + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "user_id": { + "type": "string", + "description": "epilot user id of the session owner" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the mirrored credential, and therefore the cookie, expires" + } + } + } + } + } +} diff --git a/clients/identity-client/tsconfig.json b/clients/identity-client/tsconfig.json new file mode 100644 index 000000000..91a136dec --- /dev/null +++ b/clients/identity-client/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "lib": ["esnext", "dom"], + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "noImplicitAny": true, + "strictPropertyInitialization": false, + "strictNullChecks": false, + "resolveJsonModule": true, + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "sourceMap": true, + "declaration": true + }, + "include": ["src/**/*"], + "exclude": ["**/*.test.ts"] +} diff --git a/clients/identity-client/webpack.config.js b/clients/identity-client/webpack.config.js new file mode 100644 index 000000000..a80c07ecc --- /dev/null +++ b/clients/identity-client/webpack.config.js @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const path = require('node:path'); + +const CopyPlugin = require('copy-webpack-plugin'); + +module.exports = { + entry: './src/definition.ts', + mode: 'production', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + plugins: [ + new CopyPlugin({ + patterns: [ + { from: '*.d.ts', context: './src' }, + { from: '*.json', context: './src' }, + ], + }), + ], + output: { + path: path.join(__dirname, 'dist'), + filename: 'definition.js', + libraryTarget: 'commonjs', + }, +}; diff --git a/packages/cli/README.md b/packages/cli/README.md index 49c2dfa08..855c173ed 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -88,6 +88,7 @@ APIs event-catalog Event Catalog API file File API iban Iban API + identity Identity API integration-toolkit Integration Toolkit API journey Journey API kanban Kanban API @@ -346,6 +347,7 @@ Full documentation with sample calls and responses for all APIs: | Event Catalog API | `epilot event-catalog` | [docs](./docs/event-catalog.md) | | File API | `epilot file` | [docs](./docs/file.md) | | Iban API | `epilot iban` | [docs](./docs/iban.md) | +| Identity API | `epilot identity` | [docs](./docs/identity.md) | | Integration Toolkit API | `epilot integration-toolkit` | [docs](./docs/integration-toolkit.md) | | Journey API | `epilot journey` | [docs](./docs/journey.md) | | Kanban API | `epilot kanban` | [docs](./docs/kanban.md) | diff --git a/packages/cli/definitions/identity.json b/packages/cli/definitions/identity.json new file mode 100644 index 000000000..e650d0583 --- /dev/null +++ b/packages/cli/definitions/identity.json @@ -0,0 +1,1546 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Identity API", + "version": "1.0.0", + "description": "epilot as an OpenID Connect provider (\"Sign in with epilot\").\n\nThe standard OpenID Connect endpoints (discovery, JWKS, authorize, token, UserInfo, revocation)\nare described by the discovery document at `/.well-known/openid-configuration` and are not\nrepeated here. Partners integrate against that document with any certified OpenID Connect\nrelying-party library.\n\nThis specification covers the epilot-specific parts of the service:\n\n- the **operator API** used by epilot platform staff to register partner applications\n (clients) and to enable organizations on them,\n- the **360 session endpoints** that let the portal prove a live 360 session during an\n authorization (the session hop).\n\nTenant model: one confidential client per partner application and environment; the organization\na person is acting in travels in every ID token as the claim `https://id.epilot.cloud/claims/org_id`.\nA relying party must key everything on `(iss, sub, org_id)`.\n" + }, + "tags": [ + { + "name": "Operator Clients", + "description": "Register and manage partner applications (OIDC clients). Platform operators only." + }, + { + "name": "Operator Organizations", + "description": "Enable, suspend and revoke organizations on a partner client. Platform operators only." + }, + { + "name": "Session", + "description": "360 session hop used by the epilot portal during an authorization." + }, + { + "name": "Launch", + "description": "Start a partner login from an epilot link (OpenID Connect third-party initiated login)." + } + ], + "servers": [ + { + "url": "https://id.epilot.cloud" + } + ], + "security": [ + { + "OperatorAuth": [] + } + ], + "paths": { + "/v1/identity/operator/clients": { + "get": { + "operationId": "listClients", + "summary": "listClients", + "description": "List registered partner clients. The client secret is never returned.", + "tags": [ + "Operator Clients" + ], + "parameters": [ + { + "$ref": "#/components/parameters/PartnerKeyQuery" + }, + { + "$ref": "#/components/parameters/ClientStatusQuery" + }, + { + "$ref": "#/components/parameters/SizeQuery" + }, + { + "$ref": "#/components/parameters/CursorQuery" + } + ], + "responses": { + "200": { + "description": "Clients", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientList" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + }, + "post": { + "operationId": "createClient", + "summary": "createClient", + "description": "Create a partner client in `draft` status. The client secret is returned exactly once in this\nresponse and can never be read again; use `rotateClientSecret` to obtain a new one.\n", + "tags": [ + "Operator Clients" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Created client including its one-time secret", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientWithSecret" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "get": { + "operationId": "getClient", + "summary": "getClient", + "description": "Read a partner client. The client secret is never returned.", + "tags": [ + "Operator Clients" + ], + "responses": { + "200": { + "description": "Client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "operationId": "updateClient", + "summary": "updateClient", + "description": "Replace the mutable configuration of a client. `config_version` must equal the current\nversion (compare-and-swap); a mismatch is rejected with `409`.\n", + "tags": [ + "Operator Clients" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Updated client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + }, + "delete": { + "operationId": "retireClient", + "summary": "retireClient", + "description": "Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every\norganization enablement is revoked and all provider-side artefacts are revoked.\n", + "tags": [ + "Operator Clients" + ], + "responses": { + "200": { + "description": "Retired client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}:activate": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "post": { + "operationId": "activateClient", + "summary": "activateClient", + "description": "Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute\nHTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the\nauthentication method is `client_secret_basic`.\n", + "tags": [ + "Operator Clients" + ], + "responses": { + "200": { + "description": "Activated client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}:suspend": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "post": { + "operationId": "suspendClient", + "summary": "suspendClient", + "description": "Immediately block the client for every organization. Provider-side artefacts (codes, tokens,\ngrants) are revoked; the audit trail is kept. Reversible with `activateClient`.\n", + "tags": [ + "Operator Clients" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusChangeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Suspended client", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClient" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}:rotateSecret": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "post": { + "operationId": "rotateClientSecret", + "summary": "rotateClientSecret", + "description": "Issue a new client secret. The new secret is returned exactly once. The previous secret\nkeeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which\ncase it stops working immediately.\n", + "tags": [ + "Operator Clients" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RotateSecretRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Client including its new one-time secret", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PartnerClientWithSecret" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}/organizations": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "get": { + "operationId": "listClientOrganizations", + "summary": "listClientOrganizations", + "description": "List the organizations enabled on a client, including suspended and revoked ones.", + "tags": [ + "Operator Organizations" + ], + "parameters": [ + { + "$ref": "#/components/parameters/OrgEnablementStatusQuery" + } + ], + "responses": { + "200": { + "description": "Organization enablements", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablementList" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "post": { + "operationId": "enableClientOrganization", + "summary": "enableClientOrganization", + "description": "Enable an epilot organization on a client, recording the customer's authorization policy\n(version 1). `allowed_scopes` must contain `openid` and be a subset of the client's\n`scopes_allowed`.\n", + "tags": [ + "Operator Organizations" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablementCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Created enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}/organizations/{org_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + }, + { + "$ref": "#/components/parameters/OrgIdPath" + } + ], + "get": { + "operationId": "getClientOrganization", + "summary": "getClientOrganization", + "description": "Read one organization enablement.", + "tags": [ + "Operator Organizations" + ], + "responses": { + "200": { + "description": "Enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "operationId": "updateClientOrganization", + "summary": "updateClientOrganization", + "description": "Record a new version of the customer's authorization policy. `expected_policy_version` must\nequal the current version (compare-and-swap); a mismatch is rejected with `409`.\n", + "tags": [ + "Operator Organizations" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablementUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Updated enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + }, + "delete": { + "operationId": "revokeClientOrganization", + "summary": "revokeClientOrganization", + "description": "Revoke an organization's enablement. The record is kept as an audited tombstone; future\nauthorizations for this organization fail with `access_denied` and provider-side artefacts\nbound to it are revoked.\n", + "tags": [ + "Operator Organizations" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusChangeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Revoked enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + }, + { + "$ref": "#/components/parameters/OrgIdPath" + } + ], + "post": { + "operationId": "suspendClientOrganization", + "summary": "suspendClientOrganization", + "description": "Temporarily block one organization on this client. Reversible with `activateClientOrganization`.", + "tags": [ + "Operator Organizations" + ], + "requestBody": { + "required": false, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusChangeRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Suspended enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + }, + { + "$ref": "#/components/parameters/OrgIdPath" + } + ], + "post": { + "operationId": "activateClientOrganization", + "summary": "activateClientOrganization", + "description": "Resume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy version.", + "tags": [ + "Operator Organizations" + ], + "responses": { + "200": { + "description": "Active enablement", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "409": { + "$ref": "#/components/responses/Conflict" + } + } + } + }, + "/v1/identity/session": { + "get": { + "operationId": "createIdentitySession", + "summary": "createIdentitySession", + "description": "Called by the epilot portal through the portal-host proxy after every persisted 360 session.\nValidates the 360 bearer token itself and mirrors it into the `identity_session` cookie\n(`HttpOnly; Secure; Path=/v1/identity`) whose lifetime equals the remaining token lifetime.\nThe cookie is what proves the live 360 session during `completeInteraction`.\n", + "tags": [ + "Session" + ], + "security": [ + { + "EpilotAuth": [] + } + ], + "responses": { + "200": { + "description": "Session mirrored into the cookie", + "headers": { + "Set-Cookie": { + "description": "`identity_session=<360 ID token>; HttpOnly; Secure; SameSite=None; Path=/v1/identity; Max-Age=`", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IdentitySession" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "delete": { + "operationId": "deleteIdentitySession", + "summary": "deleteIdentitySession", + "description": "Clears the `identity_session` cookie. Called by the portal on logout.", + "tags": [ + "Session" + ], + "security": [], + "responses": { + "204": { + "description": "Cookie cleared", + "headers": { + "Set-Cookie": { + "description": "Expired `identity_session` cookie", + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/v1/identity/interaction/{interaction_id}": { + "parameters": [ + { + "name": "interaction_id", + "in": "path", + "required": true, + "description": "Opaque identifier of the parked authorization request", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + ] + }, + "/v1/identity/launch/{client_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/ClientIdPath" + } + ], + "get": { + "operationId": "launchClient", + "summary": "launchClient", + "description": "One validated place for partner start links (manager UI, hosted apps). Redirects the browser to the\nclient's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep link as\n`target_link_uri` (OpenID Connect Core §4, third-party initiated login). Authenticates nobody: the partner\nthen starts a normal \"Sign in with epilot\" authorization.\n\n`target` must be an absolute HTTPS URL starting with one of the client's registered\n`launch_target_prefixes`; anything else is rejected. Retired, suspended or draft clients, and clients\nwithout an `initiate_login_uri`, answer `404`.\n", + "tags": [ + "Launch" + ], + "security": [], + "parameters": [ + { + "name": "target", + "in": "query", + "required": false, + "description": "Deep link inside the partner application to continue to after login", + "schema": { + "type": "string", + "format": "uri", + "maxLength": 2048 + } + }, + { + "$ref": "#/components/parameters/AnyOtherQueryParameters" + } + ], + "responses": { + "302": { + "description": "Redirect to the partner's initiate_login_uri", + "headers": { + "Location": { + "schema": { + "type": "string", + "format": "uri" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + } + }, + "components": { + "securitySchemes": { + "OperatorAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "Internal-auth bearer token minted for an epilot admin-portal user. Customer organization\nbearers are never accepted; the caller must belong to an allowed admin-portal group.\n" + }, + "EpilotAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "Authorization header with the caller's epilot 360 bearer token." + }, + "IdentitySessionCookie": { + "type": "apiKey", + "in": "cookie", + "name": "identity_session", + "description": "Cookie set by `createIdentitySession`, carrying the live 360 credential." + } + }, + "parameters": { + "AnyOtherQueryParameters": { + "name": "extra", + "in": "query", + "required": false, + "description": "Any other query parameters (marketing tags on a start link, parameters a proxy appends) are accepted\nand ignored. Start links and the session hop must never fail on an unknown parameter.\n", + "style": "form", + "explode": true, + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "ClientIdPath": { + "name": "client_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/PartnerClientId" + } + }, + "OrgIdPath": { + "name": "org_id", + "in": "path", + "required": true, + "schema": { + "$ref": "#/components/schemas/OrgId" + } + }, + "PartnerKeyQuery": { + "name": "partner_key", + "in": "query", + "required": false, + "description": "Filter by partner key", + "schema": { + "type": "string" + } + }, + "ClientStatusQuery": { + "name": "status", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/PartnerClientStatus" + } + }, + "OrgEnablementStatusQuery": { + "name": "status", + "in": "query", + "required": false, + "schema": { + "$ref": "#/components/schemas/OrgEnablementStatus" + } + }, + "SizeQuery": { + "name": "size", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + } + }, + "CursorQuery": { + "name": "cursor", + "in": "query", + "required": false, + "description": "Opaque cursor from a previous response", + "schema": { + "type": "string" + } + } + }, + "responses": { + "BadRequest": { + "description": "Invalid request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "Unauthorized": { + "description": "Missing or invalid credentials", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "Forbidden": { + "description": "Caller is not a platform operator", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "NotFound": { + "description": "Not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "Conflict": { + "description": "Version mismatch or invalid state transition", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "schemas": { + "Error": { + "type": "object", + "required": [ + "status", + "error" + ], + "properties": { + "status": { + "type": "integer", + "example": 404 + }, + "error": { + "type": "string", + "example": "Client not found" + }, + "details": { + "type": "array", + "description": "Request validation problems (only on `400` from schema validation)", + "items": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "PartnerClientId": { + "type": "string", + "description": "Opaque client identifier, unique within the issuer, never reused", + "example": "bf-prod-x7k2", + "minLength": 1, + "maxLength": 128, + "pattern": "^[A-Za-z0-9._~-]+$" + }, + "OrgId": { + "type": "string", + "description": "epilot organization id, an opaque case-sensitive string", + "example": "739224", + "minLength": 1, + "maxLength": 64, + "pattern": "^[A-Za-z0-9_-]+$" + }, + "Environment": { + "type": "string", + "description": "Partner-side environment this client belongs to", + "enum": [ + "dev", + "staging", + "production" + ] + }, + "PartnerClientStatus": { + "type": "string", + "enum": [ + "draft", + "active", + "suspended", + "retired" + ] + }, + "Scope": { + "type": "string", + "enum": [ + "openid", + "email", + "profile" + ] + }, + "HttpsUri": { + "type": "string", + "format": "uri", + "description": "Absolute HTTPS URL without fragment", + "example": "https://partner.example.com/auth/callback" + }, + "PartnerClientCreate": { + "type": "object", + "required": [ + "partner_key", + "display_name", + "environment", + "redirect_uris" + ], + "properties": { + "partner_key": { + "type": "string", + "description": "Stable key of the partner application, shared by its environments", + "example": "babelforce", + "minLength": 1, + "maxLength": 64, + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "display_name": { + "type": "string", + "example": "Babelforce", + "minLength": 1, + "maxLength": 128 + }, + "description": { + "type": "string", + "maxLength": 1024 + }, + "environment": { + "$ref": "#/components/schemas/Environment" + }, + "redirect_uris": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/HttpsUri" + } + }, + "sector_identifier_uri": { + "allOf": [ + { + "$ref": "#/components/schemas/HttpsUri" + } + ], + "description": "Optional. Pairwise sector for `sub` derivation. Required when the redirect URIs span\nmore than one host, so one person keeps one `sub` at this partner.\n" + }, + "initiate_login_uri": { + "allOf": [ + { + "$ref": "#/components/schemas/HttpsUri" + } + ], + "description": "Where a login started from epilot should begin at the partner" + }, + "launch_target_prefixes": { + "type": "array", + "description": "Allowed prefixes for deep-link targets when a login is started from epilot", + "items": { + "type": "string" + } + }, + "scopes_allowed": { + "type": "array", + "description": "Upper bound for every organization enablement's `allowed_scopes`. Defaults to all scopes.", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "PartnerClientUpdate": { + "type": "object", + "required": [ + "config_version", + "display_name", + "redirect_uris", + "scopes_allowed" + ], + "properties": { + "config_version": { + "type": "integer", + "description": "Current configuration version, for compare-and-swap", + "minimum": 1 + }, + "allow_sector_change": { + "type": "boolean", + "default": false, + "description": "Required (true) when the update changes the pairwise sector of an **active** client, i.e. the\n`sector_identifier_uri` host or, without one, the redirect host. Changing the sector changes the\n`sub` of every person at this partner; without this flag such an update is rejected with `409`.\n" + }, + "display_name": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "description": { + "type": "string", + "maxLength": 1024 + }, + "redirect_uris": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/components/schemas/HttpsUri" + } + }, + "sector_identifier_uri": { + "$ref": "#/components/schemas/HttpsUri" + }, + "initiate_login_uri": { + "$ref": "#/components/schemas/HttpsUri" + }, + "launch_target_prefixes": { + "type": "array", + "items": { + "type": "string" + } + }, + "scopes_allowed": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/components/schemas/Scope" + } + } + } + }, + "PartnerClient": { + "type": "object", + "required": [ + "client_id", + "partner_key", + "display_name", + "environment", + "redirect_uris", + "token_endpoint_auth_method", + "grant_types", + "scopes_allowed", + "status", + "config_version", + "created_at", + "updated_at" + ], + "properties": { + "client_id": { + "$ref": "#/components/schemas/PartnerClientId" + }, + "partner_key": { + "type": "string" + }, + "display_name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "environment": { + "$ref": "#/components/schemas/Environment" + }, + "redirect_uris": { + "type": "array", + "items": { + "$ref": "#/components/schemas/HttpsUri" + } + }, + "sector_identifier_uri": { + "$ref": "#/components/schemas/HttpsUri" + }, + "initiate_login_uri": { + "$ref": "#/components/schemas/HttpsUri" + }, + "launch_target_prefixes": { + "type": "array", + "items": { + "type": "string" + } + }, + "token_endpoint_auth_method": { + "type": "string", + "enum": [ + "client_secret_basic" + ] + }, + "grant_types": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "authorization_code" + ] + } + }, + "scopes_allowed": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Scope" + } + }, + "status": { + "$ref": "#/components/schemas/PartnerClientStatus" + }, + "status_reason": { + "type": "string", + "description": "Operator-supplied reason for the last suspension or retirement" + }, + "config_version": { + "type": "integer", + "minimum": 1 + }, + "secret_rotated_at": { + "type": "string", + "format": "date-time" + }, + "previous_secret_expires_at": { + "type": "string", + "format": "date-time", + "description": "Until when the previous secret still authenticates after a rotation" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "created_by": { + "type": "string", + "description": "Admin-portal identity of the operator who created the client" + }, + "updated_by": { + "type": "string" + } + } + }, + "PartnerClientWithSecret": { + "allOf": [ + { + "$ref": "#/components/schemas/PartnerClient" + }, + { + "type": "object", + "required": [ + "client_secret" + ], + "properties": { + "client_secret": { + "type": "string", + "description": "Returned exactly once. Store it securely; it cannot be retrieved again." + } + } + } + ] + }, + "PartnerClientList": { + "type": "object", + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PartnerClient" + } + }, + "next_cursor": { + "type": "string", + "nullable": true + } + } + }, + "StatusChangeRequest": { + "type": "object", + "properties": { + "reason": { + "type": "string", + "maxLength": 1024 + } + } + }, + "RotateSecretRequest": { + "type": "object", + "properties": { + "compromise_mode": { + "type": "boolean", + "default": false, + "description": "When true the previous secret stops working immediately" + }, + "migration_window_seconds": { + "type": "integer", + "minimum": 0, + "maximum": 2592000, + "default": 86400, + "description": "How long the previous secret keeps working (ignored in compromise mode)" + } + } + }, + "AuthorizationPolicyInput": { + "type": "object", + "required": [ + "allowed_scopes", + "customer_approval_ref", + "approved_at" + ], + "properties": { + "mode": { + "type": "string", + "enum": [ + "preauthorized" + ], + "default": "preauthorized" + }, + "population": { + "type": "string", + "enum": [ + "all_current_org_members" + ], + "default": "all_current_org_members" + }, + "allowed_scopes": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "description": "Must contain `openid` and be a subset of the client's `scopes_allowed`", + "items": { + "$ref": "#/components/schemas/Scope" + } + }, + "customer_approval_ref": { + "type": "string", + "description": "Reference to the customer's approval evidence (ticket, signed order, contract clause)", + "minLength": 1, + "maxLength": 512 + }, + "approved_at": { + "type": "string", + "format": "date-time" + } + } + }, + "AuthorizationPolicy": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthorizationPolicyInput" + }, + { + "type": "object", + "required": [ + "version", + "mandatory_claims", + "recorded_by", + "recorded_at" + ], + "properties": { + "version": { + "type": "integer", + "minimum": 1 + }, + "mandatory_claims": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "org_id" + ] + } + }, + "recorded_by": { + "type": "string" + }, + "recorded_at": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "PartnerAccount": { + "type": "object", + "description": "Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping.", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "maxLength": 256 + }, + "label": { + "type": "string", + "maxLength": 256 + } + } + }, + "OrgEnablementStatus": { + "type": "string", + "enum": [ + "active", + "suspended", + "revoked" + ] + }, + "OrgEnablementCreate": { + "type": "object", + "required": [ + "org_id", + "authorization_policy" + ], + "properties": { + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "authorization_policy": { + "$ref": "#/components/schemas/AuthorizationPolicyInput" + }, + "partner_account": { + "$ref": "#/components/schemas/PartnerAccount" + } + } + }, + "OrgEnablementUpdate": { + "type": "object", + "required": [ + "expected_policy_version", + "authorization_policy" + ], + "properties": { + "expected_policy_version": { + "type": "integer", + "minimum": 1, + "description": "Current policy version, for compare-and-swap" + }, + "authorization_policy": { + "$ref": "#/components/schemas/AuthorizationPolicyInput" + }, + "partner_account": { + "$ref": "#/components/schemas/PartnerAccount" + } + } + }, + "OrgEnablement": { + "type": "object", + "required": [ + "client_id", + "org_id", + "status", + "authorization_policy", + "created_at", + "updated_at" + ], + "properties": { + "client_id": { + "$ref": "#/components/schemas/PartnerClientId" + }, + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "status": { + "$ref": "#/components/schemas/OrgEnablementStatus" + }, + "status_reason": { + "type": "string" + }, + "authorization_policy": { + "$ref": "#/components/schemas/AuthorizationPolicy" + }, + "partner_account": { + "$ref": "#/components/schemas/PartnerAccount" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "created_by": { + "type": "string" + }, + "updated_by": { + "type": "string" + } + } + }, + "OrgEnablementList": { + "type": "object", + "required": [ + "results" + ], + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrgEnablement" + } + } + } + }, + "IdentitySession": { + "type": "object", + "required": [ + "org_id", + "user_id", + "expires_at" + ], + "properties": { + "org_id": { + "$ref": "#/components/schemas/OrgId" + }, + "user_id": { + "type": "string", + "description": "epilot user id of the session owner" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the mirrored credential, and therefore the cookie, expires" + } + } + } + } + } +} diff --git a/packages/cli/docs/identity.md b/packages/cli/docs/identity.md new file mode 100644 index 000000000..a3ccdb271 --- /dev/null +++ b/packages/cli/docs/identity.md @@ -0,0 +1,1277 @@ +# Identity API + +- **Base URL:** `https://id.epilot.cloud` +- **API Docs:** [https://docs.epilot.io/api/identity](https://docs.epilot.io/api/identity) + +epilot as an OpenID Connect provider ("Sign in with epilot"). + +## Quick Start + +```bash +# List available operations +epilot identity + +# Call an operation +epilot identity listClients +``` + +## Common Flags + +| Flag | Description | +| ---- | ----------- | +| `-p key=value` | Set a named parameter | +| `-d '{...}'` | Request body JSON | +| `-H 'Key: Value'` | Custom header | +| `-t, --token ` | Bearer token for authentication | +| `--profile ` | Use a named profile | +| `-s, --server ` | Override server base URL | +| `-i, --include` | Include response headers in output | +| `--json` | Output raw JSON (no formatting) | +| `-v, --verbose` | Verbose output (show request details) | +| `--jsonata ` | JSONata expression to transform response | +| `--definition ` | Override OpenAPI spec file/URL | +| `--guided` | Prompt for all parameters interactively | +| `--no-interactive` | Disable interactive prompts | + +## Operations + +**Operator Clients** +- [`listClients`](#listclients) — List registered partner clients. The client secret is never returned. +- [`createClient`](#createclient) — Create a partner client in `draft` status. The client secret is returned exactly once in this +- [`getClient`](#getclient) — Read a partner client. The client secret is never returned. +- [`updateClient`](#updateclient) — Replace the mutable configuration of a client. `config_version` must equal the current +- [`retireClient`](#retireclient) — Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every +- [`activateClient`](#activateclient) — Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute +- [`suspendClient`](#suspendclient) — Immediately block the client for every organization. Provider-side artefacts (codes, tokens, +- [`rotateClientSecret`](#rotateclientsecret) — Issue a new client secret. The new secret is returned exactly once. The previous secret + +**Operator Organizations** +- [`listClientOrganizations`](#listclientorganizations) — List the organizations enabled on a client, including suspended and revoked ones. +- [`enableClientOrganization`](#enableclientorganization) — Enable an epilot organization on a client, recording the customer's authorization policy +- [`getClientOrganization`](#getclientorganization) — Read one organization enablement. +- [`updateClientOrganization`](#updateclientorganization) — Record a new version of the customer's authorization policy. `expected_policy_version` must +- [`revokeClientOrganization`](#revokeclientorganization) — Revoke an organization's enablement. The record is kept as an audited tombstone; future +- [`suspendClientOrganization`](#suspendclientorganization) — Temporarily block one organization on this client. Reversible with `activateClientOrganization`. +- [`activateClientOrganization`](#activateclientorganization) — Resume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization ag + +**Session** +- [`createIdentitySession`](#createidentitysession) — Called by the epilot portal through the portal-host proxy after every persisted 360 session. +- [`deleteIdentitySession`](#deleteidentitysession) — Clears the `identity_session` cookie. Called by the portal on logout. + +**Launch** +- [`launchClient`](#launchclient) — One validated place for partner start links (manager UI, hosted apps). Redirects the browser to the + +### `listClients` + +List registered partner clients. The client secret is never returned. + +`GET /v1/identity/operator/clients` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `partner_key` | query | string | No | Filter by partner key | +| `status` | query | "draft" \| "active" \| "suspended" \| "retired" | No | | +| `size` | query | number | No | | +| `cursor` | query | string | No | Opaque cursor from a previous response | + +**Sample Call** + +```bash +epilot identity listClients +``` + +With JSONata filter: + +```bash +epilot identity listClients --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" + } + ], + "next_cursor": "string" +} +``` + +
+ +--- + +### `createClient` + +Create a partner client in `draft` status. The client secret is returned exactly once in this + +`POST /v1/identity/operator/clients` + +**Request Body** (required) + +**Sample Call** + +```bash +epilot identity createClient +``` + +With request body: + +```bash +epilot identity createClient \ + -d '{ + "partner_key": "babelforce", + "display_name": "Babelforce", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "scopes_allowed": ["openid"] +}' +``` + +Using stdin pipe: + +```bash +cat body.json | epilot identity createClient +``` + +With JSONata filter: + +```bash +epilot identity createClient --jsonata '$' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string", + "client_secret": "string" +} +``` + +
+ +--- + +### `getClient` + +Read a partner client. The client secret is never returned. + +`GET /v1/identity/operator/clients/{client_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot identity getClient \ + -p client_id=bf-prod-x7k2 +``` + +Using positional args for path parameters: + +```bash +epilot identity getClient bf-prod-x7k2 +``` + +With JSONata filter: + +```bash +epilot identity getClient -p client_id=bf-prod-x7k2 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `updateClient` + +Replace the mutable configuration of a client. `config_version` must equal the current + +`PUT /v1/identity/operator/clients/{client_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot identity updateClient \ + -p client_id=bf-prod-x7k2 +``` + +With request body: + +```bash +epilot identity updateClient \ + -p client_id=bf-prod-x7k2 \ + -d '{ + "config_version": 1, + "allow_sector_change": false, + "display_name": "string", + "description": "string", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "scopes_allowed": ["openid"] +}' +``` + +Using positional args for path parameters: + +```bash +epilot identity updateClient bf-prod-x7k2 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot identity updateClient -p client_id=bf-prod-x7k2 +``` + +With JSONata filter: + +```bash +epilot identity updateClient -p client_id=bf-prod-x7k2 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `retireClient` + +Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every + +`DELETE /v1/identity/operator/clients/{client_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot identity retireClient \ + -p client_id=bf-prod-x7k2 +``` + +Using positional args for path parameters: + +```bash +epilot identity retireClient bf-prod-x7k2 +``` + +With JSONata filter: + +```bash +epilot identity retireClient -p client_id=bf-prod-x7k2 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `activateClient` + +Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute + +`POST /v1/identity/operator/clients/{client_id}:activate` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot identity activateClient \ + -p client_id=bf-prod-x7k2 +``` + +Using positional args for path parameters: + +```bash +epilot identity activateClient bf-prod-x7k2 +``` + +With JSONata filter: + +```bash +epilot identity activateClient -p client_id=bf-prod-x7k2 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `suspendClient` + +Immediately block the client for every organization. Provider-side artefacts (codes, tokens, + +`POST /v1/identity/operator/clients/{client_id}:suspend` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot identity suspendClient \ + -p client_id=bf-prod-x7k2 \ + -d '{"reason":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot identity suspendClient bf-prod-x7k2 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot identity suspendClient -p client_id=bf-prod-x7k2 +``` + +With JSONata filter: + +```bash +epilot identity suspendClient -p client_id=bf-prod-x7k2 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `rotateClientSecret` + +Issue a new client secret. The new secret is returned exactly once. The previous secret + +`POST /v1/identity/operator/clients/{client_id}:rotateSecret` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot identity rotateClientSecret \ + -p client_id=bf-prod-x7k2 \ + -d '{"compromise_mode":false,"migration_window_seconds":86400}' +``` + +Using positional args for path parameters: + +```bash +epilot identity rotateClientSecret bf-prod-x7k2 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot identity rotateClientSecret -p client_id=bf-prod-x7k2 +``` + +With JSONata filter: + +```bash +epilot identity rotateClientSecret -p client_id=bf-prod-x7k2 --jsonata '$' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string", + "client_secret": "string" +} +``` + +
+ +--- + +### `listClientOrganizations` + +List the organizations enabled on a client, including suspended and revoked ones. + +`GET /v1/identity/operator/clients/{client_id}/organizations` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | +| `status` | query | "active" \| "suspended" \| "revoked" | No | | + +**Sample Call** + +```bash +epilot identity listClientOrganizations \ + -p client_id=bf-prod-x7k2 +``` + +Using positional args for path parameters: + +```bash +epilot identity listClientOrganizations bf-prod-x7k2 +``` + +With JSONata filter: + +```bash +epilot identity listClientOrganizations -p client_id=bf-prod-x7k2 --jsonata 'results[0]' +``` + +
+Sample Response + +```json +{ + "results": [ + { + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" + } + ] +} +``` + +
+ +--- + +### `enableClientOrganization` + +Enable an epilot organization on a client, recording the customer's authorization policy + +`POST /v1/identity/operator/clients/{client_id}/organizations` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot identity enableClientOrganization \ + -p client_id=bf-prod-x7k2 +``` + +With request body: + +```bash +epilot identity enableClientOrganization \ + -p client_id=bf-prod-x7k2 \ + -d '{ + "org_id": "739224", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + } +}' +``` + +Using positional args for path parameters: + +```bash +epilot identity enableClientOrganization bf-prod-x7k2 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot identity enableClientOrganization -p client_id=bf-prod-x7k2 +``` + +With JSONata filter: + +```bash +epilot identity enableClientOrganization -p client_id=bf-prod-x7k2 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `getClientOrganization` + +Read one organization enablement. + +`GET /v1/identity/operator/clients/{client_id}/organizations/{org_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | +| `org_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot identity getClientOrganization \ + -p client_id=bf-prod-x7k2 \ + -p org_id=739224 +``` + +Using positional args for path parameters: + +```bash +epilot identity getClientOrganization bf-prod-x7k2 739224 +``` + +With JSONata filter: + +```bash +epilot identity getClientOrganization -p client_id=bf-prod-x7k2 -p org_id=739224 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `updateClientOrganization` + +Record a new version of the customer's authorization policy. `expected_policy_version` must + +`PUT /v1/identity/operator/clients/{client_id}/organizations/{org_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | +| `org_id` | path | string | Yes | | + +**Request Body** (required) + +**Sample Call** + +```bash +epilot identity updateClientOrganization \ + -p client_id=bf-prod-x7k2 \ + -p org_id=739224 +``` + +With request body: + +```bash +epilot identity updateClientOrganization \ + -p client_id=bf-prod-x7k2 \ + -p org_id=739224 \ + -d '{ + "expected_policy_version": 1, + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + } +}' +``` + +Using positional args for path parameters: + +```bash +epilot identity updateClientOrganization bf-prod-x7k2 739224 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot identity updateClientOrganization -p client_id=bf-prod-x7k2 -p org_id=739224 +``` + +With JSONata filter: + +```bash +epilot identity updateClientOrganization -p client_id=bf-prod-x7k2 -p org_id=739224 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `revokeClientOrganization` + +Revoke an organization's enablement. The record is kept as an audited tombstone; future + +`DELETE /v1/identity/operator/clients/{client_id}/organizations/{org_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | +| `org_id` | path | string | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot identity revokeClientOrganization \ + -p client_id=bf-prod-x7k2 \ + -p org_id=739224 \ + -d '{"reason":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot identity revokeClientOrganization bf-prod-x7k2 739224 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot identity revokeClientOrganization -p client_id=bf-prod-x7k2 -p org_id=739224 +``` + +With JSONata filter: + +```bash +epilot identity revokeClientOrganization -p client_id=bf-prod-x7k2 -p org_id=739224 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `suspendClientOrganization` + +Temporarily block one organization on this client. Reversible with `activateClientOrganization`. + +`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | +| `org_id` | path | string | Yes | | + +**Request Body** + +**Sample Call** + +```bash +epilot identity suspendClientOrganization \ + -p client_id=bf-prod-x7k2 \ + -p org_id=739224 \ + -d '{"reason":"string"}' +``` + +Using positional args for path parameters: + +```bash +epilot identity suspendClientOrganization bf-prod-x7k2 739224 +``` + +Using stdin pipe: + +```bash +cat body.json | epilot identity suspendClientOrganization -p client_id=bf-prod-x7k2 -p org_id=739224 +``` + +With JSONata filter: + +```bash +epilot identity suspendClientOrganization -p client_id=bf-prod-x7k2 -p org_id=739224 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `activateClientOrganization` + +Resume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization ag + +`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | +| `org_id` | path | string | Yes | | + +**Sample Call** + +```bash +epilot identity activateClientOrganization \ + -p client_id=bf-prod-x7k2 \ + -p org_id=739224 +``` + +Using positional args for path parameters: + +```bash +epilot identity activateClientOrganization bf-prod-x7k2 739224 +``` + +With JSONata filter: + +```bash +epilot identity activateClientOrganization -p client_id=bf-prod-x7k2 -p org_id=739224 --jsonata 'client_id' +``` + +
+Sample Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `createIdentitySession` + +Called by the epilot portal through the portal-host proxy after every persisted 360 session. + +`GET /v1/identity/session` + +**Sample Call** + +```bash +epilot identity createIdentitySession +``` + +With JSONata filter: + +```bash +epilot identity createIdentitySession --jsonata 'org_id' +``` + +
+Sample Response + +```json +{ + "org_id": "739224", + "user_id": "string", + "expires_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `deleteIdentitySession` + +Clears the `identity_session` cookie. Called by the portal on logout. + +`DELETE /v1/identity/session` + +**Sample Call** + +```bash +epilot identity deleteIdentitySession +``` + +With JSONata filter: + +```bash +epilot identity deleteIdentitySession --jsonata '$' +``` + +--- + +### `launchClient` + +One validated place for partner start links (manager UI, hosted apps). Redirects the browser to the + +`GET /v1/identity/launch/{client_id}` + +**Parameters** + +| Name | In | Type | Required | Description | +| ---- | -- | ---- | -------- | ----------- | +| `client_id` | path | string | Yes | | +| `target` | query | string (uri) | No | Deep link inside the partner application to continue to after login | +| `extra` | query | object | No | Any other query parameters (marketing tags on a start link, parameters a proxy appends) are accepted +and ignored. Start links and the session hop must never fail on an unknown parameter. + | + +**Sample Call** + +```bash +epilot identity launchClient \ + -p client_id=bf-prod-x7k2 +``` + +Using positional args for path parameters: + +```bash +epilot identity launchClient bf-prod-x7k2 +``` + +With JSONata filter: + +```bash +epilot identity launchClient -p client_id=bf-prod-x7k2 --jsonata '$' +``` + +--- diff --git a/packages/cli/docs/index.md b/packages/cli/docs/index.md index 6d2941c71..bd6e288ed 100644 --- a/packages/cli/docs/index.md +++ b/packages/cli/docs/index.md @@ -1,6 +1,6 @@ # epilot CLI — API Reference -All 52 epilot APIs available via the CLI. +All 53 epilot APIs available via the CLI. ## Setup @@ -39,6 +39,7 @@ npx epilot auth login | Event Catalog API | `epilot event-catalog` | 15 | [event-catalog.md](./event-catalog.md) | | File API | `epilot file` | 38 | [file.md](./file.md) | | Iban API | `epilot iban` | 1 | [iban.md](./iban.md) | +| Identity API | `epilot identity` | 18 | [identity.md](./identity.md) | | Integration Toolkit API | `epilot integration-toolkit` | 70 | [integration-toolkit.md](./integration-toolkit.md) | | Journey API | `epilot journey` | 23 | [journey.md](./journey.md) | | Kanban API | `epilot kanban` | 10 | [kanban.md](./kanban.md) | diff --git a/packages/cli/src/commands/apis/identity.ts b/packages/cli/src/commands/apis/identity.ts new file mode 100644 index 000000000..6c823cff3 --- /dev/null +++ b/packages/cli/src/commands/apis/identity.ts @@ -0,0 +1,48 @@ +// Auto-generated by scripts/generate.ts — do not edit +import { defineCommand } from 'citty'; +import { callApi } from '../../lib/call.js'; + +export default defineCommand({ + meta: { name: 'identity', description: 'Identity API' }, + args: { + operation: { type: 'positional', description: 'operationId to call', required: false }, + param: { type: 'string', alias: 'p', description: 'Parameter key=value' }, + data: { type: 'string', alias: 'd', description: 'Request body JSON' }, + header: { type: 'string', alias: 'H', description: 'Custom header' }, + include: { type: 'boolean', alias: 'i', description: 'Include response headers' }, + definition: { type: 'string', description: 'Override OpenAPI spec file/URL' }, + server: { type: 'string', alias: 's', description: 'Override server base URL' }, + 'use-dev': { type: 'boolean', description: 'Target dev environment' }, + 'use-staging': { type: 'boolean', description: 'Target staging environment' }, + profile: { type: 'string', description: 'Use a named profile' }, + token: { type: 'string', alias: 't', description: 'Bearer token' }, + json: { type: 'boolean', description: 'Output raw JSON' }, + verbose: { type: 'boolean', alias: 'v', description: 'Verbose output' }, + guided: { type: 'boolean', description: 'Prompt for all parameters interactively' }, + interactive: { type: 'boolean', description: 'Interactive mode' }, + jsonata: { type: 'string', description: 'JSONata expression to transform response' }, + _ophelp: { type: 'boolean', description: 'Show operation help', required: false }, + _apihelp: { type: 'boolean', description: 'Show API help', required: false }, + }, + run: ({ args, rawArgs }) => { + // Extract additional positional args (after operationId) + const positionalArgs: string[] = []; + if (args.operation && rawArgs) { + const opIdx = rawArgs.indexOf(args.operation); + if (opIdx >= 0) { + for (let i = opIdx + 1; i < rawArgs.length; i++) { + const arg = rawArgs[i]; + if (arg.startsWith('-')) break; + positionalArgs.push(arg); + } + } + } + + return callApi('identity', { + ...args, + help: !!(args as Record)._ophelp, + _apihelp: !!(args as Record)._apihelp, + _args: positionalArgs, + }); + }, +}); diff --git a/packages/cli/src/generated/api-list.ts b/packages/cli/src/generated/api-list.ts index b011edc55..acc292a10 100644 --- a/packages/cli/src/generated/api-list.ts +++ b/packages/cli/src/generated/api-list.ts @@ -917,6 +917,33 @@ export const API_LIST: ApiInfo[] = [ operationCount: 1, operationIds: ['validateIban'], }, + { + apiName: 'identity', + kebabName: 'identity', + title: 'Identity API', + serverUrl: 'https://id.epilot.cloud', + operationCount: 18, + operationIds: [ + 'listClients', + 'createClient', + 'getClient', + 'updateClient', + 'retireClient', + 'activateClient', + 'suspendClient', + 'rotateClientSecret', + 'listClientOrganizations', + 'enableClientOrganization', + 'getClientOrganization', + 'updateClientOrganization', + 'revokeClientOrganization', + 'suspendClientOrganization', + 'activateClientOrganization', + 'createIdentitySession', + 'deleteIdentitySession', + 'launchClient', + ], + }, { apiName: 'integrationToolkit', kebabName: 'integration-toolkit', @@ -1256,7 +1283,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'pricing', title: 'Pricing API', serverUrl: 'https://pricing-api.sls.epilot.io', - operationCount: 39, + operationCount: 34, operationIds: [ '$calculatePricingDetails', 'createOrder', @@ -1283,20 +1310,15 @@ export const API_LIST: ApiInfo[] = [ '$getConditionSets', '$resolveConditionalEntity', '$createConditionalVariant', - '$listConditionalVariants', - '$getConditionalVariantTree', '$getActiveConditionalVariantVersion', '$replaceActiveConditionalVariantVersion', '$patchActiveConditionalVariantVersion', '$deleteConditionalVariant', - '$listConditionalVariantVersions', '$appendConditionalVariantVersion', '$getConditionalVariantVersion', '$replaceConditionalVariantVersion', '$patchConditionalVariantVersion', '$deleteConditionalVariantVersion', - '$batchUpsertConditionalVariants', - '$batchDeleteConditionalVariants', ], }, { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index eda1fd464..5fe0e331c 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -3,7 +3,7 @@ import { defineCommand } from 'citty'; export const main = defineCommand({ meta: { name: 'epilot', - version: '0.1.168', + version: '0.1.170', description: 'CLI for epilot APIs', }, args: { @@ -51,6 +51,7 @@ export const main = defineCommand({ 'event-catalog': () => import('./commands/apis/event-catalog.js').then((m) => m.default), file: () => import('./commands/apis/file.js').then((m) => m.default), iban: () => import('./commands/apis/iban.js').then((m) => m.default), + identity: () => import('./commands/apis/identity.js').then((m) => m.default), 'integration-toolkit': () => import('./commands/apis/integration-toolkit.js').then((m) => m.default), journey: () => import('./commands/apis/journey.js').then((m) => m.default), kanban: () => import('./commands/apis/kanban.js').then((m) => m.default), diff --git a/packages/epilot-sdk-v2/README.md b/packages/epilot-sdk-v2/README.md index abe1f26f0..3ef53af94 100644 --- a/packages/epilot-sdk-v2/README.md +++ b/packages/epilot-sdk-v2/README.md @@ -67,6 +67,7 @@ Full API documentation: [https://docs.epilot.io/api](https://docs.epilot.io/api) | `epilot.eventCatalog` | `@epilot/sdk/event-catalog` | [docs](./docs/event-catalog.md) | | `epilot.file` | `@epilot/sdk/file` | [docs](./docs/file.md) | | `epilot.iban` | `@epilot/sdk/iban` | [docs](./docs/iban.md) | +| `epilot.identity` | `@epilot/sdk/identity` | [docs](./docs/identity.md) | | `epilot.integrationToolkit` | `@epilot/sdk/integration-toolkit` | [docs](./docs/integration-toolkit.md) | | `epilot.journey` | `@epilot/sdk/journey` | [docs](./docs/journey.md) | | `epilot.kanban` | `@epilot/sdk/kanban` | [docs](./docs/kanban.md) | diff --git a/packages/epilot-sdk-v2/docs/identity.md b/packages/epilot-sdk-v2/docs/identity.md new file mode 100644 index 000000000..d5e63dd51 --- /dev/null +++ b/packages/epilot-sdk-v2/docs/identity.md @@ -0,0 +1,1279 @@ +# Identity API + +- **Base URL:** `https://id.epilot.cloud` +- **Full API Docs:** [https://docs.epilot.io/api/identity](https://docs.epilot.io/api/identity) + +## Usage + +```ts +import { epilot } from '@epilot/sdk' + +epilot.authorize(() => '') +const { data } = await epilot.identity.listClients(...) +``` + +### Tree-shakeable import + +```ts +import { getClient, authorize } from '@epilot/sdk/identity' + +const identityClient = getClient() +authorize(identityClient, () => '') +const { data } = await identityClient.listClients(...) +``` + +## Operations + +**Operator Clients** +- [`listClients`](#listclients) +- [`createClient`](#createclient) +- [`getClient`](#getclient) +- [`updateClient`](#updateclient) +- [`retireClient`](#retireclient) +- [`activateClient`](#activateclient) +- [`suspendClient`](#suspendclient) +- [`rotateClientSecret`](#rotateclientsecret) + +**Operator Organizations** +- [`listClientOrganizations`](#listclientorganizations) +- [`enableClientOrganization`](#enableclientorganization) +- [`getClientOrganization`](#getclientorganization) +- [`updateClientOrganization`](#updateclientorganization) +- [`revokeClientOrganization`](#revokeclientorganization) +- [`suspendClientOrganization`](#suspendclientorganization) +- [`activateClientOrganization`](#activateclientorganization) + +**Session** +- [`createIdentitySession`](#createidentitysession) +- [`deleteIdentitySession`](#deleteidentitysession) + +**Launch** +- [`launchClient`](#launchclient) + +**Schemas** +- [`Error`](#error) +- [`PartnerClientId`](#partnerclientid) +- [`OrgId`](#orgid) +- [`Environment`](#environment) +- [`PartnerClientStatus`](#partnerclientstatus) +- [`Scope`](#scope) +- [`HttpsUri`](#httpsuri) +- [`PartnerClientCreate`](#partnerclientcreate) +- [`PartnerClientUpdate`](#partnerclientupdate) +- [`PartnerClient`](#partnerclient) +- [`PartnerClientWithSecret`](#partnerclientwithsecret) +- [`PartnerClientList`](#partnerclientlist) +- [`StatusChangeRequest`](#statuschangerequest) +- [`RotateSecretRequest`](#rotatesecretrequest) +- [`AuthorizationPolicyInput`](#authorizationpolicyinput) +- [`AuthorizationPolicy`](#authorizationpolicy) +- [`PartnerAccount`](#partneraccount) +- [`OrgEnablementStatus`](#orgenablementstatus) +- [`OrgEnablementCreate`](#orgenablementcreate) +- [`OrgEnablementUpdate`](#orgenablementupdate) +- [`OrgEnablement`](#orgenablement) +- [`OrgEnablementList`](#orgenablementlist) +- [`IdentitySession`](#identitysession) + +### `listClients` + +List registered partner clients. The client secret is never returned. + +`GET /v1/identity/operator/clients` + +```ts +const { data } = await client.listClients({ + partner_key: 'example', + status: 'example', + size: 1, + cursor: 'example', +}) +``` + +
+Response + +```json +{ + "results": [ + { + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" + } + ], + "next_cursor": "string" +} +``` + +
+ +--- + +### `createClient` + +Create a partner client in `draft` status. The client secret is returned exactly once in this +response and can never be read again; use `rotateClientSecret` to obtain a new one. + +`POST /v1/identity/operator/clients` + +```ts +const { data } = await client.createClient( + null, + { + partner_key: 'babelforce', + display_name: 'Babelforce', + description: 'string', + environment: 'dev', + redirect_uris: ['https://partner.example.com/auth/callback'], + sector_identifier_uri: 'https://partner.example.com/auth/callback', + initiate_login_uri: 'https://partner.example.com/auth/callback', + launch_target_prefixes: ['string'], + scopes_allowed: ['openid'] + }, +) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string", + "client_secret": "string" +} +``` + +
+ +--- + +### `getClient` + +Read a partner client. The client secret is never returned. + +`GET /v1/identity/operator/clients/{client_id}` + +```ts +const { data } = await client.getClient({ + client_id: 'example', +}) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `updateClient` + +Replace the mutable configuration of a client. `config_version` must equal the current +version (compare-and-swap); a mismatch is rejected with `409`. + +`PUT /v1/identity/operator/clients/{client_id}` + +```ts +const { data } = await client.updateClient( + { + client_id: 'example', + }, + { + config_version: 1, + allow_sector_change: false, + display_name: 'string', + description: 'string', + redirect_uris: ['https://partner.example.com/auth/callback'], + sector_identifier_uri: 'https://partner.example.com/auth/callback', + initiate_login_uri: 'https://partner.example.com/auth/callback', + launch_target_prefixes: ['string'], + scopes_allowed: ['openid'] + }, +) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `retireClient` + +Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every +organization enablement is revoked and all provider-side artefacts are revoked. + +`DELETE /v1/identity/operator/clients/{client_id}` + +```ts +const { data } = await client.retireClient({ + client_id: 'example', +}) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `activateClient` + +Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute +HTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the +authentication method is `c + +`POST /v1/identity/operator/clients/{client_id}:activate` + +```ts +const { data } = await client.activateClient({ + client_id: 'example', +}) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `suspendClient` + +Immediately block the client for every organization. Provider-side artefacts (codes, tokens, +grants) are revoked; the audit trail is kept. Reversible with `activateClient`. + +`POST /v1/identity/operator/clients/{client_id}:suspend` + +```ts +const { data } = await client.suspendClient( + { + client_id: 'example', + }, + { + reason: 'string' + }, +) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `rotateClientSecret` + +Issue a new client secret. The new secret is returned exactly once. The previous secret +keeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which +case it stops working + +`POST /v1/identity/operator/clients/{client_id}:rotateSecret` + +```ts +const { data } = await client.rotateClientSecret( + { + client_id: 'example', + }, + { + compromise_mode: false, + migration_window_seconds: 86400 + }, +) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "partner_key": "string", + "display_name": "string", + "description": "string", + "environment": "dev", + "redirect_uris": ["https://partner.example.com/auth/callback"], + "sector_identifier_uri": "https://partner.example.com/auth/callback", + "initiate_login_uri": "https://partner.example.com/auth/callback", + "launch_target_prefixes": ["string"], + "token_endpoint_auth_method": "client_secret_basic", + "grant_types": ["authorization_code"], + "scopes_allowed": ["openid"], + "status": "draft", + "status_reason": "string", + "config_version": 1, + "secret_rotated_at": "1970-01-01T00:00:00.000Z", + "previous_secret_expires_at": "1970-01-01T00:00:00.000Z", + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string", + "client_secret": "string" +} +``` + +
+ +--- + +### `listClientOrganizations` + +List the organizations enabled on a client, including suspended and revoked ones. + +`GET /v1/identity/operator/clients/{client_id}/organizations` + +```ts +const { data } = await client.listClientOrganizations({ + client_id: 'example', + status: 'example', +}) +``` + +
+Response + +```json +{ + "results": [ + { + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" + } + ] +} +``` + +
+ +--- + +### `enableClientOrganization` + +Enable an epilot organization on a client, recording the customer's authorization policy +(version 1). `allowed_scopes` must contain `openid` and be a subset of the client's +`scopes_allowed`. + +`POST /v1/identity/operator/clients/{client_id}/organizations` + +```ts +const { data } = await client.enableClientOrganization( + { + client_id: 'example', + }, + { + org_id: '739224', + authorization_policy: { + mode: 'preauthorized', + population: 'all_current_org_members', + allowed_scopes: ['openid'], + customer_approval_ref: 'string', + approved_at: '1970-01-01T00:00:00.000Z' + }, + partner_account: { + id: 'string', + label: 'string' + } + }, +) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `getClientOrganization` + +Read one organization enablement. + +`GET /v1/identity/operator/clients/{client_id}/organizations/{org_id}` + +```ts +const { data } = await client.getClientOrganization({ + client_id: 'example', + org_id: 'example', +}) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `updateClientOrganization` + +Record a new version of the customer's authorization policy. `expected_policy_version` must +equal the current version (compare-and-swap); a mismatch is rejected with `409`. + +`PUT /v1/identity/operator/clients/{client_id}/organizations/{org_id}` + +```ts +const { data } = await client.updateClientOrganization( + { + client_id: 'example', + org_id: 'example', + }, + { + expected_policy_version: 1, + authorization_policy: { + mode: 'preauthorized', + population: 'all_current_org_members', + allowed_scopes: ['openid'], + customer_approval_ref: 'string', + approved_at: '1970-01-01T00:00:00.000Z' + }, + partner_account: { + id: 'string', + label: 'string' + } + }, +) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `revokeClientOrganization` + +Revoke an organization's enablement. The record is kept as an audited tombstone; future +authorizations for this organization fail with `access_denied` and provider-side artefacts +bound to it are revok + +`DELETE /v1/identity/operator/clients/{client_id}/organizations/{org_id}` + +```ts +const { data } = await client.revokeClientOrganization( + { + client_id: 'example', + org_id: 'example', + }, + { + reason: 'string' + }, +) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `suspendClientOrganization` + +Temporarily block one organization on this client. Reversible with `activateClientOrganization`. + +`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend` + +```ts +const { data } = await client.suspendClientOrganization( + { + client_id: 'example', + org_id: 'example', + }, + { + reason: 'string' + }, +) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `activateClientOrganization` + +Resume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy + +`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate` + +```ts +const { data } = await client.activateClientOrganization({ + client_id: 'example', + org_id: 'example', +}) +``` + +
+Response + +```json +{ + "client_id": "bf-prod-x7k2", + "org_id": "739224", + "status": "active", + "status_reason": "string", + "authorization_policy": { + "mode": "preauthorized", + "population": "all_current_org_members", + "allowed_scopes": ["openid"], + "customer_approval_ref": "string", + "approved_at": "1970-01-01T00:00:00.000Z", + "version": 1, + "mandatory_claims": ["org_id"], + "recorded_by": "string", + "recorded_at": "1970-01-01T00:00:00.000Z" + }, + "partner_account": { + "id": "string", + "label": "string" + }, + "created_at": "1970-01-01T00:00:00.000Z", + "updated_at": "1970-01-01T00:00:00.000Z", + "created_by": "string", + "updated_by": "string" +} +``` + +
+ +--- + +### `createIdentitySession` + +Called by the epilot portal through the portal-host proxy after every persisted 360 session. +Validates the 360 bearer token itself and mirrors it into the `identity_session` cookie +(`HttpOnly; Secure; + +`GET /v1/identity/session` + +```ts +const { data } = await client.createIdentitySession() +``` + +
+Response + +```json +{ + "org_id": "739224", + "user_id": "string", + "expires_at": "1970-01-01T00:00:00.000Z" +} +``` + +
+ +--- + +### `deleteIdentitySession` + +Clears the `identity_session` cookie. Called by the portal on logout. + +`DELETE /v1/identity/session` + +```ts +const { data } = await client.deleteIdentitySession() +``` + +--- + +### `launchClient` + +One validated place for partner start links (manager UI, hosted apps). Redirects the browser to the +client's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep lin + +`GET /v1/identity/launch/{client_id}` + +```ts +const { data } = await client.launchClient({ + client_id: 'example', + target: 'example', + extra: 'example', +}) +``` + +--- + +## Schemas + +### `Error` + +```ts +type Error = { + status: number + error: string + details?: Record[] +} +``` + +### `PartnerClientId` + +Opaque client identifier, unique within the issuer, never reused + +```ts +type PartnerClientId = string +``` + +### `OrgId` + +epilot organization id, an opaque case-sensitive string + +```ts +type OrgId = string +``` + +### `Environment` + +Partner-side environment this client belongs to + +```ts +type Environment = "dev" | "staging" | "production" +``` + +### `PartnerClientStatus` + +```ts +type PartnerClientStatus = "draft" | "active" | "suspended" | "retired" +``` + +### `Scope` + +```ts +type Scope = "openid" | "email" | "profile" +``` + +### `HttpsUri` + +Absolute HTTPS URL without fragment + +```ts +type HttpsUri = string // uri +``` + +### `PartnerClientCreate` + +```ts +type PartnerClientCreate = { + partner_key: string + display_name: string + description?: string + environment: "dev" | "staging" | "production" + redirect_uris: string // uri[] + sector_identifier_uri?: string // uri + initiate_login_uri?: string // uri + launch_target_prefixes?: string[] + scopes_allowed?: "openid" | "email" | "profile"[] +} +``` + +### `PartnerClientUpdate` + +```ts +type PartnerClientUpdate = { + config_version: number + allow_sector_change?: boolean + display_name: string + description?: string + redirect_uris: string // uri[] + sector_identifier_uri?: string // uri + initiate_login_uri?: string // uri + launch_target_prefixes?: string[] + scopes_allowed: "openid" | "email" | "profile"[] +} +``` + +### `PartnerClient` + +```ts +type PartnerClient = { + client_id: string + partner_key: string + display_name: string + description?: string + environment: "dev" | "staging" | "production" + redirect_uris: string // uri[] + sector_identifier_uri?: string // uri + initiate_login_uri?: string // uri + launch_target_prefixes?: string[] + token_endpoint_auth_method: "client_secret_basic" + grant_types: "authorization_code"[] + scopes_allowed: "openid" | "email" | "profile"[] + status: "draft" | "active" | "suspended" | "retired" + status_reason?: string + config_version: number + secret_rotated_at?: string // date-time + previous_secret_expires_at?: string // date-time + created_at: string // date-time + updated_at: string // date-time + created_by?: string + updated_by?: string +} +``` + +### `PartnerClientWithSecret` + +```ts +type PartnerClientWithSecret = { + client_id: string + partner_key: string + display_name: string + description?: string + environment: "dev" | "staging" | "production" + redirect_uris: string // uri[] + sector_identifier_uri?: string // uri + initiate_login_uri?: string // uri + launch_target_prefixes?: string[] + token_endpoint_auth_method: "client_secret_basic" + grant_types: "authorization_code"[] + scopes_allowed: "openid" | "email" | "profile"[] + status: "draft" | "active" | "suspended" | "retired" + status_reason?: string + config_version: number + secret_rotated_at?: string // date-time + previous_secret_expires_at?: string // date-time + created_at: string // date-time + updated_at: string // date-time + created_by?: string + updated_by?: string + client_secret: string +} +``` + +### `PartnerClientList` + +```ts +type PartnerClientList = { + results: Array<{ + client_id: string + partner_key: string + display_name: string + description?: string + environment: "dev" | "staging" | "production" + redirect_uris: string // uri[] + sector_identifier_uri?: string // uri + initiate_login_uri?: string // uri + launch_target_prefixes?: string[] + token_endpoint_auth_method: "client_secret_basic" + grant_types: "authorization_code"[] + scopes_allowed: "openid" | "email" | "profile"[] + status: "draft" | "active" | "suspended" | "retired" + status_reason?: string + config_version: number + secret_rotated_at?: string // date-time + previous_secret_expires_at?: string // date-time + created_at: string // date-time + updated_at: string // date-time + created_by?: string + updated_by?: string + }> + next_cursor?: string +} +``` + +### `StatusChangeRequest` + +```ts +type StatusChangeRequest = { + reason?: string +} +``` + +### `RotateSecretRequest` + +```ts +type RotateSecretRequest = { + compromise_mode?: boolean + migration_window_seconds?: number +} +``` + +### `AuthorizationPolicyInput` + +```ts +type AuthorizationPolicyInput = { + mode?: "preauthorized" + population?: "all_current_org_members" + allowed_scopes: "openid" | "email" | "profile"[] + customer_approval_ref: string + approved_at: string // date-time +} +``` + +### `AuthorizationPolicy` + +```ts +type AuthorizationPolicy = { + mode?: "preauthorized" + population?: "all_current_org_members" + allowed_scopes: "openid" | "email" | "profile"[] + customer_approval_ref: string + approved_at: string // date-time + version: number + mandatory_claims: "org_id"[] + recorded_by: string + recorded_at: string // date-time +} +``` + +### `PartnerAccount` + +Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping. + +```ts +type PartnerAccount = { + id: string + label?: string +} +``` + +### `OrgEnablementStatus` + +```ts +type OrgEnablementStatus = "active" | "suspended" | "revoked" +``` + +### `OrgEnablementCreate` + +```ts +type OrgEnablementCreate = { + org_id: string + authorization_policy: { + mode?: "preauthorized" + population?: "all_current_org_members" + allowed_scopes: "openid" | "email" | "profile"[] + customer_approval_ref: string + approved_at: string // date-time + } + partner_account?: { + id: string + label?: string + } +} +``` + +### `OrgEnablementUpdate` + +```ts +type OrgEnablementUpdate = { + expected_policy_version: number + authorization_policy: { + mode?: "preauthorized" + population?: "all_current_org_members" + allowed_scopes: "openid" | "email" | "profile"[] + customer_approval_ref: string + approved_at: string // date-time + } + partner_account?: { + id: string + label?: string + } +} +``` + +### `OrgEnablement` + +```ts +type OrgEnablement = { + client_id: string + org_id: string + status: "active" | "suspended" | "revoked" + status_reason?: string + authorization_policy: { + mode?: "preauthorized" + population?: "all_current_org_members" + allowed_scopes: "openid" | "email" | "profile"[] + customer_approval_ref: string + approved_at: string // date-time + version: number + mandatory_claims: "org_id"[] + recorded_by: string + recorded_at: string // date-time + } + partner_account?: { + id: string + label?: string + } + created_at: string // date-time + updated_at: string // date-time + created_by?: string + updated_by?: string +} +``` + +### `OrgEnablementList` + +```ts +type OrgEnablementList = { + results: Array<{ + client_id: string + org_id: string + status: "active" | "suspended" | "revoked" + status_reason?: string + authorization_policy: { + mode?: { ... } + population?: { ... } + allowed_scopes: { ... } + customer_approval_ref: { ... } + approved_at: { ... } + version: { ... } + mandatory_claims: { ... } + recorded_by: { ... } + recorded_at: { ... } + } + partner_account?: { + id: { ... } + label?: { ... } + } + created_at: string // date-time + updated_at: string // date-time + created_by?: string + updated_by?: string + }> +} +``` + +### `IdentitySession` + +```ts +type IdentitySession = { + org_id: string + user_id: string + expires_at: string // date-time +} +``` diff --git a/packages/epilot-sdk-v2/package.json b/packages/epilot-sdk-v2/package.json index 596555843..807e43a49 100644 --- a/packages/epilot-sdk-v2/package.json +++ b/packages/epilot-sdk-v2/package.json @@ -177,6 +177,12 @@ "require": "./dist/apis/iban.cjs" }, "./iban/openapi.json": "./definitions/iban.json", + "./identity": { + "types": "./dist/apis/identity.d.ts", + "import": "./dist/apis/identity.js", + "require": "./dist/apis/identity.cjs" + }, + "./identity/openapi.json": "./definitions/identity.json", "./integration-toolkit": { "types": "./dist/apis/integration-toolkit.d.ts", "import": "./dist/apis/integration-toolkit.js", @@ -458,6 +464,9 @@ "iban": [ "./dist/apis/iban.d.ts" ], + "identity": [ + "./dist/apis/identity.d.ts" + ], "integration-toolkit": [ "./dist/apis/integration-toolkit.d.ts" ], diff --git a/packages/epilot-sdk-v2/src/apis/_registry.ts b/packages/epilot-sdk-v2/src/apis/_registry.ts index d50fe8c6d..7d722e792 100644 --- a/packages/epilot-sdk-v2/src/apis/_registry.ts +++ b/packages/epilot-sdk-v2/src/apis/_registry.ts @@ -145,6 +145,11 @@ export const registerBuiltinApis = (registry: Map) => { name: 'iban', loader: () => expandDef(require('../definitions/iban-runtime.json')), }); + registerApi({ + registry, + name: 'identity', + loader: () => expandDef(require('../definitions/identity-runtime.json')), + }); registerApi({ registry, name: 'integrationToolkit', diff --git a/packages/epilot-sdk-v2/src/apis/identity.ts b/packages/epilot-sdk-v2/src/apis/identity.ts new file mode 100644 index 000000000..dbd66a85d --- /dev/null +++ b/packages/epilot-sdk-v2/src/apis/identity.ts @@ -0,0 +1,46 @@ +import type { Document } from 'openapi-client-axios'; + +import { createApiClient } from '../client-factory'; +import { expand } from '../compact'; +import type { CompactDefinition } from '../compact'; +import { createApiHandle } from '../proxy'; +import type { ApiHandle } from '../types'; +export { authorize } from '../authorize'; +export type { TokenArg } from '../authorize'; +import type { Client } from '../types/identity'; +export type * from '../types/identity'; +export type { OpenAPIClient } from 'openapi-client-axios'; + +/* eslint-disable @typescript-eslint/no-require-imports */ +const loadDefinition = (): Document => { + const mod = require('../definitions/identity-runtime.json'); + return expand((mod.default ?? mod) as CompactDefinition) as Document; +}; + +let _instance: Client | null = null; + +const resolve = (): Client => { + if (!_instance) { + const def = loadDefinition(); + _instance = createApiClient({ definition: def, apiName: 'identity' }); + } + return _instance; +}; + +const _handle: ApiHandle = createApiHandle({ + resolveClient: resolve, + createClient: () => createApiClient({ definition: loadDefinition(), apiName: 'identity' }), + apiName: 'identity', +}); + +/** Get the cached singleton client (lazy-initialized on first call) */ +export const getClient = _handle.getClient; + +/** Create a fresh client instance (not cached) */ +export const createClient = _handle.createClient; + +/** + * API handle — also exposes operations directly: + * `identity.someOperation(...)` calls forwarded to lazy singleton + */ +export const identity = _handle; diff --git a/packages/epilot-sdk-v2/src/client-map.ts b/packages/epilot-sdk-v2/src/client-map.ts index b6b4c32ae..4b995873b 100644 --- a/packages/epilot-sdk-v2/src/client-map.ts +++ b/packages/epilot-sdk-v2/src/client-map.ts @@ -25,6 +25,7 @@ import type { Client as EnvironmentsClient } from './types/environments'; import type { Client as EventCatalogClient } from './types/event-catalog'; import type { Client as FileClient } from './types/file'; import type { Client as IbanClient } from './types/iban'; +import type { Client as IdentityClient } from './types/identity'; import type { Client as IntegrationToolkitClient } from './types/integration-toolkit'; import type { Client as JourneyClient } from './types/journey'; import type { Client as KanbanClient } from './types/kanban'; @@ -79,6 +80,7 @@ export type SDKClientMap = { eventCatalog: EventCatalogClient; file: FileClient; iban: IbanClient; + identity: IdentityClient; integrationToolkit: IntegrationToolkitClient; journey: JourneyClient; kanban: KanbanClient; diff --git a/packages/epilot-sdk-v2/src/definitions/identity-runtime.json b/packages/epilot-sdk-v2/src/definitions/identity-runtime.json new file mode 100644 index 000000000..ebe0d265c --- /dev/null +++ b/packages/epilot-sdk-v2/src/definitions/identity-runtime.json @@ -0,0 +1 @@ +{"s":"https://id.epilot.cloud","o":[["listClients","get","/v1/identity/operator/clients",[["PartnerKeyQuery"],["ClientStatusQuery"],["SizeQuery"],["CursorQuery"]]],["createClient","post","/v1/identity/operator/clients",null,1],["getClient","get","/v1/identity/operator/clients/{client_id}"],["updateClient","put","/v1/identity/operator/clients/{client_id}",null,1],["retireClient","delete","/v1/identity/operator/clients/{client_id}"],["activateClient","post","/v1/identity/operator/clients/{client_id}:activate"],["suspendClient","post","/v1/identity/operator/clients/{client_id}:suspend",null,1],["rotateClientSecret","post","/v1/identity/operator/clients/{client_id}:rotateSecret",null,1],["listClientOrganizations","get","/v1/identity/operator/clients/{client_id}/organizations",[["OrgEnablementStatusQuery"]]],["enableClientOrganization","post","/v1/identity/operator/clients/{client_id}/organizations",null,1],["getClientOrganization","get","/v1/identity/operator/clients/{client_id}/organizations/{org_id}"],["updateClientOrganization","put","/v1/identity/operator/clients/{client_id}/organizations/{org_id}",null,1],["revokeClientOrganization","delete","/v1/identity/operator/clients/{client_id}/organizations/{org_id}",null,1],["suspendClientOrganization","post","/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend",null,1],["activateClientOrganization","post","/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate"],["createIdentitySession","get","/v1/identity/session"],["deleteIdentitySession","delete","/v1/identity/session"],["launchClient","get","/v1/identity/launch/{client_id}",[["target","q"],["AnyOtherQueryParameters"]]]],"v":"3.0.3","cp":{"AnyOtherQueryParameters":["extra","q",false,"form",true],"ClientIdPath":["client_id","p",true],"OrgIdPath":["org_id","p",true],"PartnerKeyQuery":["partner_key","q"],"ClientStatusQuery":["status","q"],"OrgEnablementStatusQuery":["status","q"],"SizeQuery":["size","q"],"CursorQuery":["cursor","q"]},"pp":{"/v1/identity/operator/clients/{client_id}":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}:activate":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}:suspend":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}:rotateSecret":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations/{org_id}":[["ClientIdPath"],["OrgIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend":[["ClientIdPath"],["OrgIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate":[["ClientIdPath"],["OrgIdPath"]],"/v1/identity/interaction/{interaction_id}":[["interaction_id","p",true]],"/v1/identity/launch/{client_id}":[["ClientIdPath"]]}} \ No newline at end of file diff --git a/packages/epilot-sdk-v2/src/docs/_index.json b/packages/epilot-sdk-v2/src/docs/_index.json index 83380f69a..36c279131 100644 --- a/packages/epilot-sdk-v2/src/docs/_index.json +++ b/packages/epilot-sdk-v2/src/docs/_index.json @@ -26,6 +26,7 @@ "eventCatalog": "event-catalog", "file": "file", "iban": "iban", + "identity": "identity", "integrationToolkit": "integration-toolkit", "journey": "journey", "kanban": "kanban", diff --git a/packages/epilot-sdk-v2/src/docs/identity.json b/packages/epilot-sdk-v2/src/docs/identity.json new file mode 100644 index 000000000..fb11cfd4b --- /dev/null +++ b/packages/epilot-sdk-v2/src/docs/identity.json @@ -0,0 +1 @@ +"# Identity API\n\n- **Base URL:** `https://id.epilot.cloud`\n- **Full API Docs:** [https://docs.epilot.io/api/identity](https://docs.epilot.io/api/identity)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.identity.listClients(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/identity'\n\nconst identityClient = getClient()\nauthorize(identityClient, () => '')\nconst { data } = await identityClient.listClients(...)\n```\n\n## Operations\n\n**Operator Clients**\n- [`listClients`](#listclients)\n- [`createClient`](#createclient)\n- [`getClient`](#getclient)\n- [`updateClient`](#updateclient)\n- [`retireClient`](#retireclient)\n- [`activateClient`](#activateclient)\n- [`suspendClient`](#suspendclient)\n- [`rotateClientSecret`](#rotateclientsecret)\n\n**Operator Organizations**\n- [`listClientOrganizations`](#listclientorganizations)\n- [`enableClientOrganization`](#enableclientorganization)\n- [`getClientOrganization`](#getclientorganization)\n- [`updateClientOrganization`](#updateclientorganization)\n- [`revokeClientOrganization`](#revokeclientorganization)\n- [`suspendClientOrganization`](#suspendclientorganization)\n- [`activateClientOrganization`](#activateclientorganization)\n\n**Session**\n- [`createIdentitySession`](#createidentitysession)\n- [`deleteIdentitySession`](#deleteidentitysession)\n\n**Launch**\n- [`launchClient`](#launchclient)\n\n**Schemas**\n- [`Error`](#error)\n- [`PartnerClientId`](#partnerclientid)\n- [`OrgId`](#orgid)\n- [`Environment`](#environment)\n- [`PartnerClientStatus`](#partnerclientstatus)\n- [`Scope`](#scope)\n- [`HttpsUri`](#httpsuri)\n- [`PartnerClientCreate`](#partnerclientcreate)\n- [`PartnerClientUpdate`](#partnerclientupdate)\n- [`PartnerClient`](#partnerclient)\n- [`PartnerClientWithSecret`](#partnerclientwithsecret)\n- [`PartnerClientList`](#partnerclientlist)\n- [`StatusChangeRequest`](#statuschangerequest)\n- [`RotateSecretRequest`](#rotatesecretrequest)\n- [`AuthorizationPolicyInput`](#authorizationpolicyinput)\n- [`AuthorizationPolicy`](#authorizationpolicy)\n- [`PartnerAccount`](#partneraccount)\n- [`OrgEnablementStatus`](#orgenablementstatus)\n- [`OrgEnablementCreate`](#orgenablementcreate)\n- [`OrgEnablementUpdate`](#orgenablementupdate)\n- [`OrgEnablement`](#orgenablement)\n- [`OrgEnablementList`](#orgenablementlist)\n- [`IdentitySession`](#identitysession)\n\n### `listClients`\n\nList registered partner clients. The client secret is never returned.\n\n`GET /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.listClients({\n partner_key: 'example',\n status: 'example',\n size: 1,\n cursor: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ],\n \"next_cursor\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createClient`\n\nCreate a partner client in `draft` status. The client secret is returned exactly once in this\nresponse and can never be read again; use `rotateClientSecret` to obtain a new one.\n\n`POST /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.createClient(\n null,\n {\n partner_key: 'babelforce',\n display_name: 'Babelforce',\n description: 'string',\n environment: 'dev',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getClient`\n\nRead a partner client. The client secret is never returned.\n\n`GET /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.getClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClient`\n\nReplace the mutable configuration of a client. `config_version` must equal the current\nversion (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.updateClient(\n {\n client_id: 'example',\n },\n {\n config_version: 1,\n allow_sector_change: false,\n display_name: 'string',\n description: 'string',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `retireClient`\n\nRetire a client. The record is kept as a tombstone, the `client_id` is never reused, every\norganization enablement is revoked and all provider-side artefacts are revoked.\n\n`DELETE /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.retireClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClient`\n\nActivate a `draft` or `suspended` client. Validates that every redirect URI is an absolute\nHTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the\nauthentication method is `c\n\n`POST /v1/identity/operator/clients/{client_id}:activate`\n\n```ts\nconst { data } = await client.activateClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClient`\n\nImmediately block the client for every organization. Provider-side artefacts (codes, tokens,\ngrants) are revoked; the audit trail is kept. Reversible with `activateClient`.\n\n`POST /v1/identity/operator/clients/{client_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClient(\n {\n client_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `rotateClientSecret`\n\nIssue a new client secret. The new secret is returned exactly once. The previous secret\nkeeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which\ncase it stops working\n\n`POST /v1/identity/operator/clients/{client_id}:rotateSecret`\n\n```ts\nconst { data } = await client.rotateClientSecret(\n {\n client_id: 'example',\n },\n {\n compromise_mode: false,\n migration_window_seconds: 86400\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `listClientOrganizations`\n\nList the organizations enabled on a client, including suspended and revoked ones.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.listClientOrganizations({\n client_id: 'example',\n status: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `enableClientOrganization`\n\nEnable an epilot organization on a client, recording the customer's authorization policy\n(version 1). `allowed_scopes` must contain `openid` and be a subset of the client's\n`scopes_allowed`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.enableClientOrganization(\n {\n client_id: 'example',\n },\n {\n org_id: '739224',\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getClientOrganization`\n\nRead one organization enablement.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.getClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClientOrganization`\n\nRecord a new version of the customer's authorization policy. `expected_policy_version` must\nequal the current version (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.updateClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n expected_policy_version: 1,\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `revokeClientOrganization`\n\nRevoke an organization's enablement. The record is kept as an audited tombstone; future\nauthorizations for this organization fail with `access_denied` and provider-side artefacts\nbound to it are revok\n\n`DELETE /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.revokeClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClientOrganization`\n\nTemporarily block one organization on this client. Reversible with `activateClientOrganization`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClientOrganization`\n\nResume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate`\n\n```ts\nconst { data } = await client.activateClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createIdentitySession`\n\nCalled by the epilot portal through the portal-host proxy after every persisted 360 session.\nValidates the 360 bearer token itself and mirrors it into the `identity_session` cookie\n(`HttpOnly; Secure;\n\n`GET /v1/identity/session`\n\n```ts\nconst { data } = await client.createIdentitySession()\n```\n\n
\nResponse\n\n```json\n{\n \"org_id\": \"739224\",\n \"user_id\": \"string\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\"\n}\n```\n\n
\n\n---\n\n### `deleteIdentitySession`\n\nClears the `identity_session` cookie. Called by the portal on logout.\n\n`DELETE /v1/identity/session`\n\n```ts\nconst { data } = await client.deleteIdentitySession()\n```\n\n---\n\n### `launchClient`\n\nOne validated place for partner start links (manager UI, hosted apps). Redirects the browser to the\nclient's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep lin\n\n`GET /v1/identity/launch/{client_id}`\n\n```ts\nconst { data } = await client.launchClient({\n client_id: 'example',\n target: 'example',\n extra: 'example',\n})\n```\n\n---\n\n## Schemas\n\n### `Error`\n\n```ts\ntype Error = {\n status: number\n error: string\n details?: Record[]\n}\n```\n\n### `PartnerClientId`\n\nOpaque client identifier, unique within the issuer, never reused\n\n```ts\ntype PartnerClientId = string\n```\n\n### `OrgId`\n\nepilot organization id, an opaque case-sensitive string\n\n```ts\ntype OrgId = string\n```\n\n### `Environment`\n\nPartner-side environment this client belongs to\n\n```ts\ntype Environment = \"dev\" | \"staging\" | \"production\"\n```\n\n### `PartnerClientStatus`\n\n```ts\ntype PartnerClientStatus = \"draft\" | \"active\" | \"suspended\" | \"retired\"\n```\n\n### `Scope`\n\n```ts\ntype Scope = \"openid\" | \"email\" | \"profile\"\n```\n\n### `HttpsUri`\n\nAbsolute HTTPS URL without fragment\n\n```ts\ntype HttpsUri = string // uri\n```\n\n### `PartnerClientCreate`\n\n```ts\ntype PartnerClientCreate = {\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed?: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClientUpdate`\n\n```ts\ntype PartnerClientUpdate = {\n config_version: number\n allow_sector_change?: boolean\n display_name: string\n description?: string\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClient`\n\n```ts\ntype PartnerClient = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `PartnerClientWithSecret`\n\n```ts\ntype PartnerClientWithSecret = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n client_secret: string\n}\n```\n\n### `PartnerClientList`\n\n```ts\ntype PartnerClientList = {\n results: Array<{\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n next_cursor?: string\n}\n```\n\n### `StatusChangeRequest`\n\n```ts\ntype StatusChangeRequest = {\n reason?: string\n}\n```\n\n### `RotateSecretRequest`\n\n```ts\ntype RotateSecretRequest = {\n compromise_mode?: boolean\n migration_window_seconds?: number\n}\n```\n\n### `AuthorizationPolicyInput`\n\n```ts\ntype AuthorizationPolicyInput = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n}\n```\n\n### `AuthorizationPolicy`\n\n```ts\ntype AuthorizationPolicy = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n}\n```\n\n### `PartnerAccount`\n\nOptional audit reference to the partner tenant this organization maps to. The partner enforces the mapping.\n\n```ts\ntype PartnerAccount = {\n id: string\n label?: string\n}\n```\n\n### `OrgEnablementStatus`\n\n```ts\ntype OrgEnablementStatus = \"active\" | \"suspended\" | \"revoked\"\n```\n\n### `OrgEnablementCreate`\n\n```ts\ntype OrgEnablementCreate = {\n org_id: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablementUpdate`\n\n```ts\ntype OrgEnablementUpdate = {\n expected_policy_version: number\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablement`\n\n```ts\ntype OrgEnablement = {\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `OrgEnablementList`\n\n```ts\ntype OrgEnablementList = {\n results: Array<{\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: { ... }\n population?: { ... }\n allowed_scopes: { ... }\n customer_approval_ref: { ... }\n approved_at: { ... }\n version: { ... }\n mandatory_claims: { ... }\n recorded_by: { ... }\n recorded_at: { ... }\n }\n partner_account?: {\n id: { ... }\n label?: { ... }\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n}\n```\n\n### `IdentitySession`\n\n```ts\ntype IdentitySession = {\n org_id: string\n user_id: string\n expires_at: string // date-time\n}\n```\n" diff --git a/packages/epilot-sdk-v2/src/types/identity.d.ts b/packages/epilot-sdk-v2/src/types/identity.d.ts new file mode 100644 index 000000000..4cae0f484 --- /dev/null +++ b/packages/epilot-sdk-v2/src/types/identity.d.ts @@ -0,0 +1,1155 @@ +/* Auto-copied from identity-client */ +import type { + OpenAPIClient, + Parameters, + UnknownParamsObject, + OperationResponse, + AxiosRequestConfig, +} from 'openapi-client-axios'; + +export declare namespace Components { + namespace Parameters { + export interface AnyOtherQueryParameters { + [name: string]: any; + } + export type ClientIdPath = /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + Schemas.PartnerClientId /* ^[A-Za-z0-9._~-]+$ */; + export type ClientStatusQuery = Schemas.PartnerClientStatus; + export type CursorQuery = string; + export type OrgEnablementStatusQuery = Schemas.OrgEnablementStatus; + export type OrgIdPath = /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + Schemas.OrgId /* ^[A-Za-z0-9_-]+$ */; + export type PartnerKeyQuery = string; + export type SizeQuery = number; + } + export interface PathParameters { + ClientIdPath?: Parameters.ClientIdPath; + OrgIdPath?: Parameters.OrgIdPath; + } + export interface QueryParameters { + AnyOtherQueryParameters?: Parameters.AnyOtherQueryParameters; + PartnerKeyQuery?: Parameters.PartnerKeyQuery; + ClientStatusQuery?: Parameters.ClientStatusQuery; + OrgEnablementStatusQuery?: Parameters.OrgEnablementStatusQuery; + SizeQuery?: Parameters.SizeQuery; + CursorQuery?: Parameters.CursorQuery; + } + namespace Responses { + export type BadRequest = Schemas.Error; + export type Conflict = Schemas.Error; + export type Forbidden = Schemas.Error; + export type NotFound = Schemas.Error; + export type Unauthorized = Schemas.Error; + } + namespace Schemas { + export interface AuthorizationPolicy { + mode?: "preauthorized"; + population?: "all_current_org_members"; + /** + * Must contain `openid` and be a subset of the client's `scopes_allowed` + */ + allowed_scopes: [ + Scope, + ...Scope[] + ]; + /** + * Reference to the customer's approval evidence (ticket, signed order, contract clause) + */ + customer_approval_ref: string; + approved_at: string; // date-time + version: number; + mandatory_claims: ("org_id")[]; + recorded_by: string; + recorded_at: string; // date-time + } + export interface AuthorizationPolicyInput { + mode?: "preauthorized"; + population?: "all_current_org_members"; + /** + * Must contain `openid` and be a subset of the client's `scopes_allowed` + */ + allowed_scopes: [ + Scope, + ...Scope[] + ]; + /** + * Reference to the customer's approval evidence (ticket, signed order, contract clause) + */ + customer_approval_ref: string; + approved_at: string; // date-time + } + /** + * Partner-side environment this client belongs to + */ + export type Environment = "dev" | "staging" | "production"; + export interface Error { + /** + * example: + * 404 + */ + status: number; + /** + * example: + * Client not found + */ + error: string; + /** + * Request validation problems (only on `400` from schema validation) + */ + details?: { + [name: string]: any; + }[]; + } + /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + export type HttpsUri = string; // uri + export interface IdentitySession { + org_id: /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + OrgId /* ^[A-Za-z0-9_-]+$ */; + /** + * epilot user id of the session owner + */ + user_id: string; + /** + * When the mirrored credential, and therefore the cookie, expires + */ + expires_at: string; // date-time + } + export interface OrgEnablement { + client_id: /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + PartnerClientId /* ^[A-Za-z0-9._~-]+$ */; + org_id: /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + OrgId /* ^[A-Za-z0-9_-]+$ */; + status: OrgEnablementStatus; + status_reason?: string; + authorization_policy: AuthorizationPolicy; + partner_account?: /* Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping. */ PartnerAccount; + created_at: string; // date-time + updated_at: string; // date-time + created_by?: string; + updated_by?: string; + } + export interface OrgEnablementCreate { + org_id: /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + OrgId /* ^[A-Za-z0-9_-]+$ */; + authorization_policy: AuthorizationPolicyInput; + partner_account?: /* Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping. */ PartnerAccount; + } + export interface OrgEnablementList { + results: OrgEnablement[]; + } + export type OrgEnablementStatus = "active" | "suspended" | "revoked"; + export interface OrgEnablementUpdate { + /** + * Current policy version, for compare-and-swap + */ + expected_policy_version: number; + authorization_policy: AuthorizationPolicyInput; + partner_account?: /* Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping. */ PartnerAccount; + } + /** + * epilot organization id, an opaque case-sensitive string + * example: + * 739224 + */ + export type OrgId = string; // ^[A-Za-z0-9_-]+$ + /** + * Optional audit reference to the partner tenant this organization maps to. The partner enforces the mapping. + */ + export interface PartnerAccount { + id: string; + label?: string; + } + export interface PartnerClient { + client_id: /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + PartnerClientId /* ^[A-Za-z0-9._~-]+$ */; + partner_key: string; + display_name: string; + description?: string; + environment: /* Partner-side environment this client belongs to */ Environment; + redirect_uris: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */[]; + sector_identifier_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + initiate_login_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + launch_target_prefixes?: string[]; + token_endpoint_auth_method: "client_secret_basic"; + grant_types: ("authorization_code")[]; + scopes_allowed: Scope[]; + status: PartnerClientStatus; + /** + * Operator-supplied reason for the last suspension or retirement + */ + status_reason?: string; + config_version: number; + secret_rotated_at?: string; // date-time + /** + * Until when the previous secret still authenticates after a rotation + */ + previous_secret_expires_at?: string; // date-time + created_at: string; // date-time + updated_at: string; // date-time + /** + * Admin-portal identity of the operator who created the client + */ + created_by?: string; + updated_by?: string; + } + export interface PartnerClientCreate { + /** + * Stable key of the partner application, shared by its environments + * example: + * babelforce + */ + partner_key: string; // ^[a-z0-9][a-z0-9-]*$ + /** + * example: + * Babelforce + */ + display_name: string; + description?: string; + environment: /* Partner-side environment this client belongs to */ Environment; + redirect_uris: [ + /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */, + .../** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */[] + ]; + /** + * Optional. Pairwise sector for `sub` derivation. Required when the redirect URIs span + * more than one host, so one person keeps one `sub` at this partner. + * + * example: + * https://partner.example.com/auth/callback + */ + sector_identifier_uri?: string; // uri + /** + * Where a login started from epilot should begin at the partner + * example: + * https://partner.example.com/auth/callback + */ + initiate_login_uri?: string; // uri + /** + * Allowed prefixes for deep-link targets when a login is started from epilot + */ + launch_target_prefixes?: string[]; + /** + * Upper bound for every organization enablement's `allowed_scopes`. Defaults to all scopes. + */ + scopes_allowed?: [ + Scope, + ...Scope[] + ]; + } + /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + export type PartnerClientId = string; // ^[A-Za-z0-9._~-]+$ + export interface PartnerClientList { + results: PartnerClient[]; + next_cursor?: string | null; + } + export type PartnerClientStatus = "draft" | "active" | "suspended" | "retired"; + export interface PartnerClientUpdate { + /** + * Current configuration version, for compare-and-swap + */ + config_version: number; + /** + * Required (true) when the update changes the pairwise sector of an **active** client, i.e. the + * `sector_identifier_uri` host or, without one, the redirect host. Changing the sector changes the + * `sub` of every person at this partner; without this flag such an update is rejected with `409`. + * + */ + allow_sector_change?: boolean; + display_name: string; + description?: string; + redirect_uris: [ + /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */, + .../** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */[] + ]; + sector_identifier_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + initiate_login_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + launch_target_prefixes?: string[]; + scopes_allowed: [ + Scope, + ...Scope[] + ]; + } + export interface PartnerClientWithSecret { + client_id: /** + * Opaque client identifier, unique within the issuer, never reused + * example: + * bf-prod-x7k2 + */ + PartnerClientId /* ^[A-Za-z0-9._~-]+$ */; + partner_key: string; + display_name: string; + description?: string; + environment: /* Partner-side environment this client belongs to */ Environment; + redirect_uris: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */[]; + sector_identifier_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + initiate_login_uri?: /** + * Absolute HTTPS URL without fragment + * example: + * https://partner.example.com/auth/callback + */ + HttpsUri /* uri */; + launch_target_prefixes?: string[]; + token_endpoint_auth_method: "client_secret_basic"; + grant_types: ("authorization_code")[]; + scopes_allowed: Scope[]; + status: PartnerClientStatus; + /** + * Operator-supplied reason for the last suspension or retirement + */ + status_reason?: string; + config_version: number; + secret_rotated_at?: string; // date-time + /** + * Until when the previous secret still authenticates after a rotation + */ + previous_secret_expires_at?: string; // date-time + created_at: string; // date-time + updated_at: string; // date-time + /** + * Admin-portal identity of the operator who created the client + */ + created_by?: string; + updated_by?: string; + /** + * Returned exactly once. Store it securely; it cannot be retrieved again. + */ + client_secret: string; + } + export interface RotateSecretRequest { + /** + * When true the previous secret stops working immediately + */ + compromise_mode?: boolean; + /** + * How long the previous secret keeps working (ignored in compromise mode) + */ + migration_window_seconds?: number; + } + export type Scope = "openid" | "email" | "profile"; + export interface StatusChangeRequest { + reason?: string; + } + } +} +export declare namespace Paths { + namespace ActivateClient { + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace ActivateClientOrganization { + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace CreateClient { + export type RequestBody = Components.Schemas.PartnerClientCreate; + namespace Responses { + export type $201 = Components.Schemas.PartnerClientWithSecret; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + } + } + namespace CreateIdentitySession { + namespace Responses { + export type $200 = Components.Schemas.IdentitySession; + export type $401 = Components.Responses.Unauthorized; + } + } + namespace DeleteIdentitySession { + namespace Responses { + export interface $204 { + } + } + } + namespace EnableClientOrganization { + export type RequestBody = Components.Schemas.OrgEnablementCreate; + namespace Responses { + export type $201 = Components.Schemas.OrgEnablement; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace GetClient { + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace GetClientOrganization { + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace LaunchClient { + namespace Parameters { + export interface Extra { + [name: string]: any; + } + export type Target = string; // uri + } + export interface QueryParameters { + target?: Parameters.Target /* uri */; + extra?: Parameters.Extra; + } + namespace Responses { + export interface $302 { + } + export type $400 = Components.Responses.BadRequest; + export type $404 = Components.Responses.NotFound; + } + } + namespace ListClientOrganizations { + namespace Parameters { + export type Status = Components.Schemas.OrgEnablementStatus; + } + export interface QueryParameters { + status?: Parameters.Status; + } + namespace Responses { + export type $200 = Components.Schemas.OrgEnablementList; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace ListClients { + namespace Parameters { + export type Cursor = string; + export type PartnerKey = string; + export type Size = number; + export type Status = Components.Schemas.PartnerClientStatus; + } + export interface QueryParameters { + partner_key?: Parameters.PartnerKey; + status?: Parameters.Status; + size?: Parameters.Size; + cursor?: Parameters.Cursor; + } + namespace Responses { + export type $200 = Components.Schemas.PartnerClientList; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + } + } + namespace RetireClient { + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace RevokeClientOrganization { + export type RequestBody = Components.Schemas.StatusChangeRequest; + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + } + } + namespace RotateClientSecret { + export type RequestBody = Components.Schemas.RotateSecretRequest; + namespace Responses { + export type $200 = Components.Schemas.PartnerClientWithSecret; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace SuspendClient { + export type RequestBody = Components.Schemas.StatusChangeRequest; + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace SuspendClientOrganization { + export type RequestBody = Components.Schemas.StatusChangeRequest; + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace UpdateClient { + export type RequestBody = Components.Schemas.PartnerClientUpdate; + namespace Responses { + export type $200 = Components.Schemas.PartnerClient; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace UpdateClientOrganization { + export type RequestBody = Components.Schemas.OrgEnablementUpdate; + namespace Responses { + export type $200 = Components.Schemas.OrgEnablement; + export type $400 = Components.Responses.BadRequest; + export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; + export type $404 = Components.Responses.NotFound; + export type $409 = Components.Responses.Conflict; + } + } + namespace V1IdentityInteraction$InteractionId { + namespace Parameters { + export type InteractionId = string; + } + export interface PathParameters { + interaction_id: Parameters.InteractionId; + } + } + namespace V1IdentityLaunch$ClientId { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientId { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdActivate { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdOrganizations { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdOrganizations$OrgId { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + export type $1 = Components.Parameters.OrgIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdOrganizations$OrgIdActivate { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + export type $1 = Components.Parameters.OrgIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdOrganizations$OrgIdSuspend { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + export type $1 = Components.Parameters.OrgIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdRotateSecret { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } + namespace V1IdentityOperatorClients$ClientIdSuspend { + namespace Parameters { + export type $0 = Components.Parameters.ClientIdPath; + } + } +} + + +export interface OperationMethods { + /** + * listClients - listClients + * + * List registered partner clients. The client secret is never returned. + */ + 'listClients'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createClient - createClient + * + * Create a partner client in `draft` status. The client secret is returned exactly once in this + * response and can never be read again; use `rotateClientSecret` to obtain a new one. + * + */ + 'createClient'( + parameters?: Parameters | null, + data?: Paths.CreateClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getClient - getClient + * + * Read a partner client. The client secret is never returned. + */ + 'getClient'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * updateClient - updateClient + * + * Replace the mutable configuration of a client. `config_version` must equal the current + * version (compare-and-swap); a mismatch is rejected with `409`. + * + */ + 'updateClient'( + parameters?: Parameters | null, + data?: Paths.UpdateClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * retireClient - retireClient + * + * Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every + * organization enablement is revoked and all provider-side artefacts are revoked. + * + */ + 'retireClient'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * activateClient - activateClient + * + * Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute + * HTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the + * authentication method is `client_secret_basic`. + * + */ + 'activateClient'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * suspendClient - suspendClient + * + * Immediately block the client for every organization. Provider-side artefacts (codes, tokens, + * grants) are revoked; the audit trail is kept. Reversible with `activateClient`. + * + */ + 'suspendClient'( + parameters?: Parameters | null, + data?: Paths.SuspendClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * rotateClientSecret - rotateClientSecret + * + * Issue a new client secret. The new secret is returned exactly once. The previous secret + * keeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which + * case it stops working immediately. + * + */ + 'rotateClientSecret'( + parameters?: Parameters | null, + data?: Paths.RotateClientSecret.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * listClientOrganizations - listClientOrganizations + * + * List the organizations enabled on a client, including suspended and revoked ones. + */ + 'listClientOrganizations'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * enableClientOrganization - enableClientOrganization + * + * Enable an epilot organization on a client, recording the customer's authorization policy + * (version 1). `allowed_scopes` must contain `openid` and be a subset of the client's + * `scopes_allowed`. + * + */ + 'enableClientOrganization'( + parameters?: Parameters | null, + data?: Paths.EnableClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * getClientOrganization - getClientOrganization + * + * Read one organization enablement. + */ + 'getClientOrganization'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * updateClientOrganization - updateClientOrganization + * + * Record a new version of the customer's authorization policy. `expected_policy_version` must + * equal the current version (compare-and-swap); a mismatch is rejected with `409`. + * + */ + 'updateClientOrganization'( + parameters?: Parameters | null, + data?: Paths.UpdateClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * revokeClientOrganization - revokeClientOrganization + * + * Revoke an organization's enablement. The record is kept as an audited tombstone; future + * authorizations for this organization fail with `access_denied` and provider-side artefacts + * bound to it are revoked. + * + */ + 'revokeClientOrganization'( + parameters?: Parameters | null, + data?: Paths.RevokeClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * suspendClientOrganization - suspendClientOrganization + * + * Temporarily block one organization on this client. Reversible with `activateClientOrganization`. + */ + 'suspendClientOrganization'( + parameters?: Parameters | null, + data?: Paths.SuspendClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * activateClientOrganization - activateClientOrganization + * + * Resume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy version. + */ + 'activateClientOrganization'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createIdentitySession - createIdentitySession + * + * Called by the epilot portal through the portal-host proxy after every persisted 360 session. + * Validates the 360 bearer token itself and mirrors it into the `identity_session` cookie + * (`HttpOnly; Secure; Path=/v1/identity`) whose lifetime equals the remaining token lifetime. + * The cookie is what proves the live 360 session during `completeInteraction`. + * + */ + 'createIdentitySession'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteIdentitySession - deleteIdentitySession + * + * Clears the `identity_session` cookie. Called by the portal on logout. + */ + 'deleteIdentitySession'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * launchClient - launchClient + * + * One validated place for partner start links (manager UI, hosted apps). Redirects the browser to the + * client's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep link as + * `target_link_uri` (OpenID Connect Core §4, third-party initiated login). Authenticates nobody: the partner + * then starts a normal "Sign in with epilot" authorization. + * + * `target` must be an absolute HTTPS URL starting with one of the client's registered + * `launch_target_prefixes`; anything else is rejected. Retired, suspended or draft clients, and clients + * without an `initiate_login_uri`, answer `404`. + * + */ + 'launchClient'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse +} + +export interface PathsDictionary { + ['/v1/identity/operator/clients']: { + /** + * listClients - listClients + * + * List registered partner clients. The client secret is never returned. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * createClient - createClient + * + * Create a partner client in `draft` status. The client secret is returned exactly once in this + * response and can never be read again; use `rotateClientSecret` to obtain a new one. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.CreateClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}']: { + /** + * getClient - getClient + * + * Read a partner client. The client secret is never returned. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * updateClient - updateClient + * + * Replace the mutable configuration of a client. `config_version` must equal the current + * version (compare-and-swap); a mismatch is rejected with `409`. + * + */ + 'put'( + parameters?: Parameters | null, + data?: Paths.UpdateClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * retireClient - retireClient + * + * Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every + * organization enablement is revoked and all provider-side artefacts are revoked. + * + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}:activate']: { + /** + * activateClient - activateClient + * + * Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute + * HTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the + * authentication method is `client_secret_basic`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}:suspend']: { + /** + * suspendClient - suspendClient + * + * Immediately block the client for every organization. Provider-side artefacts (codes, tokens, + * grants) are revoked; the audit trail is kept. Reversible with `activateClient`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.SuspendClient.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}:rotateSecret']: { + /** + * rotateClientSecret - rotateClientSecret + * + * Issue a new client secret. The new secret is returned exactly once. The previous secret + * keeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which + * case it stops working immediately. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.RotateClientSecret.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}/organizations']: { + /** + * listClientOrganizations - listClientOrganizations + * + * List the organizations enabled on a client, including suspended and revoked ones. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * enableClientOrganization - enableClientOrganization + * + * Enable an epilot organization on a client, recording the customer's authorization policy + * (version 1). `allowed_scopes` must contain `openid` and be a subset of the client's + * `scopes_allowed`. + * + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.EnableClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}/organizations/{org_id}']: { + /** + * getClientOrganization - getClientOrganization + * + * Read one organization enablement. + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * updateClientOrganization - updateClientOrganization + * + * Record a new version of the customer's authorization policy. `expected_policy_version` must + * equal the current version (compare-and-swap); a mismatch is rejected with `409`. + * + */ + 'put'( + parameters?: Parameters | null, + data?: Paths.UpdateClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + /** + * revokeClientOrganization - revokeClientOrganization + * + * Revoke an organization's enablement. The record is kept as an audited tombstone; future + * authorizations for this organization fail with `access_denied` and provider-side artefacts + * bound to it are revoked. + * + */ + 'delete'( + parameters?: Parameters | null, + data?: Paths.RevokeClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend']: { + /** + * suspendClientOrganization - suspendClientOrganization + * + * Temporarily block one organization on this client. Reversible with `activateClientOrganization`. + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.SuspendClientOrganization.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate']: { + /** + * activateClientOrganization - activateClientOrganization + * + * Resume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy version. + */ + 'post'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/session']: { + /** + * createIdentitySession - createIdentitySession + * + * Called by the epilot portal through the portal-host proxy after every persisted 360 session. + * Validates the 360 bearer token itself and mirrors it into the `identity_session` cookie + * (`HttpOnly; Secure; Path=/v1/identity`) whose lifetime equals the remaining token lifetime. + * The cookie is what proves the live 360 session during `completeInteraction`. + * + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + /** + * deleteIdentitySession - deleteIdentitySession + * + * Clears the `identity_session` cookie. Called by the portal on logout. + */ + 'delete'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } + ['/v1/identity/interaction/{interaction_id}']: { + } + ['/v1/identity/launch/{client_id}']: { + /** + * launchClient - launchClient + * + * One validated place for partner start links (manager UI, hosted apps). Redirects the browser to the + * client's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep link as + * `target_link_uri` (OpenID Connect Core §4, third-party initiated login). Authenticates nobody: the partner + * then starts a normal "Sign in with epilot" authorization. + * + * `target` must be an absolute HTTPS URL starting with one of the client's registered + * `launch_target_prefixes`; anything else is rejected. Retired, suspended or draft clients, and clients + * without an `initiate_login_uri`, answer `404`. + * + */ + 'get'( + parameters?: Parameters | null, + data?: any, + config?: AxiosRequestConfig + ): OperationResponse + } +} + +export type Client = OpenAPIClient + + +export type AuthorizationPolicy = Components.Schemas.AuthorizationPolicy; +export type AuthorizationPolicyInput = Components.Schemas.AuthorizationPolicyInput; +export type Environment = Components.Schemas.Environment; +export type Error = Components.Schemas.Error; +export type HttpsUri = Components.Schemas.HttpsUri; +export type IdentitySession = Components.Schemas.IdentitySession; +export type OrgEnablement = Components.Schemas.OrgEnablement; +export type OrgEnablementCreate = Components.Schemas.OrgEnablementCreate; +export type OrgEnablementList = Components.Schemas.OrgEnablementList; +export type OrgEnablementStatus = Components.Schemas.OrgEnablementStatus; +export type OrgEnablementUpdate = Components.Schemas.OrgEnablementUpdate; +export type OrgId = Components.Schemas.OrgId; +export type PartnerAccount = Components.Schemas.PartnerAccount; +export type PartnerClient = Components.Schemas.PartnerClient; +export type PartnerClientCreate = Components.Schemas.PartnerClientCreate; +export type PartnerClientId = Components.Schemas.PartnerClientId; +export type PartnerClientList = Components.Schemas.PartnerClientList; +export type PartnerClientStatus = Components.Schemas.PartnerClientStatus; +export type PartnerClientUpdate = Components.Schemas.PartnerClientUpdate; +export type PartnerClientWithSecret = Components.Schemas.PartnerClientWithSecret; +export type RotateSecretRequest = Components.Schemas.RotateSecretRequest; +export type Scope = Components.Schemas.Scope; +export type StatusChangeRequest = Components.Schemas.StatusChangeRequest; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eaad5a64a..ad30cf5fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1437,6 +1437,58 @@ importers: specifier: ^4.4.0 version: 4.10.0(webpack@5.105.4) + clients/identity-client: + dependencies: + '@dazn/lambda-powertools-correlation-ids': + specifier: ^1.28.1 + version: 1.28.1 + buffer: + specifier: ^6.0.3 + version: 6.0.3 + https-browserify: + specifier: ^1.0.0 + version: 1.0.0 + openapi-client-axios: + specifier: ^7.8.0 + version: 7.9.0(axios@1.13.6)(js-yaml@4.1.1) + stream-http: + specifier: ^3.1.1 + version: 3.2.0 + url: + specifier: ^0.11.0 + version: 0.11.4 + util: + specifier: ^0.12.3 + version: 0.12.5 + devDependencies: + axios: + specifier: ^1.11.0 + version: 1.13.6(debug@4.4.1) + copy-webpack-plugin: + specifier: ^7.0.0 + version: 7.0.0(webpack@5.105.4) + json-loader: + specifier: ^0.5.7 + version: 0.5.7 + openapicmd: + specifier: ^2.9.2 + version: 2.9.2(@types/node@24.2.0)(openapi-types@12.1.3)(ts-node@10.9.2(@types/node@24.2.0)(typescript@4.9.5)) + ts-loader: + specifier: ^8.0.14 + version: 8.4.0(typescript@4.9.5)(webpack@5.105.4) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@24.2.0)(typescript@4.9.5) + typescript: + specifier: ^4.1.3 + version: 4.9.5 + webpack: + specifier: ^5.18.0 + version: 5.105.4(webpack-cli@4.10.0) + webpack-cli: + specifier: ^4.4.0 + version: 4.10.0(webpack@5.105.4) + clients/integration-toolkit-client: dependencies: '@dazn/lambda-powertools-correlation-ids': diff --git a/publish-clients.sh b/publish-clients.sh index 1201970a5..ed1385a9a 100755 --- a/publish-clients.sh +++ b/publish-clients.sh @@ -27,6 +27,7 @@ CLIENTS=( "entity-mapping-client" "erp-integration-client" "file-client" + "identity-client" "journey-client" "kanban-client" "message-client" From c496c4f83f98df73eae8afbb057ecfdb566ff123 Mon Sep 17 00:00:00 2001 From: Flavius Mardare Date: Wed, 23 Sep 2026 12:16:35 +0200 Subject: [PATCH 2/4] regenarate openapi based on new specs to differentiate between sdk client creation and creating a new odic client --- clients/identity-client/README.md | 4 + .../identity-client/src/openapi-runtime.json | 4 +- clients/identity-client/src/openapi.d.ts | 46 +++---- clients/identity-client/src/openapi.json | 8 +- packages/cli/definitions/identity.json | 8 +- packages/cli/docs/identity.md | 22 +-- packages/cli/src/generated/api-list.ts | 11 +- packages/cli/src/lib/call.ts | 5 +- .../cli/test/integration/identity.test.ts | 129 ++++++++++++++++++ .../epilot-sdk-v2/__tests__/identity.test.ts | 52 +++++++ packages/epilot-sdk-v2/docs/identity.md | 12 +- .../src/definitions/identity-runtime.json | 2 +- packages/epilot-sdk-v2/src/docs/identity.json | 2 +- .../epilot-sdk-v2/src/types/identity.d.ts | 46 +++---- 14 files changed, 272 insertions(+), 79 deletions(-) create mode 100644 packages/cli/test/integration/identity.test.ts create mode 100644 packages/epilot-sdk-v2/__tests__/identity.test.ts diff --git a/clients/identity-client/README.md b/clients/identity-client/README.md index 1c36abdeb..8ab8aaec7 100644 --- a/clients/identity-client/README.md +++ b/clients/identity-client/README.md @@ -29,6 +29,10 @@ const identityClient = getClient(); const { data: clients } = await identityClient.listClients({ partner_key: "babelforce" }); ``` +Register and look up applications with `identityClient.createOidcClient()` and +`identityClient.getOidcClient()`. The package's `getClient()` and `createClient()` helpers +return SDK HTTP client instances. + ## Documentation: https://docs.epilot.io/api/identity diff --git a/clients/identity-client/src/openapi-runtime.json b/clients/identity-client/src/openapi-runtime.json index 5f53cf969..8b7d19581 100644 --- a/clients/identity-client/src/openapi-runtime.json +++ b/clients/identity-client/src/openapi-runtime.json @@ -30,7 +30,7 @@ "responses": {} }, "post": { - "operationId": "createClient", + "operationId": "createOidcClient", "requestBody": { "required": true, "content": { @@ -47,7 +47,7 @@ } ], "get": { - "operationId": "getClient", + "operationId": "getOidcClient", "responses": {} }, "put": { diff --git a/clients/identity-client/src/openapi.d.ts b/clients/identity-client/src/openapi.d.ts index f1706c484..426c29d8e 100644 --- a/clients/identity-client/src/openapi.d.ts +++ b/clients/identity-client/src/openapi.d.ts @@ -441,19 +441,19 @@ declare namespace Paths { export type $409 = Components.Responses.Conflict; } } - namespace CreateClient { - export type RequestBody = Components.Schemas.PartnerClientCreate; + namespace CreateIdentitySession { namespace Responses { - export type $201 = Components.Schemas.PartnerClientWithSecret; - export type $400 = Components.Responses.BadRequest; + export type $200 = Components.Schemas.IdentitySession; export type $401 = Components.Responses.Unauthorized; - export type $403 = Components.Responses.Forbidden; } } - namespace CreateIdentitySession { + namespace CreateOidcClient { + export type RequestBody = Components.Schemas.PartnerClientCreate; namespace Responses { - export type $200 = Components.Schemas.IdentitySession; + export type $201 = Components.Schemas.PartnerClientWithSecret; + export type $400 = Components.Responses.BadRequest; export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; } } namespace DeleteIdentitySession { @@ -473,17 +473,17 @@ declare namespace Paths { export type $409 = Components.Responses.Conflict; } } - namespace GetClient { + namespace GetClientOrganization { namespace Responses { - export type $200 = Components.Schemas.PartnerClient; + export type $200 = Components.Schemas.OrgEnablement; export type $401 = Components.Responses.Unauthorized; export type $403 = Components.Responses.Forbidden; export type $404 = Components.Responses.NotFound; } } - namespace GetClientOrganization { + namespace GetOidcClient { namespace Responses { - export type $200 = Components.Schemas.OrgEnablement; + export type $200 = Components.Schemas.PartnerClient; export type $401 = Components.Responses.Unauthorized; export type $403 = Components.Responses.Forbidden; export type $404 = Components.Responses.NotFound; @@ -680,27 +680,27 @@ export interface OperationMethods { config?: AxiosRequestConfig ): OperationResponse /** - * createClient - createClient + * createOidcClient - createOidcClient * * Create a partner client in `draft` status. The client secret is returned exactly once in this * response and can never be read again; use `rotateClientSecret` to obtain a new one. * */ - 'createClient'( + 'createOidcClient'( parameters?: Parameters | null, - data?: Paths.CreateClient.RequestBody, + data?: Paths.CreateOidcClient.RequestBody, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse /** - * getClient - getClient + * getOidcClient - getOidcClient * * Read a partner client. The client secret is never returned. */ - 'getClient'( + 'getOidcClient'( parameters?: Parameters | null, data?: any, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse /** * updateClient - updateClient * @@ -898,7 +898,7 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse /** - * createClient - createClient + * createOidcClient - createOidcClient * * Create a partner client in `draft` status. The client secret is returned exactly once in this * response and can never be read again; use `rotateClientSecret` to obtain a new one. @@ -906,13 +906,13 @@ export interface PathsDictionary { */ 'post'( parameters?: Parameters | null, - data?: Paths.CreateClient.RequestBody, + data?: Paths.CreateOidcClient.RequestBody, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse } ['/v1/identity/operator/clients/{client_id}']: { /** - * getClient - getClient + * getOidcClient - getOidcClient * * Read a partner client. The client secret is never returned. */ @@ -920,7 +920,7 @@ export interface PathsDictionary { parameters?: Parameters | null, data?: any, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse /** * updateClient - updateClient * diff --git a/clients/identity-client/src/openapi.json b/clients/identity-client/src/openapi.json index e650d0583..97e0dbc5a 100644 --- a/clients/identity-client/src/openapi.json +++ b/clients/identity-client/src/openapi.json @@ -76,8 +76,8 @@ } }, "post": { - "operationId": "createClient", - "summary": "createClient", + "operationId": "createOidcClient", + "summary": "createOidcClient", "description": "Create a partner client in `draft` status. The client secret is returned exactly once in this\nresponse and can never be read again; use `rotateClientSecret` to obtain a new one.\n", "tags": [ "Operator Clients" @@ -122,8 +122,8 @@ } ], "get": { - "operationId": "getClient", - "summary": "getClient", + "operationId": "getOidcClient", + "summary": "getOidcClient", "description": "Read a partner client. The client secret is never returned.", "tags": [ "Operator Clients" diff --git a/packages/cli/definitions/identity.json b/packages/cli/definitions/identity.json index e650d0583..97e0dbc5a 100644 --- a/packages/cli/definitions/identity.json +++ b/packages/cli/definitions/identity.json @@ -76,8 +76,8 @@ } }, "post": { - "operationId": "createClient", - "summary": "createClient", + "operationId": "createOidcClient", + "summary": "createOidcClient", "description": "Create a partner client in `draft` status. The client secret is returned exactly once in this\nresponse and can never be read again; use `rotateClientSecret` to obtain a new one.\n", "tags": [ "Operator Clients" @@ -122,8 +122,8 @@ } ], "get": { - "operationId": "getClient", - "summary": "getClient", + "operationId": "getOidcClient", + "summary": "getOidcClient", "description": "Read a partner client. The client secret is never returned.", "tags": [ "Operator Clients" diff --git a/packages/cli/docs/identity.md b/packages/cli/docs/identity.md index a3ccdb271..5ba2d7ce5 100644 --- a/packages/cli/docs/identity.md +++ b/packages/cli/docs/identity.md @@ -37,8 +37,8 @@ epilot identity listClients **Operator Clients** - [`listClients`](#listclients) — List registered partner clients. The client secret is never returned. -- [`createClient`](#createclient) — Create a partner client in `draft` status. The client secret is returned exactly once in this -- [`getClient`](#getclient) — Read a partner client. The client secret is never returned. +- [`createOidcClient`](#createoidcclient) — Create a partner client in `draft` status. The client secret is returned exactly once in this +- [`getOidcClient`](#getoidcclient) — Read a partner client. The client secret is never returned. - [`updateClient`](#updateclient) — Replace the mutable configuration of a client. `config_version` must equal the current - [`retireClient`](#retireclient) — Retire a client. The record is kept as a tombstone, the `client_id` is never reused, every - [`activateClient`](#activateclient) — Activate a `draft` or `suspended` client. Validates that every redirect URI is an absolute @@ -126,7 +126,7 @@ epilot identity listClients --jsonata 'results[0]' --- -### `createClient` +### `createOidcClient` Create a partner client in `draft` status. The client secret is returned exactly once in this @@ -137,13 +137,13 @@ Create a partner client in `draft` status. The client secret is returned exactly **Sample Call** ```bash -epilot identity createClient +epilot identity createOidcClient ``` With request body: ```bash -epilot identity createClient \ +epilot identity createOidcClient \ -d '{ "partner_key": "babelforce", "display_name": "Babelforce", @@ -160,13 +160,13 @@ epilot identity createClient \ Using stdin pipe: ```bash -cat body.json | epilot identity createClient +cat body.json | epilot identity createOidcClient ``` With JSONata filter: ```bash -epilot identity createClient --jsonata '$' +epilot identity createOidcClient --jsonata '$' ```
@@ -203,7 +203,7 @@ epilot identity createClient --jsonata '$' --- -### `getClient` +### `getOidcClient` Read a partner client. The client secret is never returned. @@ -218,20 +218,20 @@ Read a partner client. The client secret is never returned. **Sample Call** ```bash -epilot identity getClient \ +epilot identity getOidcClient \ -p client_id=bf-prod-x7k2 ``` Using positional args for path parameters: ```bash -epilot identity getClient bf-prod-x7k2 +epilot identity getOidcClient bf-prod-x7k2 ``` With JSONata filter: ```bash -epilot identity getClient -p client_id=bf-prod-x7k2 --jsonata 'client_id' +epilot identity getOidcClient -p client_id=bf-prod-x7k2 --jsonata 'client_id' ```
diff --git a/packages/cli/src/generated/api-list.ts b/packages/cli/src/generated/api-list.ts index acc292a10..87e20d28d 100644 --- a/packages/cli/src/generated/api-list.ts +++ b/packages/cli/src/generated/api-list.ts @@ -925,8 +925,8 @@ export const API_LIST: ApiInfo[] = [ operationCount: 18, operationIds: [ 'listClients', - 'createClient', - 'getClient', + 'createOidcClient', + 'getOidcClient', 'updateClient', 'retireClient', 'activateClient', @@ -1283,7 +1283,7 @@ export const API_LIST: ApiInfo[] = [ kebabName: 'pricing', title: 'Pricing API', serverUrl: 'https://pricing-api.sls.epilot.io', - operationCount: 34, + operationCount: 39, operationIds: [ '$calculatePricingDetails', 'createOrder', @@ -1310,15 +1310,20 @@ export const API_LIST: ApiInfo[] = [ '$getConditionSets', '$resolveConditionalEntity', '$createConditionalVariant', + '$listConditionalVariants', + '$getConditionalVariantTree', '$getActiveConditionalVariantVersion', '$replaceActiveConditionalVariantVersion', '$patchActiveConditionalVariantVersion', '$deleteConditionalVariant', + '$listConditionalVariantVersions', '$appendConditionalVariantVersion', '$getConditionalVariantVersion', '$replaceConditionalVariantVersion', '$patchConditionalVariantVersion', '$deleteConditionalVariantVersion', + '$batchUpsertConditionalVariants', + '$batchDeleteConditionalVariants', ], }, { diff --git a/packages/cli/src/lib/call.ts b/packages/cli/src/lib/call.ts index 7ec40c6aa..dc66a17d1 100644 --- a/packages/cli/src/lib/call.ts +++ b/packages/cli/src/lib/call.ts @@ -42,10 +42,13 @@ export type CallArgs = { /** * Rewrite a production URL to target a different stage. * e.g. https://entity.sls.epilot.io → https://entity.dev.sls.epilot.io + * Identity uses https://id.epilot.cloud → https://id.dev.epilot.cloud. */ const toStageUrl = (prodUrl: string, stage: string): string => { if (stage === 'prod') return prodUrl; - return prodUrl.replace('.sls.epilot.io', `.${stage}.sls.epilot.io`); + return prodUrl + .replace(/^https:\/\/id\.epilot\.cloud(?=\/|$)/, `https://id.${stage}.epilot.cloud`) + .replace('.sls.epilot.io', `.${stage}.sls.epilot.io`); }; /** diff --git a/packages/cli/test/integration/identity.test.ts b/packages/cli/test/integration/identity.test.ts new file mode 100644 index 000000000..b3b4e6f0e --- /dev/null +++ b/packages/cli/test/integration/identity.test.ts @@ -0,0 +1,129 @@ +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { http, HttpResponse } from 'msw'; +import { setupServer } from 'msw/node'; +import { callApi, type CallArgs } from '../../src/lib/call.js'; +import { getResolvedProfile, getStage } from '../../src/lib/profiles.js'; + +vi.mock('../../src/lib/profiles.js', () => ({ + getResolvedProfile: vi.fn(), + getStage: vi.fn(), +})); + +const server = setupServer(); +const clientsPath = '/v1/identity/operator/clients'; +const call = (args: Partial) => + callApi('identity', { token: 'test-token', interactive: false, json: true, operation: 'listClients', ...args }); + +beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); +beforeEach(() => { + vi.mocked(getResolvedProfile).mockReturnValue(null); + vi.mocked(getStage).mockReturnValue(undefined); + vi.spyOn(process.stdout, 'write').mockReturnValue(true); + vi.spyOn(process.stderr, 'write').mockReturnValue(true); + vi.spyOn(process, 'exit').mockImplementation((code) => { + throw new Error(`Unexpected process.exit(${code})`); + }); +}); +afterEach(() => { + server.resetHandlers(); + vi.restoreAllMocks(); +}); +afterAll(() => server.close()); + +describe('identity CLI stage selection', () => { + it.each<{ stage: string; args: Partial; origin: string }>([ + { stage: 'production', args: {}, origin: 'https://id.epilot.cloud' }, + { stage: 'dev', args: { 'use-dev': true }, origin: 'https://id.dev.epilot.cloud' }, + { stage: 'staging', args: { 'use-staging': true }, origin: 'https://id.staging.epilot.cloud' }, + ])('calls $stage when selected', async ({ args, origin }) => { + let requestedUrl: string | undefined; + server.use( + http.get(`https://*${clientsPath}`, ({ request }) => { + requestedUrl = request.url; + return HttpResponse.json({ results: [] }); + }), + ); + + await call(args); + + expect(requestedUrl).toBe(`${origin}${clientsPath}`); + }); + + it('honors the saved stage and lets an explicit stage flag override it', async () => { + vi.mocked(getStage).mockReturnValue('staging'); + const origins: string[] = []; + server.use( + http.get(`https://*${clientsPath}`, ({ request }) => { + origins.push(new URL(request.url).origin); + return HttpResponse.json({ results: [] }); + }), + ); + + await call({}); + await call({ 'use-dev': true }); + + expect(origins).toEqual(['https://id.staging.epilot.cloud', 'https://id.dev.epilot.cloud']); + }); + + it('preserves profile and explicit server overrides', async () => { + vi.mocked(getResolvedProfile).mockReturnValue({ name: 'test', server: 'https://profile.example.test' }); + const origins: string[] = []; + server.use( + http.get(`https://*${clientsPath}`, ({ request }) => { + origins.push(new URL(request.url).origin); + return HttpResponse.json({ results: [] }); + }), + ); + + await call({ 'use-dev': true }); + await call({ 'use-dev': true, server: 'https://explicit.example.test' }); + + expect(origins).toEqual(['https://profile.example.test', 'https://explicit.example.test']); + }); + + it.each(['dev', 'staging'] as const)('preserves existing sls routing for %s', async (stage) => { + let requestedUrl: string | undefined; + server.use( + http.get('https://*/v1/entity/schemas', ({ request }) => { + requestedUrl = request.url; + return HttpResponse.json({ results: [] }); + }), + ); + + await callApi('entity', { + token: 'test-token', + interactive: false, + json: true, + operation: 'listSchemas', + [`use-${stage}`]: true, + }); + + expect(requestedUrl).toBe(`https://entity.${stage}.sls.epilot.io/v1/entity/schemas`); + }); +}); + +it('calls the renamed OIDC operations on their unchanged HTTP routes', async () => { + const requests: string[] = []; + const input = { + partner_key: 'test', + display_name: 'Test', + environment: 'dev', + redirect_uris: ['https://example.test/cb'], + }; + server.use( + http.post(`https://id.dev.epilot.cloud${clientsPath}`, async ({ request }) => { + expect(await request.json()).toEqual(input); + requests.push(request.method); + return HttpResponse.json({ client_id: 'test-dev-id' }, { status: 201 }); + }), + http.get(`https://id.dev.epilot.cloud${clientsPath}/test-dev-id`, ({ request }) => { + requests.push(request.method); + return HttpResponse.json({ client_id: 'test-dev-id' }); + }), + ); + + await call({ 'use-dev': true, operation: 'createOidcClient', data: JSON.stringify(input) }); + await call({ 'use-dev': true, operation: 'getOidcClient', param: 'client_id=test-dev-id' }); + + expect(requests).toEqual(['POST', 'GET']); +}); diff --git a/packages/epilot-sdk-v2/__tests__/identity.test.ts b/packages/epilot-sdk-v2/__tests__/identity.test.ts new file mode 100644 index 000000000..100b35898 --- /dev/null +++ b/packages/epilot-sdk-v2/__tests__/identity.test.ts @@ -0,0 +1,52 @@ +import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { http, HttpResponse } from 'msw'; +import { setupServer } from 'msw/node'; +import { identity } from '../src/apis/identity'; +import { createSDK } from '../src/sdk'; + +const server = setupServer(); +const clientsUrl = 'https://id.epilot.cloud/v1/identity/operator/clients'; + +beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + +describe.each([ + { name: 'root SDK', getHandle: () => createSDK().identity }, + { name: 'identity subpath', getHandle: () => identity }, +])('$name OIDC client operations', ({ getHandle }) => { + it('forwards registration and lookup to HTTP while preserving SDK factories', async () => { + const handle = getHandle(); + const singleton = handle.getClient(); + expect(handle.getClient()).toBe(singleton); + expect(handle.createClient()).not.toBe(singleton); + + const input = { + partner_key: 'test', + display_name: 'Test application', + environment: 'dev' as const, + redirect_uris: ['https://example.test/cb'], + }; + const record = { client_id: 'test-dev-id', status: 'draft', ...input }; + const requests: string[] = []; + server.use( + http.post(clientsUrl, async ({ request }) => { + expect(await request.json()).toEqual(input); + requests.push(request.method); + return HttpResponse.json({ ...record, client_secret: 'one-time-test-secret' }, { status: 201 }); + }), + http.get(`${clientsUrl}/test-dev-id`, ({ request }) => { + requests.push(request.method); + return HttpResponse.json(record); + }), + ); + + const created = await handle.createOidcClient(null, input); + const fetched = await handle.getOidcClient({ client_id: created.data.client_id }); + + expect(created.status).toBe(201); + expect(created.data.client_secret).toBe('one-time-test-secret'); + expect(fetched.data).toEqual(record); + expect(requests).toEqual(['POST', 'GET']); + }); +}); diff --git a/packages/epilot-sdk-v2/docs/identity.md b/packages/epilot-sdk-v2/docs/identity.md index d5e63dd51..8f0e56ecd 100644 --- a/packages/epilot-sdk-v2/docs/identity.md +++ b/packages/epilot-sdk-v2/docs/identity.md @@ -26,8 +26,8 @@ const { data } = await identityClient.listClients(...) **Operator Clients** - [`listClients`](#listclients) -- [`createClient`](#createclient) -- [`getClient`](#getclient) +- [`createOidcClient`](#createoidcclient) +- [`getOidcClient`](#getoidcclient) - [`updateClient`](#updateclient) - [`retireClient`](#retireclient) - [`activateClient`](#activateclient) @@ -128,7 +128,7 @@ const { data } = await client.listClients({ --- -### `createClient` +### `createOidcClient` Create a partner client in `draft` status. The client secret is returned exactly once in this response and can never be read again; use `rotateClientSecret` to obtain a new one. @@ -136,7 +136,7 @@ response and can never be read again; use `rotateClientSecret` to obtain a new o `POST /v1/identity/operator/clients` ```ts -const { data } = await client.createClient( +const { data } = await client.createOidcClient( null, { partner_key: 'babelforce', @@ -186,14 +186,14 @@ const { data } = await client.createClient( --- -### `getClient` +### `getOidcClient` Read a partner client. The client secret is never returned. `GET /v1/identity/operator/clients/{client_id}` ```ts -const { data } = await client.getClient({ +const { data } = await client.getOidcClient({ client_id: 'example', }) ``` diff --git a/packages/epilot-sdk-v2/src/definitions/identity-runtime.json b/packages/epilot-sdk-v2/src/definitions/identity-runtime.json index ebe0d265c..6866154f8 100644 --- a/packages/epilot-sdk-v2/src/definitions/identity-runtime.json +++ b/packages/epilot-sdk-v2/src/definitions/identity-runtime.json @@ -1 +1 @@ -{"s":"https://id.epilot.cloud","o":[["listClients","get","/v1/identity/operator/clients",[["PartnerKeyQuery"],["ClientStatusQuery"],["SizeQuery"],["CursorQuery"]]],["createClient","post","/v1/identity/operator/clients",null,1],["getClient","get","/v1/identity/operator/clients/{client_id}"],["updateClient","put","/v1/identity/operator/clients/{client_id}",null,1],["retireClient","delete","/v1/identity/operator/clients/{client_id}"],["activateClient","post","/v1/identity/operator/clients/{client_id}:activate"],["suspendClient","post","/v1/identity/operator/clients/{client_id}:suspend",null,1],["rotateClientSecret","post","/v1/identity/operator/clients/{client_id}:rotateSecret",null,1],["listClientOrganizations","get","/v1/identity/operator/clients/{client_id}/organizations",[["OrgEnablementStatusQuery"]]],["enableClientOrganization","post","/v1/identity/operator/clients/{client_id}/organizations",null,1],["getClientOrganization","get","/v1/identity/operator/clients/{client_id}/organizations/{org_id}"],["updateClientOrganization","put","/v1/identity/operator/clients/{client_id}/organizations/{org_id}",null,1],["revokeClientOrganization","delete","/v1/identity/operator/clients/{client_id}/organizations/{org_id}",null,1],["suspendClientOrganization","post","/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend",null,1],["activateClientOrganization","post","/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate"],["createIdentitySession","get","/v1/identity/session"],["deleteIdentitySession","delete","/v1/identity/session"],["launchClient","get","/v1/identity/launch/{client_id}",[["target","q"],["AnyOtherQueryParameters"]]]],"v":"3.0.3","cp":{"AnyOtherQueryParameters":["extra","q",false,"form",true],"ClientIdPath":["client_id","p",true],"OrgIdPath":["org_id","p",true],"PartnerKeyQuery":["partner_key","q"],"ClientStatusQuery":["status","q"],"OrgEnablementStatusQuery":["status","q"],"SizeQuery":["size","q"],"CursorQuery":["cursor","q"]},"pp":{"/v1/identity/operator/clients/{client_id}":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}:activate":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}:suspend":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}:rotateSecret":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations/{org_id}":[["ClientIdPath"],["OrgIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend":[["ClientIdPath"],["OrgIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate":[["ClientIdPath"],["OrgIdPath"]],"/v1/identity/interaction/{interaction_id}":[["interaction_id","p",true]],"/v1/identity/launch/{client_id}":[["ClientIdPath"]]}} \ No newline at end of file +{"s":"https://id.epilot.cloud","o":[["listClients","get","/v1/identity/operator/clients",[["PartnerKeyQuery"],["ClientStatusQuery"],["SizeQuery"],["CursorQuery"]]],["createOidcClient","post","/v1/identity/operator/clients",null,1],["getOidcClient","get","/v1/identity/operator/clients/{client_id}"],["updateClient","put","/v1/identity/operator/clients/{client_id}",null,1],["retireClient","delete","/v1/identity/operator/clients/{client_id}"],["activateClient","post","/v1/identity/operator/clients/{client_id}:activate"],["suspendClient","post","/v1/identity/operator/clients/{client_id}:suspend",null,1],["rotateClientSecret","post","/v1/identity/operator/clients/{client_id}:rotateSecret",null,1],["listClientOrganizations","get","/v1/identity/operator/clients/{client_id}/organizations",[["OrgEnablementStatusQuery"]]],["enableClientOrganization","post","/v1/identity/operator/clients/{client_id}/organizations",null,1],["getClientOrganization","get","/v1/identity/operator/clients/{client_id}/organizations/{org_id}"],["updateClientOrganization","put","/v1/identity/operator/clients/{client_id}/organizations/{org_id}",null,1],["revokeClientOrganization","delete","/v1/identity/operator/clients/{client_id}/organizations/{org_id}",null,1],["suspendClientOrganization","post","/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend",null,1],["activateClientOrganization","post","/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate"],["createIdentitySession","get","/v1/identity/session"],["deleteIdentitySession","delete","/v1/identity/session"],["launchClient","get","/v1/identity/launch/{client_id}",[["target","q"],["AnyOtherQueryParameters"]]]],"v":"3.0.3","cp":{"AnyOtherQueryParameters":["extra","q",false,"form",true],"ClientIdPath":["client_id","p",true],"OrgIdPath":["org_id","p",true],"PartnerKeyQuery":["partner_key","q"],"ClientStatusQuery":["status","q"],"OrgEnablementStatusQuery":["status","q"],"SizeQuery":["size","q"],"CursorQuery":["cursor","q"]},"pp":{"/v1/identity/operator/clients/{client_id}":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}:activate":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}:suspend":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}:rotateSecret":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations":[["ClientIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations/{org_id}":[["ClientIdPath"],["OrgIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend":[["ClientIdPath"],["OrgIdPath"]],"/v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate":[["ClientIdPath"],["OrgIdPath"]],"/v1/identity/interaction/{interaction_id}":[["interaction_id","p",true]],"/v1/identity/launch/{client_id}":[["ClientIdPath"]]}} \ No newline at end of file diff --git a/packages/epilot-sdk-v2/src/docs/identity.json b/packages/epilot-sdk-v2/src/docs/identity.json index fb11cfd4b..63c38d595 100644 --- a/packages/epilot-sdk-v2/src/docs/identity.json +++ b/packages/epilot-sdk-v2/src/docs/identity.json @@ -1 +1 @@ -"# Identity API\n\n- **Base URL:** `https://id.epilot.cloud`\n- **Full API Docs:** [https://docs.epilot.io/api/identity](https://docs.epilot.io/api/identity)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.identity.listClients(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/identity'\n\nconst identityClient = getClient()\nauthorize(identityClient, () => '')\nconst { data } = await identityClient.listClients(...)\n```\n\n## Operations\n\n**Operator Clients**\n- [`listClients`](#listclients)\n- [`createClient`](#createclient)\n- [`getClient`](#getclient)\n- [`updateClient`](#updateclient)\n- [`retireClient`](#retireclient)\n- [`activateClient`](#activateclient)\n- [`suspendClient`](#suspendclient)\n- [`rotateClientSecret`](#rotateclientsecret)\n\n**Operator Organizations**\n- [`listClientOrganizations`](#listclientorganizations)\n- [`enableClientOrganization`](#enableclientorganization)\n- [`getClientOrganization`](#getclientorganization)\n- [`updateClientOrganization`](#updateclientorganization)\n- [`revokeClientOrganization`](#revokeclientorganization)\n- [`suspendClientOrganization`](#suspendclientorganization)\n- [`activateClientOrganization`](#activateclientorganization)\n\n**Session**\n- [`createIdentitySession`](#createidentitysession)\n- [`deleteIdentitySession`](#deleteidentitysession)\n\n**Launch**\n- [`launchClient`](#launchclient)\n\n**Schemas**\n- [`Error`](#error)\n- [`PartnerClientId`](#partnerclientid)\n- [`OrgId`](#orgid)\n- [`Environment`](#environment)\n- [`PartnerClientStatus`](#partnerclientstatus)\n- [`Scope`](#scope)\n- [`HttpsUri`](#httpsuri)\n- [`PartnerClientCreate`](#partnerclientcreate)\n- [`PartnerClientUpdate`](#partnerclientupdate)\n- [`PartnerClient`](#partnerclient)\n- [`PartnerClientWithSecret`](#partnerclientwithsecret)\n- [`PartnerClientList`](#partnerclientlist)\n- [`StatusChangeRequest`](#statuschangerequest)\n- [`RotateSecretRequest`](#rotatesecretrequest)\n- [`AuthorizationPolicyInput`](#authorizationpolicyinput)\n- [`AuthorizationPolicy`](#authorizationpolicy)\n- [`PartnerAccount`](#partneraccount)\n- [`OrgEnablementStatus`](#orgenablementstatus)\n- [`OrgEnablementCreate`](#orgenablementcreate)\n- [`OrgEnablementUpdate`](#orgenablementupdate)\n- [`OrgEnablement`](#orgenablement)\n- [`OrgEnablementList`](#orgenablementlist)\n- [`IdentitySession`](#identitysession)\n\n### `listClients`\n\nList registered partner clients. The client secret is never returned.\n\n`GET /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.listClients({\n partner_key: 'example',\n status: 'example',\n size: 1,\n cursor: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ],\n \"next_cursor\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createClient`\n\nCreate a partner client in `draft` status. The client secret is returned exactly once in this\nresponse and can never be read again; use `rotateClientSecret` to obtain a new one.\n\n`POST /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.createClient(\n null,\n {\n partner_key: 'babelforce',\n display_name: 'Babelforce',\n description: 'string',\n environment: 'dev',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getClient`\n\nRead a partner client. The client secret is never returned.\n\n`GET /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.getClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClient`\n\nReplace the mutable configuration of a client. `config_version` must equal the current\nversion (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.updateClient(\n {\n client_id: 'example',\n },\n {\n config_version: 1,\n allow_sector_change: false,\n display_name: 'string',\n description: 'string',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `retireClient`\n\nRetire a client. The record is kept as a tombstone, the `client_id` is never reused, every\norganization enablement is revoked and all provider-side artefacts are revoked.\n\n`DELETE /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.retireClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClient`\n\nActivate a `draft` or `suspended` client. Validates that every redirect URI is an absolute\nHTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the\nauthentication method is `c\n\n`POST /v1/identity/operator/clients/{client_id}:activate`\n\n```ts\nconst { data } = await client.activateClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClient`\n\nImmediately block the client for every organization. Provider-side artefacts (codes, tokens,\ngrants) are revoked; the audit trail is kept. Reversible with `activateClient`.\n\n`POST /v1/identity/operator/clients/{client_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClient(\n {\n client_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `rotateClientSecret`\n\nIssue a new client secret. The new secret is returned exactly once. The previous secret\nkeeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which\ncase it stops working\n\n`POST /v1/identity/operator/clients/{client_id}:rotateSecret`\n\n```ts\nconst { data } = await client.rotateClientSecret(\n {\n client_id: 'example',\n },\n {\n compromise_mode: false,\n migration_window_seconds: 86400\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `listClientOrganizations`\n\nList the organizations enabled on a client, including suspended and revoked ones.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.listClientOrganizations({\n client_id: 'example',\n status: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `enableClientOrganization`\n\nEnable an epilot organization on a client, recording the customer's authorization policy\n(version 1). `allowed_scopes` must contain `openid` and be a subset of the client's\n`scopes_allowed`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.enableClientOrganization(\n {\n client_id: 'example',\n },\n {\n org_id: '739224',\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getClientOrganization`\n\nRead one organization enablement.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.getClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClientOrganization`\n\nRecord a new version of the customer's authorization policy. `expected_policy_version` must\nequal the current version (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.updateClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n expected_policy_version: 1,\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `revokeClientOrganization`\n\nRevoke an organization's enablement. The record is kept as an audited tombstone; future\nauthorizations for this organization fail with `access_denied` and provider-side artefacts\nbound to it are revok\n\n`DELETE /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.revokeClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClientOrganization`\n\nTemporarily block one organization on this client. Reversible with `activateClientOrganization`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClientOrganization`\n\nResume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate`\n\n```ts\nconst { data } = await client.activateClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createIdentitySession`\n\nCalled by the epilot portal through the portal-host proxy after every persisted 360 session.\nValidates the 360 bearer token itself and mirrors it into the `identity_session` cookie\n(`HttpOnly; Secure;\n\n`GET /v1/identity/session`\n\n```ts\nconst { data } = await client.createIdentitySession()\n```\n\n
\nResponse\n\n```json\n{\n \"org_id\": \"739224\",\n \"user_id\": \"string\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\"\n}\n```\n\n
\n\n---\n\n### `deleteIdentitySession`\n\nClears the `identity_session` cookie. Called by the portal on logout.\n\n`DELETE /v1/identity/session`\n\n```ts\nconst { data } = await client.deleteIdentitySession()\n```\n\n---\n\n### `launchClient`\n\nOne validated place for partner start links (manager UI, hosted apps). Redirects the browser to the\nclient's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep lin\n\n`GET /v1/identity/launch/{client_id}`\n\n```ts\nconst { data } = await client.launchClient({\n client_id: 'example',\n target: 'example',\n extra: 'example',\n})\n```\n\n---\n\n## Schemas\n\n### `Error`\n\n```ts\ntype Error = {\n status: number\n error: string\n details?: Record[]\n}\n```\n\n### `PartnerClientId`\n\nOpaque client identifier, unique within the issuer, never reused\n\n```ts\ntype PartnerClientId = string\n```\n\n### `OrgId`\n\nepilot organization id, an opaque case-sensitive string\n\n```ts\ntype OrgId = string\n```\n\n### `Environment`\n\nPartner-side environment this client belongs to\n\n```ts\ntype Environment = \"dev\" | \"staging\" | \"production\"\n```\n\n### `PartnerClientStatus`\n\n```ts\ntype PartnerClientStatus = \"draft\" | \"active\" | \"suspended\" | \"retired\"\n```\n\n### `Scope`\n\n```ts\ntype Scope = \"openid\" | \"email\" | \"profile\"\n```\n\n### `HttpsUri`\n\nAbsolute HTTPS URL without fragment\n\n```ts\ntype HttpsUri = string // uri\n```\n\n### `PartnerClientCreate`\n\n```ts\ntype PartnerClientCreate = {\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed?: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClientUpdate`\n\n```ts\ntype PartnerClientUpdate = {\n config_version: number\n allow_sector_change?: boolean\n display_name: string\n description?: string\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClient`\n\n```ts\ntype PartnerClient = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `PartnerClientWithSecret`\n\n```ts\ntype PartnerClientWithSecret = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n client_secret: string\n}\n```\n\n### `PartnerClientList`\n\n```ts\ntype PartnerClientList = {\n results: Array<{\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n next_cursor?: string\n}\n```\n\n### `StatusChangeRequest`\n\n```ts\ntype StatusChangeRequest = {\n reason?: string\n}\n```\n\n### `RotateSecretRequest`\n\n```ts\ntype RotateSecretRequest = {\n compromise_mode?: boolean\n migration_window_seconds?: number\n}\n```\n\n### `AuthorizationPolicyInput`\n\n```ts\ntype AuthorizationPolicyInput = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n}\n```\n\n### `AuthorizationPolicy`\n\n```ts\ntype AuthorizationPolicy = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n}\n```\n\n### `PartnerAccount`\n\nOptional audit reference to the partner tenant this organization maps to. The partner enforces the mapping.\n\n```ts\ntype PartnerAccount = {\n id: string\n label?: string\n}\n```\n\n### `OrgEnablementStatus`\n\n```ts\ntype OrgEnablementStatus = \"active\" | \"suspended\" | \"revoked\"\n```\n\n### `OrgEnablementCreate`\n\n```ts\ntype OrgEnablementCreate = {\n org_id: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablementUpdate`\n\n```ts\ntype OrgEnablementUpdate = {\n expected_policy_version: number\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablement`\n\n```ts\ntype OrgEnablement = {\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `OrgEnablementList`\n\n```ts\ntype OrgEnablementList = {\n results: Array<{\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: { ... }\n population?: { ... }\n allowed_scopes: { ... }\n customer_approval_ref: { ... }\n approved_at: { ... }\n version: { ... }\n mandatory_claims: { ... }\n recorded_by: { ... }\n recorded_at: { ... }\n }\n partner_account?: {\n id: { ... }\n label?: { ... }\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n}\n```\n\n### `IdentitySession`\n\n```ts\ntype IdentitySession = {\n org_id: string\n user_id: string\n expires_at: string // date-time\n}\n```\n" +"# Identity API\n\n- **Base URL:** `https://id.epilot.cloud`\n- **Full API Docs:** [https://docs.epilot.io/api/identity](https://docs.epilot.io/api/identity)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.identity.listClients(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/identity'\n\nconst identityClient = getClient()\nauthorize(identityClient, () => '')\nconst { data } = await identityClient.listClients(...)\n```\n\n## Operations\n\n**Operator Clients**\n- [`listClients`](#listclients)\n- [`createOidcClient`](#createoidcclient)\n- [`getOidcClient`](#getoidcclient)\n- [`updateClient`](#updateclient)\n- [`retireClient`](#retireclient)\n- [`activateClient`](#activateclient)\n- [`suspendClient`](#suspendclient)\n- [`rotateClientSecret`](#rotateclientsecret)\n\n**Operator Organizations**\n- [`listClientOrganizations`](#listclientorganizations)\n- [`enableClientOrganization`](#enableclientorganization)\n- [`getClientOrganization`](#getclientorganization)\n- [`updateClientOrganization`](#updateclientorganization)\n- [`revokeClientOrganization`](#revokeclientorganization)\n- [`suspendClientOrganization`](#suspendclientorganization)\n- [`activateClientOrganization`](#activateclientorganization)\n\n**Session**\n- [`createIdentitySession`](#createidentitysession)\n- [`deleteIdentitySession`](#deleteidentitysession)\n\n**Launch**\n- [`launchClient`](#launchclient)\n\n**Schemas**\n- [`Error`](#error)\n- [`PartnerClientId`](#partnerclientid)\n- [`OrgId`](#orgid)\n- [`Environment`](#environment)\n- [`PartnerClientStatus`](#partnerclientstatus)\n- [`Scope`](#scope)\n- [`HttpsUri`](#httpsuri)\n- [`PartnerClientCreate`](#partnerclientcreate)\n- [`PartnerClientUpdate`](#partnerclientupdate)\n- [`PartnerClient`](#partnerclient)\n- [`PartnerClientWithSecret`](#partnerclientwithsecret)\n- [`PartnerClientList`](#partnerclientlist)\n- [`StatusChangeRequest`](#statuschangerequest)\n- [`RotateSecretRequest`](#rotatesecretrequest)\n- [`AuthorizationPolicyInput`](#authorizationpolicyinput)\n- [`AuthorizationPolicy`](#authorizationpolicy)\n- [`PartnerAccount`](#partneraccount)\n- [`OrgEnablementStatus`](#orgenablementstatus)\n- [`OrgEnablementCreate`](#orgenablementcreate)\n- [`OrgEnablementUpdate`](#orgenablementupdate)\n- [`OrgEnablement`](#orgenablement)\n- [`OrgEnablementList`](#orgenablementlist)\n- [`IdentitySession`](#identitysession)\n\n### `listClients`\n\nList registered partner clients. The client secret is never returned.\n\n`GET /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.listClients({\n partner_key: 'example',\n status: 'example',\n size: 1,\n cursor: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ],\n \"next_cursor\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createOidcClient`\n\nCreate a partner client in `draft` status. The client secret is returned exactly once in this\nresponse and can never be read again; use `rotateClientSecret` to obtain a new one.\n\n`POST /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.createOidcClient(\n null,\n {\n partner_key: 'babelforce',\n display_name: 'Babelforce',\n description: 'string',\n environment: 'dev',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getOidcClient`\n\nRead a partner client. The client secret is never returned.\n\n`GET /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.getOidcClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClient`\n\nReplace the mutable configuration of a client. `config_version` must equal the current\nversion (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.updateClient(\n {\n client_id: 'example',\n },\n {\n config_version: 1,\n allow_sector_change: false,\n display_name: 'string',\n description: 'string',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `retireClient`\n\nRetire a client. The record is kept as a tombstone, the `client_id` is never reused, every\norganization enablement is revoked and all provider-side artefacts are revoked.\n\n`DELETE /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.retireClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClient`\n\nActivate a `draft` or `suspended` client. Validates that every redirect URI is an absolute\nHTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the\nauthentication method is `c\n\n`POST /v1/identity/operator/clients/{client_id}:activate`\n\n```ts\nconst { data } = await client.activateClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClient`\n\nImmediately block the client for every organization. Provider-side artefacts (codes, tokens,\ngrants) are revoked; the audit trail is kept. Reversible with `activateClient`.\n\n`POST /v1/identity/operator/clients/{client_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClient(\n {\n client_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `rotateClientSecret`\n\nIssue a new client secret. The new secret is returned exactly once. The previous secret\nkeeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which\ncase it stops working\n\n`POST /v1/identity/operator/clients/{client_id}:rotateSecret`\n\n```ts\nconst { data } = await client.rotateClientSecret(\n {\n client_id: 'example',\n },\n {\n compromise_mode: false,\n migration_window_seconds: 86400\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `listClientOrganizations`\n\nList the organizations enabled on a client, including suspended and revoked ones.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.listClientOrganizations({\n client_id: 'example',\n status: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `enableClientOrganization`\n\nEnable an epilot organization on a client, recording the customer's authorization policy\n(version 1). `allowed_scopes` must contain `openid` and be a subset of the client's\n`scopes_allowed`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.enableClientOrganization(\n {\n client_id: 'example',\n },\n {\n org_id: '739224',\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getClientOrganization`\n\nRead one organization enablement.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.getClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClientOrganization`\n\nRecord a new version of the customer's authorization policy. `expected_policy_version` must\nequal the current version (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.updateClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n expected_policy_version: 1,\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `revokeClientOrganization`\n\nRevoke an organization's enablement. The record is kept as an audited tombstone; future\nauthorizations for this organization fail with `access_denied` and provider-side artefacts\nbound to it are revok\n\n`DELETE /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.revokeClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClientOrganization`\n\nTemporarily block one organization on this client. Reversible with `activateClientOrganization`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClientOrganization`\n\nResume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate`\n\n```ts\nconst { data } = await client.activateClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createIdentitySession`\n\nCalled by the epilot portal through the portal-host proxy after every persisted 360 session.\nValidates the 360 bearer token itself and mirrors it into the `identity_session` cookie\n(`HttpOnly; Secure;\n\n`GET /v1/identity/session`\n\n```ts\nconst { data } = await client.createIdentitySession()\n```\n\n
\nResponse\n\n```json\n{\n \"org_id\": \"739224\",\n \"user_id\": \"string\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\"\n}\n```\n\n
\n\n---\n\n### `deleteIdentitySession`\n\nClears the `identity_session` cookie. Called by the portal on logout.\n\n`DELETE /v1/identity/session`\n\n```ts\nconst { data } = await client.deleteIdentitySession()\n```\n\n---\n\n### `launchClient`\n\nOne validated place for partner start links (manager UI, hosted apps). Redirects the browser to the\nclient's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep lin\n\n`GET /v1/identity/launch/{client_id}`\n\n```ts\nconst { data } = await client.launchClient({\n client_id: 'example',\n target: 'example',\n extra: 'example',\n})\n```\n\n---\n\n## Schemas\n\n### `Error`\n\n```ts\ntype Error = {\n status: number\n error: string\n details?: Record[]\n}\n```\n\n### `PartnerClientId`\n\nOpaque client identifier, unique within the issuer, never reused\n\n```ts\ntype PartnerClientId = string\n```\n\n### `OrgId`\n\nepilot organization id, an opaque case-sensitive string\n\n```ts\ntype OrgId = string\n```\n\n### `Environment`\n\nPartner-side environment this client belongs to\n\n```ts\ntype Environment = \"dev\" | \"staging\" | \"production\"\n```\n\n### `PartnerClientStatus`\n\n```ts\ntype PartnerClientStatus = \"draft\" | \"active\" | \"suspended\" | \"retired\"\n```\n\n### `Scope`\n\n```ts\ntype Scope = \"openid\" | \"email\" | \"profile\"\n```\n\n### `HttpsUri`\n\nAbsolute HTTPS URL without fragment\n\n```ts\ntype HttpsUri = string // uri\n```\n\n### `PartnerClientCreate`\n\n```ts\ntype PartnerClientCreate = {\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed?: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClientUpdate`\n\n```ts\ntype PartnerClientUpdate = {\n config_version: number\n allow_sector_change?: boolean\n display_name: string\n description?: string\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClient`\n\n```ts\ntype PartnerClient = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `PartnerClientWithSecret`\n\n```ts\ntype PartnerClientWithSecret = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n client_secret: string\n}\n```\n\n### `PartnerClientList`\n\n```ts\ntype PartnerClientList = {\n results: Array<{\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n next_cursor?: string\n}\n```\n\n### `StatusChangeRequest`\n\n```ts\ntype StatusChangeRequest = {\n reason?: string\n}\n```\n\n### `RotateSecretRequest`\n\n```ts\ntype RotateSecretRequest = {\n compromise_mode?: boolean\n migration_window_seconds?: number\n}\n```\n\n### `AuthorizationPolicyInput`\n\n```ts\ntype AuthorizationPolicyInput = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n}\n```\n\n### `AuthorizationPolicy`\n\n```ts\ntype AuthorizationPolicy = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n}\n```\n\n### `PartnerAccount`\n\nOptional audit reference to the partner tenant this organization maps to. The partner enforces the mapping.\n\n```ts\ntype PartnerAccount = {\n id: string\n label?: string\n}\n```\n\n### `OrgEnablementStatus`\n\n```ts\ntype OrgEnablementStatus = \"active\" | \"suspended\" | \"revoked\"\n```\n\n### `OrgEnablementCreate`\n\n```ts\ntype OrgEnablementCreate = {\n org_id: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablementUpdate`\n\n```ts\ntype OrgEnablementUpdate = {\n expected_policy_version: number\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablement`\n\n```ts\ntype OrgEnablement = {\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `OrgEnablementList`\n\n```ts\ntype OrgEnablementList = {\n results: Array<{\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: { ... }\n population?: { ... }\n allowed_scopes: { ... }\n customer_approval_ref: { ... }\n approved_at: { ... }\n version: { ... }\n mandatory_claims: { ... }\n recorded_by: { ... }\n recorded_at: { ... }\n }\n partner_account?: {\n id: { ... }\n label?: { ... }\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n}\n```\n\n### `IdentitySession`\n\n```ts\ntype IdentitySession = {\n org_id: string\n user_id: string\n expires_at: string // date-time\n}\n```\n" \ No newline at end of file diff --git a/packages/epilot-sdk-v2/src/types/identity.d.ts b/packages/epilot-sdk-v2/src/types/identity.d.ts index 4cae0f484..0ca5633f5 100644 --- a/packages/epilot-sdk-v2/src/types/identity.d.ts +++ b/packages/epilot-sdk-v2/src/types/identity.d.ts @@ -442,19 +442,19 @@ export declare namespace Paths { export type $409 = Components.Responses.Conflict; } } - namespace CreateClient { - export type RequestBody = Components.Schemas.PartnerClientCreate; + namespace CreateIdentitySession { namespace Responses { - export type $201 = Components.Schemas.PartnerClientWithSecret; - export type $400 = Components.Responses.BadRequest; + export type $200 = Components.Schemas.IdentitySession; export type $401 = Components.Responses.Unauthorized; - export type $403 = Components.Responses.Forbidden; } } - namespace CreateIdentitySession { + namespace CreateOidcClient { + export type RequestBody = Components.Schemas.PartnerClientCreate; namespace Responses { - export type $200 = Components.Schemas.IdentitySession; + export type $201 = Components.Schemas.PartnerClientWithSecret; + export type $400 = Components.Responses.BadRequest; export type $401 = Components.Responses.Unauthorized; + export type $403 = Components.Responses.Forbidden; } } namespace DeleteIdentitySession { @@ -474,17 +474,17 @@ export declare namespace Paths { export type $409 = Components.Responses.Conflict; } } - namespace GetClient { + namespace GetClientOrganization { namespace Responses { - export type $200 = Components.Schemas.PartnerClient; + export type $200 = Components.Schemas.OrgEnablement; export type $401 = Components.Responses.Unauthorized; export type $403 = Components.Responses.Forbidden; export type $404 = Components.Responses.NotFound; } } - namespace GetClientOrganization { + namespace GetOidcClient { namespace Responses { - export type $200 = Components.Schemas.OrgEnablement; + export type $200 = Components.Schemas.PartnerClient; export type $401 = Components.Responses.Unauthorized; export type $403 = Components.Responses.Forbidden; export type $404 = Components.Responses.NotFound; @@ -681,27 +681,27 @@ export interface OperationMethods { config?: AxiosRequestConfig ): OperationResponse /** - * createClient - createClient + * createOidcClient - createOidcClient * * Create a partner client in `draft` status. The client secret is returned exactly once in this * response and can never be read again; use `rotateClientSecret` to obtain a new one. * */ - 'createClient'( + 'createOidcClient'( parameters?: Parameters | null, - data?: Paths.CreateClient.RequestBody, + data?: Paths.CreateOidcClient.RequestBody, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse /** - * getClient - getClient + * getOidcClient - getOidcClient * * Read a partner client. The client secret is never returned. */ - 'getClient'( + 'getOidcClient'( parameters?: Parameters | null, data?: any, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse /** * updateClient - updateClient * @@ -899,7 +899,7 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse /** - * createClient - createClient + * createOidcClient - createOidcClient * * Create a partner client in `draft` status. The client secret is returned exactly once in this * response and can never be read again; use `rotateClientSecret` to obtain a new one. @@ -907,13 +907,13 @@ export interface PathsDictionary { */ 'post'( parameters?: Parameters | null, - data?: Paths.CreateClient.RequestBody, + data?: Paths.CreateOidcClient.RequestBody, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse } ['/v1/identity/operator/clients/{client_id}']: { /** - * getClient - getClient + * getOidcClient - getOidcClient * * Read a partner client. The client secret is never returned. */ @@ -921,7 +921,7 @@ export interface PathsDictionary { parameters?: Parameters | null, data?: any, config?: AxiosRequestConfig - ): OperationResponse + ): OperationResponse /** * updateClient - updateClient * From 4b1db69d4e55ff9db19bf7aafeb5e5767eccf8a0 Mon Sep 17 00:00:00 2001 From: Flavius Mardare Date: Wed, 23 Sep 2026 14:10:13 +0200 Subject: [PATCH 3/4] fix fail lint --- packages/epilot-sdk-v2/src/docs/identity.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/epilot-sdk-v2/src/docs/identity.json b/packages/epilot-sdk-v2/src/docs/identity.json index 63c38d595..42db3f4e8 100644 --- a/packages/epilot-sdk-v2/src/docs/identity.json +++ b/packages/epilot-sdk-v2/src/docs/identity.json @@ -1 +1 @@ -"# Identity API\n\n- **Base URL:** `https://id.epilot.cloud`\n- **Full API Docs:** [https://docs.epilot.io/api/identity](https://docs.epilot.io/api/identity)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.identity.listClients(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/identity'\n\nconst identityClient = getClient()\nauthorize(identityClient, () => '')\nconst { data } = await identityClient.listClients(...)\n```\n\n## Operations\n\n**Operator Clients**\n- [`listClients`](#listclients)\n- [`createOidcClient`](#createoidcclient)\n- [`getOidcClient`](#getoidcclient)\n- [`updateClient`](#updateclient)\n- [`retireClient`](#retireclient)\n- [`activateClient`](#activateclient)\n- [`suspendClient`](#suspendclient)\n- [`rotateClientSecret`](#rotateclientsecret)\n\n**Operator Organizations**\n- [`listClientOrganizations`](#listclientorganizations)\n- [`enableClientOrganization`](#enableclientorganization)\n- [`getClientOrganization`](#getclientorganization)\n- [`updateClientOrganization`](#updateclientorganization)\n- [`revokeClientOrganization`](#revokeclientorganization)\n- [`suspendClientOrganization`](#suspendclientorganization)\n- [`activateClientOrganization`](#activateclientorganization)\n\n**Session**\n- [`createIdentitySession`](#createidentitysession)\n- [`deleteIdentitySession`](#deleteidentitysession)\n\n**Launch**\n- [`launchClient`](#launchclient)\n\n**Schemas**\n- [`Error`](#error)\n- [`PartnerClientId`](#partnerclientid)\n- [`OrgId`](#orgid)\n- [`Environment`](#environment)\n- [`PartnerClientStatus`](#partnerclientstatus)\n- [`Scope`](#scope)\n- [`HttpsUri`](#httpsuri)\n- [`PartnerClientCreate`](#partnerclientcreate)\n- [`PartnerClientUpdate`](#partnerclientupdate)\n- [`PartnerClient`](#partnerclient)\n- [`PartnerClientWithSecret`](#partnerclientwithsecret)\n- [`PartnerClientList`](#partnerclientlist)\n- [`StatusChangeRequest`](#statuschangerequest)\n- [`RotateSecretRequest`](#rotatesecretrequest)\n- [`AuthorizationPolicyInput`](#authorizationpolicyinput)\n- [`AuthorizationPolicy`](#authorizationpolicy)\n- [`PartnerAccount`](#partneraccount)\n- [`OrgEnablementStatus`](#orgenablementstatus)\n- [`OrgEnablementCreate`](#orgenablementcreate)\n- [`OrgEnablementUpdate`](#orgenablementupdate)\n- [`OrgEnablement`](#orgenablement)\n- [`OrgEnablementList`](#orgenablementlist)\n- [`IdentitySession`](#identitysession)\n\n### `listClients`\n\nList registered partner clients. The client secret is never returned.\n\n`GET /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.listClients({\n partner_key: 'example',\n status: 'example',\n size: 1,\n cursor: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ],\n \"next_cursor\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createOidcClient`\n\nCreate a partner client in `draft` status. The client secret is returned exactly once in this\nresponse and can never be read again; use `rotateClientSecret` to obtain a new one.\n\n`POST /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.createOidcClient(\n null,\n {\n partner_key: 'babelforce',\n display_name: 'Babelforce',\n description: 'string',\n environment: 'dev',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getOidcClient`\n\nRead a partner client. The client secret is never returned.\n\n`GET /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.getOidcClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClient`\n\nReplace the mutable configuration of a client. `config_version` must equal the current\nversion (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.updateClient(\n {\n client_id: 'example',\n },\n {\n config_version: 1,\n allow_sector_change: false,\n display_name: 'string',\n description: 'string',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `retireClient`\n\nRetire a client. The record is kept as a tombstone, the `client_id` is never reused, every\norganization enablement is revoked and all provider-side artefacts are revoked.\n\n`DELETE /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.retireClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClient`\n\nActivate a `draft` or `suspended` client. Validates that every redirect URI is an absolute\nHTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the\nauthentication method is `c\n\n`POST /v1/identity/operator/clients/{client_id}:activate`\n\n```ts\nconst { data } = await client.activateClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClient`\n\nImmediately block the client for every organization. Provider-side artefacts (codes, tokens,\ngrants) are revoked; the audit trail is kept. Reversible with `activateClient`.\n\n`POST /v1/identity/operator/clients/{client_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClient(\n {\n client_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `rotateClientSecret`\n\nIssue a new client secret. The new secret is returned exactly once. The previous secret\nkeeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which\ncase it stops working\n\n`POST /v1/identity/operator/clients/{client_id}:rotateSecret`\n\n```ts\nconst { data } = await client.rotateClientSecret(\n {\n client_id: 'example',\n },\n {\n compromise_mode: false,\n migration_window_seconds: 86400\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `listClientOrganizations`\n\nList the organizations enabled on a client, including suspended and revoked ones.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.listClientOrganizations({\n client_id: 'example',\n status: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `enableClientOrganization`\n\nEnable an epilot organization on a client, recording the customer's authorization policy\n(version 1). `allowed_scopes` must contain `openid` and be a subset of the client's\n`scopes_allowed`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.enableClientOrganization(\n {\n client_id: 'example',\n },\n {\n org_id: '739224',\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getClientOrganization`\n\nRead one organization enablement.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.getClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClientOrganization`\n\nRecord a new version of the customer's authorization policy. `expected_policy_version` must\nequal the current version (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.updateClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n expected_policy_version: 1,\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `revokeClientOrganization`\n\nRevoke an organization's enablement. The record is kept as an audited tombstone; future\nauthorizations for this organization fail with `access_denied` and provider-side artefacts\nbound to it are revok\n\n`DELETE /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.revokeClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClientOrganization`\n\nTemporarily block one organization on this client. Reversible with `activateClientOrganization`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClientOrganization`\n\nResume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate`\n\n```ts\nconst { data } = await client.activateClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createIdentitySession`\n\nCalled by the epilot portal through the portal-host proxy after every persisted 360 session.\nValidates the 360 bearer token itself and mirrors it into the `identity_session` cookie\n(`HttpOnly; Secure;\n\n`GET /v1/identity/session`\n\n```ts\nconst { data } = await client.createIdentitySession()\n```\n\n
\nResponse\n\n```json\n{\n \"org_id\": \"739224\",\n \"user_id\": \"string\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\"\n}\n```\n\n
\n\n---\n\n### `deleteIdentitySession`\n\nClears the `identity_session` cookie. Called by the portal on logout.\n\n`DELETE /v1/identity/session`\n\n```ts\nconst { data } = await client.deleteIdentitySession()\n```\n\n---\n\n### `launchClient`\n\nOne validated place for partner start links (manager UI, hosted apps). Redirects the browser to the\nclient's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep lin\n\n`GET /v1/identity/launch/{client_id}`\n\n```ts\nconst { data } = await client.launchClient({\n client_id: 'example',\n target: 'example',\n extra: 'example',\n})\n```\n\n---\n\n## Schemas\n\n### `Error`\n\n```ts\ntype Error = {\n status: number\n error: string\n details?: Record[]\n}\n```\n\n### `PartnerClientId`\n\nOpaque client identifier, unique within the issuer, never reused\n\n```ts\ntype PartnerClientId = string\n```\n\n### `OrgId`\n\nepilot organization id, an opaque case-sensitive string\n\n```ts\ntype OrgId = string\n```\n\n### `Environment`\n\nPartner-side environment this client belongs to\n\n```ts\ntype Environment = \"dev\" | \"staging\" | \"production\"\n```\n\n### `PartnerClientStatus`\n\n```ts\ntype PartnerClientStatus = \"draft\" | \"active\" | \"suspended\" | \"retired\"\n```\n\n### `Scope`\n\n```ts\ntype Scope = \"openid\" | \"email\" | \"profile\"\n```\n\n### `HttpsUri`\n\nAbsolute HTTPS URL without fragment\n\n```ts\ntype HttpsUri = string // uri\n```\n\n### `PartnerClientCreate`\n\n```ts\ntype PartnerClientCreate = {\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed?: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClientUpdate`\n\n```ts\ntype PartnerClientUpdate = {\n config_version: number\n allow_sector_change?: boolean\n display_name: string\n description?: string\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClient`\n\n```ts\ntype PartnerClient = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `PartnerClientWithSecret`\n\n```ts\ntype PartnerClientWithSecret = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n client_secret: string\n}\n```\n\n### `PartnerClientList`\n\n```ts\ntype PartnerClientList = {\n results: Array<{\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n next_cursor?: string\n}\n```\n\n### `StatusChangeRequest`\n\n```ts\ntype StatusChangeRequest = {\n reason?: string\n}\n```\n\n### `RotateSecretRequest`\n\n```ts\ntype RotateSecretRequest = {\n compromise_mode?: boolean\n migration_window_seconds?: number\n}\n```\n\n### `AuthorizationPolicyInput`\n\n```ts\ntype AuthorizationPolicyInput = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n}\n```\n\n### `AuthorizationPolicy`\n\n```ts\ntype AuthorizationPolicy = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n}\n```\n\n### `PartnerAccount`\n\nOptional audit reference to the partner tenant this organization maps to. The partner enforces the mapping.\n\n```ts\ntype PartnerAccount = {\n id: string\n label?: string\n}\n```\n\n### `OrgEnablementStatus`\n\n```ts\ntype OrgEnablementStatus = \"active\" | \"suspended\" | \"revoked\"\n```\n\n### `OrgEnablementCreate`\n\n```ts\ntype OrgEnablementCreate = {\n org_id: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablementUpdate`\n\n```ts\ntype OrgEnablementUpdate = {\n expected_policy_version: number\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablement`\n\n```ts\ntype OrgEnablement = {\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `OrgEnablementList`\n\n```ts\ntype OrgEnablementList = {\n results: Array<{\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: { ... }\n population?: { ... }\n allowed_scopes: { ... }\n customer_approval_ref: { ... }\n approved_at: { ... }\n version: { ... }\n mandatory_claims: { ... }\n recorded_by: { ... }\n recorded_at: { ... }\n }\n partner_account?: {\n id: { ... }\n label?: { ... }\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n}\n```\n\n### `IdentitySession`\n\n```ts\ntype IdentitySession = {\n org_id: string\n user_id: string\n expires_at: string // date-time\n}\n```\n" \ No newline at end of file +"# Identity API\n\n- **Base URL:** `https://id.epilot.cloud`\n- **Full API Docs:** [https://docs.epilot.io/api/identity](https://docs.epilot.io/api/identity)\n\n## Usage\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nconst { data } = await epilot.identity.listClients(...)\n```\n\n### Tree-shakeable import\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/identity'\n\nconst identityClient = getClient()\nauthorize(identityClient, () => '')\nconst { data } = await identityClient.listClients(...)\n```\n\n## Operations\n\n**Operator Clients**\n- [`listClients`](#listclients)\n- [`createOidcClient`](#createoidcclient)\n- [`getOidcClient`](#getoidcclient)\n- [`updateClient`](#updateclient)\n- [`retireClient`](#retireclient)\n- [`activateClient`](#activateclient)\n- [`suspendClient`](#suspendclient)\n- [`rotateClientSecret`](#rotateclientsecret)\n\n**Operator Organizations**\n- [`listClientOrganizations`](#listclientorganizations)\n- [`enableClientOrganization`](#enableclientorganization)\n- [`getClientOrganization`](#getclientorganization)\n- [`updateClientOrganization`](#updateclientorganization)\n- [`revokeClientOrganization`](#revokeclientorganization)\n- [`suspendClientOrganization`](#suspendclientorganization)\n- [`activateClientOrganization`](#activateclientorganization)\n\n**Session**\n- [`createIdentitySession`](#createidentitysession)\n- [`deleteIdentitySession`](#deleteidentitysession)\n\n**Launch**\n- [`launchClient`](#launchclient)\n\n**Schemas**\n- [`Error`](#error)\n- [`PartnerClientId`](#partnerclientid)\n- [`OrgId`](#orgid)\n- [`Environment`](#environment)\n- [`PartnerClientStatus`](#partnerclientstatus)\n- [`Scope`](#scope)\n- [`HttpsUri`](#httpsuri)\n- [`PartnerClientCreate`](#partnerclientcreate)\n- [`PartnerClientUpdate`](#partnerclientupdate)\n- [`PartnerClient`](#partnerclient)\n- [`PartnerClientWithSecret`](#partnerclientwithsecret)\n- [`PartnerClientList`](#partnerclientlist)\n- [`StatusChangeRequest`](#statuschangerequest)\n- [`RotateSecretRequest`](#rotatesecretrequest)\n- [`AuthorizationPolicyInput`](#authorizationpolicyinput)\n- [`AuthorizationPolicy`](#authorizationpolicy)\n- [`PartnerAccount`](#partneraccount)\n- [`OrgEnablementStatus`](#orgenablementstatus)\n- [`OrgEnablementCreate`](#orgenablementcreate)\n- [`OrgEnablementUpdate`](#orgenablementupdate)\n- [`OrgEnablement`](#orgenablement)\n- [`OrgEnablementList`](#orgenablementlist)\n- [`IdentitySession`](#identitysession)\n\n### `listClients`\n\nList registered partner clients. The client secret is never returned.\n\n`GET /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.listClients({\n partner_key: 'example',\n status: 'example',\n size: 1,\n cursor: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ],\n \"next_cursor\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createOidcClient`\n\nCreate a partner client in `draft` status. The client secret is returned exactly once in this\nresponse and can never be read again; use `rotateClientSecret` to obtain a new one.\n\n`POST /v1/identity/operator/clients`\n\n```ts\nconst { data } = await client.createOidcClient(\n null,\n {\n partner_key: 'babelforce',\n display_name: 'Babelforce',\n description: 'string',\n environment: 'dev',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getOidcClient`\n\nRead a partner client. The client secret is never returned.\n\n`GET /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.getOidcClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClient`\n\nReplace the mutable configuration of a client. `config_version` must equal the current\nversion (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.updateClient(\n {\n client_id: 'example',\n },\n {\n config_version: 1,\n allow_sector_change: false,\n display_name: 'string',\n description: 'string',\n redirect_uris: ['https://partner.example.com/auth/callback'],\n sector_identifier_uri: 'https://partner.example.com/auth/callback',\n initiate_login_uri: 'https://partner.example.com/auth/callback',\n launch_target_prefixes: ['string'],\n scopes_allowed: ['openid']\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `retireClient`\n\nRetire a client. The record is kept as a tombstone, the `client_id` is never reused, every\norganization enablement is revoked and all provider-side artefacts are revoked.\n\n`DELETE /v1/identity/operator/clients/{client_id}`\n\n```ts\nconst { data } = await client.retireClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClient`\n\nActivate a `draft` or `suspended` client. Validates that every redirect URI is an absolute\nHTTPS URL without fragment, that `scopes_allowed` contains `openid`, and that the\nauthentication method is `c\n\n`POST /v1/identity/operator/clients/{client_id}:activate`\n\n```ts\nconst { data } = await client.activateClient({\n client_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClient`\n\nImmediately block the client for every organization. Provider-side artefacts (codes, tokens,\ngrants) are revoked; the audit trail is kept. Reversible with `activateClient`.\n\n`POST /v1/identity/operator/clients/{client_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClient(\n {\n client_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `rotateClientSecret`\n\nIssue a new client secret. The new secret is returned exactly once. The previous secret\nkeeps working until `previous_secret_expires_at` unless `compromise_mode` is set, in which\ncase it stops working\n\n`POST /v1/identity/operator/clients/{client_id}:rotateSecret`\n\n```ts\nconst { data } = await client.rotateClientSecret(\n {\n client_id: 'example',\n },\n {\n compromise_mode: false,\n migration_window_seconds: 86400\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"partner_key\": \"string\",\n \"display_name\": \"string\",\n \"description\": \"string\",\n \"environment\": \"dev\",\n \"redirect_uris\": [\"https://partner.example.com/auth/callback\"],\n \"sector_identifier_uri\": \"https://partner.example.com/auth/callback\",\n \"initiate_login_uri\": \"https://partner.example.com/auth/callback\",\n \"launch_target_prefixes\": [\"string\"],\n \"token_endpoint_auth_method\": \"client_secret_basic\",\n \"grant_types\": [\"authorization_code\"],\n \"scopes_allowed\": [\"openid\"],\n \"status\": \"draft\",\n \"status_reason\": \"string\",\n \"config_version\": 1,\n \"secret_rotated_at\": \"1970-01-01T00:00:00.000Z\",\n \"previous_secret_expires_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\",\n \"client_secret\": \"string\"\n}\n```\n\n
\n\n---\n\n### `listClientOrganizations`\n\nList the organizations enabled on a client, including suspended and revoked ones.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.listClientOrganizations({\n client_id: 'example',\n status: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"results\": [\n {\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n }\n ]\n}\n```\n\n
\n\n---\n\n### `enableClientOrganization`\n\nEnable an epilot organization on a client, recording the customer's authorization policy\n(version 1). `allowed_scopes` must contain `openid` and be a subset of the client's\n`scopes_allowed`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations`\n\n```ts\nconst { data } = await client.enableClientOrganization(\n {\n client_id: 'example',\n },\n {\n org_id: '739224',\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `getClientOrganization`\n\nRead one organization enablement.\n\n`GET /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.getClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `updateClientOrganization`\n\nRecord a new version of the customer's authorization policy. `expected_policy_version` must\nequal the current version (compare-and-swap); a mismatch is rejected with `409`.\n\n`PUT /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.updateClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n expected_policy_version: 1,\n authorization_policy: {\n mode: 'preauthorized',\n population: 'all_current_org_members',\n allowed_scopes: ['openid'],\n customer_approval_ref: 'string',\n approved_at: '1970-01-01T00:00:00.000Z'\n },\n partner_account: {\n id: 'string',\n label: 'string'\n }\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `revokeClientOrganization`\n\nRevoke an organization's enablement. The record is kept as an audited tombstone; future\nauthorizations for this organization fail with `access_denied` and provider-side artefacts\nbound to it are revok\n\n`DELETE /v1/identity/operator/clients/{client_id}/organizations/{org_id}`\n\n```ts\nconst { data } = await client.revokeClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `suspendClientOrganization`\n\nTemporarily block one organization on this client. Reversible with `activateClientOrganization`.\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:suspend`\n\n```ts\nconst { data } = await client.suspendClientOrganization(\n {\n client_id: 'example',\n org_id: 'example',\n },\n {\n reason: 'string'\n },\n)\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `activateClientOrganization`\n\nResume a suspended organization on this client. Revoked enablements cannot be re-activated; enabling the organization again (`POST …/organizations`) replaces the revoked entry and continues its policy\n\n`POST /v1/identity/operator/clients/{client_id}/organizations/{org_id}:activate`\n\n```ts\nconst { data } = await client.activateClientOrganization({\n client_id: 'example',\n org_id: 'example',\n})\n```\n\n
\nResponse\n\n```json\n{\n \"client_id\": \"bf-prod-x7k2\",\n \"org_id\": \"739224\",\n \"status\": \"active\",\n \"status_reason\": \"string\",\n \"authorization_policy\": {\n \"mode\": \"preauthorized\",\n \"population\": \"all_current_org_members\",\n \"allowed_scopes\": [\"openid\"],\n \"customer_approval_ref\": \"string\",\n \"approved_at\": \"1970-01-01T00:00:00.000Z\",\n \"version\": 1,\n \"mandatory_claims\": [\"org_id\"],\n \"recorded_by\": \"string\",\n \"recorded_at\": \"1970-01-01T00:00:00.000Z\"\n },\n \"partner_account\": {\n \"id\": \"string\",\n \"label\": \"string\"\n },\n \"created_at\": \"1970-01-01T00:00:00.000Z\",\n \"updated_at\": \"1970-01-01T00:00:00.000Z\",\n \"created_by\": \"string\",\n \"updated_by\": \"string\"\n}\n```\n\n
\n\n---\n\n### `createIdentitySession`\n\nCalled by the epilot portal through the portal-host proxy after every persisted 360 session.\nValidates the 360 bearer token itself and mirrors it into the `identity_session` cookie\n(`HttpOnly; Secure;\n\n`GET /v1/identity/session`\n\n```ts\nconst { data } = await client.createIdentitySession()\n```\n\n
\nResponse\n\n```json\n{\n \"org_id\": \"739224\",\n \"user_id\": \"string\",\n \"expires_at\": \"1970-01-01T00:00:00.000Z\"\n}\n```\n\n
\n\n---\n\n### `deleteIdentitySession`\n\nClears the `identity_session` cookie. Called by the portal on logout.\n\n`DELETE /v1/identity/session`\n\n```ts\nconst { data } = await client.deleteIdentitySession()\n```\n\n---\n\n### `launchClient`\n\nOne validated place for partner start links (manager UI, hosted apps). Redirects the browser to the\nclient's registered `initiate_login_uri` with `iss` set to this issuer and, when given, the deep lin\n\n`GET /v1/identity/launch/{client_id}`\n\n```ts\nconst { data } = await client.launchClient({\n client_id: 'example',\n target: 'example',\n extra: 'example',\n})\n```\n\n---\n\n## Schemas\n\n### `Error`\n\n```ts\ntype Error = {\n status: number\n error: string\n details?: Record[]\n}\n```\n\n### `PartnerClientId`\n\nOpaque client identifier, unique within the issuer, never reused\n\n```ts\ntype PartnerClientId = string\n```\n\n### `OrgId`\n\nepilot organization id, an opaque case-sensitive string\n\n```ts\ntype OrgId = string\n```\n\n### `Environment`\n\nPartner-side environment this client belongs to\n\n```ts\ntype Environment = \"dev\" | \"staging\" | \"production\"\n```\n\n### `PartnerClientStatus`\n\n```ts\ntype PartnerClientStatus = \"draft\" | \"active\" | \"suspended\" | \"retired\"\n```\n\n### `Scope`\n\n```ts\ntype Scope = \"openid\" | \"email\" | \"profile\"\n```\n\n### `HttpsUri`\n\nAbsolute HTTPS URL without fragment\n\n```ts\ntype HttpsUri = string // uri\n```\n\n### `PartnerClientCreate`\n\n```ts\ntype PartnerClientCreate = {\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed?: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClientUpdate`\n\n```ts\ntype PartnerClientUpdate = {\n config_version: number\n allow_sector_change?: boolean\n display_name: string\n description?: string\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n}\n```\n\n### `PartnerClient`\n\n```ts\ntype PartnerClient = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `PartnerClientWithSecret`\n\n```ts\ntype PartnerClientWithSecret = {\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n client_secret: string\n}\n```\n\n### `PartnerClientList`\n\n```ts\ntype PartnerClientList = {\n results: Array<{\n client_id: string\n partner_key: string\n display_name: string\n description?: string\n environment: \"dev\" | \"staging\" | \"production\"\n redirect_uris: string // uri[]\n sector_identifier_uri?: string // uri\n initiate_login_uri?: string // uri\n launch_target_prefixes?: string[]\n token_endpoint_auth_method: \"client_secret_basic\"\n grant_types: \"authorization_code\"[]\n scopes_allowed: \"openid\" | \"email\" | \"profile\"[]\n status: \"draft\" | \"active\" | \"suspended\" | \"retired\"\n status_reason?: string\n config_version: number\n secret_rotated_at?: string // date-time\n previous_secret_expires_at?: string // date-time\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n next_cursor?: string\n}\n```\n\n### `StatusChangeRequest`\n\n```ts\ntype StatusChangeRequest = {\n reason?: string\n}\n```\n\n### `RotateSecretRequest`\n\n```ts\ntype RotateSecretRequest = {\n compromise_mode?: boolean\n migration_window_seconds?: number\n}\n```\n\n### `AuthorizationPolicyInput`\n\n```ts\ntype AuthorizationPolicyInput = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n}\n```\n\n### `AuthorizationPolicy`\n\n```ts\ntype AuthorizationPolicy = {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n}\n```\n\n### `PartnerAccount`\n\nOptional audit reference to the partner tenant this organization maps to. The partner enforces the mapping.\n\n```ts\ntype PartnerAccount = {\n id: string\n label?: string\n}\n```\n\n### `OrgEnablementStatus`\n\n```ts\ntype OrgEnablementStatus = \"active\" | \"suspended\" | \"revoked\"\n```\n\n### `OrgEnablementCreate`\n\n```ts\ntype OrgEnablementCreate = {\n org_id: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablementUpdate`\n\n```ts\ntype OrgEnablementUpdate = {\n expected_policy_version: number\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n}\n```\n\n### `OrgEnablement`\n\n```ts\ntype OrgEnablement = {\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: \"preauthorized\"\n population?: \"all_current_org_members\"\n allowed_scopes: \"openid\" | \"email\" | \"profile\"[]\n customer_approval_ref: string\n approved_at: string // date-time\n version: number\n mandatory_claims: \"org_id\"[]\n recorded_by: string\n recorded_at: string // date-time\n }\n partner_account?: {\n id: string\n label?: string\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n}\n```\n\n### `OrgEnablementList`\n\n```ts\ntype OrgEnablementList = {\n results: Array<{\n client_id: string\n org_id: string\n status: \"active\" | \"suspended\" | \"revoked\"\n status_reason?: string\n authorization_policy: {\n mode?: { ... }\n population?: { ... }\n allowed_scopes: { ... }\n customer_approval_ref: { ... }\n approved_at: { ... }\n version: { ... }\n mandatory_claims: { ... }\n recorded_by: { ... }\n recorded_at: { ... }\n }\n partner_account?: {\n id: { ... }\n label?: { ... }\n }\n created_at: string // date-time\n updated_at: string // date-time\n created_by?: string\n updated_by?: string\n }>\n}\n```\n\n### `IdentitySession`\n\n```ts\ntype IdentitySession = {\n org_id: string\n user_id: string\n expires_at: string // date-time\n}\n```\n" From fdae58cd138e5a7b419d4fded47e7fb70de18414 Mon Sep 17 00:00:00 2001 From: Flavius Mardare Date: Wed, 23 Sep 2026 14:36:10 +0200 Subject: [PATCH 4/4] align identity api spec --- clients/identity-client/package.json | 1 + packages/epilot-sdk-v2/src/docs/README.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/clients/identity-client/package.json b/clients/identity-client/package.json index 0f9191427..2044ca5a1 100644 --- a/clients/identity-client/package.json +++ b/clients/identity-client/package.json @@ -17,6 +17,7 @@ }, "homepage": "https://github.com/epilot-dev/sdk-js/tree/main/clients/identity-client#readme", "keywords": [ + "epilot", "identity", "oidc", "openid-connect", diff --git a/packages/epilot-sdk-v2/src/docs/README.json b/packages/epilot-sdk-v2/src/docs/README.json index 07a072752..1602b92de 100644 --- a/packages/epilot-sdk-v2/src/docs/README.json +++ b/packages/epilot-sdk-v2/src/docs/README.json @@ -1 +1 @@ -"

\"epilot\"
@epilot/sdk

\n\n

\n \"CI\"\n \"npm\n \"License\"\n

\n\n

JavaScript/TypeScript SDK for epilot APIs. Full types, tree-shakeable imports, and lazy-loaded OpenAPI specs.

\n\n## Install\n\n```bash\nnpm i @epilot/sdk axios openapi-client-axios\n```\n\n## Quick Start\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\n\nconst { data: entity } = await epilot.entity.createEntity(\n { slug: 'contact' },\n { first_name: 'John', last_name: 'Doe' },\n)\n\nconst { data: file } = await epilot.file.getFile({ id: 'file-123' })\n\nconst { data: executions } = await epilot.workflow.getExecutions()\n```\n\nAPI clients are built on [openapi-client-axios](https://openapistack.co/docs/openapi-client-axios/intro/), which generates fully typed operation methods on top of regular [axios](https://axios-http.com/docs/intro) instances. All standard axios features (interceptors, defaults, config) work as expected. Each operation is forwarded to a lazy singleton — the spec is loaded and the client initialized on first use, then cached.\n\nFull API documentation: [https://docs.epilot.io/api](https://docs.epilot.io/api)\n\n## API Reference\n\n\n| API | Import | Docs |\n| --- | ------ | ---- |\n| `epilot.accessToken` | `@epilot/sdk/access-token` | [docs](./docs/access-token.md) |\n| `epilot.address` | `@epilot/sdk/address` | [docs](./docs/address.md) |\n| `epilot.addressSuggestions` | `@epilot/sdk/address-suggestions` | [docs](./docs/address-suggestions.md) |\n| `epilot.aiAgents` | `@epilot/sdk/ai-agents` | [docs](./docs/ai-agents.md) |\n| `epilot.app` | `@epilot/sdk/app` | [docs](./docs/app.md) |\n| `epilot.auditLogs` | `@epilot/sdk/audit-logs` | [docs](./docs/audit-logs.md) |\n| `epilot.automation` | `@epilot/sdk/automation` | [docs](./docs/automation.md) |\n| `epilot.billing` | `@epilot/sdk/billing` | [docs](./docs/billing.md) |\n| `epilot.blueprintManifest` | `@epilot/sdk/blueprint-manifest` | [docs](./docs/blueprint-manifest.md) |\n| `epilot.calendar` | `@epilot/sdk/calendar` | [docs](./docs/calendar.md) |\n| `epilot.chat` | `@epilot/sdk/chat` | [docs](./docs/chat.md) |\n| `epilot.configurationHub` | `@epilot/sdk/configuration-hub` | [docs](./docs/configuration-hub.md) |\n| `epilot.consent` | `@epilot/sdk/consent` | [docs](./docs/consent.md) |\n| `epilot.customerPortal` | `@epilot/sdk/customer-portal` | [docs](./docs/customer-portal.md) |\n| `epilot.dashboard` | `@epilot/sdk/dashboard` | [docs](./docs/dashboard.md) |\n| `epilot.dataGovernance` | `@epilot/sdk/data-governance` | [docs](./docs/data-governance.md) |\n| `epilot.deduplication` | `@epilot/sdk/deduplication` | [docs](./docs/deduplication.md) |\n| `epilot.design` | `@epilot/sdk/design` | [docs](./docs/design.md) |\n| `epilot.document` | `@epilot/sdk/document` | [docs](./docs/document.md) |\n| `epilot.emailSettings` | `@epilot/sdk/email-settings` | [docs](./docs/email-settings.md) |\n| `epilot.emailTemplate` | `@epilot/sdk/email-template` | [docs](./docs/email-template.md) |\n| `epilot.entity` | `@epilot/sdk/entity` | [docs](./docs/entity.md) |\n| `epilot.entityMapping` | `@epilot/sdk/entity-mapping` | [docs](./docs/entity-mapping.md) |\n| `epilot.environments` | `@epilot/sdk/environments` | [docs](./docs/environments.md) |\n| `epilot.eventCatalog` | `@epilot/sdk/event-catalog` | [docs](./docs/event-catalog.md) |\n| `epilot.file` | `@epilot/sdk/file` | [docs](./docs/file.md) |\n| `epilot.iban` | `@epilot/sdk/iban` | [docs](./docs/iban.md) |\n| `epilot.integrationToolkit` | `@epilot/sdk/integration-toolkit` | [docs](./docs/integration-toolkit.md) |\n| `epilot.journey` | `@epilot/sdk/journey` | [docs](./docs/journey.md) |\n| `epilot.kanban` | `@epilot/sdk/kanban` | [docs](./docs/kanban.md) |\n| `epilot.message` | `@epilot/sdk/message` | [docs](./docs/message.md) |\n| `epilot.metering` | `@epilot/sdk/metering` | [docs](./docs/metering.md) |\n| `epilot.notes` | `@epilot/sdk/notes` | [docs](./docs/notes.md) |\n| `epilot.notification` | `@epilot/sdk/notification` | [docs](./docs/notification.md) |\n| `epilot.organization` | `@epilot/sdk/organization` | [docs](./docs/organization.md) |\n| `epilot.partnerDirectory` | `@epilot/sdk/partner-directory` | [docs](./docs/partner-directory.md) |\n| `epilot.permissions` | `@epilot/sdk/permissions` | [docs](./docs/permissions.md) |\n| `epilot.pricing` | `@epilot/sdk/pricing` | [docs](./docs/pricing.md) |\n| `epilot.pricingTier` | `@epilot/sdk/pricing-tier` | [docs](./docs/pricing-tier.md) |\n| `epilot.purpose` | `@epilot/sdk/purpose` | [docs](./docs/purpose.md) |\n| `epilot.query` | `@epilot/sdk/query` | [docs](./docs/query.md) |\n| `epilot.sandbox` | `@epilot/sdk/sandbox` | [docs](./docs/sandbox.md) |\n| `epilot.sharing` | `@epilot/sdk/sharing` | [docs](./docs/sharing.md) |\n| `epilot.snapshot` | `@epilot/sdk/snapshot` | [docs](./docs/snapshot.md) |\n| `epilot.submission` | `@epilot/sdk/submission` | [docs](./docs/submission.md) |\n| `epilot.targeting` | `@epilot/sdk/targeting` | [docs](./docs/targeting.md) |\n| `epilot.templateVariables` | `@epilot/sdk/template-variables` | [docs](./docs/template-variables.md) |\n| `epilot.user` | `@epilot/sdk/user` | [docs](./docs/user.md) |\n| `epilot.validationRules` | `@epilot/sdk/validation-rules` | [docs](./docs/validation-rules.md) |\n| `epilot.webhooks` | `@epilot/sdk/webhooks` | [docs](./docs/webhooks.md) |\n| `epilot.workflow` | `@epilot/sdk/workflow` | [docs](./docs/workflow.md) |\n| `epilot.workflowDefinition` | `@epilot/sdk/workflow-definition` | [docs](./docs/workflow-definition.md) |\n\n\n## OpenAPI Spec\n\nRetrieve the full OpenAPI specification for any API at runtime. The spec is lazy-loaded on first call and cached.\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\n// Via API handle\nconst entitySpec = await epilot.entity.openapi()\nconsole.log(entitySpec.info.title) // \"Entity API\"\nconsole.log(entitySpec.paths) // all paths with full schemas\n\n// Via top-level method\nconst spec = await epilot.openapi('entity')\n```\n\nFor tree-shakeable imports:\n\n```ts\nimport { openapi } from '@epilot/sdk/entity'\n\nconst spec = await openapi()\n```\n\n## Explicit Client Access\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\n\n// Get the cached singleton client\nconst entityClient = epilot.entity.getClient()\nconst { data } = await entityClient.getEntity({ slug: 'contact', id: '123' })\n\n// Create a fresh (non-singleton) client instance\nconst freshClient = epilot.entity.createClient()\nauthorize(freshClient, () => '')\n```\n\n## Tree-Shakeable Imports\n\nImport only what you need. Other APIs never touch your bundle.\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/entity'\n\nconst entityClient = getClient()\nauthorize(entityClient, () => '')\n\nconst { data } = await entityClient.getEntity({ slug: 'contact', id: '123' })\n\n// Or use the handle for direct operation forwarding\nimport { entity } from '@epilot/sdk/entity'\nconst { data } = await entity.getEntity({ slug: 'contact', id: '123' })\n```\n\n## Types\n\nEach API subpath re-exports all schema types generated from the OpenAPI spec. Import them directly:\n\n```ts\nimport type { Entity, EntitySchema, RelationAttribute } from '@epilot/sdk/entity'\nimport type { FileItem } from '@epilot/sdk/file'\nimport type { AutomationFlow } from '@epilot/sdk/automation'\n```\n\nThe `Client`, `OperationMethods`, and `PathsDictionary` types are also available for typing client instances:\n\n```ts\nimport type { Client } from '@epilot/sdk/entity'\n\nconst entityClient: Client = epilot.entity.getClient()\n```\n\n## Headers\n\n### Global Headers\n\nSet default headers applied to all clients. Useful for `x-epilot-org-id`, `x-epilot-user-id`, etc.\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nepilot.headers({\n 'x-epilot-org-id': 'org-123',\n 'x-epilot-user-id': 'user-456',\n})\n\nconst { data } = await epilot.entity.searchEntities(...)\n```\n\n### Standard Axios Headers\n\nUse standard axios `defaults.headers.common` on individual clients:\n\n```ts\nconst entityClient = epilot.entity.getClient()\nentityClient.defaults.headers.common['x-epilot-org-id'] = 'org-123'\n```\n\n## Auth Patterns\n\n`authorize()` accepts a string or a function. The function form is preferred — it is called on every request, so tokens stay fresh.\n\n```ts\nimport { authorize } from '@epilot/sdk'\nimport { getClient } from '@epilot/sdk/entity'\n\n// Per-client — function predicate (recommended)\nconst entityClient = getClient()\nauthorize(entityClient, () => '')\n\n// Per-client — async function (e.g. OAuth / session)\nauthorize(entityClient, async () => {\n return await getTokenFromSession()\n})\n\n// Per-client — static string (sets default header once)\nauthorize(entityClient, 'my-static-api-token')\n```\n\n```ts\n// Global — applies to all clients resolved from the SDK\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nepilot.authorize(async () => await getTokenFromSession())\nepilot.authorize('my-static-api-token')\n```\n\n## Fresh Client Instance\n\n```ts\nimport { createClient, authorize } from '@epilot/sdk/entity'\n\nconst entityClient = createClient()\nauthorize(entityClient, () => '')\nentityClient.defaults.headers.common['x-epilot-org-id'] = 'org-123'\n```\n\n## Multiple SDK Instances\n\n```ts\nimport { createSDK } from '@epilot/sdk'\n\nconst sdk1 = createSDK()\nsdk1.authorize(() => '')\nsdk1.headers({ 'x-epilot-org-id': 'org-1' })\n\nconst sdk2 = createSDK()\nsdk2.authorize(() => '')\nsdk2.headers({ 'x-epilot-org-id': 'org-2' })\n```\n\n\n## Interceptors\n\nUse axios interceptors for custom request/response processing. Since clients are axios instances, you can use `client.interceptors` directly:\n\n```ts\nentityClient.interceptors.response.use((response) => {\n console.debug(`${response.config.method?.toUpperCase()} ${response.config.url}`, {\n status: response.status,\n data: response.data,\n })\n return response\n})\n```\n\nOr register global interceptors applied to all clients:\n\n```ts\nepilot.interceptors.request((config) => {\n config.headers['x-correlation-id'] = generateTraceId()\n return config\n})\n```\n\n## Auto-Retry (429 Too Many Requests)\n\nThe SDK automatically retries requests that receive a `429 Too Many Requests` response. It respects the `Retry-After` header (in seconds) to determine how long to wait before retrying.\n\nEnabled by default with up to 3 retries.\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\n// Customize retry behavior\nepilot.retry({ maxRetries: 5, defaultDelayMs: 2000 })\n\n// Disable retries\nepilot.retry({ maxRetries: 0 })\n```\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `maxRetries` | `3` | Maximum number of retries. Set to `0` to disable. |\n| `defaultDelayMs` | `1000` | Fallback delay in ms when `Retry-After` header is missing. |\n\nFor individually imported clients (tree-shakeable imports), apply the interceptor manually:\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/entity'\nimport { applyRetryInterceptor } from '@epilot/sdk'\n\nconst entityClient = getClient()\nauthorize(entityClient, () => '')\napplyRetryInterceptor({ client: entityClient, config: { maxRetries: 3 } })\n```\n\n## Large Response Handling (413 Payload Too Large)\n\nepilot APIs use a [large response middleware](https://github.com/epilot-dev/aws-lambda-utility-middlewares) to work around the AWS Lambda 6MB response limit. When a response exceeds ~5.1MB, the API uploads the payload to S3 and returns a presigned URL instead.\n\nThe SDK handles this transparently — it sends the opt-in `Accept` header and automatically fetches the full payload from S3 when a large response URL is returned. Enabled by default.\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\n// Disable large response handling\nepilot.largeResponse({ enabled: false })\n```\n\nFor individually imported clients (tree-shakeable imports), apply the interceptor manually:\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/entity'\nimport { applyLargeResponseInterceptor } from '@epilot/sdk'\n\nconst entityClient = getClient()\nauthorize(entityClient, () => '')\napplyLargeResponseInterceptor({ client: entityClient, config: { enabled: true } })\n```\n\n## Overrides & Custom APIs\n\nOverride built-in API specs or register custom APIs via `.epilot/sdk-overrides.json`. This is useful for testing new versions of an API spec or getting the latest types without waiting for an SDK release.\n\n```json\n{\n \"entity\": \"./specs/entity-openapi.json\",\n \"myNewApi\": \"./specs/my-new-api-openapi.json\"\n}\n```\n\n```ts\n// Built-in API with overridden spec\nconst { data } = await epilot.entity.getEntity({ slug: 'contact', id: '123' })\n```\n\n### Override Commands\n\n```bash\n# Apply all overrides from .epilot/sdk-overrides.json\nnpx epilot-sdk override\n\n# Override a single API\nnpx epilot-sdk override entity ./my-local-entity-spec.yaml\n\n# Regenerate types after spec changes\nnpx epilot-sdk typegen\n```\n\n
\nMigration from @epilot/*-client\n\nDrop-in replacement — just change the import path:\n\n```ts\n// Before\nimport { getClient, createClient, authorize } from '@epilot/entity-client'\nimport type { Client, Entity } from '@epilot/entity-client'\n\n// After\nimport { getClient, createClient, authorize } from '@epilot/sdk/entity'\nimport type { Client, Entity } from '@epilot/sdk/entity'\n```\n\n
\n\n
\nClient Lifecycle\n\nWhen you call `authorize()`, `headers()`, `retry()`, `largeResponse()`, or `interceptors`, the SDK invalidates all cached client instances. The next operation call creates a fresh client with the updated configuration.\n\n**Operation methods are always up to date** — calls like `epilot.entity.getEntity(...)` re-resolve the client on every invocation, so they always use the latest config.\n\n**Direct `getClient()` references can go stale** — if you hold a reference and then change config, your reference still points to the old client:\n\n```ts\nconst entityClient = epilot.entity.getClient()\n\nepilot.authorize('new-token') // invalidates all cached clients\n\n// entityClient still has the old token\n// epilot.entity.getEntity(...) will use a new client with the new token\n```\n\nIf you need a long-lived reference that survives config changes, call `getClient()` again after changing config, or use operation methods directly.\n\n
\n" +"

\"epilot\"
@epilot/sdk

\n\n

\n \"CI\"\n \"npm\n \"License\"\n

\n\n

JavaScript/TypeScript SDK for epilot APIs. Full types, tree-shakeable imports, and lazy-loaded OpenAPI specs.

\n\n## Install\n\n```bash\nnpm i @epilot/sdk axios openapi-client-axios\n```\n\n## Quick Start\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\n\nconst { data: entity } = await epilot.entity.createEntity(\n { slug: 'contact' },\n { first_name: 'John', last_name: 'Doe' },\n)\n\nconst { data: file } = await epilot.file.getFile({ id: 'file-123' })\n\nconst { data: executions } = await epilot.workflow.getExecutions()\n```\n\nAPI clients are built on [openapi-client-axios](https://openapistack.co/docs/openapi-client-axios/intro/), which generates fully typed operation methods on top of regular [axios](https://axios-http.com/docs/intro) instances. All standard axios features (interceptors, defaults, config) work as expected. Each operation is forwarded to a lazy singleton — the spec is loaded and the client initialized on first use, then cached.\n\nFull API documentation: [https://docs.epilot.io/api](https://docs.epilot.io/api)\n\n## API Reference\n\n\n| API | Import | Docs |\n| --- | ------ | ---- |\n| `epilot.accessToken` | `@epilot/sdk/access-token` | [docs](./docs/access-token.md) |\n| `epilot.address` | `@epilot/sdk/address` | [docs](./docs/address.md) |\n| `epilot.addressSuggestions` | `@epilot/sdk/address-suggestions` | [docs](./docs/address-suggestions.md) |\n| `epilot.aiAgents` | `@epilot/sdk/ai-agents` | [docs](./docs/ai-agents.md) |\n| `epilot.app` | `@epilot/sdk/app` | [docs](./docs/app.md) |\n| `epilot.auditLogs` | `@epilot/sdk/audit-logs` | [docs](./docs/audit-logs.md) |\n| `epilot.automation` | `@epilot/sdk/automation` | [docs](./docs/automation.md) |\n| `epilot.billing` | `@epilot/sdk/billing` | [docs](./docs/billing.md) |\n| `epilot.blueprintManifest` | `@epilot/sdk/blueprint-manifest` | [docs](./docs/blueprint-manifest.md) |\n| `epilot.calendar` | `@epilot/sdk/calendar` | [docs](./docs/calendar.md) |\n| `epilot.chat` | `@epilot/sdk/chat` | [docs](./docs/chat.md) |\n| `epilot.configurationHub` | `@epilot/sdk/configuration-hub` | [docs](./docs/configuration-hub.md) |\n| `epilot.consent` | `@epilot/sdk/consent` | [docs](./docs/consent.md) |\n| `epilot.customerPortal` | `@epilot/sdk/customer-portal` | [docs](./docs/customer-portal.md) |\n| `epilot.dashboard` | `@epilot/sdk/dashboard` | [docs](./docs/dashboard.md) |\n| `epilot.dataGovernance` | `@epilot/sdk/data-governance` | [docs](./docs/data-governance.md) |\n| `epilot.deduplication` | `@epilot/sdk/deduplication` | [docs](./docs/deduplication.md) |\n| `epilot.design` | `@epilot/sdk/design` | [docs](./docs/design.md) |\n| `epilot.document` | `@epilot/sdk/document` | [docs](./docs/document.md) |\n| `epilot.emailSettings` | `@epilot/sdk/email-settings` | [docs](./docs/email-settings.md) |\n| `epilot.emailTemplate` | `@epilot/sdk/email-template` | [docs](./docs/email-template.md) |\n| `epilot.entity` | `@epilot/sdk/entity` | [docs](./docs/entity.md) |\n| `epilot.entityMapping` | `@epilot/sdk/entity-mapping` | [docs](./docs/entity-mapping.md) |\n| `epilot.environments` | `@epilot/sdk/environments` | [docs](./docs/environments.md) |\n| `epilot.eventCatalog` | `@epilot/sdk/event-catalog` | [docs](./docs/event-catalog.md) |\n| `epilot.file` | `@epilot/sdk/file` | [docs](./docs/file.md) |\n| `epilot.iban` | `@epilot/sdk/iban` | [docs](./docs/iban.md) |\n| `epilot.identity` | `@epilot/sdk/identity` | [docs](./docs/identity.md) |\n| `epilot.integrationToolkit` | `@epilot/sdk/integration-toolkit` | [docs](./docs/integration-toolkit.md) |\n| `epilot.journey` | `@epilot/sdk/journey` | [docs](./docs/journey.md) |\n| `epilot.kanban` | `@epilot/sdk/kanban` | [docs](./docs/kanban.md) |\n| `epilot.message` | `@epilot/sdk/message` | [docs](./docs/message.md) |\n| `epilot.metering` | `@epilot/sdk/metering` | [docs](./docs/metering.md) |\n| `epilot.notes` | `@epilot/sdk/notes` | [docs](./docs/notes.md) |\n| `epilot.notification` | `@epilot/sdk/notification` | [docs](./docs/notification.md) |\n| `epilot.organization` | `@epilot/sdk/organization` | [docs](./docs/organization.md) |\n| `epilot.partnerDirectory` | `@epilot/sdk/partner-directory` | [docs](./docs/partner-directory.md) |\n| `epilot.permissions` | `@epilot/sdk/permissions` | [docs](./docs/permissions.md) |\n| `epilot.pricing` | `@epilot/sdk/pricing` | [docs](./docs/pricing.md) |\n| `epilot.pricingTier` | `@epilot/sdk/pricing-tier` | [docs](./docs/pricing-tier.md) |\n| `epilot.purpose` | `@epilot/sdk/purpose` | [docs](./docs/purpose.md) |\n| `epilot.query` | `@epilot/sdk/query` | [docs](./docs/query.md) |\n| `epilot.sandbox` | `@epilot/sdk/sandbox` | [docs](./docs/sandbox.md) |\n| `epilot.sharing` | `@epilot/sdk/sharing` | [docs](./docs/sharing.md) |\n| `epilot.snapshot` | `@epilot/sdk/snapshot` | [docs](./docs/snapshot.md) |\n| `epilot.submission` | `@epilot/sdk/submission` | [docs](./docs/submission.md) |\n| `epilot.targeting` | `@epilot/sdk/targeting` | [docs](./docs/targeting.md) |\n| `epilot.templateVariables` | `@epilot/sdk/template-variables` | [docs](./docs/template-variables.md) |\n| `epilot.user` | `@epilot/sdk/user` | [docs](./docs/user.md) |\n| `epilot.validationRules` | `@epilot/sdk/validation-rules` | [docs](./docs/validation-rules.md) |\n| `epilot.webhooks` | `@epilot/sdk/webhooks` | [docs](./docs/webhooks.md) |\n| `epilot.workflow` | `@epilot/sdk/workflow` | [docs](./docs/workflow.md) |\n| `epilot.workflowDefinition` | `@epilot/sdk/workflow-definition` | [docs](./docs/workflow-definition.md) |\n\n\n## OpenAPI Spec\n\nRetrieve the full OpenAPI specification for any API at runtime. The spec is lazy-loaded on first call and cached.\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\n// Via API handle\nconst entitySpec = await epilot.entity.openapi()\nconsole.log(entitySpec.info.title) // \"Entity API\"\nconsole.log(entitySpec.paths) // all paths with full schemas\n\n// Via top-level method\nconst spec = await epilot.openapi('entity')\n```\n\nFor tree-shakeable imports:\n\n```ts\nimport { openapi } from '@epilot/sdk/entity'\n\nconst spec = await openapi()\n```\n\n## Explicit Client Access\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\n\n// Get the cached singleton client\nconst entityClient = epilot.entity.getClient()\nconst { data } = await entityClient.getEntity({ slug: 'contact', id: '123' })\n\n// Create a fresh (non-singleton) client instance\nconst freshClient = epilot.entity.createClient()\nauthorize(freshClient, () => '')\n```\n\n## Tree-Shakeable Imports\n\nImport only what you need. Other APIs never touch your bundle.\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/entity'\n\nconst entityClient = getClient()\nauthorize(entityClient, () => '')\n\nconst { data } = await entityClient.getEntity({ slug: 'contact', id: '123' })\n\n// Or use the handle for direct operation forwarding\nimport { entity } from '@epilot/sdk/entity'\nconst { data } = await entity.getEntity({ slug: 'contact', id: '123' })\n```\n\n## Types\n\nEach API subpath re-exports all schema types generated from the OpenAPI spec. Import them directly:\n\n```ts\nimport type { Entity, EntitySchema, RelationAttribute } from '@epilot/sdk/entity'\nimport type { FileItem } from '@epilot/sdk/file'\nimport type { AutomationFlow } from '@epilot/sdk/automation'\n```\n\nThe `Client`, `OperationMethods`, and `PathsDictionary` types are also available for typing client instances:\n\n```ts\nimport type { Client } from '@epilot/sdk/entity'\n\nconst entityClient: Client = epilot.entity.getClient()\n```\n\n## Headers\n\n### Global Headers\n\nSet default headers applied to all clients. Useful for `x-epilot-org-id`, `x-epilot-user-id`, etc.\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nepilot.headers({\n 'x-epilot-org-id': 'org-123',\n 'x-epilot-user-id': 'user-456',\n})\n\nconst { data } = await epilot.entity.searchEntities(...)\n```\n\n### Standard Axios Headers\n\nUse standard axios `defaults.headers.common` on individual clients:\n\n```ts\nconst entityClient = epilot.entity.getClient()\nentityClient.defaults.headers.common['x-epilot-org-id'] = 'org-123'\n```\n\n## Auth Patterns\n\n`authorize()` accepts a string or a function. The function form is preferred — it is called on every request, so tokens stay fresh.\n\n```ts\nimport { authorize } from '@epilot/sdk'\nimport { getClient } from '@epilot/sdk/entity'\n\n// Per-client — function predicate (recommended)\nconst entityClient = getClient()\nauthorize(entityClient, () => '')\n\n// Per-client — async function (e.g. OAuth / session)\nauthorize(entityClient, async () => {\n return await getTokenFromSession()\n})\n\n// Per-client — static string (sets default header once)\nauthorize(entityClient, 'my-static-api-token')\n```\n\n```ts\n// Global — applies to all clients resolved from the SDK\nimport { epilot } from '@epilot/sdk'\n\nepilot.authorize(() => '')\nepilot.authorize(async () => await getTokenFromSession())\nepilot.authorize('my-static-api-token')\n```\n\n## Fresh Client Instance\n\n```ts\nimport { createClient, authorize } from '@epilot/sdk/entity'\n\nconst entityClient = createClient()\nauthorize(entityClient, () => '')\nentityClient.defaults.headers.common['x-epilot-org-id'] = 'org-123'\n```\n\n## Multiple SDK Instances\n\n```ts\nimport { createSDK } from '@epilot/sdk'\n\nconst sdk1 = createSDK()\nsdk1.authorize(() => '')\nsdk1.headers({ 'x-epilot-org-id': 'org-1' })\n\nconst sdk2 = createSDK()\nsdk2.authorize(() => '')\nsdk2.headers({ 'x-epilot-org-id': 'org-2' })\n```\n\n\n## Interceptors\n\nUse axios interceptors for custom request/response processing. Since clients are axios instances, you can use `client.interceptors` directly:\n\n```ts\nentityClient.interceptors.response.use((response) => {\n console.debug(`${response.config.method?.toUpperCase()} ${response.config.url}`, {\n status: response.status,\n data: response.data,\n })\n return response\n})\n```\n\nOr register global interceptors applied to all clients:\n\n```ts\nepilot.interceptors.request((config) => {\n config.headers['x-correlation-id'] = generateTraceId()\n return config\n})\n```\n\n## Auto-Retry (429 Too Many Requests)\n\nThe SDK automatically retries requests that receive a `429 Too Many Requests` response. It respects the `Retry-After` header (in seconds) to determine how long to wait before retrying.\n\nEnabled by default with up to 3 retries.\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\n// Customize retry behavior\nepilot.retry({ maxRetries: 5, defaultDelayMs: 2000 })\n\n// Disable retries\nepilot.retry({ maxRetries: 0 })\n```\n\n| Option | Default | Description |\n| --- | --- | --- |\n| `maxRetries` | `3` | Maximum number of retries. Set to `0` to disable. |\n| `defaultDelayMs` | `1000` | Fallback delay in ms when `Retry-After` header is missing. |\n\nFor individually imported clients (tree-shakeable imports), apply the interceptor manually:\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/entity'\nimport { applyRetryInterceptor } from '@epilot/sdk'\n\nconst entityClient = getClient()\nauthorize(entityClient, () => '')\napplyRetryInterceptor({ client: entityClient, config: { maxRetries: 3 } })\n```\n\n## Large Response Handling (413 Payload Too Large)\n\nepilot APIs use a [large response middleware](https://github.com/epilot-dev/aws-lambda-utility-middlewares) to work around the AWS Lambda 6MB response limit. When a response exceeds ~5.1MB, the API uploads the payload to S3 and returns a presigned URL instead.\n\nThe SDK handles this transparently — it sends the opt-in `Accept` header and automatically fetches the full payload from S3 when a large response URL is returned. Enabled by default.\n\n```ts\nimport { epilot } from '@epilot/sdk'\n\n// Disable large response handling\nepilot.largeResponse({ enabled: false })\n```\n\nFor individually imported clients (tree-shakeable imports), apply the interceptor manually:\n\n```ts\nimport { getClient, authorize } from '@epilot/sdk/entity'\nimport { applyLargeResponseInterceptor } from '@epilot/sdk'\n\nconst entityClient = getClient()\nauthorize(entityClient, () => '')\napplyLargeResponseInterceptor({ client: entityClient, config: { enabled: true } })\n```\n\n## Overrides & Custom APIs\n\nOverride built-in API specs or register custom APIs via `.epilot/sdk-overrides.json`. This is useful for testing new versions of an API spec or getting the latest types without waiting for an SDK release.\n\n```json\n{\n \"entity\": \"./specs/entity-openapi.json\",\n \"myNewApi\": \"./specs/my-new-api-openapi.json\"\n}\n```\n\n```ts\n// Built-in API with overridden spec\nconst { data } = await epilot.entity.getEntity({ slug: 'contact', id: '123' })\n```\n\n### Override Commands\n\n```bash\n# Apply all overrides from .epilot/sdk-overrides.json\nnpx epilot-sdk override\n\n# Override a single API\nnpx epilot-sdk override entity ./my-local-entity-spec.yaml\n\n# Regenerate types after spec changes\nnpx epilot-sdk typegen\n```\n\n
\nMigration from @epilot/*-client\n\nDrop-in replacement — just change the import path:\n\n```ts\n// Before\nimport { getClient, createClient, authorize } from '@epilot/entity-client'\nimport type { Client, Entity } from '@epilot/entity-client'\n\n// After\nimport { getClient, createClient, authorize } from '@epilot/sdk/entity'\nimport type { Client, Entity } from '@epilot/sdk/entity'\n```\n\n
\n\n
\nClient Lifecycle\n\nWhen you call `authorize()`, `headers()`, `retry()`, `largeResponse()`, or `interceptors`, the SDK invalidates all cached client instances. The next operation call creates a fresh client with the updated configuration.\n\n**Operation methods are always up to date** — calls like `epilot.entity.getEntity(...)` re-resolve the client on every invocation, so they always use the latest config.\n\n**Direct `getClient()` references can go stale** — if you hold a reference and then change config, your reference still points to the old client:\n\n```ts\nconst entityClient = epilot.entity.getClient()\n\nepilot.authorize('new-token') // invalidates all cached clients\n\n// entityClient still has the old token\n// epilot.entity.getEntity(...) will use a new client with the new token\n```\n\nIf you need a long-lived reference that survives config changes, call `getClient()` again after changing config, or use operation methods directly.\n\n
\n"