@faable/auth-sdk 2.7.22 → 2.7.23

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.
@@ -5167,6 +5167,50 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
5167
5167
  createdAt: string;
5168
5168
  updatedAt?: string | undefined;
5169
5169
  }>;
5170
+ /**
5171
+ * `DELETE /user/{user_id}/factors/{factor_id}` — operationId: `user/deleteFactor`
5172
+ *
5173
+ * Remove one of a user's second factors
5174
+ *
5175
+ * The support path out of a lockout: with the factor gone, a `required` policy sends the user through enrolment on their next login instead of refusing it.
5176
+ *
5177
+ * @throws {FaableApiError} `factor_not_found` (400) — No factor with that id belongs to this user.
5178
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
5179
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
5180
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
5181
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
5182
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
5183
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
5184
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
5185
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
5186
+ */
5187
+ userDeleteFactor(user_id: string, factor_id: string): Promise<{
5188
+ deleted: boolean;
5189
+ }>;
5190
+ /**
5191
+ * `GET /user/{user_id}/factors` — operationId: `user/factors`
5192
+ *
5193
+ * List a user's second factors
5194
+ *
5195
+ * Management view of the security methods a user has enrolled. Carries no secret material — TOTP seeds and recovery-code hashes are redacted by the store, so this endpoint cannot be used to impersonate the user.
5196
+ *
5197
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
5198
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
5199
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
5200
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
5201
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
5202
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
5203
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
5204
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
5205
+ */
5206
+ userFactors(user_id: string): Promise<{
5207
+ id: string;
5208
+ type: string;
5209
+ name?: string | undefined;
5210
+ confirmed_at?: string | undefined;
5211
+ last_used_at?: string | undefined;
5212
+ remaining?: number | undefined;
5213
+ }[]>;
5170
5214
  /**
5171
5215
  * `GET /user/{user_id}` — operationId: `user/get`
5172
5216
  *
@@ -5496,6 +5540,25 @@ export declare abstract class GeneratedFaableAuthApi extends FaableApi {
5496
5540
  ticket_id: string;
5497
5541
  channel: "email" | "sms" | "whatsapp" | "factor";
5498
5542
  }>;
5543
+ /**
5544
+ * `POST /user/{user_id}/passkey-prompt/reset` — operationId: `user/resetPasskeyPrompt`
5545
+ *
5546
+ * Reset a user's passkey offer counter
5547
+ *
5548
+ * The post-login "create a passkey" offer stops after `login_methods.passkey_promotion_max_prompts` times and snoozes between them. This clears both, so the user is offered a passkey again on their next login — for "why does it keep asking" and "please ask me again" alike.
5549
+ *
5550
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
5551
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
5552
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
5553
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
5554
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
5555
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
5556
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
5557
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
5558
+ */
5559
+ userResetPasskeyPrompt(user_id: string): Promise<{
5560
+ reset: boolean;
5561
+ }>;
5499
5562
  /**
5500
5563
  * `POST /user/{user_id}/tickets/{ticket_id}/revoke` — operationId: `user/revokeTicket`
5501
5564
  *
@@ -1922,6 +1922,48 @@ export class GeneratedFaableAuthApi extends FaableApi {
1922
1922
  requireId("user_id", user_id);
1923
1923
  return this.fetcher.delete(`/user/${user_id}`);
1924
1924
  }
1925
+ /**
1926
+ * `DELETE /user/{user_id}/factors/{factor_id}` — operationId: `user/deleteFactor`
1927
+ *
1928
+ * Remove one of a user's second factors
1929
+ *
1930
+ * The support path out of a lockout: with the factor gone, a `required` policy sends the user through enrolment on their next login instead of refusing it.
1931
+ *
1932
+ * @throws {FaableApiError} `factor_not_found` (400) — No factor with that id belongs to this user.
1933
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
1934
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
1935
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
1936
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
1937
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
1938
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
1939
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
1940
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
1941
+ */
1942
+ userDeleteFactor(user_id, factor_id) {
1943
+ requireId("user_id", user_id);
1944
+ requireId("factor_id", factor_id);
1945
+ return this.fetcher.delete(`/user/${user_id}/factors/${factor_id}`);
1946
+ }
1947
+ /**
1948
+ * `GET /user/{user_id}/factors` — operationId: `user/factors`
1949
+ *
1950
+ * List a user's second factors
1951
+ *
1952
+ * Management view of the security methods a user has enrolled. Carries no secret material — TOTP seeds and recovery-code hashes are redacted by the store, so this endpoint cannot be used to impersonate the user.
1953
+ *
1954
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
1955
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
1956
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
1957
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
1958
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
1959
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
1960
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
1961
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
1962
+ */
1963
+ userFactors(user_id) {
1964
+ requireId("user_id", user_id);
1965
+ return this.fetcher.get(`/user/${user_id}/factors`);
1966
+ }
1925
1967
  /**
1926
1968
  * `GET /user/{user_id}` — operationId: `user/get`
1927
1969
  *
@@ -2006,6 +2048,26 @@ export class GeneratedFaableAuthApi extends FaableApi {
2006
2048
  requireId("user_id", user_id);
2007
2049
  return this.fetcher.post(`/user/${user_id}/password-setup`, data);
2008
2050
  }
2051
+ /**
2052
+ * `POST /user/{user_id}/passkey-prompt/reset` — operationId: `user/resetPasskeyPrompt`
2053
+ *
2054
+ * Reset a user's passkey offer counter
2055
+ *
2056
+ * The post-login "create a passkey" offer stops after `login_methods.passkey_promotion_max_prompts` times and snoozes between them. This clears both, so the user is offered a passkey again on their next login — for "why does it keep asking" and "please ask me again" alike.
2057
+ *
2058
+ * @throws {FaableApiError} `validation_error` (400) — The body, query or path failed schema validation. `details.issues` lists each failing field.
2059
+ * @throws {FaableApiError} `unauthorized` (401) — The request carries no valid credentials.
2060
+ * @throws {FaableApiError} `forbidden` (403) — The credentials are valid but do not allow this operation.
2061
+ * @throws {FaableApiError} `insufficient_scope` (403) — The token lacks a scope this operation requires. `message` names it.
2062
+ * @throws {FaableApiError} `user_suspended` (403) — The user is suspended and cannot sign in or be modified.
2063
+ * @throws {FaableApiError} `account_not_found` (404) — No Auth Account matches the request (domain, header or token).
2064
+ * @throws {FaableApiError} `too_many_requests` (429) — Rate limit exceeded. Honour the `Retry-After` header.
2065
+ * @throws {FaableApiError} `internal_error` (500) — Unexpected server error. Retry later; the request id is logged.
2066
+ */
2067
+ userResetPasskeyPrompt(user_id) {
2068
+ requireId("user_id", user_id);
2069
+ return this.fetcher.post(`/user/${user_id}/passkey-prompt/reset`, {});
2070
+ }
2009
2071
  /**
2010
2072
  * `POST /user/{user_id}/tickets/{ticket_id}/revoke` — operationId: `user/revokeTicket`
2011
2073
  *
@@ -153,7 +153,7 @@ export interface paths {
153
153
  };
154
154
  /**
155
155
  * Find Account by host
156
- * @description Looks up the Account that owns the given hostname, checking both the built-in `*.auth.faable.link` slug and any verified custom domains.
156
+ * @description Looks up the Account that owns the given hostname, checking both the built-in `*.auth.faable.link` slug and any verified custom domains. Returns only its public fields.
157
157
  */
