@faable/auth-sdk 2.9.9 → 2.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -104,6 +104,7 @@ export declare class FaableAuthApi extends GeneratedFaableAuthApi {
104
104
  webauthn_rp_id?: string | null | undefined;
105
105
  logout_confirm_required?: boolean | undefined;
106
106
  dynamic_client_registration_enabled?: boolean | undefined;
107
+ oauth21_enabled?: boolean | undefined;
107
108
  client_id_metadata_documents_enabled?: boolean | undefined;
108
109
  canonical_host?: string | null | undefined;
109
110
  createdAt: string;
@@ -103,6 +103,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
103
103
  webauthn_rp_id?: string | null | undefined;
104
104
  logout_confirm_required?: boolean | undefined;
105
105
  dynamic_client_registration_enabled?: boolean | undefined;
106
+ oauth21_enabled?: boolean | undefined;
106
107
  client_id_metadata_documents_enabled?: boolean | undefined;
107
108
  canonical_host?: string | null | undefined;
108
109
  createdAt: string;
@@ -189,6 +190,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
189
190
  webauthn_rp_id?: string | null | undefined;
190
191
  logout_confirm_required?: boolean | undefined;
191
192
  dynamic_client_registration_enabled?: boolean | undefined;
193
+ oauth21_enabled?: boolean | undefined;
192
194
  client_id_metadata_documents_enabled?: boolean | undefined;
193
195
  canonical_host?: string | null | undefined;
194
196
  createdAt: string;
@@ -295,6 +297,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
295
297
  webauthn_rp_id?: string | null | undefined;
296
298
  logout_confirm_required?: boolean | undefined;
297
299
  dynamic_client_registration_enabled?: boolean | undefined;
300
+ oauth21_enabled?: boolean | undefined;
298
301
  client_id_metadata_documents_enabled?: boolean | undefined;
299
302
  canonical_host?: string | null | undefined;
300
303
  createdAt: string;
@@ -384,6 +387,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
384
387
  webauthn_rp_id?: string | null | undefined;
385
388
  logout_confirm_required?: boolean | undefined;
386
389
  dynamic_client_registration_enabled?: boolean | undefined;
390
+ oauth21_enabled?: boolean | undefined;
387
391
  client_id_metadata_documents_enabled?: boolean | undefined;
388
392
  canonical_host?: string | null | undefined;
389
393
  createdAt: string;
@@ -521,6 +525,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
521
525
  webauthn_rp_id?: string | null | undefined;
522
526
  logout_confirm_required?: boolean | undefined;
523
527
  dynamic_client_registration_enabled?: boolean | undefined;
528
+ oauth21_enabled?: boolean | undefined;
524
529
  client_id_metadata_documents_enabled?: boolean | undefined;
525
530
  canonical_host?: string | null | undefined;
526
531
  createdAt: string;
@@ -591,6 +596,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
591
596
  webauthn_rp_id?: string | null | undefined;
592
597
  logout_confirm_required?: boolean | undefined;
593
598
  dynamic_client_registration_enabled?: boolean | undefined;
599
+ oauth21_enabled?: boolean | undefined;
594
600
  client_id_metadata_documents_enabled?: boolean | undefined;
595
601
  canonical_host?: string | null | undefined;
596
602
  createdAt: string;
@@ -664,6 +670,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
664
670
  webauthn_rp_id?: string | null | undefined;
665
671
  logout_confirm_required?: boolean | undefined;
666
672
  dynamic_client_registration_enabled?: boolean | undefined;
673
+ oauth21_enabled?: boolean | undefined;
667
674
  client_id_metadata_documents_enabled?: boolean | undefined;
668
675
  canonical_host?: string | null | undefined;
669
676
  createdAt: string;
@@ -929,6 +936,7 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
929
936
  webauthn_rp_id?: string | null | undefined;
930
937
  logout_confirm_required?: boolean | undefined;
931
938
  dynamic_client_registration_enabled?: boolean | undefined;
939
+ oauth21_enabled?: boolean | undefined;
932
940
  client_id_metadata_documents_enabled?: boolean | undefined;
933
941
  canonical_host?: string | null | undefined;
934
942
  createdAt: string;
@@ -2263,6 +2271,30 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
2263
2271
  createdAt: string;
2264
2272
  updatedAt?: string | undefined;
2265
2273
  }>;