158
158
  get: operations["account/getByHost"];
159
159
  put?: never;
@@ -7600,7 +7600,7 @@ export interface operations {
7600
7600
  };
7601
7601
  requestBody?: never;
7602
7602
  responses: {
7603
- /** @description AuthAccount */
7603
+ /** @description Default Response */
7604
7604
  200: {
7605
7605
  headers: {
7606
7606
  [name: string]: unknown;
@@ -7614,108 +7614,6 @@ export interface operations {
7614
7614
  slug: string;
7615
7615
  logo_src?: string | null;
7616
7616
  icon_src?: string | null;
7617
- callback_hostnames: string[];
7618
- /** @description Shared HMAC secret. Used when token_signing_alg is an HS* algorithm; ignored for RS*\/ES*\/PS*. */
7619
- token_signature: string;
7620
- /** @description JWA algorithm used to sign tokens issued by this Account. Today only RS256 is implemented. */
7621
- token_signing_alg: "RS256";
7622
- default_connection?: components["schemas"]["Connection"] | string | unknown;
7623
- enabled_locales: string[];
7624
- /** @description Free-form labels on the account. The dashboard stores the environment as an `env:<name>` tag (e.g. `env:production`, `env:staging`, `env:test`). */
7625
- tags?: string[];
7626
- team?: string | null;
7627
- notification_settings: {
7628
- /**
7629
- * @description Send the built-in welcome email on user.created
7630
- * @default false
7631
- */
7632
- welcome_email_enabled: boolean;
7633
- /**
7634
- * @description Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.
7635
- * @default false
7636
- */
7637
- verify_email_auto_send: boolean;
7638
- };
7639
- /**
7640
- * Format: email
7641
- * @description Reply-To on every email this tenant sends to its users. Absent = no Reply-To, so a reply goes to the From address — which is a `no-reply@` mailbox nobody reads. Set it to your own support address if you want your users to be able to answer.
7642
- */
7643
- email_reply_to?: string | null;
7644
- welcome_email?: {
7645
- /**
7646
- * Format: uri
7647
- * @description Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.
7648
- */
7649
- cta_url?: string | null;
7650
- /** @description Replaces the default one-line body ("Your account on … is ready to go."). Plain text, sent verbatim in every enabled locale. */
7651
- body?: string | null;
7652
- /** @description Rendered as a "Follow <account>" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block. */
7653
- social_links?: {
7654
- label: string;
7655
- /** Format: uri */
7656
- url: string;
7657
- }[] | null;
7658
- } | null;
7659
- /** @description Policy for the user email-change flow. `new_only` (default) sends a single confirmation link to the new email. `old_and_new` requires the user to also click a link sent to the previous email before the swap takes effect — stricter, useful for tenants with higher-risk users. */
7660
- email_change_verification_mode?: "new_only" | "old_and_new";
7661
- /**
7662
- * @description Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).
7663
- *
7664
- * - `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.
7665
- * - `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.).
7666
- */
7667
- email_oauth_sync_policy?: "preserve_manual" | "always_sync";
7668
- /** @description Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults. */
7669
- login_methods?: {
7670
- /** @description Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods(). */
7671
- order?: string[];
7672
- /** @description Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say "this app does not do Google"; use `enabled_clients` on the connection itself to say "that connection is not for this app". A connection has to pass both. */
7673
- disabled_connections?: string[];
7674
- /** @description Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false. */
7675
- passkey_login_enabled?: boolean;
7676
- /** @description Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false. */
7677
- identifier_first?: boolean;
7678
- /** @description Surface the method the returning user chose last time. Defaults to false. */
7679
- remember_last_method?: boolean;
7680
- /** @description Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`. */
7681
- passkey_promotion?: "off" | "offer";
7682
- /** @description Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30. */
7683
- passkey_promotion_snooze_days?: number;
7684
- /** @description How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3. */
7685
- passkey_promotion_max_prompts?: number;
7686
- /** @description Show a "Remember me on this device" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days. */
7687
- remember_me?: "off" | "optional";
7688
- /** @description How long a session lasts when the user ticked "Remember me". Defaults to 30. */
7689
- remember_me_days?: number;
7690
- };
7691
- /** @description The login flow bound to this account (`loginflow_xxx`). Absent = the flow compiled from the settings. A Client may bind its own. */
7692
- login_flow?: string | null;
7693
- /** @description Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest. */
7694
- mfa_policy?: {
7695
- /** @description `off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place. */
7696
- mode?: "off" | "optional" | "required";
7697
- /** @description Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none. */
7698
- allowed_factors?: ("totp" | "webauthn")[];
7699
- /** @description How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time. */
7700
- remember_device_days?: number;
7701
- };
7702
- /** @description Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`. */
7703
- recovery_channels?: {
7704
- /** @description Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it. */
7705
- enabled?: ("email" | "sms" | "whatsapp" | "factor")[];
7706
- /** @description The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user. */
7707
- default?: "email" | "sms" | "whatsapp" | "factor";
7708
- /** @description Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision. */
7709
- visible?: ("email" | "sms" | "whatsapp" | "factor")[];
7710
- };
7711
- /** @description ISO 3166-1 alpha-2 country assumed for phone numbers written without an international prefix (`636647460` → `+34…` with `ES`). Needed before SMS can reach anyone: most people type their number without a prefix. */
7712
- default_country_iso?: string | null;
7713
- /** @description WebAuthn Relying Party ID for this tenant's passkeys. Defaults to the account domain. Set it to a registrable suffix you own (e.g. `acme.com`) when the login screen is served from more than one host — the RP ID is frozen into every credential at registration, so changing it afterwards invalidates every passkey already enrolled. */
7714
- webauthn_rp_id?: string | null;
7715
- /** @description AuthAccount creation date */
7716
- createdAt: string;
7717
- /** @description AuthAccount updated date */
7718
- updatedAt?: string;
7719
7617
  };
7720
7618
  };
7721
7619
  };
@@ -12794,6 +12692,36 @@ export interface operations {
12794
12692
  };
12795
12693
  };
12796
12694
  };
12695
+ /** @description `unauthorized` — The request carries no valid credentials. */
12696
+ 401: {
12697
+ headers: {
12698
+ [name: string]: unknown;
12699
+ };
12700
+ content: {
12701
+ "application/json": components["schemas"]["ErrorResponse"] & {
12702
+ /** @enum {string} */
12703
+ error_code?: "unauthorized";
12704
+ };
12705
+ };
12706
+ };
12707
+ /**
12708
+ * @description `forbidden` — The credentials are valid but do not allow this operation.
12709
+ *
12710
+ * `insufficient_scope` — The token lacks a scope this operation requires. `message` names it.
12711
+ *
12712
+ * `user_suspended` — The user is suspended and cannot sign in or be modified.
12713
+ */
12714
+ 403: {
12715
+ headers: {
12716
+ [name: string]: unknown;
12717
+ };
12718
+ content: {
12719
+ "application/json": components["schemas"]["ErrorResponse"] & {
12720
+ /** @enum {string} */
12721
+ error_code?: "forbidden" | "insufficient_scope" | "user_suspended";
12722
+ };
12723
+ };
12724
+ };
12797
12725
  /** @description `account_not_found` — No Auth Account matches the request (domain, header or token). */
12798
12726
  404: {
12799
12727
  headers: {
@@ -12871,6 +12799,36 @@ export interface operations {
12871
12799
  };
12872
12800
  };
12873
12801
  };
12802
+ /** @description `unauthorized` — The request carries no valid credentials. */
12803
+ 401: {
12804
+ headers: {
12805
+ [name: string]: unknown;
12806
+ };
12807
+ content: {
12808
+ "application/json": components["schemas"]["ErrorResponse"] & {
12809
+ /** @enum {string} */
12810
+ error_code?: "unauthorized";
12811
+ };
12812
+ };
12813
+ };
12814
+ /**
12815
+ * @description `forbidden` — The credentials are valid but do not allow this operation.
12816
+ *
12817
+ * `insufficient_scope` — The token lacks a scope this operation requires. `message` names it.
12818
+ *
12819
+ * `user_suspended` — The user is suspended and cannot sign in or be modified.
12820
+ */
12821
+ 403: {
12822
+ headers: {
12823
+ [name: string]: unknown;
12824
+ };
12825
+ content: {
12826
+ "application/json": components["schemas"]["ErrorResponse"] & {
12827
+ /** @enum {string} */
12828
+ error_code?: "forbidden" | "insufficient_scope" | "user_suspended";
12829
+ };
12830
+ };
12831
+ };
12874
12832
  /** @description `account_not_found` — No Auth Account matches the request (domain, header or token). */