2274
+ /**
2275
+ * `POST /connect/handoff` — operationId: `connect/createHandoff`
2276
+ *
2277
+ * Create a one-time handoff of a client’s credentials
2278
+ *
2279
+ * Used by the dashboard in the «Connect with Faable» flow. Checks that `client_id` belongs to `account_id` and that its callbacks contain `<site_url>/wp-login.php?faable-auth=callback`, then stores a one-time handoff bound to the PKCE `code_challenge`, valid for 10 minutes. The response `code` is sent to the site, which redeems it with `POST /connect/exchange`. The client secret is never part of this request or response. Requires a management token for `account_id` with `update:clients`.
2280
+ *
2281
+ * @throws {FaableApiError} `account_mismatch` (400) — The `account_id` in the body is not the account the request is scoped to (`x-faableauth-account`).
2282
+ * @throws {FaableApiError} `connect_redirect_uri_missing` (400) — The client's callbacks do not include `<site_url>/wp-login.php?faable-auth=callback`.
2283
+ * @throws {FaableApiError} `invalid_site_url` (400) — `site_url` is not an absolute http(s) URL without query string or fragment.
2284
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
2285
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
2286
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
2287
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
2288
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
2289
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
2290
+ * @throws {FaableApiError} `client_not_found` (404) — No client with that `client_id`.
2291
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
2292
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
2293
+ */
2294
+ connectCreateHandoff(data: OpBody<"connect/createHandoff">): Promise<{
2295
+ code: string;
2296
+ expires_at: string;
2297
+ }>;
2266
2298
  /**
2267
2299
  * `POST /connection` — operationId: `connection/create`
2268
2300
  *
@@ -621,6 +621,29 @@ export class GeneratedFaableAuthApi extends FaableApi {
621
621
  requireId("client_id", client_id);
622
622
  return this.fetcher.post(`/client/${client_id}`, data);
623
623
  }
624
+ /**
625
+ * `POST /connect/handoff` — operationId: `connect/createHandoff`
626
+ *
627
+ * Create a one-time handoff of a client’s credentials
628
+ *
629
+ * Used by the dashboard in the «Connect with Faable» flow. Checks that `client_id` belongs to `account_id` and that its callbacks contain `<site_url>/wp-login.php?faable-auth=callback`, then stores a one-time handoff bound to the PKCE `code_challenge`, valid for 10 minutes. The response `code` is sent to the site, which redeems it with `POST /connect/exchange`. The client secret is never part of this request or response. Requires a management token for `account_id` with `update:clients`.
630
+ *
631
+ * @throws {FaableApiError} `account_mismatch` (400) — The `account_id` in the body is not the account the request is scoped to (`x-faableauth-account`).
632
+ * @throws {FaableApiError} `connect_redirect_uri_missing` (400) — The client's callbacks do not include `<site_url>/wp-login.php?faable-auth=callback`.
633
+ * @throws {FaableApiError} `invalid_site_url` (400) — `site_url` is not an absolute http(s) URL without query string or fragment.
634
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
635
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
636
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
637
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
638
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
639
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
640
+ * @throws {FaableApiError} `client_not_found` (404) — No client with that `client_id`.
641
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
642
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
643
+ */
644
+ connectCreateHandoff(data) {
645
+ return this.fetcher.post(`/connect/handoff`, data);
646
+ }
624
647
  /**
625
648
  * `POST /connection` — operationId: `connection/create`
626
649
  *
@@ -2477,6 +2477,46 @@ export interface paths {
2477
2477
  patch?: never;
2478
2478
  trace?: never;
2479
2479
  };
2480
+ "/connect/handoff": {
2481
+ parameters: {
2482
+ query?: never;
2483
+ header?: never;
2484
+ path?: never;
2485
+ cookie?: never;
2486
+ };
2487
+ get?: never;
2488
+ put?: never;
2489
+ /**
2490
+ * Create a one-time handoff of a client’s credentials
2491
+ * @description Used by the dashboard in the «Connect with Faable» flow. Checks that `client_id` belongs to `account_id` and that its callbacks contain `<site_url>/wp-login.php?faable-auth=callback`, then stores a one-time handoff bound to the PKCE `code_challenge`, valid for 10 minutes. The response `code` is sent to the site, which redeems it with `POST /connect/exchange`. The client secret is never part of this request or response. Requires a management token for `account_id` with `update:clients`.
2492
+ */
2493
+ post: operations["connect/createHandoff"];
2494
+ delete?: never;
2495
+ options?: never;
2496
+ head?: never;
2497
+ patch?: never;
2498
+ trace?: never;
2499
+ };
2500
+ "/connect/exchange": {
2501
+ parameters: {
2502
+ query?: never;
2503
+ header?: never;
2504
+ path?: never;
2505
+ cookie?: never;
2506
+ };
2507
+ get?: never;
2508
+ put?: never;
2509
+ /**
2510
+ * Redeem a connect handoff for the client’s credentials
2511
+ * @description Server-to-server, unauthenticated: the PKCE `code_verifier` is the proof. Returns the credentials of the client the handoff names, once — a second redemption answers 410 `handoff_used`. Errors carry `error` (same value as `error_code`). Responses are `Cache-Control: no-store`.
2512
+ */
2513
+ post: operations["connect/exchange"];
2514
+ delete?: never;
2515
+ options?: never;
2516
+ head?: never;
2517
+ patch?: never;
2518
+ trace?: never;
2519
+ };
2480
2520
  "/passwordless/verify_redirect": {
2481
2521
  parameters: {
2482
2522
  query?: never;
@@ -5808,6 +5848,8 @@ export interface components {
5808
5848
  logout_confirm_required?: boolean;
5809
5849
  /** @description When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`. */
5810
5850
  dynamic_client_registration_enabled?: boolean;
5851
+ /** @description When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later. */
5852
+ oauth21_enabled?: boolean;
5811
5853
  /** @description When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default. */
5812
5854
  client_id_metadata_documents_enabled?: boolean;
5813
5855
  /** @description The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host. */
@@ -8534,6 +8576,7 @@ export interface components {
8534
8576
  logout_confirm_required?: boolean | null;
8535
8577
  dynamic_client_registration_enabled?: boolean | null;
8536
8578
  client_id_metadata_documents_enabled?: boolean | null;
8579
+ oauth21_enabled?: boolean | null;
8537
8580
  canonical_host?: string | null;
8538
8581
  login_flow?: string | null;
8539
8582
  };
@@ -8759,6 +8802,7 @@ export interface components {
8759
8802
  * - `not_a_member` (404): The user is not a member of the team.
8760
8803
  * - `consent_options_unavailable` (502): The consent screen could not load the choices from the API being authorized (its `consent_options.url`). Try again in a moment.
8761
8804
  * - `consent_choice_invalid` (400): The choice sent with the consent decision is not one the API offered for this user.
8805
+ * - `oauth21_required` (400): The tenant enforces OAuth 2.1 (`oauth21_enabled`) and the request is one OAuth 2.1 removed: a `response_type` other than `code`, an authorization request without PKCE (`code_challenge` with `S256`), or the password grant. `message` says which. Update the application (with `@faable/auth-js`, version 3.1.0 or later) or turn `oauth21_enabled` off for the tenant.
8762
8806
  * - `registration_disabled` (403): Dynamic client registration is turned off for this tenant. Create the client from the dashboard or the Management API, or turn on `dynamic_client_registration_enabled`.
8763
8807
  * - `invalid_email_domain` (400): Not an email domain this organization can claim: it must be a plain domain name (`acme.com`), lowercase, without scheme, path or wildcard.
8764
8808
  * - `public_email_domain` (400): A public email provider (gmail.com, outlook.com…) cannot belong to an organization: anyone can have an address there.
@@ -8785,9 +8829,16 @@ export interface components {
8785
8829
  * - `not_a_webhook` (400): The operation only applies to `webhook` subscriptions.
8786
8830
  * - `delivery_not_found` (404): No webhook delivery with that id for this subscription.
8787
8831
  * - `delivery_in_progress` (409): The delivery is being attempted right now; retry the redeliver in a minute.
8832
+ * - `account_mismatch` (400): The `account_id` in the body is not the account the request is scoped to (`x-faableauth-account`).
8833
+ * - `connect_redirect_uri_missing` (400): The client's callbacks do not include `<site_url>/wp-login.php?faable-auth=callback`.
8834
+ * - `invalid_site_url` (400): `site_url` is not an absolute http(s) URL without query string or fragment.
8835
+ * - `handoff_not_found` (404): No connect handoff matches that code.
8836
+ * - `handoff_expired` (410): The connect handoff is past its expiry. Start again.
8837
+ * - `handoff_used` (410): The connect handoff was already redeemed.
8838
+ * - `invalid_code_verifier` (400): `code_verifier` does not match the `code_challenge` of the handoff.
8788
8839
  * @enum {string}
8789
8840
  */
8790
- ErrorCode: "bad_request" | "validation_error" | "unauthorized" | "payment_required" | "forbidden" | "not_found" | "method_not_allowed" | "conflict" | "last_sign_in_method" | "payload_too_large" | "unsupported_media_type" | "unprocessable_entity" | "too_many_requests" | "internal_error" | "already_exists" | "invalid_id" | "account_not_found" | "invalid_query" | "search_not_supported" | "invalid_expand" | "access_denied" | "insufficient_scope" | "team_required" | "account_limit_reached" | "invalid_project_id" | "account_transfer_refused" | "email_sender_domain_not_allowed" | "plan_required" | "invalid_canonical_host" | "canonical_host_breaks_passkeys" | "not_logged_in" | "session_already_revoked" | "invalid_token" | "not_owner" | "user_suspended" | "invalid_credentials" | "login_denied" | "signup_denied" | "client_credentials_denied" | "m2m_quota_exceeded" | "signup_disabled" | "email_taken" | "username_taken" | "invalid_username" | "invalid_email" | "invalid_name" | "same_email" | "user_has_no_email" | "password_too_weak" | "invalid_password_hash" | "weak_password_hash" | "invalid_code" | "invalid_phone" | "phone_changed" | "phone_already_verified" | "sms_unavailable" | "sms_failed" | "invalid_state" | "state_mismatch" | "invalid_ticket" | "ticket_used" | "ticket_expired" | "ticket_not_found" | "credential_not_found" | "invalid_link" | "expired_link" | "invalid_otp" | "invalid_client" | "client_not_found" | "client_mismatch" | "invalid_client_metadata" | "invalid_connection" | "no_database_connection" | "connection_required" | "connection_misconfigured" | "passwordless_unavailable" | "ambiguous_connection" | "invalid_redirect_uri" | "invalid_saml_request" | "saml_sp_mismatch" | "saml_not_enabled" | "invalid_saml_sp" | "origin_not_allowed" | "audience_not_found" | "system_resource" | "invalid_request" | "invalid_device_code" | "provider_error" | "identity_already_linked" | "identity_link_denied" | "identity_conflict" | "identity_orphaned" | "no_provider_token" | "no_refresh_token" | "action_unavailable" | "login_flow_misconfigured" | "mfa_required" | "step_up_required" | "reauth_required" | "invalid_current_password" | "email_not_verified" | "invalid_profile_field" | "interaction_required" | "passkey_login_disabled" | "mfa_pending" | "mfa_invalid_code" | "mfa_invalid_recovery_code" | "too_many_attempts" | "too_many_login_attempts" | "totp_not_allowed" | "no_usable_factor" | "factor_not_found" | "factor_already_confirmed" | "invalid_factor" | "session_missing" | "passkey_verification_failed" | "no_passkey_enrolled" | "malformed_credential" | "unknown_passkey" | "invalid_passkey" | "invalid_invite_url" | "invite_email_mismatch" | "invite_email_unverified" | "invite_not_found" | "invite_used" | "already_member" | "not_a_member" | "consent_options_unavailable" | "consent_choice_invalid" | "registration_disabled" | "invalid_email_domain" | "public_email_domain" | "organization_domain_not_found" | "organization_domain_taken" | "organization_domain_unverified" | "plan_unavailable" | "organization_domain_mismatch" | "team_not_in_organization" | "organization_has_no_idp" | "organization_not_found" | "invalid_user" | "not_an_organization_admin" | "last_organization_admin" | "team_id_unavailable" | "use_team_member_route" | "role_not_found" | "invalid_role" | "invalid_permission" | "flow_not_found" | "revision_not_found" | "invalid_webhook_event" | "invalid_webhook_url" | "not_a_webhook" | "delivery_not_found" | "delivery_in_progress";
8841
+ ErrorCode: "bad_request" | "validation_error" | "unauthorized" | "payment_required" | "forbidden" | "not_found" | "method_not_allowed" | "conflict" | "last_sign_in_method" | "payload_too_large" | "unsupported_media_type" | "unprocessable_entity" | "too_many_requests" | "internal_error" | "already_exists" | "invalid_id" | "account_not_found" | "invalid_query" | "search_not_supported" | "invalid_expand" | "access_denied" | "insufficient_scope" | "team_required" | "account_limit_reached" | "invalid_project_id" | "account_transfer_refused" | "email_sender_domain_not_allowed" | "plan_required" | "invalid_canonical_host" | "canonical_host_breaks_passkeys" | "not_logged_in" | "session_already_revoked" | "invalid_token" | "not_owner" | "user_suspended" | "invalid_credentials" | "login_denied" | "signup_denied" | "client_credentials_denied" | "m2m_quota_exceeded" | "signup_disabled" | "email_taken" | "username_taken" | "invalid_username" | "invalid_email" | "invalid_name" | "same_email" | "user_has_no_email" | "password_too_weak" | "invalid_password_hash" | "weak_password_hash" | "invalid_code" | "invalid_phone" | "phone_changed" | "phone_already_verified" | "sms_unavailable" | "sms_failed" | "invalid_state" | "state_mismatch" | "invalid_ticket" | "ticket_used" | "ticket_expired" | "ticket_not_found" | "credential_not_found" | "invalid_link" | "expired_link" | "invalid_otp" | "invalid_client" | "client_not_found" | "client_mismatch" | "invalid_client_metadata" | "invalid_connection" | "no_database_connection" | "connection_required" | "connection_misconfigured" | "passwordless_unavailable" | "ambiguous_connection" | "invalid_redirect_uri" | "invalid_saml_request" | "saml_sp_mismatch" | "saml_not_enabled" | "invalid_saml_sp" | "origin_not_allowed" | "audience_not_found" | "system_resource" | "invalid_request" | "invalid_device_code" | "provider_error" | "identity_already_linked" | "identity_link_denied" | "identity_conflict" | "identity_orphaned" | "no_provider_token" | "no_refresh_token" | "action_unavailable" | "login_flow_misconfigured" | "mfa_required" | "step_up_required" | "reauth_required" | "invalid_current_password" | "email_not_verified" | "invalid_profile_field" | "interaction_required" | "passkey_login_disabled" | "mfa_pending" | "mfa_invalid_code" | "mfa_invalid_recovery_code" | "too_many_attempts" | "too_many_login_attempts" | "totp_not_allowed" | "no_usable_factor" | "factor_not_found" | "factor_already_confirmed" | "invalid_factor" | "session_missing" | "passkey_verification_failed" | "no_passkey_enrolled" | "malformed_credential" | "unknown_passkey" | "invalid_passkey" | "invalid_invite_url" | "invite_email_mismatch" | "invite_email_unverified" | "invite_not_found" | "invite_used" | "already_member" | "not_a_member" | "consent_options_unavailable" | "consent_choice_invalid" | "oauth21_required" | "registration_disabled" | "invalid_email_domain" | "public_email_domain" | "organization_domain_not_found" | "organization_domain_taken" | "organization_domain_unverified" | "plan_unavailable" | "organization_domain_mismatch" | "team_not_in_organization" | "organization_has_no_idp" | "organization_not_found" | "invalid_user" | "not_an_organization_admin" | "last_organization_admin" | "team_id_unavailable" | "use_team_member_route" | "role_not_found" | "invalid_role" | "invalid_permission" | "flow_not_found" | "revision_not_found" | "invalid_webhook_event" | "invalid_webhook_url" | "not_a_webhook" | "delivery_not_found" | "delivery_in_progress" | "account_mismatch" | "connect_redirect_uri_missing" | "invalid_site_url" | "handoff_not_found" | "handoff_expired" | "handoff_used" | "invalid_code_verifier";
8791
8842
  /** @description Every error response has this shape. Branch on `error_code`, never on `message`: the message is written for a person and may change. */
8792
8843
  ErrorResponse: {
8793
8844
  /** @description The HTTP status, repeated. */
@@ -8960,6 +9011,8 @@ export interface operations {
8960
9011
  logout_confirm_required?: boolean;
8961
9012
  /** @description When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`. */
8962
9013
  dynamic_client_registration_enabled?: boolean;
9014
+ /** @description When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later. */
9015
+ oauth21_enabled?: boolean;
8963
9016
  /** @description When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default. */
8964
9017
  client_id_metadata_documents_enabled?: boolean;
8965
9018
  /** @description The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host. */
@@ -9330,6 +9383,8 @@ export interface operations {
9330
9383
  logout_confirm_required?: boolean;
9331
9384
  /** @description When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`. */
9332
9385
  dynamic_client_registration_enabled?: boolean;
9386
+ /** @description When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later. */
9387
+ oauth21_enabled?: boolean;
9333
9388
  /** @description When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default. */
9334
9389
  client_id_metadata_documents_enabled?: boolean;
9335
9390
  /** @description The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host. */
@@ -9588,6 +9643,8 @@ export interface operations {
9588
9643
  logout_confirm_required?: boolean;
9589
9644
  /** @description When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`. */
9590
9645
  dynamic_client_registration_enabled?: boolean;
9646
+ /** @description When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later. */
9647
+ oauth21_enabled?: boolean;
9591
9648
  /** @description When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default. */
9592
9649
  client_id_metadata_documents_enabled?: boolean;
9593
9650
  /** @description The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host. */
@@ -9795,6 +9852,7 @@ export interface operations {
9795
9852
  logout_confirm_required?: boolean | null;
9796
9853
  dynamic_client_registration_enabled?: boolean | null;
9797
9854
  client_id_metadata_documents_enabled?: boolean | null;
9855
+ oauth21_enabled?: boolean | null;
9798
9856
  canonical_host?: string | null;
9799
9857
  login_flow?: string | null;
9800
9858
  };
@@ -9934,6 +9992,8 @@ export interface operations {
9934
9992
  logout_confirm_required?: boolean;
9935
9993
  /** @description When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`. */
9936
9994
  dynamic_client_registration_enabled?: boolean;
9995
+ /** @description When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later. */
9996
+ oauth21_enabled?: boolean;
9937
9997
  /** @description When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default. */
9938
9998
  client_id_metadata_documents_enabled?: boolean;
9939
9999
  /** @description The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host. */
@@ -11315,6 +11375,8 @@ export interface operations {
11315
11375
  logout_confirm_required?: boolean;
11316
11376
  /** @description When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`. */
11317
11377
  dynamic_client_registration_enabled?: boolean;
11378
+ /** @description When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later. */
11379
+ oauth21_enabled?: boolean;
11318
11380
  /** @description When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default. */
11319
11381
  client_id_metadata_documents_enabled?: boolean;
11320
11382
  /** @description The one host this tenant's OIDC issuer and discovery document are always minted with, regardless of which host actually served the request — must be the account's own platform host or one of its verified custom domains. `/authorize` and `/logout` 302 here from any other host of the tenant. Unset (the default): every URL uses whatever host served the request, as before this field existed. Changing it can invalidate every WebAuthn passkey already enrolled unless `webauthn_rp_id` already covers the new host. */
@@ -33494,6 +33556,245 @@ export interface operations {
33494
33556
  };
33495
33557
  };
33496
33558
  };
33559
+ "connect/createHandoff": {
33560
+ parameters: {
33561
+ query?: never;
33562
+ header?: never;
33563
+ path?: never;
33564
+ cookie?: never;
33565
+ };
33566
+ requestBody: {
33567
+ content: {
33568
+ "application/json": {
33569
+ /** @description The tenant that owns the client. */
33570
+ account_id: string;
33571
+ /** @description The OAuth `client_id` of the client created for the site (the resource `id`, `client_…`, is accepted too). */
33572
+ client_id: string;
33573
+ /** @description The site's PKCE challenge: BASE64URL(SHA256(code_verifier)), unpadded. */
33574
+ code_challenge: string;
33575
+ /** @enum {string} */
33576
+ code_challenge_method: "S256";
33577
+ /** @description The site's base URL. The client's callbacks must contain `<site_url>/wp-login.php?faable-auth=callback`. */
33578
+ site_url: string;
33579
+ };
33580
+ };
33581
+ };
33582
+ responses: {
33583
+ /** @description Default Response */
33584
+ 201: {
33585
+ headers: {
33586
+ [name: string]: unknown;
33587
+ };
33588
+ content: {
33589
+ "application/json": {
33590
+ /** @description One-time code for `POST /connect/exchange`. Shown once; only its hash is stored. */
33591
+ code: string;
33592
+ /** Format: date-time */
33593
+ expires_at: string;
33594
+ };
33595
+ };
33596
+ };
33597
+ /**
33598
+ * @description `account_mismatch` — The `account_id` in the body is not the account the request is scoped to (`x-faableauth-account`).
33599
+ *
33600
+ * `connect_redirect_uri_missing` — The client's callbacks do not include `<site_url>/wp-login.php?faable-auth=callback`.
33601
+ *
33602
+ * `invalid_site_url` — `site_url` is not an absolute http(s) URL without query string or fragment.
33603
+ *
33604
+ * `validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.
33605
+ */
33606
+ 400: {
33607
+ headers: {
33608
+ [name: string]: unknown;
33609
+ };
33610
+ content: {
33611
+ "application/json": components["schemas"]["ErrorResponse"] & {
33612
+ /** @enum {string} */
33613
+ error_code?: "account_mismatch" | "connect_redirect_uri_missing" | "invalid_site_url" | "validation_error";
33614
+ };
33615
+ };
33616
+ };
33617
+ /** @description `unauthorized` — The request carries no valid credentials. */
33618
+ 401: {
33619
+ headers: {
33620
+ [name: string]: unknown;
33621
+ };
33622
+ content: {
33623
+ "application/json": components["schemas"]["ErrorResponse"] & {
33624
+ /** @enum {string} */
33625
+ error_code?: "unauthorized";
33626
+ };
33627
+ };
33628
+ };
33629
+ /**
33630
+ * @description `forbidden` — The credentials are valid but do not allow this operation.
33631
+ *
33632
+ * `insufficient_scope` — The token lacks a scope this operation requires. `message` names it.
33633
+ *
33634
+ * `user_suspended` — The user is suspended and cannot sign in or be modified.
33635
+ */
33636
+ 403: {
33637
+ headers: {
33638
+ [name: string]: unknown;
33639
+ };
33640
+ content: {
33641
+ "application/json": components["schemas"]["ErrorResponse"] & {
33642
+ /** @enum {string} */
33643
+ error_code?: "forbidden" | "insufficient_scope" | "user_suspended";
33644
+ };
33645
+ };
33646
+ };
33647
+ /**
33648
+ * @description `account_not_found` — No Auth Account matches the request (domain, header or token).
33649
+ *
33650
+ * `client_not_found` — No client with that `client_id`.
33651
+ */
33652
+ 404: {
33653
+ headers: {
33654
+ [name: string]: unknown;
33655
+ };
33656
+ content: {
33657
+ "application/json": components["schemas"]["ErrorResponse"] & {
33658
+ /** @enum {string} */
33659
+ error_code?: "account_not_found" | "client_not_found";
33660
+ };
33661
+ };
33662
+ };
33663
+ /** @description `too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header. */
33664
+ 429: {
33665
+ headers: {
33666
+ [name: string]: unknown;
33667
+ };
33668
+ content: {
33669
+ "application/json": components["schemas"]["ErrorResponse"] & {
33670
+ /** @enum {string} */
33671
+ error_code?: "too_many_requests";
33672
+ };
33673
+ };
33674
+ };
33675
+ /** @description `internal_error` — Unexpected server error. Retry later; the request id is logged. */
33676
+ 500: {
33677
+ headers: {
33678
+ [name: string]: unknown;
33679
+ };
33680
+ content: {
33681
+ "application/json": components["schemas"]["ErrorResponse"] & {
33682
+ /** @enum {string} */
33683
+ error_code?: "internal_error";
33684
+ };
33685
+ };
33686
+ };
33687
+ };
33688
+ };
33689
+ "connect/exchange": {
33690
+ parameters: {
33691
+ query?: never;
33692
+ header?: never;
33693
+ path?: never;
33694
+ cookie?: never;
33695
+ };
33696
+ requestBody: {
33697
+ content: {
33698
+ "application/json": {
33699
+ code: string;
33700
+ /** @description The PKCE verifier behind the handoff `code_challenge`. */
33701
+ code_verifier: string;
33702
+ };
33703
+ };
33704
+ };
33705
+ responses: {
33706
+ /** @description Default Response */
33707
+ 200: {
33708
+ headers: {
33709
+ [name: string]: unknown;
33710
+ };
33711
+ content: {
33712
+ "application/json": {
33713
+ /** @description Base URL of the tenant: `<auth_domain>/.well-known/openid-configuration` is its discovery document. */
33714
+ auth_domain: string;
33715
+ client_id: string;
33716
+ client_secret: string;
33717
+ project_id: string | null;
33718
+ account_id: string;
33719
+ };
33720
+ };
33721
+ };
33722
+ /**
33723
+ * @description `invalid_code_verifier` — `code_verifier` does not match the `code_challenge` of the handoff.
33724
+ *
33725
+ * `validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.
33726
+ */
33727
+ 400: {
33728
+ headers: {
33729
+ [name: string]: unknown;
33730
+ };
33731
+ content: {
33732
+ "application/json": components["schemas"]["ErrorResponse"] & {
33733
+ /** @enum {string} */
33734
+ error_code?: "invalid_code_verifier" | "validation_error";
33735
+ };
33736
+ };
33737
+ };
33738
+ /**
33739
+ * @description `handoff_not_found` — No connect handoff matches that code.
33740
+ *
33741
+ * `client_not_found` — No client with that `client_id`.
33742
+ *
33743
+ * `account_not_found` — No Auth Account matches the request (domain, header or token).
33744
+ */
33745
+ 404: {
33746
+ headers: {
33747
+ [name: string]: unknown;
33748
+ };
33749
+ content: {
33750
+ "application/json": components["schemas"]["ErrorResponse"] & {
33751
+ /** @enum {string} */
33752
+ error_code?: "handoff_not_found" | "client_not_found" | "account_not_found";
33753
+ };
33754
+ };
33755
+ };
33756
+ /**
33757
+ * @description `handoff_expired` — The connect handoff is past its expiry. Start again.
33758
+ *
33759
+ * `handoff_used` — The connect handoff was already redeemed.
33760
+ */
33761
+ 410: {
33762
+ headers: {
33763
+ [name: string]: unknown;
33764
+ };
33765
+ content: {
33766
+ "application/json": components["schemas"]["ErrorResponse"] & {
33767
+ /** @enum {string} */
33768
+ error_code?: "handoff_expired" | "handoff_used";
33769
+ };
33770
+ };
33771
+ };
33772
+ /** @description `too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header. */
33773
+ 429: {
33774
+ headers: {
33775
+ [name: string]: unknown;
33776
+ };
33777
+ content: {
33778
+ "application/json": components["schemas"]["ErrorResponse"] & {
33779
+ /** @enum {string} */
33780
+ error_code?: "too_many_requests";
33781
+ };
33782
+ };
33783
+ };
33784
+ /** @description `internal_error` — Unexpected server error. Retry later; the request id is logged. */
33785
+ 500: {
33786
+ headers: {
33787
+ [name: string]: unknown;
33788
+ };
33789
+ content: {
33790
+ "application/json": components["schemas"]["ErrorResponse"] & {
33791
+ /** @enum {string} */
33792
+ error_code?: "internal_error";
33793
+ };
33794
+ };
33795
+ };
33796
+ };
33797
+ };
33497
33798
  "passwordless/verify_redirect": {
33498
33799
  parameters: {
33499
33800
  query: {
@@ -37373,6 +37674,8 @@ export interface operations {
37373
37674
  *
37374
37675
  * `invalid_request` — A required OAuth parameter is missing or two of them are incompatible. `message` says which.
37375
37676
  *
37677
+ * `oauth21_required` — The tenant enforces OAuth 2.1 (`oauth21_enabled`) and the request is one OAuth 2.1 removed: a `response_type` other than `code`, an authorization request without PKCE (`code_challenge` with `S256`), or the password grant. `message` says which. Update the application (with `@faable/auth-js`, version 3.1.0 or later) or turn `oauth21_enabled` off for the tenant.
37678
+ *
37376
37679
  * `validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.
37377
37680
  */
37378
37681
  400: {
@@ -37382,7 +37685,7 @@ export interface operations {
37382
37685
  content: {
37383
37686
  "application/json": components["schemas"]["ErrorResponse"] & {
37384
37687
  /** @enum {string} */
37385
- error_code?: "invalid_code" | "invalid_connection" | "invalid_credentials" | "invalid_request" | "validation_error";
37688
+ error_code?: "invalid_code" | "invalid_connection" | "invalid_credentials" | "invalid_request" | "oauth21_required" | "validation_error";
37386
37689
  };
37387
37690
  };
37388
37691
  };
package/dist/version.js CHANGED
@@ -9,13 +9,13 @@
9
9
  // login pages could not import the SDK at all. Same pattern as auth-js.
10
10
  //
11
11
  // The sentinels MUST stay byte-identical to the `from` values in `.releaserc`.
12
- export const version = "2.9.9";
12
+ export const version = "2.9.11";
13
13
  // Short git SHA of the released commit. The version dates a build; this names
14
14
  // the exact tree, so a canonical log line or an audit entry leads straight to
15
15
  // `git show <sha>`. Deliberately NOT hex: an unreleased build (dev, a local
16
16
  // link) cannot be mistaken for a real commit — auth only records values that
17
17
  // look like a SHA, and this one never will.
18
- export const commit = "dba1c7a";
18
+ export const commit = "57570b7";
19
19
  // What this SDK writes in `x-faable-client`. Exported for a consumer that
20
20
  // builds a strategy on its own (outside `FaableAuthApi`) and still wants the
21
21
  // token request attributed — a bare `authClientCredentials` stamps only what
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faable/auth-sdk",
3
- "version": "2.9.9",
3
+ "version": "2.9.11",
4
4
  "author": "Marc Pomar <marc@faable.com>",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/spec/openapi.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "info": {
4
4
  "title": "@faablecloud/auth",
5
5
  "description": "Auth Platform made by Faable. Manage Users and Roles",
6
- "version": "2.97.0",
6
+ "version": "2.99.1",
7
7
  "license": {
8
8
  "name": "private",
9
9
  "url": "https://faable.com/docs/platform/privacy-policy"
@@ -591,6 +591,10 @@
591
591
  "type": "boolean",
592
592
  "description": "When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."
593
593
  },
594
+ "oauth21_enabled": {
595
+ "type": "boolean",
596
+ "description": "When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."
597
+ },
594
598
  "client_id_metadata_documents_enabled": {
595
599
  "type": "boolean",
596
600
  "description": "When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."
@@ -9374,6 +9378,10 @@
9374
9378
  "type": "boolean",
9375
9379
  "nullable": true
9376
9380
  },
9381
+ "oauth21_enabled": {
9382
+ "type": "boolean",
9383
+ "nullable": true
9384
+ },
9377
9385
  "canonical_host": {
9378
9386
  "type": "string",
9379
9387
  "nullable": true
@@ -9717,7 +9725,7 @@
9717
9725
  },
9718
9726
  "ErrorCode": {
9719
9727
  "type": "string",
9720
- "description": "Machine-readable error code. One HTTP status per code.\n\n- `bad_request` (400): The request is malformed. `message` says what is wrong.\n- `validation_error` (400): The body, query or path failed schema validation. `details.issues` lists each failing field.\n- `unauthorized` (401): The request carries no valid credentials.\n- `payment_required` (402): The feature is not included in the current plan.\n- `forbidden` (403): The credentials are valid but do not allow this operation.\n- `not_found` (404): The resource does not exist in this account.\n- `method_not_allowed` (405): The HTTP method is not supported on this path.\n- `conflict` (409): The request conflicts with the current state of the resource.\n- `last_sign_in_method` (409): Unlinking this account would leave the user with no way to sign in: set a password, verify the email or link another account first.\n- `payload_too_large` (413): The request body exceeds the size limit.\n- `unsupported_media_type` (415): The `Content-Type` is not accepted by this endpoint.\n- `unprocessable_entity` (422): The request is well-formed but cannot be processed.\n- `too_many_requests` (429): Rate limit exceeded. Honour the `Retry-After` header.\n- `internal_error` (500): Unexpected server error. Retry later; the request id is logged.\n- `already_exists` (400): A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n- `invalid_id` (400): The id in the path is not a valid id for this resource.\n- `account_not_found` (404): No Auth Account matches the request (domain, header or token).\n- `invalid_query` (400): The `?query=` FaableQL expression could not be parsed.\n- `search_not_supported` (400): This resource does not support `?search=`.\n- `invalid_expand` (400): An `?expand=` path is not allowed on this resource.\n- `access_denied` (403): The caller may not access this row (ownership or machine-to-machine restriction).\n- `insufficient_scope` (403): The token lacks a scope this operation requires. `message` names it.\n- `team_required` (400): An Auth Account must belong to a project (team).\n- `account_limit_reached` (409): The project already has the maximum number of Auth Accounts.\n- `invalid_project_id` (400): The project id is not of the form `project_<hex>` or `team_<hex>`.\n- `account_transfer_refused` (409): The Auth Account cannot be moved to that project. `details.blockers` lists why.\n- `email_sender_domain_not_allowed` (400): The sender address is not in a domain this platform signs email for (AUTH_EMAIL_SENDER_DOMAINS).\n- `plan_required` (402): The setting requires a higher plan. `message` says which.\n- `invalid_canonical_host` (400): `canonical_host` must be this account's own platform host or one of its verified custom domains.\n- `canonical_host_breaks_passkeys` (400): `canonical_host` is outside the account's current WebAuthn RP ID — every passkey already enrolled would stop working. Set `webauthn_rp_id` in the same request to a suffix that covers both.\n- `not_logged_in` (401): There is no signed-in session for this request.\n- `session_already_revoked` (409): The session was already revoked.\n- `invalid_token` (401): The bearer token is missing, malformed, expired or has no subject.\n- `not_owner` (403): The caller may only perform this operation on their own user.\n- `user_suspended` (403): The user is suspended and cannot sign in or be modified.\n- `invalid_credentials` (400): The email/username or password is incorrect.\n- `login_denied` (401): A post-login Action denied the sign-in. `message` carries the reason it gave. On token grants it is a 403 with `error: invalid_grant`.\n- `signup_denied` (403): A pre-signup Action or an active block denied the sign-up. `message` carries the reason.\n- `client_credentials_denied` (401): An Action denied the client_credentials grant. `message` carries the reason.\n- `m2m_quota_exceeded` (402): The project used the machine-to-machine tokens its plan includes this month. Free is capped; Hobby and Pro are billed per token instead.\n- `signup_disabled` (403): Self-service sign-up is disabled on this connection.\n- `email_taken` (409): Another user in this account already uses that email.\n- `username_taken` (409): Another user of this database connection already uses that username.\n- `invalid_username` (400): The username is empty, or contains an `@` or whitespace. `message` says which.\n- `invalid_email` (400): The email address is not valid.\n- `invalid_name` (400): The name contains a link. `message` says which field.\n- `same_email` (400): The new email is the same as the current one.\n- `user_has_no_email` (400): The operation needs an email but the user has none.\n- `password_too_weak` (400): The password does not meet the connection policy. `message` lists each unmet rule.\n- `invalid_password_hash` (400): The imported password hash cannot be read, or its cost is above what a login can afford. `message` names the field.\n- `weak_password_hash` (400): The imported password hash uses a fast digest (MD5, SHA-1, SHA-2) or a cost below the minimum. `message` says which.\n- `invalid_code` (400): The verification code is wrong, expired or already used.\n- `invalid_phone` (400): The phone number is not E.164 and could not be resolved with the account default country. Include the country code.\n- `phone_changed` (400): The phone on the user changed after the code was sent. Start again.\n- `phone_already_verified` (409): The user already has a verified phone; the number cannot be swapped from the screen.\n- `sms_unavailable` (409): This account cannot send SMS right now. The suffix says why: `sms_unavailable:plan`, `:quota`, `:provider`.\n- `sms_failed` (409): The SMS provider rejected the message.\n- `invalid_state` (400): The `state` is missing, expired, or does not describe a resumable step.\n- `state_mismatch` (401): The `state` belongs to another account, session, client or ceremony.\n- `invalid_ticket` (400): The ticket does not exist or is not the kind this endpoint accepts.\n- `ticket_used` (400): The ticket was already consumed.\n- `ticket_expired` (400): The ticket is past its expiry.\n- `ticket_not_found` (404): No ticket with that id belongs to this user.\n- `credential_not_found` (404): The user has no password credential on this connection.\n- `invalid_link` (400): The magic link is malformed.\n- `expired_link` (400): The magic link token was not found or has expired.\n- `invalid_otp` (401): The one-time password is wrong or expired.\n- `invalid_client` (400): The `client_id` in the request does not name a client of this account.\n- `client_not_found` (404): No client with that `client_id`.\n- `client_mismatch` (401): The client belongs to a different account.\n- `invalid_client_metadata` (400): A client field was rejected: by dynamic client registration (RFC 7591), or on a client create/update (`initiate_login_uri`). `message` names it.\n- `invalid_connection` (400): The connection does not exist, is disabled, or is not of the type this flow needs.\n- `no_database_connection` (400): The tenant has no database connection, so there is nowhere to store a password. Create one and retry.\n- `connection_required` (400): No connection was given and the client has no `default_connection`.\n- `connection_misconfigured` (400): The connection is missing settings needed to talk to its provider.\n- `passwordless_unavailable` (400): No passwordless connection is configured or enabled for this client.\n- `ambiguous_connection` (400): Several connections match; pass `connection_id`.\n- `invalid_redirect_uri` (400): The redirect URI is not registered for the client.\n- `invalid_saml_request` (400): The SAMLRequest is missing, too large, not well-formed or not a SAML 2.0 AuthnRequest.\n- `saml_sp_mismatch` (400): The AuthnRequest does not match the client's SAML configuration: its Issuer is not the configured entity_id, or it asks for another Assertion Consumer Service URL or binding.\n- `saml_not_enabled` (404): This client does not have SAML enabled.\n- `invalid_saml_sp` (400): The `saml_sp` configuration is invalid: `acs_url` must be an absolute https URL without a fragment.\n- `origin_not_allowed` (403): The request `Origin` is not in the client's Allowed Web Origins.\n- `audience_not_found` (403): The `audience` does not name an API registered in this account.\n- `system_resource` (403): The resource is managed by Faable and cannot be modified or deleted.\n- `invalid_request` (400): A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n- `invalid_device_code` (400): The device or user code is invalid or expired.\n- `provider_error` (400): The upstream identity provider returned an error. The suffix is the provider’s own code, when it sent one: `provider_error:bad_refresh_token` (the user must re-authorize), `provider_error:incorrect_client_credentials` (our configuration, the user can do nothing). `details.provider_error_description` carries the provider’s sentence.\n- `identity_already_linked` (409): That external identity is already linked to another user.\n- `identity_link_denied` (409): That external identity cannot be linked to this user.\n- `identity_conflict` (409): The user already has a linked identity for this connection.\n- `identity_orphaned` (400): The identity pointed at a user that no longer exists; it was removed. Sign in again.\n- `no_provider_token` (400): The identity holds no provider access token.\n- `no_refresh_token` (400): The identity holds no provider refresh token, so it cannot be refreshed.\n- `action_unavailable` (401): The Action that paused this login is disabled or belongs elsewhere.\n- `login_flow_misconfigured` (500): The login flow of this account cannot run. `message` carries the node error.\n- `mfa_required` (403): The login needs a second factor that this flow cannot collect, or one is still pending on the session.\n- `step_up_required` (403): Confirm an existing factor before changing your factors.\n- `reauth_required` (403): The operation needs a recent sign-in (and the second factor, when the user has one). Sign in again with `max_age=0` and retry with the new token.\n- `invalid_current_password` (400): The current password is wrong.\n- `email_not_verified` (403): The user's email is not verified, and the operation needs it to be.\n- `invalid_profile_field` (400): A profile field is not declared for this application, or its value does not match the declaration. `message` names the key.\n- `interaction_required` (403): An Action asked for a redirect on a non-interactive flow.\n- `passkey_login_disabled` (400): Sign in with a passkey is not enabled for this client.\n- `mfa_pending` (401): A second-factor challenge is pending; answer it before continuing.\n- `mfa_invalid_code` (401): The authenticator code did not verify.\n- `mfa_invalid_recovery_code` (401): The recovery code did not verify.\n- `too_many_attempts` (429): Too many wrong codes. Wait a few minutes and try again.\n- `too_many_login_attempts` (429): Too many failed sign-in attempts for this email or username from this network. Wait a few minutes and try again.\n- `totp_not_allowed` (401): This account does not accept authenticator apps.\n- `no_usable_factor` (400): The user has no confirmed second factor to challenge.\n- `factor_not_found` (400): No factor with that id belongs to this user.\n- `factor_already_confirmed` (400): The factor was already confirmed.\n- `invalid_factor` (400): The factor cannot be verified (no secret material).\n- `session_missing` (401): The session that started this login no longer exists.\n- `passkey_verification_failed` (400): The WebAuthn response could not be verified.\n- `no_passkey_enrolled` (400): The user has no passkey to sign in with.\n- `malformed_credential` (400): The WebAuthn credential is malformed.\n- `unknown_passkey` (401): The passkey is not enrolled for this user.\n- `invalid_passkey` (401): The passkey assertion did not verify.\n- `invalid_invite_url` (400): An invite's `accept_url` or `redirect_uri` is not an http(s) URL at an origin of this tenant's client callbacks or `callback_hostnames`, or it carries a fragment.\n- `invite_email_mismatch` (403): The invite is for another email address than the accepting user's. `details.invited_email` shows it masked.\n- `invite_email_unverified` (403): The accepting user's email matches the invite but is not verified yet.\n- `invite_not_found` (404): No invite with that id in this account.\n- `invite_used` (400): The invite was already accepted.\n- `already_member` (400): The user is already a member of the team.\n- `not_a_member` (404): The user is not a member of the team.\n- `consent_options_unavailable` (502): The consent screen could not load the choices from the API being authorized (its `consent_options.url`). Try again in a moment.\n- `consent_choice_invalid` (400): The choice sent with the consent decision is not one the API offered for this user.\n- `registration_disabled` (403): Dynamic client registration is turned off for this tenant. Create the client from the dashboard or the Management API, or turn on `dynamic_client_registration_enabled`.\n- `invalid_email_domain` (400): Not an email domain this organization can claim: it must be a plain domain name (`acme.com`), lowercase, without scheme, path or wildcard.\n- `public_email_domain` (400): A public email provider (gmail.com, outlook.com…) cannot belong to an organization: anyone can have an address there.\n- `organization_domain_not_found` (404): The organization has no such domain.\n- `organization_domain_taken` (409): Another organization of this tenant already verified this domain. A domain belongs to one organization.\n- `organization_domain_unverified` (400): The TXT record was not found or does not hold the expected value yet. `details.record` says what was expected and what resolves now; DNS can take a few minutes to propagate.\n- `plan_unavailable` (503): The plan could not be checked right now, and this setting is only for paid plans. Try again in a minute.\n- `organization_domain_mismatch` (403): This organization's identity provider can only sign in addresses of the organization's verified email domains.\n- `team_not_in_organization` (400): Auto-join can only put people in teams of the same organization. `details.teams` lists the others.\n- `organization_has_no_idp` (400): Requiring SSO needs an identity provider: bind an `oidc` connection to the organization first.\n- `organization_not_found` (404): No organization with that id in this account.\n- `invalid_user` (400): The user does not exist in this account.\n- `not_an_organization_admin` (404): The user is not an admin of the organization.\n- `last_organization_admin` (409): An organization keeps at least one admin. Add another admin before removing this one.\n- `team_id_unavailable` (409): The team id cannot be used in this tenant. Pick another id or let the API generate one.\n- `use_team_member_route` (400): This generic route cannot create memberships. Use `POST /team/{team_id}/member` for team members or `POST /role/{role_id}/users` for role grants.\n- `role_not_found` (404): No role with that id in this account.\n- `invalid_role` (400): The role belongs to another account.\n- `invalid_permission` (400): A permission is not in the catalog of its Api, or the Api is not in this account. `details.permissions` lists the ones refused.\n- `flow_not_found` (400): The login flow does not exist in this account.\n- `revision_not_found` (404): The revision is not in the flow history.\n- `invalid_webhook_event` (400): An event is not in the webhook event catalog. `message` and `details.valid_events` list the valid ones.\n- `invalid_webhook_url` (400): The webhook URL must be an absolute `https` URL.\n- `not_a_webhook` (400): The operation only applies to `webhook` subscriptions.\n- `delivery_not_found` (404): No webhook delivery with that id for this subscription.\n- `delivery_in_progress` (409): The delivery is being attempted right now; retry the redeliver in a minute.",
9728
+ "description": "Machine-readable error code. One HTTP status per code.\n\n- `bad_request` (400): The request is malformed. `message` says what is wrong.\n- `validation_error` (400): The body, query or path failed schema validation. `details.issues` lists each failing field.\n- `unauthorized` (401): The request carries no valid credentials.\n- `payment_required` (402): The feature is not included in the current plan.\n- `forbidden` (403): The credentials are valid but do not allow this operation.\n- `not_found` (404): The resource does not exist in this account.\n- `method_not_allowed` (405): The HTTP method is not supported on this path.\n- `conflict` (409): The request conflicts with the current state of the resource.\n- `last_sign_in_method` (409): Unlinking this account would leave the user with no way to sign in: set a password, verify the email or link another account first.\n- `payload_too_large` (413): The request body exceeds the size limit.\n- `unsupported_media_type` (415): The `Content-Type` is not accepted by this endpoint.\n- `unprocessable_entity` (422): The request is well-formed but cannot be processed.\n- `too_many_requests` (429): Rate limit exceeded. Honour the `Retry-After` header.\n- `internal_error` (500): Unexpected server error. Retry later; the request id is logged.\n- `already_exists` (400): A resource with the same unique field already exists (for a user, usually `email`). `details.fields` names the field.\n- `invalid_id` (400): The id in the path is not a valid id for this resource.\n- `account_not_found` (404): No Auth Account matches the request (domain, header or token).\n- `invalid_query` (400): The `?query=` FaableQL expression could not be parsed.\n- `search_not_supported` (400): This resource does not support `?search=`.\n- `invalid_expand` (400): An `?expand=` path is not allowed on this resource.\n- `access_denied` (403): The caller may not access this row (ownership or machine-to-machine restriction).\n- `insufficient_scope` (403): The token lacks a scope this operation requires. `message` names it.\n- `team_required` (400): An Auth Account must belong to a project (team).\n- `account_limit_reached` (409): The project already has the maximum number of Auth Accounts.\n- `invalid_project_id` (400): The project id is not of the form `project_<hex>` or `team_<hex>`.\n- `account_transfer_refused` (409): The Auth Account cannot be moved to that project. `details.blockers` lists why.\n- `email_sender_domain_not_allowed` (400): The sender address is not in a domain this platform signs email for (AUTH_EMAIL_SENDER_DOMAINS).\n- `plan_required` (402): The setting requires a higher plan. `message` says which.\n- `invalid_canonical_host` (400): `canonical_host` must be this account's own platform host or one of its verified custom domains.\n- `canonical_host_breaks_passkeys` (400): `canonical_host` is outside the account's current WebAuthn RP ID — every passkey already enrolled would stop working. Set `webauthn_rp_id` in the same request to a suffix that covers both.\n- `not_logged_in` (401): There is no signed-in session for this request.\n- `session_already_revoked` (409): The session was already revoked.\n- `invalid_token` (401): The bearer token is missing, malformed, expired or has no subject.\n- `not_owner` (403): The caller may only perform this operation on their own user.\n- `user_suspended` (403): The user is suspended and cannot sign in or be modified.\n- `invalid_credentials` (400): The email/username or password is incorrect.\n- `login_denied` (401): A post-login Action denied the sign-in. `message` carries the reason it gave. On token grants it is a 403 with `error: invalid_grant`.\n- `signup_denied` (403): A pre-signup Action or an active block denied the sign-up. `message` carries the reason.\n- `client_credentials_denied` (401): An Action denied the client_credentials grant. `message` carries the reason.\n- `m2m_quota_exceeded` (402): The project used the machine-to-machine tokens its plan includes this month. Free is capped; Hobby and Pro are billed per token instead.\n- `signup_disabled` (403): Self-service sign-up is disabled on this connection.\n- `email_taken` (409): Another user in this account already uses that email.\n- `username_taken` (409): Another user of this database connection already uses that username.\n- `invalid_username` (400): The username is empty, or contains an `@` or whitespace. `message` says which.\n- `invalid_email` (400): The email address is not valid.\n- `invalid_name` (400): The name contains a link. `message` says which field.\n- `same_email` (400): The new email is the same as the current one.\n- `user_has_no_email` (400): The operation needs an email but the user has none.\n- `password_too_weak` (400): The password does not meet the connection policy. `message` lists each unmet rule.\n- `invalid_password_hash` (400): The imported password hash cannot be read, or its cost is above what a login can afford. `message` names the field.\n- `weak_password_hash` (400): The imported password hash uses a fast digest (MD5, SHA-1, SHA-2) or a cost below the minimum. `message` says which.\n- `invalid_code` (400): The verification code is wrong, expired or already used.\n- `invalid_phone` (400): The phone number is not E.164 and could not be resolved with the account default country. Include the country code.\n- `phone_changed` (400): The phone on the user changed after the code was sent. Start again.\n- `phone_already_verified` (409): The user already has a verified phone; the number cannot be swapped from the screen.\n- `sms_unavailable` (409): This account cannot send SMS right now. The suffix says why: `sms_unavailable:plan`, `:quota`, `:provider`.\n- `sms_failed` (409): The SMS provider rejected the message.\n- `invalid_state` (400): The `state` is missing, expired, or does not describe a resumable step.\n- `state_mismatch` (401): The `state` belongs to another account, session, client or ceremony.\n- `invalid_ticket` (400): The ticket does not exist or is not the kind this endpoint accepts.\n- `ticket_used` (400): The ticket was already consumed.\n- `ticket_expired` (400): The ticket is past its expiry.\n- `ticket_not_found` (404): No ticket with that id belongs to this user.\n- `credential_not_found` (404): The user has no password credential on this connection.\n- `invalid_link` (400): The magic link is malformed.\n- `expired_link` (400): The magic link token was not found or has expired.\n- `invalid_otp` (401): The one-time password is wrong or expired.\n- `invalid_client` (400): The `client_id` in the request does not name a client of this account.\n- `client_not_found` (404): No client with that `client_id`.\n- `client_mismatch` (401): The client belongs to a different account.\n- `invalid_client_metadata` (400): A client field was rejected: by dynamic client registration (RFC 7591), or on a client create/update (`initiate_login_uri`). `message` names it.\n- `invalid_connection` (400): The connection does not exist, is disabled, or is not of the type this flow needs.\n- `no_database_connection` (400): The tenant has no database connection, so there is nowhere to store a password. Create one and retry.\n- `connection_required` (400): No connection was given and the client has no `default_connection`.\n- `connection_misconfigured` (400): The connection is missing settings needed to talk to its provider.\n- `passwordless_unavailable` (400): No passwordless connection is configured or enabled for this client.\n- `ambiguous_connection` (400): Several connections match; pass `connection_id`.\n- `invalid_redirect_uri` (400): The redirect URI is not registered for the client.\n- `invalid_saml_request` (400): The SAMLRequest is missing, too large, not well-formed or not a SAML 2.0 AuthnRequest.\n- `saml_sp_mismatch` (400): The AuthnRequest does not match the client's SAML configuration: its Issuer is not the configured entity_id, or it asks for another Assertion Consumer Service URL or binding.\n- `saml_not_enabled` (404): This client does not have SAML enabled.\n- `invalid_saml_sp` (400): The `saml_sp` configuration is invalid: `acs_url` must be an absolute https URL without a fragment.\n- `origin_not_allowed` (403): The request `Origin` is not in the client's Allowed Web Origins.\n- `audience_not_found` (403): The `audience` does not name an API registered in this account.\n- `system_resource` (403): The resource is managed by Faable and cannot be modified or deleted.\n- `invalid_request` (400): A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n- `invalid_device_code` (400): The device or user code is invalid or expired.\n- `provider_error` (400): The upstream identity provider returned an error. The suffix is the provider’s own code, when it sent one: `provider_error:bad_refresh_token` (the user must re-authorize), `provider_error:incorrect_client_credentials` (our configuration, the user can do nothing). `details.provider_error_description` carries the provider’s sentence.\n- `identity_already_linked` (409): That external identity is already linked to another user.\n- `identity_link_denied` (409): That external identity cannot be linked to this user.\n- `identity_conflict` (409): The user already has a linked identity for this connection.\n- `identity_orphaned` (400): The identity pointed at a user that no longer exists; it was removed. Sign in again.\n- `no_provider_token` (400): The identity holds no provider access token.\n- `no_refresh_token` (400): The identity holds no provider refresh token, so it cannot be refreshed.\n- `action_unavailable` (401): The Action that paused this login is disabled or belongs elsewhere.\n- `login_flow_misconfigured` (500): The login flow of this account cannot run. `message` carries the node error.\n- `mfa_required` (403): The login needs a second factor that this flow cannot collect, or one is still pending on the session.\n- `step_up_required` (403): Confirm an existing factor before changing your factors.\n- `reauth_required` (403): The operation needs a recent sign-in (and the second factor, when the user has one). Sign in again with `max_age=0` and retry with the new token.\n- `invalid_current_password` (400): The current password is wrong.\n- `email_not_verified` (403): The user's email is not verified, and the operation needs it to be.\n- `invalid_profile_field` (400): A profile field is not declared for this application, or its value does not match the declaration. `message` names the key.\n- `interaction_required` (403): An Action asked for a redirect on a non-interactive flow.\n- `passkey_login_disabled` (400): Sign in with a passkey is not enabled for this client.\n- `mfa_pending` (401): A second-factor challenge is pending; answer it before continuing.\n- `mfa_invalid_code` (401): The authenticator code did not verify.\n- `mfa_invalid_recovery_code` (401): The recovery code did not verify.\n- `too_many_attempts` (429): Too many wrong codes. Wait a few minutes and try again.\n- `too_many_login_attempts` (429): Too many failed sign-in attempts for this email or username from this network. Wait a few minutes and try again.\n- `totp_not_allowed` (401): This account does not accept authenticator apps.\n- `no_usable_factor` (400): The user has no confirmed second factor to challenge.\n- `factor_not_found` (400): No factor with that id belongs to this user.\n- `factor_already_confirmed` (400): The factor was already confirmed.\n- `invalid_factor` (400): The factor cannot be verified (no secret material).\n- `session_missing` (401): The session that started this login no longer exists.\n- `passkey_verification_failed` (400): The WebAuthn response could not be verified.\n- `no_passkey_enrolled` (400): The user has no passkey to sign in with.\n- `malformed_credential` (400): The WebAuthn credential is malformed.\n- `unknown_passkey` (401): The passkey is not enrolled for this user.\n- `invalid_passkey` (401): The passkey assertion did not verify.\n- `invalid_invite_url` (400): An invite's `accept_url` or `redirect_uri` is not an http(s) URL at an origin of this tenant's client callbacks or `callback_hostnames`, or it carries a fragment.\n- `invite_email_mismatch` (403): The invite is for another email address than the accepting user's. `details.invited_email` shows it masked.\n- `invite_email_unverified` (403): The accepting user's email matches the invite but is not verified yet.\n- `invite_not_found` (404): No invite with that id in this account.\n- `invite_used` (400): The invite was already accepted.\n- `already_member` (400): The user is already a member of the team.\n- `not_a_member` (404): The user is not a member of the team.\n- `consent_options_unavailable` (502): The consent screen could not load the choices from the API being authorized (its `consent_options.url`). Try again in a moment.\n- `consent_choice_invalid` (400): The choice sent with the consent decision is not one the API offered for this user.\n- `oauth21_required` (400): The tenant enforces OAuth 2.1 (`oauth21_enabled`) and the request is one OAuth 2.1 removed: a `response_type` other than `code`, an authorization request without PKCE (`code_challenge` with `S256`), or the password grant. `message` says which. Update the application (with `@faable/auth-js`, version 3.1.0 or later) or turn `oauth21_enabled` off for the tenant.\n- `registration_disabled` (403): Dynamic client registration is turned off for this tenant. Create the client from the dashboard or the Management API, or turn on `dynamic_client_registration_enabled`.\n- `invalid_email_domain` (400): Not an email domain this organization can claim: it must be a plain domain name (`acme.com`), lowercase, without scheme, path or wildcard.\n- `public_email_domain` (400): A public email provider (gmail.com, outlook.com…) cannot belong to an organization: anyone can have an address there.\n- `organization_domain_not_found` (404): The organization has no such domain.\n- `organization_domain_taken` (409): Another organization of this tenant already verified this domain. A domain belongs to one organization.\n- `organization_domain_unverified` (400): The TXT record was not found or does not hold the expected value yet. `details.record` says what was expected and what resolves now; DNS can take a few minutes to propagate.\n- `plan_unavailable` (503): The plan could not be checked right now, and this setting is only for paid plans. Try again in a minute.\n- `organization_domain_mismatch` (403): This organization's identity provider can only sign in addresses of the organization's verified email domains.\n- `team_not_in_organization` (400): Auto-join can only put people in teams of the same organization. `details.teams` lists the others.\n- `organization_has_no_idp` (400): Requiring SSO needs an identity provider: bind an `oidc` connection to the organization first.\n- `organization_not_found` (404): No organization with that id in this account.\n- `invalid_user` (400): The user does not exist in this account.\n- `not_an_organization_admin` (404): The user is not an admin of the organization.\n- `last_organization_admin` (409): An organization keeps at least one admin. Add another admin before removing this one.\n- `team_id_unavailable` (409): The team id cannot be used in this tenant. Pick another id or let the API generate one.\n- `use_team_member_route` (400): This generic route cannot create memberships. Use `POST /team/{team_id}/member` for team members or `POST /role/{role_id}/users` for role grants.\n- `role_not_found` (404): No role with that id in this account.\n- `invalid_role` (400): The role belongs to another account.\n- `invalid_permission` (400): A permission is not in the catalog of its Api, or the Api is not in this account. `details.permissions` lists the ones refused.\n- `flow_not_found` (400): The login flow does not exist in this account.\n- `revision_not_found` (404): The revision is not in the flow history.\n- `invalid_webhook_event` (400): An event is not in the webhook event catalog. `message` and `details.valid_events` list the valid ones.\n- `invalid_webhook_url` (400): The webhook URL must be an absolute `https` URL.\n- `not_a_webhook` (400): The operation only applies to `webhook` subscriptions.\n- `delivery_not_found` (404): No webhook delivery with that id for this subscription.\n- `delivery_in_progress` (409): The delivery is being attempted right now; retry the redeliver in a minute.\n- `account_mismatch` (400): The `account_id` in the body is not the account the request is scoped to (`x-faableauth-account`).\n- `connect_redirect_uri_missing` (400): The client's callbacks do not include `<site_url>/wp-login.php?faable-auth=callback`.\n- `invalid_site_url` (400): `site_url` is not an absolute http(s) URL without query string or fragment.\n- `handoff_not_found` (404): No connect handoff matches that code.\n- `handoff_expired` (410): The connect handoff is past its expiry. Start again.\n- `handoff_used` (410): The connect handoff was already redeemed.\n- `invalid_code_verifier` (400): `code_verifier` does not match the `code_challenge` of the handoff.",
9721
9729
  "enum": [
9722
9730
  "bad_request",
9723
9731
  "validation_error",
@@ -9848,6 +9856,7 @@
9848
9856
  "not_a_member",
9849
9857
  "consent_options_unavailable",
9850
9858
  "consent_choice_invalid",
9859
+ "oauth21_required",
9851
9860
  "registration_disabled",
9852
9861
  "invalid_email_domain",
9853
9862
  "public_email_domain",
@@ -9873,7 +9882,14 @@
9873
9882
  "invalid_webhook_url",
9874
9883
  "not_a_webhook",
9875
9884
  "delivery_not_found",
9876
- "delivery_in_progress"
9885
+ "delivery_in_progress",
9886
+ "account_mismatch",
9887
+ "connect_redirect_uri_missing",
9888
+ "invalid_site_url",
9889
+ "handoff_not_found",
9890
+ "handoff_expired",
9891
+ "handoff_used",
9892
+ "invalid_code_verifier"
9877
9893
  ]
9878
9894
  },
9879
9895
  "ErrorResponse": {
@@ -10585,6 +10601,10 @@
10585
10601
  "type": "boolean",
10586
10602
  "description": "When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."
10587
10603
  },
10604
+ "oauth21_enabled": {
10605
+ "type": "boolean",
10606
+ "description": "When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."
10607
+ },
10588
10608
  "client_id_metadata_documents_enabled": {
10589
10609
  "type": "boolean",
10590
10610
  "description": "When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."
@@ -11392,6 +11412,10 @@
11392
11412
  "type": "boolean",
11393
11413
  "description": "When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."
11394
11414
  },
11415
+ "oauth21_enabled": {
11416
+ "type": "boolean",
11417
+ "description": "When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."
11418
+ },
11395
11419
  "client_id_metadata_documents_enabled": {
11396
11420
  "type": "boolean",
11397
11421
  "description": "When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."
@@ -12436,6 +12460,10 @@
12436
12460
  "type": "boolean",
12437
12461
  "description": "When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."
12438
12462
  },
12463
+ "oauth21_enabled": {
12464
+ "type": "boolean",
12465
+ "description": "When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."
12466
+ },
12439
12467
  "client_id_metadata_documents_enabled": {
12440
12468
  "type": "boolean",
12441
12469
  "description": "When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."
@@ -13128,6 +13156,10 @@
13128
13156
  "type": "boolean",
13129
13157
  "nullable": true
13130
13158
  },
13159
+ "oauth21_enabled": {
13160
+ "type": "boolean",
13161
+ "nullable": true
13162
+ },
13131
13163
  "canonical_host": {
13132
13164
  "type": "string",
13133
13165
  "nullable": true
@@ -13719,6 +13751,10 @@
13719
13751
  "type": "boolean",
13720
13752
  "description": "When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."
13721
13753
  },
13754
+ "oauth21_enabled": {
13755
+ "type": "boolean",
13756
+ "description": "When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."
13757
+ },
13722
13758
  "client_id_metadata_documents_enabled": {
13723
13759
  "type": "boolean",
13724
13760
  "description": "When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."
@@ -16980,6 +17016,10 @@
16980
17016
  "type": "boolean",
16981
17017
  "description": "When true, anyone can register a client with `POST /oidc/register` (OIDC Dynamic Client Registration / RFC 7591) and the discovery document advertises `registration_endpoint`. Off by default: an open registration lets anyone create a client with their own `redirect_uri`."
16982
17018
  },
17019
+ "oauth21_enabled": {
17020
+ "type": "boolean",
17021
+ "description": "When true, the tenant enforces OAuth 2.1: `/authorize` accepts only `response_type=code` and requires PKCE (`code_challenge` with `S256`) from every client, a password login without `/authorize` needs PKCE too, and the password grant is refused. Refused requests answer `oauth21_required`. On for new tenants; an existing tenant turns it on when its applications are ready — with `@faable/auth-js` that is version 3.1.0 or later."
17022
+ },
16983
17023
  "client_id_metadata_documents_enabled": {
16984
17024
  "type": "boolean",
16985
17025
  "description": "When true, /authorize accepts a `client_id` that is an https URL serving a Client ID Metadata Document (the MCP authorization way to identify a client without registering it). Such clients always see the consent screen. Off by default."
@@ -68450,6 +68490,462 @@
68450
68490
  }
68451
68491
  }
68452
68492
  },
68493
+ "/connect/handoff": {
68494
+ "post": {
68495
+ "operationId": "connect/createHandoff",
68496
+ "summary": "Create a one-time handoff of a client’s credentials",
68497
+ "tags": [
68498
+ "connect"
68499
+ ],
68500
+ "description": "Used by the dashboard in the «Connect with Faable» flow. Checks that `client_id` belongs to `account_id` and that its callbacks contain `<site_url>/wp-login.php?faable-auth=callback`, then stores a one-time handoff bound to the PKCE `code_challenge`, valid for 10 minutes. The response `code` is sent to the site, which redeems it with `POST /connect/exchange`. The client secret is never part of this request or response. Requires a management token for `account_id` with `update:clients`.",
68501
+ "requestBody": {
68502
+ "required": true,
68503
+ "content": {
68504
+ "application/json": {
68505
+ "schema": {
68506
+ "type": "object",
68507
+ "required": [
68508
+ "account_id",
68509
+ "client_id",
68510
+ "code_challenge",
68511
+ "code_challenge_method",
68512
+ "site_url"
68513
+ ],
68514
+ "properties": {
68515
+ "account_id": {
68516
+ "type": "string",
68517
+ "pattern": "^account_[0-9a-f]{24}$",
68518
+ "description": "The tenant that owns the client."
68519
+ },
68520
+ "client_id": {
68521
+ "type": "string",
68522
+ "minLength": 1,
68523
+ "maxLength": 200,
68524
+ "description": "The OAuth `client_id` of the client created for the site (the resource `id`, `client_…`, is accepted too)."
68525
+ },
68526
+ "code_challenge": {
68527
+ "type": "string",
68528
+ "pattern": "^[A-Za-z0-9_-]{43}$",
68529
+ "description": "The site's PKCE challenge: BASE64URL(SHA256(code_verifier)), unpadded."
68530
+ },
68531
+ "code_challenge_method": {
68532
+ "type": "string",
68533
+ "enum": [
68534
+ "S256"
68535
+ ]
68536
+ },
68537
+ "site_url": {
68538
+ "type": "string",
68539
+ "minLength": 1,
68540
+ "maxLength": 2048,
68541
+ "description": "The site's base URL. The client's callbacks must contain `<site_url>/wp-login.php?faable-auth=callback`."
68542
+ }
68543
+ },
68544
+ "additionalProperties": false
68545
+ }
68546
+ }
68547
+ }
68548
+ },
68549
+ "security": [
68550
+ {
68551
+ "bearerAuth": []
68552
+ }
68553
+ ],
68554
+ "responses": {
68555
+ "201": {
68556
+ "description": "Default Response",
68557
+ "content": {
68558
+ "application/json": {
68559
+ "schema": {
68560
+ "type": "object",
68561
+ "required": [
68562
+ "code",
68563
+ "expires_at"
68564
+ ],
68565
+ "properties": {
68566
+ "code": {
68567
+ "type": "string",
68568
+ "description": "One-time code for `POST /connect/exchange`. Shown once; only its hash is stored."
68569
+ },
68570
+ "expires_at": {
68571
+ "type": "string",
68572
+ "format": "date-time"
68573
+ }
68574
+ }
68575
+ }
68576
+ }
68577
+ }
68578
+ },
68579
+ "400": {
68580
+ "description": "`account_mismatch` — The `account_id` in the body is not the account the request is scoped to (`x-faableauth-account`).\n\n`connect_redirect_uri_missing` — The client's callbacks do not include `<site_url>/wp-login.php?faable-auth=callback`.\n\n`invalid_site_url` — `site_url` is not an absolute http(s) URL without query string or fragment.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.",
68581
+ "content": {
68582
+ "application/json": {
68583
+ "schema": {
68584
+ "allOf": [
68585
+ {
68586
+ "$ref": "#/components/schemas/ErrorResponse"
68587
+ },
68588
+ {
68589
+ "type": "object",
68590
+ "properties": {
68591
+ "error_code": {
68592
+ "type": "string",
68593
+ "enum": [
68594
+ "account_mismatch",
68595
+ "connect_redirect_uri_missing",
68596
+ "invalid_site_url",
68597
+ "validation_error"
68598
+ ]
68599
+ }
68600
+ }
68601
+ }
68602
+ ]
68603
+ }
68604
+ }
68605
+ }
68606
+ },
68607
+ "401": {
68608
+ "description": "`unauthorized` — The request carries no valid credentials.",
68609
+ "content": {
68610
+ "application/json": {
68611
+ "schema": {
68612
+ "allOf": [
68613
+ {
68614
+ "$ref": "#/components/schemas/ErrorResponse"
68615
+ },
68616
+ {
68617
+ "type": "object",
68618
+ "properties": {
68619
+ "error_code": {
68620
+ "type": "string",
68621
+ "enum": [
68622
+ "unauthorized"
68623
+ ]
68624
+ }
68625
+ }
68626
+ }
68627
+ ]
68628
+ }
68629
+ }
68630
+ }
68631
+ },
68632
+ "403": {
68633
+ "description": "`forbidden` — The credentials are valid but do not allow this operation.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `message` names it.\n\n`user_suspended` — The user is suspended and cannot sign in or be modified.",
68634
+ "content": {
68635
+ "application/json": {
68636
+ "schema": {
68637
+ "allOf": [
68638
+ {
68639
+ "$ref": "#/components/schemas/ErrorResponse"
68640
+ },
68641
+ {
68642
+ "type": "object",
68643
+ "properties": {
68644
+ "error_code": {
68645
+ "type": "string",
68646
+ "enum": [
68647
+ "forbidden",
68648
+ "insufficient_scope",
68649
+ "user_suspended"
68650
+ ]
68651
+ }
68652
+ }
68653
+ }
68654
+ ]
68655
+ }
68656
+ }
68657
+ }
68658
+ },
68659
+ "404": {
68660
+ "description": "`account_not_found` — No Auth Account matches the request (domain, header or token).\n\n`client_not_found` — No client with that `client_id`.",
68661
+ "content": {
68662
+ "application/json": {
68663
+ "schema": {
68664
+ "allOf": [
68665
+ {
68666
+ "$ref": "#/components/schemas/ErrorResponse"
68667
+ },
68668
+ {
68669
+ "type": "object",
68670
+ "properties": {
68671
+ "error_code": {
68672
+ "type": "string",
68673
+ "enum": [
68674
+ "account_not_found",
68675
+ "client_not_found"
68676
+ ]
68677
+ }
68678
+ }
68679
+ }
68680
+ ]
68681
+ }
68682
+ }
68683
+ }
68684
+ },
68685
+ "429": {
68686
+ "description": "`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.",
68687
+ "content": {
68688
+ "application/json": {
68689
+ "schema": {
68690
+ "allOf": [
68691
+ {
68692
+ "$ref": "#/components/schemas/ErrorResponse"
68693
+ },
68694
+ {
68695
+ "type": "object",
68696
+ "properties": {
68697
+ "error_code": {
68698
+ "type": "string",
68699
+ "enum": [
68700
+ "too_many_requests"
68701
+ ]
68702
+ }
68703
+ }
68704
+ }
68705
+ ]
68706
+ }
68707
+ }
68708
+ }
68709
+ },
68710
+ "500": {
68711
+ "description": "`internal_error` — Unexpected server error. Retry later; the request id is logged.",
68712
+ "content": {
68713
+ "application/json": {
68714
+ "schema": {
68715
+ "allOf": [
68716
+ {
68717
+ "$ref": "#/components/schemas/ErrorResponse"
68718
+ },
68719
+ {
68720
+ "type": "object",
68721
+ "properties": {
68722
+ "error_code": {
68723
+ "type": "string",
68724
+ "enum": [
68725
+ "internal_error"
68726
+ ]
68727
+ }
68728
+ }
68729
+ }
68730
+ ]
68731
+ }
68732
+ }
68733
+ }
68734
+ }
68735
+ }
68736
+ }
68737
+ },
68738
+ "/connect/exchange": {
68739
+ "post": {
68740
+ "operationId": "connect/exchange",
68741
+ "summary": "Redeem a connect handoff for the client’s credentials",
68742
+ "tags": [
68743
+ "connect"
68744
+ ],
68745
+ "description": "Server-to-server, unauthenticated: the PKCE `code_verifier` is the proof. Returns the credentials of the client the handoff names, once — a second redemption answers 410 `handoff_used`. Errors carry `error` (same value as `error_code`). Responses are `Cache-Control: no-store`.",
68746
+ "requestBody": {
68747
+ "required": true,
68748
+ "content": {
68749
+ "application/json": {
68750
+ "schema": {
68751
+ "type": "object",
68752
+ "required": [
68753
+ "code",
68754
+ "code_verifier"
68755
+ ],
68756
+ "properties": {
68757
+ "code": {
68758
+ "type": "string",
68759
+ "minLength": 1,
68760
+ "maxLength": 200
68761
+ },
68762
+ "code_verifier": {
68763
+ "type": "string",
68764
+ "pattern": "^[A-Za-z0-9._~-]{43,128}$",
68765
+ "description": "The PKCE verifier behind the handoff `code_challenge`."
68766
+ }
68767
+ },
68768
+ "additionalProperties": false
68769
+ }
68770
+ }
68771
+ }
68772
+ },
68773
+ "security": [],
68774
+ "responses": {
68775
+ "200": {
68776
+ "description": "Default Response",
68777
+ "content": {
68778
+ "application/json": {
68779
+ "schema": {
68780
+ "type": "object",
68781
+ "required": [
68782
+ "auth_domain",
68783
+ "client_id",
68784
+ "client_secret",
68785
+ "project_id",
68786
+ "account_id"
68787
+ ],
68788
+ "properties": {
68789
+ "auth_domain": {
68790
+ "type": "string",
68791
+ "description": "Base URL of the tenant: `<auth_domain>/.well-known/openid-configuration` is its discovery document."
68792
+ },
68793
+ "client_id": {
68794
+ "type": "string"
68795
+ },
68796
+ "client_secret": {
68797
+ "type": "string"
68798
+ },
68799
+ "project_id": {
68800
+ "anyOf": [
68801
+ {
68802
+ "type": "string"
68803
+ },
68804
+ {
68805
+ "type": "null"
68806
+ }
68807
+ ]
68808
+ },
68809
+ "account_id": {
68810
+ "type": "string"
68811
+ }
68812
+ }
68813
+ }
68814
+ }
68815
+ }
68816
+ },
68817
+ "400": {
68818
+ "description": "`invalid_code_verifier` — `code_verifier` does not match the `code_challenge` of the handoff.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.",
68819
+ "content": {
68820
+ "application/json": {
68821
+ "schema": {
68822
+ "allOf": [
68823
+ {
68824
+ "$ref": "#/components/schemas/ErrorResponse"
68825
+ },
68826
+ {
68827
+ "type": "object",
68828
+ "properties": {
68829
+ "error_code": {
68830
+ "type": "string",
68831
+ "enum": [
68832
+ "invalid_code_verifier",
68833
+ "validation_error"
68834
+ ]
68835
+ }
68836
+ }
68837
+ }
68838
+ ]
68839
+ }
68840
+ }
68841
+ }
68842
+ },
68843
+ "404": {
68844
+ "description": "`handoff_not_found` — No connect handoff matches that code.\n\n`client_not_found` — No client with that `client_id`.\n\n`account_not_found` — No Auth Account matches the request (domain, header or token).",
68845
+ "content": {
68846
+ "application/json": {
68847
+ "schema": {
68848
+ "allOf": [
68849
+ {
68850
+ "$ref": "#/components/schemas/ErrorResponse"
68851
+ },
68852
+ {
68853
+ "type": "object",
68854
+ "properties": {
68855
+ "error_code": {
68856
+ "type": "string",
68857
+ "enum": [
68858
+ "handoff_not_found",
68859
+ "client_not_found",
68860
+ "account_not_found"
68861
+ ]
68862
+ }
68863
+ }
68864
+ }
68865
+ ]
68866
+ }
68867
+ }
68868
+ }
68869
+ },
68870
+ "410": {
68871
+ "description": "`handoff_expired` — The connect handoff is past its expiry. Start again.\n\n`handoff_used` — The connect handoff was already redeemed.",
68872
+ "content": {
68873
+ "application/json": {
68874
+ "schema": {
68875
+ "allOf": [
68876
+ {
68877
+ "$ref": "#/components/schemas/ErrorResponse"
68878
+ },
68879
+ {
68880
+ "type": "object",
68881
+ "properties": {
68882
+ "error_code": {
68883
+ "type": "string",
68884
+ "enum": [
68885
+ "handoff_expired",
68886
+ "handoff_used"
68887
+ ]
68888
+ }
68889
+ }
68890
+ }
68891
+ ]
68892
+ }
68893
+ }
68894
+ }
68895
+ },
68896
+ "429": {
68897
+ "description": "`too_many_requests` — Rate limit exceeded. Honour the `Retry-After` header.",
68898
+ "content": {
68899
+ "application/json": {
68900
+ "schema": {
68901
+ "allOf": [
68902
+ {
68903
+ "$ref": "#/components/schemas/ErrorResponse"
68904
+ },
68905
+ {
68906
+ "type": "object",
68907
+ "properties": {
68908
+ "error_code": {
68909
+ "type": "string",
68910
+ "enum": [
68911
+ "too_many_requests"
68912
+ ]
68913
+ }
68914
+ }
68915
+ }
68916
+ ]
68917
+ }
68918
+ }
68919
+ }
68920
+ },
68921
+ "500": {
68922
+ "description": "`internal_error` — Unexpected server error. Retry later; the request id is logged.",
68923
+ "content": {
68924
+ "application/json": {
68925
+ "schema": {
68926
+ "allOf": [
68927
+ {
68928
+ "$ref": "#/components/schemas/ErrorResponse"
68929
+ },
68930
+ {
68931
+ "type": "object",
68932
+ "properties": {
68933
+ "error_code": {
68934
+ "type": "string",
68935
+ "enum": [
68936
+ "internal_error"
68937
+ ]
68938
+ }
68939
+ }
68940
+ }
68941
+ ]
68942
+ }
68943
+ }
68944
+ }
68945
+ }
68946
+ }
68947
+ }
68948
+ },
68453
68949
  "/passwordless/verify_redirect": {
68454
68950
  "get": {
68455
68951
  "operationId": "passwordless/verify_redirect",
@@ -80491,7 +80987,7 @@
80491
80987
  "description": "Default Response"
80492
80988
  },
80493
80989
  "400": {
80494
- "description": "`invalid_code` — The verification code is wrong, expired or already used.\n\n`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`invalid_credentials` — The email/username or password is incorrect.\n\n`invalid_request` — A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.",
80990
+ "description": "`invalid_code` — The verification code is wrong, expired or already used.\n\n`invalid_connection` — The connection does not exist, is disabled, or is not of the type this flow needs.\n\n`invalid_credentials` — The email/username or password is incorrect.\n\n`invalid_request` — A required OAuth parameter is missing or two of them are incompatible. `message` says which.\n\n`oauth21_required` — The tenant enforces OAuth 2.1 (`oauth21_enabled`) and the request is one OAuth 2.1 removed: a `response_type` other than `code`, an authorization request without PKCE (`code_challenge` with `S256`), or the password grant. `message` says which. Update the application (with `@faable/auth-js`, version 3.1.0 or later) or turn `oauth21_enabled` off for the tenant.\n\n`validation_error` — The body, query or path failed schema validation. `details.issues` lists each failing field.",
80495
80991
  "content": {
80496
80992
  "application/json": {
80497
80993
  "schema": {
@@ -80509,6 +81005,7 @@
80509
81005
  "invalid_connection",
80510
81006
  "invalid_credentials",
80511
81007
  "invalid_request",
81008
+ "oauth21_required",
80512
81009
  "validation_error"
80513
81010
  ]
80514
81011
  }
@@ -81870,6 +82367,10 @@
81870
82367
  "url": "https://faable.com/docs/auth/clients"
81871
82368
  }
81872
82369
  },
82370
+ {
82371
+ "name": "connect",
82372
+ "description": "«Connect with Faable»: a one-time, PKCE-bound handoff of an OAuth client’s credentials to a third-party site (the WordPress plugin), so nobody copies a secret by hand."
82373
+ },
81873
82374
  {
81874
82375
  "name": "oauth",
81875
82376
  "description": "OAuth Endpoints",