12875
12833
  404: {
12876
12834
  headers: {
@@ -12943,6 +12901,36 @@ export interface operations {
12943
12901
  };
12944
12902
  };
12945
12903
  };
12904
+ /** @description `unauthorized` — The request carries no valid credentials. */
12905
+ 401: {
12906
+ headers: {
12907
+ [name: string]: unknown;
12908
+ };
12909
+ content: {
12910
+ "application/json": components["schemas"]["ErrorResponse"] & {
12911
+ /** @enum {string} */
12912
+ error_code?: "unauthorized";
12913
+ };
12914
+ };
12915
+ };
12916
+ /**
12917
+ * @description `forbidden` — The credentials are valid but do not allow this operation.
12918
+ *
12919
+ * `insufficient_scope` — The token lacks a scope this operation requires. `message` names it.
12920
+ *
12921
+ * `user_suspended` — The user is suspended and cannot sign in or be modified.
12922
+ */
12923
+ 403: {
12924
+ headers: {
12925
+ [name: string]: unknown;
12926
+ };
12927
+ content: {
12928
+ "application/json": components["schemas"]["ErrorResponse"] & {
12929
+ /** @enum {string} */
12930
+ error_code?: "forbidden" | "insufficient_scope" | "user_suspended";
12931
+ };
12932
+ };
12933
+ };
12946
12934
  /** @description `account_not_found` — No Auth Account matches the request (domain, header or token). */
12947
12935
  404: {
12948
12936
  headers: {
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.7.22";
12
+ export const version = "2.7.23";
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 = "49d06d3";
18
+ export const commit = "e683d71";
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.7.22",
3
+ "version": "2.7.23",
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.31.0",
6
+ "version": "2.31.1",
7
7
  "license": {
8
8
  "name": "private",
9
9
  "url": "https://faable.com/docs/platform/privacy-policy"
@@ -10762,7 +10762,7 @@
10762
10762
  "tags": [
10763
10763
  "account"
10764
10764
  ],
10765
- "description": "Looks up the Account that owns the given hostname, checking both the built-in `*.auth.faable.link` slug and any verified custom domains.",
10765
+ "description": "Looks up the Account that owns the given hostname, checking both the built-in `*.auth.faable.link` slug and any verified custom domains. Returns only its public fields.",
10766
10766
  "parameters": [
10767
10767
  {
10768
10768
  "schema": {
@@ -10775,477 +10775,41 @@
10775
10775
  ],
10776
10776
  "responses": {
10777
10777
  "200": {
10778
- "description": "AuthAccount",
10779
- "content": {
10780
- "application/json": {
10781
- "schema": {
10782
- "type": "object",
10783
- "required": [
10784
- "id",
10785
- "name",
10786
- "domain",
10787
- "slug",
10788
- "callback_hostnames",
10789
- "token_signature",
10790
- "token_signing_alg",
10791
- "enabled_locales",
10792
- "notification_settings",
10793
- "createdAt"
10794
- ],
10795
- "properties": {
10796
- "id": {
10797
- "type": "string",
10798
- "description": "AuthAccount ID"
10799
- },
10800
- "name": {
10801
- "type": "string"
10802
- },
10803
- "domain": {
10804
- "type": "string"
10805
- },
10806
- "slug": {
10807
- "type": "string"
10808
- },
10809
- "logo_src": {
10810
- "type": "string",
10811
- "nullable": true
10812
- },
10813
- "icon_src": {
10814
- "type": "string",
10815
- "nullable": true
10816
- },
10817
- "callback_hostnames": {
10818
- "type": "array",
10819
- "items": {
10820
- "type": "string"
10821
- }
10822
- },
10823
- "token_signature": {
10824
- "type": "string",
10825
- "description": "Shared HMAC secret. Used when token_signing_alg is an HS* algorithm; ignored for RS*/ES*/PS*."
10826
- },
10827
- "token_signing_alg": {
10828
- "anyOf": [
10829
- {
10830
- "type": "string",
10831
- "enum": [
10832
- "RS256"
10833
- ]
10834
- }
10835
- ],
10836
- "description": "JWA algorithm used to sign tokens issued by this Account. Today only RS256 is implemented."
10837
- },
10838
- "default_connection": {
10839
- "anyOf": [
10840
- {
10841
- "$ref": "#/components/schemas/Connection"
10842
- },
10843
- {
10844
- "type": "string"
10845
- },
10846
- {}
10847
- ]
10848
- },
10849
- "enabled_locales": {
10850
- "type": "array",
10851
- "items": {
10852
- "type": "string"
10853
- }
10854
- },
10855
- "tags": {
10856
- "type": "array",
10857
- "items": {
10858
- "type": "string"
10859
- },
10860
- "description": "Free-form labels on the account. The dashboard stores the environment as an `env:<name>` tag (e.g. `env:production`, `env:staging`, `env:test`)."
10861
- },
10862
- "team": {
10863
- "type": "string",
10864
- "nullable": true
10865
- },
10866
- "notification_settings": {
10867
- "type": "object",
10868
- "required": [
10869
- "welcome_email_enabled",
10870
- "verify_email_auto_send"
10871
- ],
10872
- "properties": {
10873
- "welcome_email_enabled": {
10874
- "type": "boolean",
10875
- "description": "Send the built-in welcome email on user.created",
10876
- "default": false
10877
- },
10878
- "verify_email_auto_send": {
10879
- "type": "boolean",
10880
- "description": "Send a verification email automatically when a user is created with `email_verified=false` and an email address. The link in the email lands on `GET /verify-email?ticket=...` and flips `email_verified=true` with `email_verified_method=verification_flow`. When `false`, verification emails must be requested explicitly via `POST /user/:id/verify-email/start`.",
10881
- "default": false
10882
- }
10883
- },
10884
- "additionalProperties": false
10885
- },
10886
- "email_reply_to": {
10887
- "type": "string",
10888
- "format": "email",
10889
- "maxLength": 320,
10890
- "description": "Reply-To on every email this tenant sends to its users. Absent = no Reply-To, so a reply goes to the From address — which is a `no-reply@` mailbox nobody reads. Set it to your own support address if you want your users to be able to answer.",
10891
- "nullable": true
10892
- },
10893
- "welcome_email": {
10894
- "type": "object",
10895
- "properties": {
10896
- "cta_url": {
10897
- "type": "string",
10898
- "format": "uri",
10899
- "maxLength": 500,
10900
- "description": "Where the welcome button sends the user. Defaults to `https://<first callback hostname>`. Point it at your app (not your marketing site) so a fresh user lands somewhere useful.",
10901
- "nullable": true
10902
- },
10903
- "body": {
10904
- "type": "string",
10905
- "maxLength": 600,
10906
- "description": "Replaces the default one-line body (\"Your account on … is ready to go.\"). Plain text, sent verbatim in every enabled locale.",
10907
- "nullable": true
10908
- },
10909
- "social_links": {
10910
- "type": "array",
10911
- "items": {
10912
- "type": "object",
10913
- "required": [
10914
- "label",
10915
- "url"
10916
- ],
10917
- "properties": {
10918
- "label": {
10919
- "type": "string",
10920
- "minLength": 1,
10921
- "maxLength": 40
10922
- },
10923
- "url": {
10924
- "type": "string",
10925
- "format": "uri",
10926
- "maxLength": 500
10927
- }
10928
- },
10929
- "additionalProperties": false
10930
- },
10931
- "maxItems": 6,
10932
- "description": "Rendered as a \"Follow <account>\" block after the button — GitHub, LinkedIn, X, YouTube… Empty or absent hides the block.",
10933
- "nullable": true
10934
- }
10935
- },
10936
- "additionalProperties": false,
10937
- "nullable": true
10938
- },
10939
- "email_change_verification_mode": {
10940
- "anyOf": [
10941
- {
10942
- "type": "string",
10943
- "enum": [
10944
- "new_only"
10945
- ]
10946
- },
10947
- {
10948
- "type": "string",
10949
- "enum": [
10950
- "old_and_new"
10951
- ]
10952
- }
10953
- ],
10954
- "description": "Policy for the user email-change flow. `new_only` (default) sends a single confirmation link to the new email. `old_and_new` requires the user to also click a link sent to the previous email before the swap takes effect — stricter, useful for tenants with higher-risk users."
10955
- },
10956
- "email_oauth_sync_policy": {
10957
- "anyOf": [
10958
- {
10959
- "type": "string",
10960
- "enum": [
10961
- "preserve_manual"
10962
- ]
10963
- },
10964
- {
10965
- "type": "string",
10966
- "enum": [
10967
- "always_sync"
10968
- ]
10969
- }
10970
- ],
10971
- "description": "Controls what happens to `user.email` on subsequent OAuth/federated logins when the user previously changed their email manually through this auth server (i.e. `user.email_change_locked_at` is set).\n\n- `preserve_manual` (default): the manually-set email wins. The federated provider's email is ignored on re-sync; `email` and `email_verified` are not touched. The identity link stays valid via `provider_user_id`, so the user can still log in with Google/etc.\n- `always_sync`: the federated provider's email is always written back, overwriting any manual change. Useful for tenants whose source of truth for identity is the IdP (corporate SSO, etc.)."
10972
- },
10973
- "login_methods": {
10974
- "type": "object",
10975
- "properties": {
10976
- "order": {
10977
- "type": "array",
10978
- "items": {
10979
- "type": "string"
10980
- },
10981
- "description": "Order in which the hosted login screen renders the available methods. Entries are Connection resource ids (`connection_xxx`) or the literal `passkey`. Methods not listed keep the built-in order (passwordless, database, social) after the listed ones; ids that no longer resolve are ignored. Listing an `oidc` connection here is also the only way to surface it on the login screen — see resolveLoginMethods()."
10982
- },
10983
- "disabled_connections": {
10984
- "type": "array",
10985
- "items": {
10986
- "type": "string"
10987
- },
10988
- "description": "Connection resource ids (`connection_xxx`) this client does NOT offer. Removing a method here is enforced, not cosmetic: the connection disappears from the login screen AND `/authorize?connection=<id>` is refused for this client, the same as a connection whose `enabled_clients` excludes it. Use it to say \"this app does not do Google\"; use `enabled_clients` on the connection itself to say \"that connection is not for this app\". A connection has to pass both."
10989
- },
10990
- "passkey_login_enabled": {
10991
- "type": "boolean",
10992
- "description": "Offer passkey (WebAuthn) as a primary login method on the hosted login screen. Defaults to false."
10993
- },
10994
- "identifier_first": {
10995
- "type": "boolean",
10996
- "description": "Ask for the email first and only then reveal the methods that apply to it, instead of showing every method up front. Defaults to false."
10997
- },
10998
- "remember_last_method": {
10999
- "type": "boolean",
11000
- "description": "Surface the method the returning user chose last time. Defaults to false."
11001
- },
11002
- "passkey_promotion": {
11003
- "anyOf": [
11004
- {
11005
- "type": "string",
11006
- "enum": [
11007
- "off"
11008
- ]
11009
- },
11010
- {
11011
- "type": "string",
11012
- "enum": [
11013
- "offer"
11014
- ]
11015
- }
11016
- ],
11017
- "description": "Invite users to create a passkey right after a successful login with another method. `offer` shows a hosted screen once per `passkey_promotion_snooze_days`, at most `passkey_promotion_max_prompts` times, to users who have no second factor yet; it never blocks the login. Requires `passkey_login_enabled`. Defaults to `off`."
11018
- },
11019
- "passkey_promotion_snooze_days": {
11020
- "type": "integer",
11021
- "minimum": 0,
11022
- "maximum": 365,
11023
- "description": "Days to wait before offering a passkey again to a user who dismissed it. Defaults to 30."
11024
- },
11025
- "passkey_promotion_max_prompts": {
11026
- "type": "integer",
11027
- "minimum": 0,
11028
- "maximum": 10,
11029
- "description": "How many times a user is offered a passkey over their lifetime. `0` switches the offer off for this tenant without changing `passkey_promotion`. Defaults to 3."
11030
- },
11031
- "remember_me": {
11032
- "anyOf": [
11033
- {
11034
- "type": "string",
11035
- "enum": [
11036
- "off"
11037
- ]
11038
- },
11039
- {
11040
- "type": "string",
11041
- "enum": [
11042
- "optional"
11043
- ]
11044
- }
11045
- ],
11046
- "description": "Show a \"Remember me on this device\" checkbox on the password and email-code forms. `optional`: unchecked, the session ends when the browser closes; checked, it lasts `remember_me_days`. `off` (the default): no checkbox, every session lasts the built-in 30 days."
11047
- },
11048
- "remember_me_days": {
11049
- "type": "integer",
11050
- "minimum": 1,
11051
- "maximum": 365,
11052
- "description": "How long a session lasts when the user ticked \"Remember me\". Defaults to 30."
11053
- }
11054
- },
11055
- "additionalProperties": false,
11056
- "description": "Which login methods the hosted login screen offers and in what order. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so a client can flip one flag without restating the rest. Absent everywhere = built-in defaults."
11057
- },
11058
- "login_flow": {
11059
- "type": "string",
11060
- "description": "The login flow bound to this account (`loginflow_xxx`). Absent = the flow compiled from the settings. A Client may bind its own.",
11061
- "nullable": true
11062
- },
11063
- "mfa_policy": {
11064
- "type": "object",
11065
- "properties": {
11066
- "mode": {
11067
- "anyOf": [
11068
- {
11069
- "anyOf": [
11070
- {
11071
- "type": "string",
11072
- "enum": [
11073
- "off"
11074
- ]
11075
- },
11076
- {
11077
- "type": "string",
11078
- "enum": [
11079
- "optional"
11080
- ]
11081
- },
11082
- {
11083
- "type": "string",
11084
- "enum": [
11085
- "required"
11086
- ]
11087
- }
11088
- ]
11089
- }
11090
- ],
11091
- "description": "`off` (default) never challenges. `optional` challenges a user who has enrolled a factor, and lets everyone else in. `required` challenges everyone and sends users with no factor through enrolment during login — it never locks anyone out in place."
11092
- },
11093
- "allowed_factors": {
11094
- "type": "array",
11095
- "items": {
11096
- "anyOf": [
11097
- {
11098
- "type": "string",
11099
- "enum": [
11100
- "totp"
11101
- ]
11102
- },
11103
- {
11104
- "type": "string",
11105
- "enum": [
11106
- "webauthn"
11107
- ]
11108
- }
11109
- ]
11110
- },
11111
- "description": "Which kinds of second factor satisfy the policy. Empty or unset means all of them. A user whose only factor is of a kind not listed here is treated as having none."
11112
- },
11113
- "remember_device_days": {
11114
- "type": "integer",
11115
- "minimum": 0,
11116
- "maximum": 365,
11117
- "description": "How long a browser that already passed a challenge may skip the next one. 0 (default) challenges every time."
11118
- }
11119
- },
11120
- "additionalProperties": false,
11121
- "description": "Second-factor policy. Set on the Account as the tenant default; the same object on a Client overrides it **field by field**, so one app can require MFA without changing the rest."
11122
- },
11123
- "recovery_channels": {
11124
- "type": "object",
11125
- "properties": {
11126
- "enabled": {
11127
- "type": "array",
11128
- "items": {
11129
- "anyOf": [
11130
- {
11131
- "type": "string",
11132
- "enum": [
11133
- "email"
11134
- ]
11135
- },
11136
- {
11137
- "type": "string",
11138
- "enum": [
11139
- "sms"
11140
- ]
11141
- },
11142
- {
11143
- "type": "string",
11144
- "enum": [
11145
- "whatsapp"
11146
- ]
11147
- },
11148
- {
11149
- "type": "string",
11150
- "enum": [
11151
- "factor"
11152
- ]
11153
- }
11154
- ]
11155
- },
11156
- "description": "Channels the tenant has turned on. `email` is always on regardless of this list: it is what every other channel falls back to. A channel listed here is still offered only if the platform can send it, the plan allows it and the user has a verified destination for it."
11157
- },
11158
- "default": {
11159
- "anyOf": [
11160
- {
11161
- "anyOf": [
11162
- {
11163
- "type": "string",
11164
- "enum": [
11165
- "email"
11166
- ]
11167
- },
11168
- {
11169
- "type": "string",
11170
- "enum": [
11171
- "sms"
11172
- ]
11173
- },
11174
- {
11175
- "type": "string",
11176
- "enum": [
11177
- "whatsapp"
11178
- ]
11179
- },
11180
- {
11181
- "type": "string",
11182
- "enum": [
11183
- "factor"
11184
- ]
11185
- }
11186
- ]
11187
- }
11188
- ],
11189
- "description": "The channel used when the user is not asked to choose. Falls back to `email` when it is not available for that user."
11190
- },
11191
- "visible": {
11192
- "type": "array",
11193
- "items": {
11194
- "anyOf": [
11195
- {
11196
- "type": "string",
11197
- "enum": [
11198
- "email"
11199
- ]
11200
- },
11201
- {
11202
- "type": "string",
11203
- "enum": [
11204
- "sms"
11205
- ]
11206
- },
11207
- {
11208
- "type": "string",
11209
- "enum": [
11210
- "whatsapp"
11211
- ]
11212
- },
11213
- {
11214
- "type": "string",
11215
- "enum": [
11216
- "factor"
11217
- ]
11218
- }
11219
- ]
11220
- },
11221
- "description": "Channels the recovery screen offers the user to pick from, after they enter their identifier. Empty (the default) keeps today's behaviour: the default channel is used silently and the response never reveals whether the account exists. A non-empty list shows a picker with masked destinations — which does reveal that the account exists and which channels it has, the same trade-off Google and Microsoft make. A tenant decision."
11222
- }
11223
- },
11224
- "additionalProperties": false,
11225
- "description": "Password-recovery delivery channels. Set on the Account as the tenant default; the same object on a Client overrides it field by field, like `login_methods` and `mfa_policy`."
11226
- },
11227
- "default_country_iso": {
10778
+ "description": "Default Response",
10779
+ "content": {
10780
+ "application/json": {
10781
+ "schema": {
10782
+ "type": "object",
10783
+ "required": [
10784
+ "id",
10785
+ "name",
10786
+ "domain",
10787
+ "slug"
10788
+ ],
10789
+ "properties": {
10790
+ "id": {
11228
10791
  "type": "string",
11229
- "minLength": 2,
11230
- "maxLength": 2,
11231
- "description": "ISO 3166-1 alpha-2 country assumed for phone numbers written without an international prefix (`636647460` → `+34…` with `ES`). Needed before SMS can reach anyone: most people type their number without a prefix.",
11232
- "nullable": true
10792
+ "description": "AuthAccount ID"
11233
10793
  },
11234
- "webauthn_rp_id": {
11235
- "type": "string",
11236
- "description": "WebAuthn Relying Party ID for this tenant's passkeys. Defaults to the account domain. Set it to a registrable suffix you own (e.g. `acme.com`) when the login screen is served from more than one host — the RP ID is frozen into every credential at registration, so changing it afterwards invalidates every passkey already enrolled.",
11237
- "nullable": true
10794
+ "name": {
10795
+ "type": "string"
11238
10796
  },
11239
- "createdAt": {
10797
+ "domain": {
10798
+ "type": "string"
10799
+ },
10800
+ "slug": {
10801
+ "type": "string"
10802
+ },
10803
+ "logo_src": {
11240
10804
  "type": "string",
11241
- "description": "AuthAccount creation date"
10805
+ "nullable": true
11242
10806
  },
11243
- "updatedAt": {
10807
+ "icon_src": {
11244
10808
  "type": "string",
11245
- "description": "AuthAccount updated date"
10809
+ "nullable": true
11246
10810
  }
11247
10811
  },
11248
- "description": "AuthAccount"
10812
+ "additionalProperties": false
11249
10813
  }
11250
10814
  }
11251
10815
  }
@@ -23754,6 +23318,11 @@
23754
23318
  "required": true
23755
23319
  }
23756
23320
  ],
23321
+ "security": [
23322
+ {
23323
+ "bearerAuth": []
23324
+ }
23325
+ ],
23757
23326
  "responses": {
23758
23327
  "200": {
23759
23328
  "description": "Default Response",
@@ -23793,6 +23362,58 @@
23793
23362
  }
23794
23363
  }
23795
23364
  },
23365
+ "401": {
23366
+ "description": "`unauthorized` — The request carries no valid credentials.",
23367
+ "content": {
23368
+ "application/json": {
23369
+ "schema": {
23370
+ "allOf": [
23371
+ {
23372
+ "$ref": "#/components/schemas/ErrorResponse"
23373
+ },
23374
+ {
23375
+ "type": "object",
23376
+ "properties": {
23377
+ "error_code": {
23378
+ "type": "string",
23379
+ "enum": [
23380
+ "unauthorized"
23381
+ ]
23382
+ }
23383
+ }
23384
+ }
23385
+ ]
23386
+ }
23387
+ }
23388
+ }
23389
+ },
23390
+ "403": {
23391
+ "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.",
23392
+ "content": {
23393
+ "application/json": {
23394
+ "schema": {
23395
+ "allOf": [
23396
+ {
23397
+ "$ref": "#/components/schemas/ErrorResponse"
23398
+ },
23399
+ {
23400
+ "type": "object",
23401
+ "properties": {
23402
+ "error_code": {
23403
+ "type": "string",
23404
+ "enum": [
23405
+ "forbidden",
23406
+ "insufficient_scope",
23407
+ "user_suspended"
23408
+ ]
23409
+ }
23410
+ }
23411
+ }
23412
+ ]
23413
+ }
23414
+ }
23415
+ }
23416
+ },
23796
23417
  "404": {
23797
23418
  "description": "`account_not_found` — No Auth Account matches the request (domain, header or token).",
23798
23419
  "content": {
@@ -23897,6 +23518,11 @@
23897
23518
  "required": true
23898
23519
  }
23899
23520
  ],
23521
+ "security": [
23522
+ {
23523
+ "bearerAuth": []
23524
+ }
23525
+ ],
23900
23526
  "responses": {
23901
23527
  "200": {
23902
23528
  "description": "Default Response",
@@ -23942,6 +23568,58 @@
23942
23568
  }
23943
23569
  }
23944
23570
  },
23571
+ "401": {
23572
+ "description": "`unauthorized` — The request carries no valid credentials.",
23573
+ "content": {
23574
+ "application/json": {
23575
+ "schema": {
23576
+ "allOf": [
23577
+ {
23578
+ "$ref": "#/components/schemas/ErrorResponse"
23579
+ },
23580
+ {
23581
+ "type": "object",
23582
+ "properties": {
23583
+ "error_code": {
23584
+ "type": "string",
23585
+ "enum": [
23586
+ "unauthorized"
23587
+ ]
23588
+ }
23589
+ }
23590
+ }
23591
+ ]
23592
+ }
23593
+ }
23594
+ }
23595
+ },
23596
+ "403": {
23597
+ "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.",
23598
+ "content": {
23599
+ "application/json": {
23600
+ "schema": {
23601
+ "allOf": [
23602
+ {
23603
+ "$ref": "#/components/schemas/ErrorResponse"
23604
+ },
23605
+ {
23606
+ "type": "object",
23607
+ "properties": {
23608
+ "error_code": {
23609
+ "type": "string",
23610
+ "enum": [
23611
+ "forbidden",
23612
+ "insufficient_scope",
23613
+ "user_suspended"
23614
+ ]
23615
+ }
23616
+ }
23617
+ }
23618
+ ]
23619
+ }
23620
+ }
23621
+ }
23622
+ },
23945
23623
  "404": {
23946
23624
  "description": "`account_not_found` — No Auth Account matches the request (domain, header or token).",
23947
23625
  "content": {
@@ -24038,6 +23716,11 @@
24038
23716
  "required": true
24039
23717
  }
24040
23718
  ],
23719
+ "security": [
23720
+ {
23721
+ "bearerAuth": []
23722
+ }
23723
+ ],
24041
23724
  "responses": {
24042
23725
  "200": {
24043
23726
  "description": "Default Response",
@@ -24082,6 +23765,58 @@
24082
23765
  }
24083
23766
  }
24084
23767
  },
23768
+ "401": {
23769
+ "description": "`unauthorized` — The request carries no valid credentials.",
23770
+ "content": {
23771
+ "application/json": {
23772
+ "schema": {
23773
+ "allOf": [
23774
+ {
23775
+ "$ref": "#/components/schemas/ErrorResponse"
23776
+ },
23777
+ {
23778
+ "type": "object",
23779
+ "properties": {
23780
+ "error_code": {
23781
+ "type": "string",
23782
+ "enum": [
23783
+ "unauthorized"
23784
+ ]
23785
+ }
23786
+ }
23787
+ }
23788
+ ]
23789
+ }
23790
+ }
23791
+ }
23792
+ },
23793
+ "403": {
23794
+ "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.",
23795
+ "content": {
23796
+ "application/json": {
23797
+ "schema": {
23798
+ "allOf": [
23799
+ {
23800
+ "$ref": "#/components/schemas/ErrorResponse"
23801
+ },
23802
+ {
23803
+ "type": "object",
23804
+ "properties": {
23805
+ "error_code": {
23806
+ "type": "string",
23807
+ "enum": [
23808
+ "forbidden",
23809
+ "insufficient_scope",
23810
+ "user_suspended"
23811
+ ]
23812
+ }
23813
+ }
23814
+ }
23815
+ ]
23816
+ }
23817
+ }
23818
+ }
23819
+ },
24085
23820
  "404": {
24086
23821
  "description": "`account_not_found` — No Auth Account matches the request (domain, header or token).",
24087
23822
  "content": {