{
  "info": {
    "_postman_id": "e8c48c98-6b5d-2b05-674e-02815fa2ae15",
    "name": "Apiq — Unified Travel API (1.64.0) — sandbox",
    "description": "Generated from spec/connect-v1.yaml by tools/postman.mjs — do not edit; regenerate.\n\nContract 1.64.0 · API version 2026-09-30 · spec sha256 fb7233c1b4eaa3ce9936e4ec2c4dfd4524957812cb19ce96391c8ac4286da8c6.\n\nBase URL: the SANDBOX server only. Set {{apiKey}} to a sandbox key (mo_test_…): every operation that accepts a key sends it as X-Api-Key. The few that need a signed-in person send Bearer {{accessToken}} (a session from POST /v1/auth/login); the token endpoint uses Basic {{clientId}}:{{apiKey}}.\n\nPrices are Money: integer minor units + an ISO-4217 currency. Branch on ApiErrorResponse.code + retryable, never on HTTP status alone.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-Api-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://apiq-sandbox.tripproagencysites.com",
      "type": "string"
    },
    {
      "key": "accessToken",
      "value": "",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "clientId",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "identity",
      "description": "Sign in, refresh and end sessions, read the signed-in caller, and create, rotate and revoke API keys; these routes take and answer plain JSON, not the travel envelope.",
      "item": [
        {
          "name": "Console sign-in — {username, password, siteId} → access + refresh token (FEAT-020 §5.4, 15 §1.1) — demoAdmin",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "login"
              ]
            },
            "description": "`POST /v1/auth/login` · operationId `login`\n\nExample: The FEAT-020 demo tenant's admin\n\nFirst-party UIs only. Answers the same 401 for an unknown user, a wrong password, a wrong site and a locked account — details.reason LOGIN_FAILED — so the route cannot be used to enumerate accounts. The access token is an ES256 JWT (15 min); the refresh token is opaque and rotates on every refresh. Not envelope-shaped (see the identity tag).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"demo-admin\",\n  \"password\": \"correct-horse-battery-staple\",\n  \"siteId\": \"SITE-DEMO\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "One sign-in for every realm — {username, password, siteId?} → the account's realm and its session (v1.14.0, OD-882) — anyone",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/sign-in",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "sign-in"
              ]
            },
            "description": "`POST /v1/auth/sign-in` · operationId `signIn`\n\nExample: Email and password only — the platform finds the realm\n\nAdded in v1.14.0 (OD-882). One login page for Apiq staff (OPERATOR), customer users (TENANT) and supplier users (SUPPLIER): the platform finds the account by its email in the platform-wide login directory (one email is one account on the whole platform) and runs that realm's own login, with the realm's own lockout, MFA and session rules. The realm routes (/v1/auth/login, /ops/v1/auth/login, /supplier/v1/auth/login) are unchanged. 200 is a session of the named realm: exactly one of tenantSession, operatorSession or supplierSession is present. 202 is a next step after the password was accepted: MFA (the challenge is completed at that realm's own mfa/verify route) or SITE_CHOICE, when a customer user belongs to more than one site and sent no siteId (repeat the call with one of the listed siteIds). An unknown email, a wrong password, a locked or disabled account and a siteId the user does not belong to are one and the same 401 details.reason LOGIN_FAILED, so the route cannot be used to learn which realm, if any, an email belongs to. Plain JSON, not an envelope. Rate-limited per client and per subject as the identity routes are.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"owner@aikonic.example\",\n  \"password\": \"correct-horse-battery-staple\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "One sign-in for every realm — {username, password, siteId?} → the account's realm and its session (v1.14.0, OD-882) — withSite",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/sign-in",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "sign-in"
              ]
            },
            "description": "`POST /v1/auth/sign-in` · operationId `signIn`\n\nExample: A customer user with several sites, after SITE_CHOICE\n\nAdded in v1.14.0 (OD-882). One login page for Apiq staff (OPERATOR), customer users (TENANT) and supplier users (SUPPLIER): the platform finds the account by its email in the platform-wide login directory (one email is one account on the whole platform) and runs that realm's own login, with the realm's own lockout, MFA and session rules. The realm routes (/v1/auth/login, /ops/v1/auth/login, /supplier/v1/auth/login) are unchanged. 200 is a session of the named realm: exactly one of tenantSession, operatorSession or supplierSession is present. 202 is a next step after the password was accepted: MFA (the challenge is completed at that realm's own mfa/verify route) or SITE_CHOICE, when a customer user belongs to more than one site and sent no siteId (repeat the call with one of the listed siteIds). An unknown email, a wrong password, a locked or disabled account and a siteId the user does not belong to are one and the same 401 details.reason LOGIN_FAILED, so the route cannot be used to learn which realm, if any, an email belongs to. Plain JSON, not an envelope. Rate-limited per client and per subject as the identity routes are.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"owner@aikonic.example\",\n  \"password\": \"correct-horse-battery-staple\",\n  \"siteId\": \"SITE-DEMO\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Rotate the refresh token and issue a new access token (15 §1.1) — refresh",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/refresh",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "refresh"
              ]
            },
            "description": "`POST /v1/auth/refresh` · operationId `refreshSession`\n\nExample: The refresh token from the last login or refresh\n\nA refresh token is single-use. Presenting one twice is treated as theft: every session in its family ends (401, details.reason REFRESH_REUSED) and the user must sign in again. A client with several requests in flight must therefore refresh once and share the result.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refreshToken\": \"rt_2kq9vX0cS1mZ7pL4dR8wYb\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "End this session — revokes the refresh family of the presented access token",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/logout",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "logout"
              ]
            },
            "description": "`POST /v1/auth/logout` · operationId `logout`",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "End every session of the calling user (FEAT-020 §5.5)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/logout-all",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "logout-all"
              ]
            },
            "description": "`POST /v1/auth/logout-all` · operationId `logoutAll`",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Exchange an API key for a short-lived Bearer access token (OAuth2 client-credentials grant, RFC 6749 §4.4) — basicAuth",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "token"
              ]
            },
            "description": "`POST /v1/oauth/token` · operationId `issueOAuthToken`\n\nExample: Client authenticated by HTTP Basic — the body carries the grant only\n\nExchanges an API key for a short-lived Bearer access token (an ES256 JWT, at most 15 min, no refresh token — RFC 6749 §4.4.3). The client IS the key: client_id is the key's keyPrefix and client_secret is the WHOLE API key exactly as shown at mint (not only the part after the prefix). Authenticate by HTTP Basic (each part form-urlencoded, joined by a colon, base64 — RFC 6749 §2.3.1) OR by client_id + client_secret in the body — never both (invalid_request). X-Api-Key and any other Authorization scheme are refused invalid_client. The body must be application/x-www-form-urlencoded, at most 16 KiB; a query string of any kind and a parameter sent twice are refused invalid_request (a secret in a URL lands in access logs). Checks run in the order request shape, client authentication, grant_type, scope, so an unauthenticated caller learns nothing about grant or scope handling. scope may only NARROW the key's scopes; absent, the token gets all of them. The token is accepted exactly as its key would be, and the key is re-checked on EVERY call made with the token: revoking the key refuses its tokens at once, and a token never holds more than the key holds now. A key in its rotation overlap gets a token that expires no later than the overlap, with the key's Deprecation / Sunset headers. 4xx answers other than 429 are RFC 6749 §5.2's OAuthError, not ApiErrorResponse; error_description is fixed text and never echoes input. The abuse limit's 429 RATE_LIMITED (RFC 6749 names no error for it) and a platform fault's 500 are ApiErrorResponse, whose traceId is the support handle. The limit is counted per client address (every attempt), per address and per (address, client_id) (failed client authentications) and per client_id (tokens issued, spent only by a request that passed every check) — never per client_id before the secret is checked; see IdentityRateLimited. connect-v1 1.26.0 (OD-884 Phase 2, DR-67; ADR-C10 §Phase 2 design D1 to D3): four more grant types, served only where aikonic.mcp.grants.enabled is true (DEV and SANDBOX until OD-28 signs; elsewhere unsupported_grant_type). authorization_code: an MCP client (public, no secret) redeems the code from the consent redirect with its PKCE code_verifier, the same redirect_uri, client_id and resource; the code is single-use and lives 60 s, and a second use revokes the grant. refresh_token: rotates; a spent refresh token revokes the whole grant (reason REFRESH_REUSE); every refresh re-checks that the bound human session is live and re-caps the grant's expiry at the earlier of 8 hours from approval and the session's expiry. urn:ietf:params:oauth:grant-type:device_code: polled with the device_code no faster than every 5 s; authorization_pending until the human decides, slow_down when polled too fast, access_denied when denied, expired_token after 10 minutes. These three answer an access token of at most 10 minutes, never past the grant's expiry, whose audience is the MCP resource; the platform API refuses it (401 TOKEN_WRONG_AUDIENCE). urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693): the AI plane only, authenticated by HTTP Basic as a configured exchange client, exchanges a client's access token (subject_token) for the downstream platform token it calls the platform with, and so learns that the grant is live. It checks the signature, that the token's audience equals resource, that this exchange client may exchange for resource, that the grant is ACTIVE and unexpired, that the bound session is live, the human or supplier principal active, the tenant admitted (not SUSPENDED or OFFBOARDED), the tenant's toolset switch ON and grants enabled; for an operator grant also that operator grants are enabled and aikonic.ops.mfa.required is true (else invalid_target reason OPERATOR_GRANTS_DISABLED or OPERATOR_MFA_NOT_ENFORCED). Refusals are 400 invalid_grant, invalid_target, invalid_client or unauthorized_client with a reason (OAuthError). aikonic_grant.writeOperations is computed at each exchange: the console writes ticked at consent that the tenant still has ON in writesEnabled, empty when the platform write ceiling is off (ADR-C10 Phase 3, DR-68); aikonic_grant.crossTenantReads names the operator cross-tenant reads ticked at consent. Exchanges are limited per grant (aikonic.mcp.grants.exchange.per-grant-per-minute, default 120): past it the answer is 429 RATE_LIMITED (ApiErrorResponse, IdentityRateLimited) details.reason EXCHANGE_RATE_LIMITED, details.action oauth-exchange-grant, with Retry-After; another grant is unaffected. The downstream token lives at most 5 minutes and never past the subject token or the grant's expiry; it is never a session (no session family) and reaches only the grant's toolset's read operations its grant lists, plus, for a console grant, the commit-lite operations in writeOperations that the tenant has ON at the time of the call (else 403 SESSION_REQUIRED). An operator downstream token reaches /ops/v1 reads only, never a step-up route (it carries no MFA time). The client's token is never accepted by the platform API, and the plane never forwards it.",
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "client_credentials",
                  "type": "text"
                },
                {
                  "key": "scope",
                  "value": "search:hotels orders:read",
                  "type": "text"
                }
              ]
            },
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Exchange an API key for a short-lived Bearer access token (OAuth2 client-credentials grant, RFC 6749 §4.4) — bodyAuth",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "token"
              ]
            },
            "description": "`POST /v1/oauth/token` · operationId `issueOAuthToken`\n\nExample: Client authenticated in the body — client_secret is the whole API key\n\nExchanges an API key for a short-lived Bearer access token (an ES256 JWT, at most 15 min, no refresh token — RFC 6749 §4.4.3). The client IS the key: client_id is the key's keyPrefix and client_secret is the WHOLE API key exactly as shown at mint (not only the part after the prefix). Authenticate by HTTP Basic (each part form-urlencoded, joined by a colon, base64 — RFC 6749 §2.3.1) OR by client_id + client_secret in the body — never both (invalid_request). X-Api-Key and any other Authorization scheme are refused invalid_client. The body must be application/x-www-form-urlencoded, at most 16 KiB; a query string of any kind and a parameter sent twice are refused invalid_request (a secret in a URL lands in access logs). Checks run in the order request shape, client authentication, grant_type, scope, so an unauthenticated caller learns nothing about grant or scope handling. scope may only NARROW the key's scopes; absent, the token gets all of them. The token is accepted exactly as its key would be, and the key is re-checked on EVERY call made with the token: revoking the key refuses its tokens at once, and a token never holds more than the key holds now. A key in its rotation overlap gets a token that expires no later than the overlap, with the key's Deprecation / Sunset headers. 4xx answers other than 429 are RFC 6749 §5.2's OAuthError, not ApiErrorResponse; error_description is fixed text and never echoes input. The abuse limit's 429 RATE_LIMITED (RFC 6749 names no error for it) and a platform fault's 500 are ApiErrorResponse, whose traceId is the support handle. The limit is counted per client address (every attempt), per address and per (address, client_id) (failed client authentications) and per client_id (tokens issued, spent only by a request that passed every check) — never per client_id before the secret is checked; see IdentityRateLimited. connect-v1 1.26.0 (OD-884 Phase 2, DR-67; ADR-C10 §Phase 2 design D1 to D3): four more grant types, served only where aikonic.mcp.grants.enabled is true (DEV and SANDBOX until OD-28 signs; elsewhere unsupported_grant_type). authorization_code: an MCP client (public, no secret) redeems the code from the consent redirect with its PKCE code_verifier, the same redirect_uri, client_id and resource; the code is single-use and lives 60 s, and a second use revokes the grant. refresh_token: rotates; a spent refresh token revokes the whole grant (reason REFRESH_REUSE); every refresh re-checks that the bound human session is live and re-caps the grant's expiry at the earlier of 8 hours from approval and the session's expiry. urn:ietf:params:oauth:grant-type:device_code: polled with the device_code no faster than every 5 s; authorization_pending until the human decides, slow_down when polled too fast, access_denied when denied, expired_token after 10 minutes. These three answer an access token of at most 10 minutes, never past the grant's expiry, whose audience is the MCP resource; the platform API refuses it (401 TOKEN_WRONG_AUDIENCE). urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693): the AI plane only, authenticated by HTTP Basic as a configured exchange client, exchanges a client's access token (subject_token) for the downstream platform token it calls the platform with, and so learns that the grant is live. It checks the signature, that the token's audience equals resource, that this exchange client may exchange for resource, that the grant is ACTIVE and unexpired, that the bound session is live, the human or supplier principal active, the tenant admitted (not SUSPENDED or OFFBOARDED), the tenant's toolset switch ON and grants enabled; for an operator grant also that operator grants are enabled and aikonic.ops.mfa.required is true (else invalid_target reason OPERATOR_GRANTS_DISABLED or OPERATOR_MFA_NOT_ENFORCED). Refusals are 400 invalid_grant, invalid_target, invalid_client or unauthorized_client with a reason (OAuthError). aikonic_grant.writeOperations is computed at each exchange: the console writes ticked at consent that the tenant still has ON in writesEnabled, empty when the platform write ceiling is off (ADR-C10 Phase 3, DR-68); aikonic_grant.crossTenantReads names the operator cross-tenant reads ticked at consent. Exchanges are limited per grant (aikonic.mcp.grants.exchange.per-grant-per-minute, default 120): past it the answer is 429 RATE_LIMITED (ApiErrorResponse, IdentityRateLimited) details.reason EXCHANGE_RATE_LIMITED, details.action oauth-exchange-grant, with Retry-After; another grant is unaffected. The downstream token lives at most 5 minutes and never past the subject token or the grant's expiry; it is never a session (no session family) and reaches only the grant's toolset's read operations its grant lists, plus, for a console grant, the commit-lite operations in writeOperations that the tenant has ON at the time of the call (else 403 SESSION_REQUIRED). An operator downstream token reaches /ops/v1 reads only, never a step-up route (it carries no MFA time). The client's token is never accepted by the platform API, and the plane never forwards it.",
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "client_credentials",
                  "type": "text"
                },
                {
                  "key": "client_id",
                  "value": "mo_test_jqgukb22egiu642n",
                  "type": "text"
                },
                {
                  "key": "client_secret",
                  "value": "mo_test_jqgukb22egiu642n_muvv3f7zfnpcmgfmqstqkhbroopdatriv4jr3kkzhi3wohdm3llf",
                  "type": "text"
                }
              ]
            },
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Exchange an API key for a short-lived Bearer access token (OAuth2 client-credentials grant, RFC 6749 §4.4) — authorizationCode",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "token"
              ]
            },
            "description": "`POST /v1/oauth/token` · operationId `issueOAuthToken`\n\nExample: An MCP client redeems its code with the PKCE verifier (connect-v1 1.26.0)\n\nExchanges an API key for a short-lived Bearer access token (an ES256 JWT, at most 15 min, no refresh token — RFC 6749 §4.4.3). The client IS the key: client_id is the key's keyPrefix and client_secret is the WHOLE API key exactly as shown at mint (not only the part after the prefix). Authenticate by HTTP Basic (each part form-urlencoded, joined by a colon, base64 — RFC 6749 §2.3.1) OR by client_id + client_secret in the body — never both (invalid_request). X-Api-Key and any other Authorization scheme are refused invalid_client. The body must be application/x-www-form-urlencoded, at most 16 KiB; a query string of any kind and a parameter sent twice are refused invalid_request (a secret in a URL lands in access logs). Checks run in the order request shape, client authentication, grant_type, scope, so an unauthenticated caller learns nothing about grant or scope handling. scope may only NARROW the key's scopes; absent, the token gets all of them. The token is accepted exactly as its key would be, and the key is re-checked on EVERY call made with the token: revoking the key refuses its tokens at once, and a token never holds more than the key holds now. A key in its rotation overlap gets a token that expires no later than the overlap, with the key's Deprecation / Sunset headers. 4xx answers other than 429 are RFC 6749 §5.2's OAuthError, not ApiErrorResponse; error_description is fixed text and never echoes input. The abuse limit's 429 RATE_LIMITED (RFC 6749 names no error for it) and a platform fault's 500 are ApiErrorResponse, whose traceId is the support handle. The limit is counted per client address (every attempt), per address and per (address, client_id) (failed client authentications) and per client_id (tokens issued, spent only by a request that passed every check) — never per client_id before the secret is checked; see IdentityRateLimited. connect-v1 1.26.0 (OD-884 Phase 2, DR-67; ADR-C10 §Phase 2 design D1 to D3): four more grant types, served only where aikonic.mcp.grants.enabled is true (DEV and SANDBOX until OD-28 signs; elsewhere unsupported_grant_type). authorization_code: an MCP client (public, no secret) redeems the code from the consent redirect with its PKCE code_verifier, the same redirect_uri, client_id and resource; the code is single-use and lives 60 s, and a second use revokes the grant. refresh_token: rotates; a spent refresh token revokes the whole grant (reason REFRESH_REUSE); every refresh re-checks that the bound human session is live and re-caps the grant's expiry at the earlier of 8 hours from approval and the session's expiry. urn:ietf:params:oauth:grant-type:device_code: polled with the device_code no faster than every 5 s; authorization_pending until the human decides, slow_down when polled too fast, access_denied when denied, expired_token after 10 minutes. These three answer an access token of at most 10 minutes, never past the grant's expiry, whose audience is the MCP resource; the platform API refuses it (401 TOKEN_WRONG_AUDIENCE). urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693): the AI plane only, authenticated by HTTP Basic as a configured exchange client, exchanges a client's access token (subject_token) for the downstream platform token it calls the platform with, and so learns that the grant is live. It checks the signature, that the token's audience equals resource, that this exchange client may exchange for resource, that the grant is ACTIVE and unexpired, that the bound session is live, the human or supplier principal active, the tenant admitted (not SUSPENDED or OFFBOARDED), the tenant's toolset switch ON and grants enabled; for an operator grant also that operator grants are enabled and aikonic.ops.mfa.required is true (else invalid_target reason OPERATOR_GRANTS_DISABLED or OPERATOR_MFA_NOT_ENFORCED). Refusals are 400 invalid_grant, invalid_target, invalid_client or unauthorized_client with a reason (OAuthError). aikonic_grant.writeOperations is computed at each exchange: the console writes ticked at consent that the tenant still has ON in writesEnabled, empty when the platform write ceiling is off (ADR-C10 Phase 3, DR-68); aikonic_grant.crossTenantReads names the operator cross-tenant reads ticked at consent. Exchanges are limited per grant (aikonic.mcp.grants.exchange.per-grant-per-minute, default 120): past it the answer is 429 RATE_LIMITED (ApiErrorResponse, IdentityRateLimited) details.reason EXCHANGE_RATE_LIMITED, details.action oauth-exchange-grant, with Retry-After; another grant is unaffected. The downstream token lives at most 5 minutes and never past the subject token or the grant's expiry; it is never a session (no session family) and reaches only the grant's toolset's read operations its grant lists, plus, for a console grant, the commit-lite operations in writeOperations that the tenant has ON at the time of the call (else 403 SESSION_REQUIRED). An operator downstream token reaches /ops/v1 reads only, never a step-up route (it carries no MFA time). The client's token is never accepted by the platform API, and the plane never forwards it.",
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "authorization_code",
                  "type": "text"
                },
                {
                  "key": "code",
                  "value": "mca_EXAMPLEONLYcode0001",
                  "type": "text"
                },
                {
                  "key": "redirect_uri",
                  "value": "http://127.0.0.1:53682/callback",
                  "type": "text"
                },
                {
                  "key": "code_verifier",
                  "value": "EXAMPLEONLY-verifier-dBjftJeZ4CVPmB92K27uhbUJU1p1r",
                  "type": "text"
                },
                {
                  "key": "client_id",
                  "value": "https://assistant.example.com/oauth/client.json",
                  "type": "text"
                },
                {
                  "key": "resource",
                  "value": "https://mcp.example/mcp/console",
                  "type": "text"
                }
              ]
            },
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Exchange an API key for a short-lived Bearer access token (OAuth2 client-credentials grant, RFC 6749 §4.4) — refresh",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "token"
              ]
            },
            "description": "`POST /v1/oauth/token` · operationId `issueOAuthToken`\n\nExample: An MCP client rotates its refresh token (connect-v1 1.26.0)\n\nExchanges an API key for a short-lived Bearer access token (an ES256 JWT, at most 15 min, no refresh token — RFC 6749 §4.4.3). The client IS the key: client_id is the key's keyPrefix and client_secret is the WHOLE API key exactly as shown at mint (not only the part after the prefix). Authenticate by HTTP Basic (each part form-urlencoded, joined by a colon, base64 — RFC 6749 §2.3.1) OR by client_id + client_secret in the body — never both (invalid_request). X-Api-Key and any other Authorization scheme are refused invalid_client. The body must be application/x-www-form-urlencoded, at most 16 KiB; a query string of any kind and a parameter sent twice are refused invalid_request (a secret in a URL lands in access logs). Checks run in the order request shape, client authentication, grant_type, scope, so an unauthenticated caller learns nothing about grant or scope handling. scope may only NARROW the key's scopes; absent, the token gets all of them. The token is accepted exactly as its key would be, and the key is re-checked on EVERY call made with the token: revoking the key refuses its tokens at once, and a token never holds more than the key holds now. A key in its rotation overlap gets a token that expires no later than the overlap, with the key's Deprecation / Sunset headers. 4xx answers other than 429 are RFC 6749 §5.2's OAuthError, not ApiErrorResponse; error_description is fixed text and never echoes input. The abuse limit's 429 RATE_LIMITED (RFC 6749 names no error for it) and a platform fault's 500 are ApiErrorResponse, whose traceId is the support handle. The limit is counted per client address (every attempt), per address and per (address, client_id) (failed client authentications) and per client_id (tokens issued, spent only by a request that passed every check) — never per client_id before the secret is checked; see IdentityRateLimited. connect-v1 1.26.0 (OD-884 Phase 2, DR-67; ADR-C10 §Phase 2 design D1 to D3): four more grant types, served only where aikonic.mcp.grants.enabled is true (DEV and SANDBOX until OD-28 signs; elsewhere unsupported_grant_type). authorization_code: an MCP client (public, no secret) redeems the code from the consent redirect with its PKCE code_verifier, the same redirect_uri, client_id and resource; the code is single-use and lives 60 s, and a second use revokes the grant. refresh_token: rotates; a spent refresh token revokes the whole grant (reason REFRESH_REUSE); every refresh re-checks that the bound human session is live and re-caps the grant's expiry at the earlier of 8 hours from approval and the session's expiry. urn:ietf:params:oauth:grant-type:device_code: polled with the device_code no faster than every 5 s; authorization_pending until the human decides, slow_down when polled too fast, access_denied when denied, expired_token after 10 minutes. These three answer an access token of at most 10 minutes, never past the grant's expiry, whose audience is the MCP resource; the platform API refuses it (401 TOKEN_WRONG_AUDIENCE). urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693): the AI plane only, authenticated by HTTP Basic as a configured exchange client, exchanges a client's access token (subject_token) for the downstream platform token it calls the platform with, and so learns that the grant is live. It checks the signature, that the token's audience equals resource, that this exchange client may exchange for resource, that the grant is ACTIVE and unexpired, that the bound session is live, the human or supplier principal active, the tenant admitted (not SUSPENDED or OFFBOARDED), the tenant's toolset switch ON and grants enabled; for an operator grant also that operator grants are enabled and aikonic.ops.mfa.required is true (else invalid_target reason OPERATOR_GRANTS_DISABLED or OPERATOR_MFA_NOT_ENFORCED). Refusals are 400 invalid_grant, invalid_target, invalid_client or unauthorized_client with a reason (OAuthError). aikonic_grant.writeOperations is computed at each exchange: the console writes ticked at consent that the tenant still has ON in writesEnabled, empty when the platform write ceiling is off (ADR-C10 Phase 3, DR-68); aikonic_grant.crossTenantReads names the operator cross-tenant reads ticked at consent. Exchanges are limited per grant (aikonic.mcp.grants.exchange.per-grant-per-minute, default 120): past it the answer is 429 RATE_LIMITED (ApiErrorResponse, IdentityRateLimited) details.reason EXCHANGE_RATE_LIMITED, details.action oauth-exchange-grant, with Retry-After; another grant is unaffected. The downstream token lives at most 5 minutes and never past the subject token or the grant's expiry; it is never a session (no session family) and reaches only the grant's toolset's read operations its grant lists, plus, for a console grant, the commit-lite operations in writeOperations that the tenant has ON at the time of the call (else 403 SESSION_REQUIRED). An operator downstream token reaches /ops/v1 reads only, never a step-up route (it carries no MFA time). The client's token is never accepted by the platform API, and the plane never forwards it.",
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "refresh_token",
                  "type": "text"
                },
                {
                  "key": "refresh_token",
                  "value": "mcr_EXAMPLEONLYnotARealRefreshToken0000",
                  "type": "text"
                },
                {
                  "key": "client_id",
                  "value": "https://assistant.example.com/oauth/client.json",
                  "type": "text"
                }
              ]
            },
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Exchange an API key for a short-lived Bearer access token (OAuth2 client-credentials grant, RFC 6749 §4.4) — devicePoll",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "token"
              ]
            },
            "description": "`POST /v1/oauth/token` · operationId `issueOAuthToken`\n\nExample: A headless MCP client polls with its device code (connect-v1 1.26.0)\n\nExchanges an API key for a short-lived Bearer access token (an ES256 JWT, at most 15 min, no refresh token — RFC 6749 §4.4.3). The client IS the key: client_id is the key's keyPrefix and client_secret is the WHOLE API key exactly as shown at mint (not only the part after the prefix). Authenticate by HTTP Basic (each part form-urlencoded, joined by a colon, base64 — RFC 6749 §2.3.1) OR by client_id + client_secret in the body — never both (invalid_request). X-Api-Key and any other Authorization scheme are refused invalid_client. The body must be application/x-www-form-urlencoded, at most 16 KiB; a query string of any kind and a parameter sent twice are refused invalid_request (a secret in a URL lands in access logs). Checks run in the order request shape, client authentication, grant_type, scope, so an unauthenticated caller learns nothing about grant or scope handling. scope may only NARROW the key's scopes; absent, the token gets all of them. The token is accepted exactly as its key would be, and the key is re-checked on EVERY call made with the token: revoking the key refuses its tokens at once, and a token never holds more than the key holds now. A key in its rotation overlap gets a token that expires no later than the overlap, with the key's Deprecation / Sunset headers. 4xx answers other than 429 are RFC 6749 §5.2's OAuthError, not ApiErrorResponse; error_description is fixed text and never echoes input. The abuse limit's 429 RATE_LIMITED (RFC 6749 names no error for it) and a platform fault's 500 are ApiErrorResponse, whose traceId is the support handle. The limit is counted per client address (every attempt), per address and per (address, client_id) (failed client authentications) and per client_id (tokens issued, spent only by a request that passed every check) — never per client_id before the secret is checked; see IdentityRateLimited. connect-v1 1.26.0 (OD-884 Phase 2, DR-67; ADR-C10 §Phase 2 design D1 to D3): four more grant types, served only where aikonic.mcp.grants.enabled is true (DEV and SANDBOX until OD-28 signs; elsewhere unsupported_grant_type). authorization_code: an MCP client (public, no secret) redeems the code from the consent redirect with its PKCE code_verifier, the same redirect_uri, client_id and resource; the code is single-use and lives 60 s, and a second use revokes the grant. refresh_token: rotates; a spent refresh token revokes the whole grant (reason REFRESH_REUSE); every refresh re-checks that the bound human session is live and re-caps the grant's expiry at the earlier of 8 hours from approval and the session's expiry. urn:ietf:params:oauth:grant-type:device_code: polled with the device_code no faster than every 5 s; authorization_pending until the human decides, slow_down when polled too fast, access_denied when denied, expired_token after 10 minutes. These three answer an access token of at most 10 minutes, never past the grant's expiry, whose audience is the MCP resource; the platform API refuses it (401 TOKEN_WRONG_AUDIENCE). urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693): the AI plane only, authenticated by HTTP Basic as a configured exchange client, exchanges a client's access token (subject_token) for the downstream platform token it calls the platform with, and so learns that the grant is live. It checks the signature, that the token's audience equals resource, that this exchange client may exchange for resource, that the grant is ACTIVE and unexpired, that the bound session is live, the human or supplier principal active, the tenant admitted (not SUSPENDED or OFFBOARDED), the tenant's toolset switch ON and grants enabled; for an operator grant also that operator grants are enabled and aikonic.ops.mfa.required is true (else invalid_target reason OPERATOR_GRANTS_DISABLED or OPERATOR_MFA_NOT_ENFORCED). Refusals are 400 invalid_grant, invalid_target, invalid_client or unauthorized_client with a reason (OAuthError). aikonic_grant.writeOperations is computed at each exchange: the console writes ticked at consent that the tenant still has ON in writesEnabled, empty when the platform write ceiling is off (ADR-C10 Phase 3, DR-68); aikonic_grant.crossTenantReads names the operator cross-tenant reads ticked at consent. Exchanges are limited per grant (aikonic.mcp.grants.exchange.per-grant-per-minute, default 120): past it the answer is 429 RATE_LIMITED (ApiErrorResponse, IdentityRateLimited) details.reason EXCHANGE_RATE_LIMITED, details.action oauth-exchange-grant, with Retry-After; another grant is unaffected. The downstream token lives at most 5 minutes and never past the subject token or the grant's expiry; it is never a session (no session family) and reaches only the grant's toolset's read operations its grant lists, plus, for a console grant, the commit-lite operations in writeOperations that the tenant has ON at the time of the call (else 403 SESSION_REQUIRED). An operator downstream token reaches /ops/v1 reads only, never a step-up route (it carries no MFA time). The client's token is never accepted by the platform API, and the plane never forwards it.",
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:device_code",
                  "type": "text"
                },
                {
                  "key": "device_code",
                  "value": "mcd_EXAMPLEONLYnotARealDeviceCode00",
                  "type": "text"
                },
                {
                  "key": "client_id",
                  "value": "mcpc_8Hn2kQ4rT7vW1xZ3bC5dF6",
                  "type": "text"
                }
              ]
            },
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Exchange an API key for a short-lived Bearer access token (OAuth2 client-credentials grant, RFC 6749 §4.4) — tokenExchange",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "token"
              ]
            },
            "description": "`POST /v1/oauth/token` · operationId `issueOAuthToken`\n\nExample: The AI plane exchanges a client's token (HTTP Basic as the exchange client; connect-v1 1.26.0)\n\nExchanges an API key for a short-lived Bearer access token (an ES256 JWT, at most 15 min, no refresh token — RFC 6749 §4.4.3). The client IS the key: client_id is the key's keyPrefix and client_secret is the WHOLE API key exactly as shown at mint (not only the part after the prefix). Authenticate by HTTP Basic (each part form-urlencoded, joined by a colon, base64 — RFC 6749 §2.3.1) OR by client_id + client_secret in the body — never both (invalid_request). X-Api-Key and any other Authorization scheme are refused invalid_client. The body must be application/x-www-form-urlencoded, at most 16 KiB; a query string of any kind and a parameter sent twice are refused invalid_request (a secret in a URL lands in access logs). Checks run in the order request shape, client authentication, grant_type, scope, so an unauthenticated caller learns nothing about grant or scope handling. scope may only NARROW the key's scopes; absent, the token gets all of them. The token is accepted exactly as its key would be, and the key is re-checked on EVERY call made with the token: revoking the key refuses its tokens at once, and a token never holds more than the key holds now. A key in its rotation overlap gets a token that expires no later than the overlap, with the key's Deprecation / Sunset headers. 4xx answers other than 429 are RFC 6749 §5.2's OAuthError, not ApiErrorResponse; error_description is fixed text and never echoes input. The abuse limit's 429 RATE_LIMITED (RFC 6749 names no error for it) and a platform fault's 500 are ApiErrorResponse, whose traceId is the support handle. The limit is counted per client address (every attempt), per address and per (address, client_id) (failed client authentications) and per client_id (tokens issued, spent only by a request that passed every check) — never per client_id before the secret is checked; see IdentityRateLimited. connect-v1 1.26.0 (OD-884 Phase 2, DR-67; ADR-C10 §Phase 2 design D1 to D3): four more grant types, served only where aikonic.mcp.grants.enabled is true (DEV and SANDBOX until OD-28 signs; elsewhere unsupported_grant_type). authorization_code: an MCP client (public, no secret) redeems the code from the consent redirect with its PKCE code_verifier, the same redirect_uri, client_id and resource; the code is single-use and lives 60 s, and a second use revokes the grant. refresh_token: rotates; a spent refresh token revokes the whole grant (reason REFRESH_REUSE); every refresh re-checks that the bound human session is live and re-caps the grant's expiry at the earlier of 8 hours from approval and the session's expiry. urn:ietf:params:oauth:grant-type:device_code: polled with the device_code no faster than every 5 s; authorization_pending until the human decides, slow_down when polled too fast, access_denied when denied, expired_token after 10 minutes. These three answer an access token of at most 10 minutes, never past the grant's expiry, whose audience is the MCP resource; the platform API refuses it (401 TOKEN_WRONG_AUDIENCE). urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693): the AI plane only, authenticated by HTTP Basic as a configured exchange client, exchanges a client's access token (subject_token) for the downstream platform token it calls the platform with, and so learns that the grant is live. It checks the signature, that the token's audience equals resource, that this exchange client may exchange for resource, that the grant is ACTIVE and unexpired, that the bound session is live, the human or supplier principal active, the tenant admitted (not SUSPENDED or OFFBOARDED), the tenant's toolset switch ON and grants enabled; for an operator grant also that operator grants are enabled and aikonic.ops.mfa.required is true (else invalid_target reason OPERATOR_GRANTS_DISABLED or OPERATOR_MFA_NOT_ENFORCED). Refusals are 400 invalid_grant, invalid_target, invalid_client or unauthorized_client with a reason (OAuthError). aikonic_grant.writeOperations is computed at each exchange: the console writes ticked at consent that the tenant still has ON in writesEnabled, empty when the platform write ceiling is off (ADR-C10 Phase 3, DR-68); aikonic_grant.crossTenantReads names the operator cross-tenant reads ticked at consent. Exchanges are limited per grant (aikonic.mcp.grants.exchange.per-grant-per-minute, default 120): past it the answer is 429 RATE_LIMITED (ApiErrorResponse, IdentityRateLimited) details.reason EXCHANGE_RATE_LIMITED, details.action oauth-exchange-grant, with Retry-After; another grant is unaffected. The downstream token lives at most 5 minutes and never past the subject token or the grant's expiry; it is never a session (no session family) and reaches only the grant's toolset's read operations its grant lists, plus, for a console grant, the commit-lite operations in writeOperations that the tenant has ON at the time of the call (else 403 SESSION_REQUIRED). An operator downstream token reaches /ops/v1 reads only, never a step-up route (it carries no MFA time). The client's token is never accepted by the platform API, and the plane never forwards it.",
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:token-exchange",
                  "type": "text"
                },
                {
                  "key": "subject_token",
                  "value": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRlbW8tMjAyNi0wOS0yMyIsInR5cCI6ImF0K2p3dCJ9.eyJleGFtcGxlIjoibWNwLWNsaWVudC10b2tlbiJ9.c2lnbmF0dXJl",
                  "type": "text"
                },
                {
                  "key": "subject_token_type",
                  "value": "urn:ietf:params:oauth:token-type:access_token",
                  "type": "text"
                },
                {
                  "key": "resource",
                  "value": "https://mcp.example/mcp/console",
                  "type": "text"
                }
              ]
            },
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Self-serve sandbox signup, step 1 — send a code to the address (FEAT-020 §5 flow 1, 11 §6) — acme",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup"
              ]
            },
            "description": "`POST /v1/signup` · operationId `requestSignup`\n\nExample: A prospect signing up for a sandbox\n\nAnswers the same 202 for every well-formed address, so it confirms nothing about who has an account. Disabled unless the deployment enables signup (FEAT-020 §10) and has a way to deliver the code (FEAT-020 Q3); either missing is a 501 that names which. Not envelope-shaped (see the identity tag). connect-v1 1.18.0, served since 1.18.1 (FEAT-030 §5.1 step 1, WP-1): organization is optional — the work e-mail alone starts a signup, and the platform prefills the organisation name from the e-mail domain (rule on SignupRequest.organization). The console may still send it. Signup is off by default (aikonic.signup.enabled) and on at DEV and SANDBOX; UAT and PROD keep it off. Where it is on it needs a sender: identity mail goes through SMTP (Mailgun, OD-ENR-7; RB-10 §7) on every rung, and a rung whose SMTP credential is not deployed still answers 501 NO_VERIFICATION_SENDER (OD-ENR-16).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"founder@acme.example\",\n  \"organization\": \"Acme Travel\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Self-serve sandbox signup, step 1 — send a code to the address (FEAT-020 §5 flow 1, 11 §6) — emailOnly",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup"
              ]
            },
            "description": "`POST /v1/signup` · operationId `requestSignup`\n\nExample: Work e-mail only — the organisation is prefilled from the domain (FEAT-030 §5.1)\n\nAnswers the same 202 for every well-formed address, so it confirms nothing about who has an account. Disabled unless the deployment enables signup (FEAT-020 §10) and has a way to deliver the code (FEAT-020 Q3); either missing is a 501 that names which. Not envelope-shaped (see the identity tag). connect-v1 1.18.0, served since 1.18.1 (FEAT-030 §5.1 step 1, WP-1): organization is optional — the work e-mail alone starts a signup, and the platform prefills the organisation name from the e-mail domain (rule on SignupRequest.organization). The console may still send it. Signup is off by default (aikonic.signup.enabled) and on at DEV and SANDBOX; UAT and PROD keep it off. Where it is on it needs a sender: identity mail goes through SMTP (Mailgun, OD-ENR-7; RB-10 §7) on every rung, and a rung whose SMTP credential is not deployed still answers 501 NO_VERIFICATION_SENDER (OD-ENR-16).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"founder@acme-travel.example\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Self-serve sandbox signup, step 2 — spend the code; get an evaluation tenant and a sandbox key — verify",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "verify"
              ]
            },
            "description": "`POST /v1/signup/verify` · operationId `verifySignup`\n\nExample: The code from the message, and a chosen password\n\nProvisions an EVALUATION tenant in TRIAL (07 §5) with the person as its admin under the password they chose (12 to 1024 characters), and mints a sandbox key returned here ONCE. Production stays behind the D-60 approval gate. A wrong, expired, spent or exhausted code (6 digits, 15 minutes, at most 5 attempts) is one and the same 400. connect-v1 1.18.0, served since 1.18.1 (FEAT-030 §5.1 steps 2 and 3, WP-1), additive: (1) password is optional — the password moves out of signup and the person sets a password later from the console (POST /v1/auth/password/set, connect-v1 1.29.0); when sent it keeps the 12 to 1024 rule. An account created without one has no password until it is set, so POST /v1/auth/login and /v1/auth/sign-in refuse it with the usual 401 LOGIN_FAILED. (2) organization is optional, prefilled from the e-mail domain by the rule on SignupRequest.organization. (3) buildingIntent (optional, skippable) records the answer to \"What are you building?\" and later reads back as ActivationState.buildingIntent. (4) The console session starts on code verification: SignupResult.session carries a TENANT-realm session — the same SessionResponse POST /v1/auth/login answers (15-minute access token, single-use rotating refresh token), for the new admin on the new tenant's site — so a first-party console (its BFF) stores it as it stores a login's session and never needs the password. A brand-new account has no second factor, so there is no MFA step here. Like apiKey, session is a secret in the response body: it is never logged, and the BFF keeps the tokens server-side (httpOnly cookie), never in the page. No session is issued where the tenant admin-MFA policy (DR-51) demands a second factor for the new admin (UAT, PROD), nor when issuing it fails; the tenant and the key exist either way (201). A client that gets no session signs in with POST /v1/auth/login as before. buildingIntent never fails a signup: a rail that cannot be written is logged and skipped. (5) The first key's channel (FEAT-030 phase 5, decided 2026-10-10): on a rung whose keys are SANDBOX the key minted here is a SYSTEM key (actorKind system, DR-40) — the person is the new tenant's admin and no real money moves — so the console quick-start books with it without a D-50 approval grant (DR-39). On a rung whose keys are LIVE no key is minted (unchanged). A key handed to an AI agent should be minted actorKind ai from POST /v1/keys; the signup key is not marked AI-channel. Its scopes are search:hotels, search:flights, orders:write, orders:read and orders:cancel — enough for the whole quick-start (Search, Price, Book, Cancel; AC-030-03). (6) connect-v1 1.47.0 (FEAT-033, OD-906 proposed), additive: an individual account. accountType INDIVIDUAL creates a tenant for one person with no company: givenName and familyName are then required (a missing one is 400 VALIDATION details.reason NAME_REQUIRED, details.field naming it), organization is ignored and the tenant is named \"<givenName> <familyName>\". accountType absent is BUSINESS, today's signup, unchanged; a BUSINESS signup may send the names (stored on the user). country (ISO 3166-1 alpha-2) is optional for both. SignupResult.accountType echoes the type. An INDIVIDUAL tenant's activation rail is the five-step rail of GET /v1/activation. (7) connect-v1 1.51.0 (PROPOSAL, FEAT-030 §18b): the proposed one-page BUSINESS sign-up sends the code with the person's names (givenName, familyName — stored on the admin user and read back as AccessContext.givenName and familyName), country, organization and password, all on the 1.47.0 fields; nothing new is added to this body. For a BUSINESS tenant country is also recorded on the business profile (BusinessProfile.country) and reads back as ActivationState.country, which decides whether the India-only steps of the CAPPED flow are offered. The `business` example below.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"founder@acme.example\",\n  \"code\": \"482913\",\n  \"password\": \"correct horse battery staple\",\n  \"organization\": \"Acme Travel\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Self-serve sandbox signup, step 2 — spend the code; get an evaluation tenant and a sandbox key — codeOnly",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "verify"
              ]
            },
            "description": "`POST /v1/signup/verify` · operationId `verifySignup`\n\nExample: The code only — no password, organisation prefilled from the domain (FEAT-030 §5.1)\n\nProvisions an EVALUATION tenant in TRIAL (07 §5) with the person as its admin under the password they chose (12 to 1024 characters), and mints a sandbox key returned here ONCE. Production stays behind the D-60 approval gate. A wrong, expired, spent or exhausted code (6 digits, 15 minutes, at most 5 attempts) is one and the same 400. connect-v1 1.18.0, served since 1.18.1 (FEAT-030 §5.1 steps 2 and 3, WP-1), additive: (1) password is optional — the password moves out of signup and the person sets a password later from the console (POST /v1/auth/password/set, connect-v1 1.29.0); when sent it keeps the 12 to 1024 rule. An account created without one has no password until it is set, so POST /v1/auth/login and /v1/auth/sign-in refuse it with the usual 401 LOGIN_FAILED. (2) organization is optional, prefilled from the e-mail domain by the rule on SignupRequest.organization. (3) buildingIntent (optional, skippable) records the answer to \"What are you building?\" and later reads back as ActivationState.buildingIntent. (4) The console session starts on code verification: SignupResult.session carries a TENANT-realm session — the same SessionResponse POST /v1/auth/login answers (15-minute access token, single-use rotating refresh token), for the new admin on the new tenant's site — so a first-party console (its BFF) stores it as it stores a login's session and never needs the password. A brand-new account has no second factor, so there is no MFA step here. Like apiKey, session is a secret in the response body: it is never logged, and the BFF keeps the tokens server-side (httpOnly cookie), never in the page. No session is issued where the tenant admin-MFA policy (DR-51) demands a second factor for the new admin (UAT, PROD), nor when issuing it fails; the tenant and the key exist either way (201). A client that gets no session signs in with POST /v1/auth/login as before. buildingIntent never fails a signup: a rail that cannot be written is logged and skipped. (5) The first key's channel (FEAT-030 phase 5, decided 2026-10-10): on a rung whose keys are SANDBOX the key minted here is a SYSTEM key (actorKind system, DR-40) — the person is the new tenant's admin and no real money moves — so the console quick-start books with it without a D-50 approval grant (DR-39). On a rung whose keys are LIVE no key is minted (unchanged). A key handed to an AI agent should be minted actorKind ai from POST /v1/keys; the signup key is not marked AI-channel. Its scopes are search:hotels, search:flights, orders:write, orders:read and orders:cancel — enough for the whole quick-start (Search, Price, Book, Cancel; AC-030-03). (6) connect-v1 1.47.0 (FEAT-033, OD-906 proposed), additive: an individual account. accountType INDIVIDUAL creates a tenant for one person with no company: givenName and familyName are then required (a missing one is 400 VALIDATION details.reason NAME_REQUIRED, details.field naming it), organization is ignored and the tenant is named \"<givenName> <familyName>\". accountType absent is BUSINESS, today's signup, unchanged; a BUSINESS signup may send the names (stored on the user). country (ISO 3166-1 alpha-2) is optional for both. SignupResult.accountType echoes the type. An INDIVIDUAL tenant's activation rail is the five-step rail of GET /v1/activation. (7) connect-v1 1.51.0 (PROPOSAL, FEAT-030 §18b): the proposed one-page BUSINESS sign-up sends the code with the person's names (givenName, familyName — stored on the admin user and read back as AccessContext.givenName and familyName), country, organization and password, all on the 1.47.0 fields; nothing new is added to this body. For a BUSINESS tenant country is also recorded on the business profile (BusinessProfile.country) and reads back as ActivationState.country, which decides whether the India-only steps of the CAPPED flow are offered. The `business` example below.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"founder@acme-travel.example\",\n  \"code\": \"482913\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Self-serve sandbox signup, step 2 — spend the code; get an evaluation tenant and a sandbox key — withBuildingIntent",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "verify"
              ]
            },
            "description": "`POST /v1/signup/verify` · operationId `verifySignup`\n\nExample: The code, the prefilled organisation as edited, and the \"What are you building?\" answer (FEAT-030 §5.1)\n\nProvisions an EVALUATION tenant in TRIAL (07 §5) with the person as its admin under the password they chose (12 to 1024 characters), and mints a sandbox key returned here ONCE. Production stays behind the D-60 approval gate. A wrong, expired, spent or exhausted code (6 digits, 15 minutes, at most 5 attempts) is one and the same 400. connect-v1 1.18.0, served since 1.18.1 (FEAT-030 §5.1 steps 2 and 3, WP-1), additive: (1) password is optional — the password moves out of signup and the person sets a password later from the console (POST /v1/auth/password/set, connect-v1 1.29.0); when sent it keeps the 12 to 1024 rule. An account created without one has no password until it is set, so POST /v1/auth/login and /v1/auth/sign-in refuse it with the usual 401 LOGIN_FAILED. (2) organization is optional, prefilled from the e-mail domain by the rule on SignupRequest.organization. (3) buildingIntent (optional, skippable) records the answer to \"What are you building?\" and later reads back as ActivationState.buildingIntent. (4) The console session starts on code verification: SignupResult.session carries a TENANT-realm session — the same SessionResponse POST /v1/auth/login answers (15-minute access token, single-use rotating refresh token), for the new admin on the new tenant's site — so a first-party console (its BFF) stores it as it stores a login's session and never needs the password. A brand-new account has no second factor, so there is no MFA step here. Like apiKey, session is a secret in the response body: it is never logged, and the BFF keeps the tokens server-side (httpOnly cookie), never in the page. No session is issued where the tenant admin-MFA policy (DR-51) demands a second factor for the new admin (UAT, PROD), nor when issuing it fails; the tenant and the key exist either way (201). A client that gets no session signs in with POST /v1/auth/login as before. buildingIntent never fails a signup: a rail that cannot be written is logged and skipped. (5) The first key's channel (FEAT-030 phase 5, decided 2026-10-10): on a rung whose keys are SANDBOX the key minted here is a SYSTEM key (actorKind system, DR-40) — the person is the new tenant's admin and no real money moves — so the console quick-start books with it without a D-50 approval grant (DR-39). On a rung whose keys are LIVE no key is minted (unchanged). A key handed to an AI agent should be minted actorKind ai from POST /v1/keys; the signup key is not marked AI-channel. Its scopes are search:hotels, search:flights, orders:write, orders:read and orders:cancel — enough for the whole quick-start (Search, Price, Book, Cancel; AC-030-03). (6) connect-v1 1.47.0 (FEAT-033, OD-906 proposed), additive: an individual account. accountType INDIVIDUAL creates a tenant for one person with no company: givenName and familyName are then required (a missing one is 400 VALIDATION details.reason NAME_REQUIRED, details.field naming it), organization is ignored and the tenant is named \"<givenName> <familyName>\". accountType absent is BUSINESS, today's signup, unchanged; a BUSINESS signup may send the names (stored on the user). country (ISO 3166-1 alpha-2) is optional for both. SignupResult.accountType echoes the type. An INDIVIDUAL tenant's activation rail is the five-step rail of GET /v1/activation. (7) connect-v1 1.51.0 (PROPOSAL, FEAT-030 §18b): the proposed one-page BUSINESS sign-up sends the code with the person's names (givenName, familyName — stored on the admin user and read back as AccessContext.givenName and familyName), country, organization and password, all on the 1.47.0 fields; nothing new is added to this body. For a BUSINESS tenant country is also recorded on the business profile (BusinessProfile.country) and reads back as ActivationState.country, which decides whether the India-only steps of the CAPPED flow are offered. The `business` example below.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"founder@acme-travel.example\",\n  \"code\": \"482913\",\n  \"organization\": \"Acme Travel Group\",\n  \"buildingIntent\": \"AI_AGENT\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Self-serve sandbox signup, step 2 — spend the code; get an evaluation tenant and a sandbox key — individual",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "verify"
              ]
            },
            "description": "`POST /v1/signup/verify` · operationId `verifySignup`\n\nExample: An individual — one person, no company; the tenant is named after the person (FEAT-033)\n\nProvisions an EVALUATION tenant in TRIAL (07 §5) with the person as its admin under the password they chose (12 to 1024 characters), and mints a sandbox key returned here ONCE. Production stays behind the D-60 approval gate. A wrong, expired, spent or exhausted code (6 digits, 15 minutes, at most 5 attempts) is one and the same 400. connect-v1 1.18.0, served since 1.18.1 (FEAT-030 §5.1 steps 2 and 3, WP-1), additive: (1) password is optional — the password moves out of signup and the person sets a password later from the console (POST /v1/auth/password/set, connect-v1 1.29.0); when sent it keeps the 12 to 1024 rule. An account created without one has no password until it is set, so POST /v1/auth/login and /v1/auth/sign-in refuse it with the usual 401 LOGIN_FAILED. (2) organization is optional, prefilled from the e-mail domain by the rule on SignupRequest.organization. (3) buildingIntent (optional, skippable) records the answer to \"What are you building?\" and later reads back as ActivationState.buildingIntent. (4) The console session starts on code verification: SignupResult.session carries a TENANT-realm session — the same SessionResponse POST /v1/auth/login answers (15-minute access token, single-use rotating refresh token), for the new admin on the new tenant's site — so a first-party console (its BFF) stores it as it stores a login's session and never needs the password. A brand-new account has no second factor, so there is no MFA step here. Like apiKey, session is a secret in the response body: it is never logged, and the BFF keeps the tokens server-side (httpOnly cookie), never in the page. No session is issued where the tenant admin-MFA policy (DR-51) demands a second factor for the new admin (UAT, PROD), nor when issuing it fails; the tenant and the key exist either way (201). A client that gets no session signs in with POST /v1/auth/login as before. buildingIntent never fails a signup: a rail that cannot be written is logged and skipped. (5) The first key's channel (FEAT-030 phase 5, decided 2026-10-10): on a rung whose keys are SANDBOX the key minted here is a SYSTEM key (actorKind system, DR-40) — the person is the new tenant's admin and no real money moves — so the console quick-start books with it without a D-50 approval grant (DR-39). On a rung whose keys are LIVE no key is minted (unchanged). A key handed to an AI agent should be minted actorKind ai from POST /v1/keys; the signup key is not marked AI-channel. Its scopes are search:hotels, search:flights, orders:write, orders:read and orders:cancel — enough for the whole quick-start (Search, Price, Book, Cancel; AC-030-03). (6) connect-v1 1.47.0 (FEAT-033, OD-906 proposed), additive: an individual account. accountType INDIVIDUAL creates a tenant for one person with no company: givenName and familyName are then required (a missing one is 400 VALIDATION details.reason NAME_REQUIRED, details.field naming it), organization is ignored and the tenant is named \"<givenName> <familyName>\". accountType absent is BUSINESS, today's signup, unchanged; a BUSINESS signup may send the names (stored on the user). country (ISO 3166-1 alpha-2) is optional for both. SignupResult.accountType echoes the type. An INDIVIDUAL tenant's activation rail is the five-step rail of GET /v1/activation. (7) connect-v1 1.51.0 (PROPOSAL, FEAT-030 §18b): the proposed one-page BUSINESS sign-up sends the code with the person's names (givenName, familyName — stored on the admin user and read back as AccessContext.givenName and familyName), country, organization and password, all on the 1.47.0 fields; nothing new is added to this body. For a BUSINESS tenant country is also recorded on the business profile (BusinessProfile.country) and reads back as ActivationState.country, which decides whether the India-only steps of the CAPPED flow are offered. The `business` example below.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"priya.sharma@mail.example\",\n  \"code\": \"482913\",\n  \"password\": \"correct horse battery staple\",\n  \"accountType\": \"INDIVIDUAL\",\n  \"givenName\": \"Priya\",\n  \"familyName\": \"Sharma\",\n  \"country\": \"IN\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Self-serve sandbox signup, step 2 — spend the code; get an evaluation tenant and a sandbox key — business",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "verify"
              ]
            },
            "description": "`POST /v1/signup/verify` · operationId `verifySignup`\n\nExample: A business — code, the person's names, country, company and password on one page (FEAT-030 §18b, v1.51.0)\n\nProvisions an EVALUATION tenant in TRIAL (07 §5) with the person as its admin under the password they chose (12 to 1024 characters), and mints a sandbox key returned here ONCE. Production stays behind the D-60 approval gate. A wrong, expired, spent or exhausted code (6 digits, 15 minutes, at most 5 attempts) is one and the same 400. connect-v1 1.18.0, served since 1.18.1 (FEAT-030 §5.1 steps 2 and 3, WP-1), additive: (1) password is optional — the password moves out of signup and the person sets a password later from the console (POST /v1/auth/password/set, connect-v1 1.29.0); when sent it keeps the 12 to 1024 rule. An account created without one has no password until it is set, so POST /v1/auth/login and /v1/auth/sign-in refuse it with the usual 401 LOGIN_FAILED. (2) organization is optional, prefilled from the e-mail domain by the rule on SignupRequest.organization. (3) buildingIntent (optional, skippable) records the answer to \"What are you building?\" and later reads back as ActivationState.buildingIntent. (4) The console session starts on code verification: SignupResult.session carries a TENANT-realm session — the same SessionResponse POST /v1/auth/login answers (15-minute access token, single-use rotating refresh token), for the new admin on the new tenant's site — so a first-party console (its BFF) stores it as it stores a login's session and never needs the password. A brand-new account has no second factor, so there is no MFA step here. Like apiKey, session is a secret in the response body: it is never logged, and the BFF keeps the tokens server-side (httpOnly cookie), never in the page. No session is issued where the tenant admin-MFA policy (DR-51) demands a second factor for the new admin (UAT, PROD), nor when issuing it fails; the tenant and the key exist either way (201). A client that gets no session signs in with POST /v1/auth/login as before. buildingIntent never fails a signup: a rail that cannot be written is logged and skipped. (5) The first key's channel (FEAT-030 phase 5, decided 2026-10-10): on a rung whose keys are SANDBOX the key minted here is a SYSTEM key (actorKind system, DR-40) — the person is the new tenant's admin and no real money moves — so the console quick-start books with it without a D-50 approval grant (DR-39). On a rung whose keys are LIVE no key is minted (unchanged). A key handed to an AI agent should be minted actorKind ai from POST /v1/keys; the signup key is not marked AI-channel. Its scopes are search:hotels, search:flights, orders:write, orders:read and orders:cancel — enough for the whole quick-start (Search, Price, Book, Cancel; AC-030-03). (6) connect-v1 1.47.0 (FEAT-033, OD-906 proposed), additive: an individual account. accountType INDIVIDUAL creates a tenant for one person with no company: givenName and familyName are then required (a missing one is 400 VALIDATION details.reason NAME_REQUIRED, details.field naming it), organization is ignored and the tenant is named \"<givenName> <familyName>\". accountType absent is BUSINESS, today's signup, unchanged; a BUSINESS signup may send the names (stored on the user). country (ISO 3166-1 alpha-2) is optional for both. SignupResult.accountType echoes the type. An INDIVIDUAL tenant's activation rail is the five-step rail of GET /v1/activation. (7) connect-v1 1.51.0 (PROPOSAL, FEAT-030 §18b): the proposed one-page BUSINESS sign-up sends the code with the person's names (givenName, familyName — stored on the admin user and read back as AccessContext.givenName and familyName), country, organization and password, all on the 1.47.0 fields; nothing new is added to this body. For a BUSINESS tenant country is also recorded on the business profile (BusinessProfile.country) and reads back as ActivationState.country, which decides whether the India-only steps of the CAPPED flow are offered. The `business` example below.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"arjun@sampletravels.in\",\n  \"code\": \"482913\",\n  \"password\": \"correct horse battery staple\",\n  \"givenName\": \"Arjun\",\n  \"familyName\": \"Mehta\",\n  \"country\": \"IN\",\n  \"organization\": \"Sample Travels\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Start a device-code sign-up — an agent asks a person to approve a new tenant (FEAT-034 §5.2, RFC 8628 shape) — agentSignup",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Agent",
                "value": "claude-code/2.4 (claude-fable-5-1)",
                "disabled": true,
                "description": "connect-v1 1.57.0 (PROPOSAL, FEAT-034 §6). The calling agent, as <name>/<version> (<model>), at most 120 characters, e.g. claude-code/2.4 (claude-fable-5-1). Optional on every key-authenticated route in phase 1 (OD-AGT-5 default; required on agt keys in phase 2). Logged and stamped as the agent of the request's records (Attribution.agent) and of a key minted on it (ApiKeyMetadata.agent); a product string, never personal data. POST /v1/signup/device reads it when the body names no agent."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/device",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "device"
              ]
            },
            "description": "`POST /v1/signup/device` · operationId `startDeviceSignup`\n\nExample: Claude Code signs up a person at a travel company\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.signup.device-flow.enabled is on — off by default, on at DEV and SANDBOX — else 501 NOT_IMPLEMENTED details.reason DEVICE_SIGNUP_UNAVAILABLE). A coding agent starts a sign-up for the person whose work e-mail it was given and receives an RFC 8628 §3.2 shaped answer: a device_code to poll POST /v1/signup/device:token with, and a user_code the person confirms at verification_uri (the consent page, which reads GET /v1/signup/device/requests/{userCode}). The platform e-mails the person a 6-digit code at the same moment; approving needs it (POST /v1/signup/device:approve), so only the owner of the inbox can create the tenant [assumption: FEAT-034 §5.2 also names Google and Microsoft sign-in, which wait on OD-ENR-5]. user_code is 8 letters of the RFC 8628 §6.1 base-20 alphabet shown as XXXX-XXXX, single use; the request lives 15 minutes (expires_in 900); poll no faster than interval (5 s). The body takes the sign-up fields of POST /v1/signup/verify (organization, accountType, givenName, familyName, country) and the agent name; agent falls back to the X-Agent header. Nothing is created until the person approves; the tenant then carries origin agent and the agent name (DeviceSignupResult). The start answers 200 whatever the e-mail's domain (OD-AGT-21 option (a)): whether the domain already has a tenant is decided only once the e-mail is proven, at POST /v1/signup/device:approve (409 CONFLICT details.reason TENANT_EXISTS to the person, nextAction request_invite; the request is REFUSED) and then at the agent's poll (the same 409), and only then is that tenant's Owner notified (signup.tenant_exists). So the anonymous start tells no one which domains have an account, notifies no Owner, and no second tenant is created for a domain (an agent cannot fork a company's account). A free-mail domain is accepted for a sandbox tenant only (OD-AGT-4 default). On DEV and SANDBOX a harness identity domain (AgentManifest.signup.harnessDomains) is approved without inbox proof and creates a sandbox tenant, limited by the per-domain count below. Limits: 5 starts per e-mail and 20 per domain per UTC day (aikonic.signup.rate.per-identity-per-day, .per-domain-per-day), beyond them 429 RATE_LIMITED details.reason SIGNUP_RATE_LIMITED with Retry-After; the address limits of IdentityRateLimited also apply (details.action signup-device). Not envelope-shaped (see the identity tag). MCP: excluded.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"priya@skyhop.example\",\n  \"organization\": \"SkyHop Travel\",\n  \"givenName\": \"Priya\",\n  \"familyName\": \"Raman\",\n  \"country\": \"IN\",\n  \"agent\": \"claude-code/2.4 (claude-fable-5-1)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Poll a device-code sign-up — pending, slow down, or the new tenant with its sandbox key (FEAT-034 §5.2) — poll",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/device:token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "device:token"
              ]
            },
            "description": "`POST /v1/signup/device:token` · operationId `pollDeviceSignup`\n\nExample: Poll with the device_code\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.signup.device-flow.enabled is on, else 501 NOT_IMPLEMENTED details.reason DEVICE_SIGNUP_UNAVAILABLE). The agent polls with the device_code of POST /v1/signup/device. Until the person decides it is 400 OAuthError authorization_pending; polling faster than interval is slow_down (add 5 s to the interval, RFC 8628 §3.5); a request not approved within 15 minutes is expired_token; a denied request, or one cancelled because it was approved with another e-mail (reason EMAIL_MISMATCH), is access_denied; an unknown device_code, or one already redeemed, is invalid_grant. Once approved, the first poll creates the tenant and answers 200 DeviceSignupResult — the FEAT-030 §5.1 SignupResult of the new tenant with its SYSTEM sandbox key (its secret shown once; the sandbox default scope of aikonic.keys.defaults.sandbox, FEAT-034 §10; principal the Owner, agent the sign-up agent) plus the MCP endpoint and the manifest URL — and the device_code is spent. It never carries session (OD-AGT-20 option (a)): no Owner session reaches the agent; the person signs in to the console themselves (the e-mailed code, or a password they set). Under aikonic.keys.agent-mint.enabled the key may mint narrower keys for the agent (POST /v1/keys, the narrowing rule). A request whose e-mail, once proven, belongs to a domain that already has a tenant was REFUSED at approval: the poll answers 409 CONFLICT details.reason TENANT_EXISTS (nextAction request_invite) and the device_code is spent (OD-AGT-21). The 400s speak RFC 6749 §5.2 OAuthError (as POST /v1/oauth/token), because a device-flow client parses {error}; the 409 and the 5xx are ApiErrorResponse. MCP: excluded.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"device_code\": \"dsu_Gk3Lr9QzVb7XnM2pWc5Ht8Jd4Fs6Ay1E\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Read a pending device-code sign-up for the consent page — who asks, for which e-mail, what will be created",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/device/requests/:userCode",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "device",
                "requests",
                ":userCode"
              ],
              "variable": [
                {
                  "key": "userCode",
                  "value": "<string>",
                  "description": "The user_code as shown (XXXX-XXXX); the hyphen is optional and case is ignored"
                }
              ]
            },
            "description": "`GET /v1/signup/device/requests/{userCode}` · operationId `getDeviceSignupRequest`\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.signup.device-flow.enabled is on, else 501 NOT_IMPLEMENTED details.reason DEVICE_SIGNUP_UNAVAILABLE). The consent page reads the request the person typed the user_code for and shows \"Let <agent> set up Apiq for <e-mail>\" with what approving creates (a tenant and a sandbox key for the agent; no session reaches the agent, and the person signs in to the console themselves, OD-AGT-20). The e-mail is masked (first two characters of the local part, then *** and the domain); the page asks the person to type it in full on approval. An unknown, used or expired user_code is 404 NOT_FOUND, alike for all three. Limited per client address (IdentityRateLimited, details.action signup-device-lookup) so codes cannot be enumerated [assumption: the action name]. MCP: excluded.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Approve a device-code sign-up with the e-mailed code — the agent's next poll creates the tenant (FEAT-034 §5.2) — approve",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/device:approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "device:approve"
              ]
            },
            "description": "`POST /v1/signup/device:approve` · operationId `approveDeviceSignup`\n\nExample: The person approves with the e-mailed code\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.signup.device-flow.enabled is on, else 501 NOT_IMPLEMENTED details.reason DEVICE_SIGNUP_UNAVAILABLE). The person types the user_code, their e-mail in full and the 6-digit code e-mailed when the agent started the request. The code proves the inbox, exactly as POST /v1/signup/verify does, and lives as long as the request (15 minutes) with at most 5 wrong attempts [assumption: the sign-up code's attempt limit]. Once the code proves the inbox, a domain that already has a tenant is refused (OD-AGT-21 option (a)): 409 CONFLICT details.reason TENANT_EXISTS with nextAction request_invite, answered to the person here; the request becomes REFUSED, the agent's poll answers the same 409, and only then does that tenant's Owner receive the notification signup.tenant_exists. Otherwise the request is APPROVED; the agent's next poll of POST /v1/signup/device:token creates the tenant (origin agent, signupAgent the agent name; the FEAT-030 §5.1 sign-up with the request's fields and its SYSTEM sandbox key) and receives the key, so the one-time secret is never stored at rest. No session is issued to the agent (OD-AGT-20): the person, now the Owner, signs in to the console themselves (the e-mailed code, or a password they set). The event signup.device.approved and the notification of the same kind are recorded for the new tenant when the poll creates it. An e-mail other than the requested one is 400 VALIDATION details.reason EMAIL_MISMATCH and CANCELS the request (the agent's poll answers access_denied); a wrong or expired code is 400 VALIDATION details.reason CODE_INVALID. A request already approved, denied, refused, cancelled or expired is 409 CONFLICT details.reason DEVICE_SIGNUP_DECIDED; an unknown user_code is 404. On DEV and SANDBOX only, aikonic.signup.device-flow.harness-approve approves a request for a harness identity domain (AgentManifest.signup.harnessDomains) without a person and without inbox proof (AC-034-16), creating a sandbox tenant, limited per domain (20 starts a day); a harness identity domain is never treated as an existing tenant's domain [assumption], so it never meets TENANT_EXISTS. Refused at boot above SANDBOX. Address limits: IdentityRateLimited, details.action signup-device-approve [assumption: the action name]. MCP: excluded.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userCode\": \"WDJB-MJHT\",\n  \"email\": \"priya@skyhop.example\",\n  \"code\": \"482913\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Deny a device-code sign-up from the consent page (FEAT-034 §5.2) — deny",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/signup/device:deny",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "signup",
                "device:deny"
              ]
            },
            "description": "`POST /v1/signup/device:deny` · operationId `denyDeviceSignup`\n\nExample: The person refuses\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.signup.device-flow.enabled is on, else 501 NOT_IMPLEMENTED details.reason DEVICE_SIGNUP_UNAVAILABLE). The person refuses the request; nothing is created, the request is DENIED and the agent's next poll answers access_denied. Needs only the user_code: denying creates nothing and spends no code. A request already decided or expired is 409 CONFLICT details.reason DEVICE_SIGNUP_DECIDED; an unknown user_code is 404. Address limits: IdentityRateLimited, the per-address limiter of the lookup and the approval, counted with the approvals (details.action signup-device-approve). MCP: excluded.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userCode\": \"WDJB-MJHT\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Invite a colleague into the tenant (FEAT-020 §5 flow 3) — reporting",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/invites",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "invites"
              ]
            },
            "description": "`POST /v1/invites` · operationId `createInvite`\n\nExample: A read-only colleague on hotels\n\nNeeds users:manage, which no API key can hold. The permissions granted must be a subset of the inviter's; the invitee joins the site the inviter is signed in to. The invite token goes to the invitee only — it is never in this response — and is valid for 7 days, once. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2; served when aikonic.activation.capped-flow.enabled is on, else role is 400 VALIDATION details.field role): invite by role. The body carries exactly one of role (a ConsoleRole) or permissions; both or neither is 400 VALIDATION details.field role (the backend enforces it, no oneOf). With role the invitee is granted that role's permission bundle intersected with the permissions the inviter holds: a permission the inviter lacks is dropped, never granted, and the 201 permissions list exactly what was granted (role echoes the role). Inviting as OWNER needs an Owner [assumption: 403 PERMISSION_DENIED details.reason OWNER_ONLY otherwise]. With permissions, as before.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"colleague@acme.example\",\n  \"permissions\": [\n    \"orders:read\",\n    \"search:hotels\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Invite a colleague into the tenant (FEAT-020 §5 flow 3) — byRole",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/invites",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "invites"
              ]
            },
            "description": "`POST /v1/invites` · operationId `createInvite`\n\nExample: A finance colleague invited by role (v1.63.0 PROPOSAL)\n\nNeeds users:manage, which no API key can hold. The permissions granted must be a subset of the inviter's; the invitee joins the site the inviter is signed in to. The invite token goes to the invitee only — it is never in this response — and is valid for 7 days, once. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2; served when aikonic.activation.capped-flow.enabled is on, else role is 400 VALIDATION details.field role): invite by role. The body carries exactly one of role (a ConsoleRole) or permissions; both or neither is 400 VALIDATION details.field role (the backend enforces it, no oneOf). With role the invitee is granted that role's permission bundle intersected with the permissions the inviter holds: a permission the inviter lacks is dropped, never granted, and the 201 permissions list exactly what was granted (role echoes the role). Inviting as OWNER needs an Owner [assumption: 403 PERMISSION_DENIED details.reason OWNER_ONLY otherwise]. With permissions, as before.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"accounts@sampletravels.co.uk\",\n  \"role\": \"FINANCE\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Accept an invite with a chosen password — once (FEAT-020 §5 flow 3) — accept",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/invites/accept",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "invites",
                "accept"
              ]
            },
            "description": "`POST /v1/invites/accept` · operationId `acceptInvite`\n\nExample: The token from the invite, and a chosen password\n\nThe token is carried in the BODY, deliberately, rather than the path FEAT-020 §6 sketched: a path lands in access logs and proxy caches. A token that is unknown, used or expired is one and the same 400. An address that already has an account in the tenant is a 409.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"q8Zp3VxN6tYb1mK0rW9sE2uH7jL4cF5dA8gT3yU6iO0\",\n  \"password\": \"another long passphrase\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Ask for a password-reset code (FEAT-020 §5 flow 5) — forgot",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/password/forgot",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "password",
                "forgot"
              ]
            },
            "description": "`POST /v1/auth/password/forgot` · operationId `forgotPassword`\n\nExample: Username and site, as at sign-in\n\nThe same 202 whether or not the account exists (no account enumeration); a code is sent only when it does. With no way to deliver a code the deployment answers 501.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"founder@acme.example\",\n  \"siteId\": \"SITE-B3XK9T2Q7NWR5M4A\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Set a new password with the code — ends every session of the user (FEAT-020 §5 flow 5) — reset",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/password/reset",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "password",
                "reset"
              ]
            },
            "description": "`POST /v1/auth/password/reset` · operationId `resetPassword`\n\nExample: The code, and the new password",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"username\": \"founder@acme.example\",\n  \"siteId\": \"SITE-B3XK9T2Q7NWR5M4A\",\n  \"code\": \"771204\",\n  \"newPassword\": \"a brand new long passphrase\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "The caller's resolved AccessContext — who the platform thinks you are (FEAT-020 §3a)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me"
              ]
            },
            "description": "`GET /v1/me` · operationId `getMe`\n\nWhat a console renders its shell from, and the quickest way for an integrator to check a key: the tenant, site and permissions come from the verified credential, never the body. tenantStatus and productionWithheld say what the tenant may do on this deployment, read on this request: the console badges a SUSPENDED tenant from tenantStatus first, then a tenant whose production is withheld from productionWithheld (FEAT-020 §18c). For a human session passwordSet says whether the account has a password; a false one is offered POST /v1/auth/password/set (connect-v1 1.29.0, OD-ENR-15). connect-v1 1.47.0 (FEAT-033): accountType says whether the tenant is a BUSINESS or an INDIVIDUAL (absent: treat as BUSINESS), and a human session carries givenName and familyName when they are known (set at signup); an API key or service token carries neither."
          },
          "response": []
        },
        {
          "name": "The tenant's API keys — metadata only, never a secret (15 §1.1)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys"
              ]
            },
            "description": "`GET /v1/keys` · operationId `listApiKeys`\n\nSession only, as built (IMP-P2-041): every /v1/keys route accepts a human session token and nothing else — an API key or a service token is 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED (keys:manage is never on a key). Credentials are checked before the body is read. connect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.keys.agent-mint.enabled is on, else session only as above): an API key (either actorKind — the device sign-up hands the agent a system key, OD-AGT-20) may also list, and sees only itself and the keys it minted, metadata only (an MCP tool, list_api_keys). Every item carries the key's scope (kind, writes, products, spendPerDay, lookToBook, reqPerSec, holdsPerHour, cancelsPerDay, expiresAt), its principal (masked e-mail) and principalUserId, agent, mintedBy, state (ACTIVE, PAUSED, REVOKED, EXPIRED), the pause reason and evidence of a PAUSED key, successorKeyPrefix and today's usage. A key minted before 1.57.0 reads kind own, state ACTIVE and no caps. With aikonic.keys.velocity.enabled on, a PAUSED key is 423 and a key over its own rate 429.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Mint a scoped API key — the secret is in this response and nowhere else, ever (15 §1.1) — bookingKey",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys"
              ]
            },
            "description": "`POST /v1/keys` · operationId `mintApiKey`\n\nExample: A sandbox key that can shop hotels and book\n\nRequires keys:manage (session only, as built — an API key is 403 ACCESS_TOKEN_REQUIRED). The key's scopes must be a subset of the minting user's; the key is a service principal that inherits nothing from the user who minted it. Stored hashed: a lost key is revoked and re-minted, never recovered. A key can also be exchanged for a short-lived Bearer token at POST /v1/oauth/token (FEAT-020 phase C). While the tenant is SUSPENDED no key is minted or rotated (403 TENANT_SUSPENDED); revoke still works. actorKind (DR-40, default ai) sets the channel the key acts as: an ai key is the AI channel and needs a D-50 grant on money verbs; a system key is the SYSTEM channel for non-AI server integrations and skips the D-50 gate, so minting one also needs the tenant admin role (403 PERMISSION_DENIED details.reason SYSTEM_KEY_REQUIRES_ADMIN otherwise). A system key can never file an agent proposal (POST /v1/agent-proposals, AGENT_CREDENTIAL_REQUIRED). connect-v1 v1.46.0: suppliers (optional) narrows the key to a supplier allowlist on top of its scopes — see MintApiKeyRequest.suppliers for the validation and the runtime answers. It is fixed at mint and survives rotation; console sessions are never narrowed by it. connect-v1 1.57.0 (PROPOSAL, FEAT-034; the scope members are served when aikonic.keys.agent-mint.enabled is on, else a request carrying any of them is 501 NOT_IMPLEMENTED details.reason AGENT_KEYS_UNAVAILABLE [assumption] and the route is as above). The request takes the key's scope — writes (KeyWrites), products, spendPerDay, lookToBook, reqPerSec, holdsPerHour, cancelsPerDay, expiresAt — and onBehalfOf, the Member the key acts for (default the caller; its principal and principalUserId). Minted by a session, the key is kind own and a member the request leaves out or sends null sets no key cap (the tenant's limits still govern), unchanged for people [assumption]; a session's onBehalfOf naming a Member other than the caller needs users:manage (else 403 PERMISSION_DENIED details.reason MISSING_PERMISSION) [assumption]. The key records the agent of the X-Agent header. NARROWING RULE (FEAT-034 §5.3, OD-AGT-1): an API key may mint, only while aikonic.keys.agent-mint.enabled is on, and only a key whose every dimension is equal or narrower than its own — scopes and suppliers a subset, environment the same, writes no higher on the KeyWrites ladder, products a subset, each numeric cap no higher, expiresAt no later. Either actorKind may mint: the device sign-up hands the agent a system key (OD-AGT-20). A cap the request leaves out or sends null is the parent's cap, never null where the parent has one [assumption]; the child keeps the parent's actorKind unless it asks for ai under a system parent (the narrower channel: the D-50 gate applies), and an ai parent asking for system is widening (dimension actorKind) [assumption]. Anything wider is 403 PERMISSION_DENIED details.reason SCOPE_WIDENING with details.dimensions naming each wider member (nextAction widen_scope_via_owner; three in an hour count as a soft velocity breach, FEAT-034 §12). The child is kind agt, mintedBy the parent's keyPrefix, and its principal is the parent's, fixed at mint: an onBehalfOf naming anyone else is 403 PERMISSION_DENIED details.reason PRINCIPAL_UNKNOWN, as is an onBehalfOf that is not an active Member of the tenant. A child never widens its ancestors' blast radius (FEAT-034 G4) [assumption; OD-AGT-6]: every call of a minted key also counts against each ancestor key's velocity and spend windows, and while an ancestor is PAUSED its descendants answer 423 KEY_PAUSED (resuming the ancestor resumes them), so minting N children never multiplies what one key may do. With aikonic.keys.agent-mint.enabled off an API key is refused 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED, as today (FEAT-034 AC-034-07 calls it SESSION_REQUIRED). Emits key.minted. MCP: the tool mint_api_key, on the same rule.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"checkout-service\",\n  \"scopes\": [\n    \"search:hotels\",\n    \"orders:write\",\n    \"orders:read\"\n  ],\n  \"ipAllowlist\": [],\n  \"environment\": \"sandbox\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Mint a scoped API key — the secret is in this response and nowhere else, ever (15 §1.1) — supplierLimitedKey",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys"
              ]
            },
            "description": "`POST /v1/keys` · operationId `mintApiKey`\n\nExample: A hotels key that reaches two suppliers only\n\nRequires keys:manage (session only, as built — an API key is 403 ACCESS_TOKEN_REQUIRED). The key's scopes must be a subset of the minting user's; the key is a service principal that inherits nothing from the user who minted it. Stored hashed: a lost key is revoked and re-minted, never recovered. A key can also be exchanged for a short-lived Bearer token at POST /v1/oauth/token (FEAT-020 phase C). While the tenant is SUSPENDED no key is minted or rotated (403 TENANT_SUSPENDED); revoke still works. actorKind (DR-40, default ai) sets the channel the key acts as: an ai key is the AI channel and needs a D-50 grant on money verbs; a system key is the SYSTEM channel for non-AI server integrations and skips the D-50 gate, so minting one also needs the tenant admin role (403 PERMISSION_DENIED details.reason SYSTEM_KEY_REQUIRES_ADMIN otherwise). A system key can never file an agent proposal (POST /v1/agent-proposals, AGENT_CREDENTIAL_REQUIRED). connect-v1 v1.46.0: suppliers (optional) narrows the key to a supplier allowlist on top of its scopes — see MintApiKeyRequest.suppliers for the validation and the runtime answers. It is fixed at mint and survives rotation; console sessions are never narrowed by it. connect-v1 1.57.0 (PROPOSAL, FEAT-034; the scope members are served when aikonic.keys.agent-mint.enabled is on, else a request carrying any of them is 501 NOT_IMPLEMENTED details.reason AGENT_KEYS_UNAVAILABLE [assumption] and the route is as above). The request takes the key's scope — writes (KeyWrites), products, spendPerDay, lookToBook, reqPerSec, holdsPerHour, cancelsPerDay, expiresAt — and onBehalfOf, the Member the key acts for (default the caller; its principal and principalUserId). Minted by a session, the key is kind own and a member the request leaves out or sends null sets no key cap (the tenant's limits still govern), unchanged for people [assumption]; a session's onBehalfOf naming a Member other than the caller needs users:manage (else 403 PERMISSION_DENIED details.reason MISSING_PERMISSION) [assumption]. The key records the agent of the X-Agent header. NARROWING RULE (FEAT-034 §5.3, OD-AGT-1): an API key may mint, only while aikonic.keys.agent-mint.enabled is on, and only a key whose every dimension is equal or narrower than its own — scopes and suppliers a subset, environment the same, writes no higher on the KeyWrites ladder, products a subset, each numeric cap no higher, expiresAt no later. Either actorKind may mint: the device sign-up hands the agent a system key (OD-AGT-20). A cap the request leaves out or sends null is the parent's cap, never null where the parent has one [assumption]; the child keeps the parent's actorKind unless it asks for ai under a system parent (the narrower channel: the D-50 gate applies), and an ai parent asking for system is widening (dimension actorKind) [assumption]. Anything wider is 403 PERMISSION_DENIED details.reason SCOPE_WIDENING with details.dimensions naming each wider member (nextAction widen_scope_via_owner; three in an hour count as a soft velocity breach, FEAT-034 §12). The child is kind agt, mintedBy the parent's keyPrefix, and its principal is the parent's, fixed at mint: an onBehalfOf naming anyone else is 403 PERMISSION_DENIED details.reason PRINCIPAL_UNKNOWN, as is an onBehalfOf that is not an active Member of the tenant. A child never widens its ancestors' blast radius (FEAT-034 G4) [assumption; OD-AGT-6]: every call of a minted key also counts against each ancestor key's velocity and spend windows, and while an ancestor is PAUSED its descendants answer 423 KEY_PAUSED (resuming the ancestor resumes them), so minting N children never multiplies what one key may do. With aikonic.keys.agent-mint.enabled off an API key is refused 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED, as today (FEAT-034 AC-034-07 calls it SESSION_REQUIRED). Emits key.minted. MCP: the tool mint_api_key, on the same rule.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"checkout-service\",\n  \"scopes\": [\n    \"search:hotels\",\n    \"orders:write\",\n    \"orders:read\"\n  ],\n  \"suppliers\": [\n    \"SUP-HTL-EXAMPLE6\",\n    \"SUP-HTL-EXAMPLE7\"\n  ],\n  \"environment\": \"sandbox\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Mint a scoped API key — the secret is in this response and nowhere else, ever (15 §1.1) — systemKey",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys"
              ]
            },
            "description": "`POST /v1/keys` · operationId `mintApiKey`\n\nExample: A system key for a non-AI server integration (tenant admin only, DR-40)\n\nRequires keys:manage (session only, as built — an API key is 403 ACCESS_TOKEN_REQUIRED). The key's scopes must be a subset of the minting user's; the key is a service principal that inherits nothing from the user who minted it. Stored hashed: a lost key is revoked and re-minted, never recovered. A key can also be exchanged for a short-lived Bearer token at POST /v1/oauth/token (FEAT-020 phase C). While the tenant is SUSPENDED no key is minted or rotated (403 TENANT_SUSPENDED); revoke still works. actorKind (DR-40, default ai) sets the channel the key acts as: an ai key is the AI channel and needs a D-50 grant on money verbs; a system key is the SYSTEM channel for non-AI server integrations and skips the D-50 gate, so minting one also needs the tenant admin role (403 PERMISSION_DENIED details.reason SYSTEM_KEY_REQUIRES_ADMIN otherwise). A system key can never file an agent proposal (POST /v1/agent-proposals, AGENT_CREDENTIAL_REQUIRED). connect-v1 v1.46.0: suppliers (optional) narrows the key to a supplier allowlist on top of its scopes — see MintApiKeyRequest.suppliers for the validation and the runtime answers. It is fixed at mint and survives rotation; console sessions are never narrowed by it. connect-v1 1.57.0 (PROPOSAL, FEAT-034; the scope members are served when aikonic.keys.agent-mint.enabled is on, else a request carrying any of them is 501 NOT_IMPLEMENTED details.reason AGENT_KEYS_UNAVAILABLE [assumption] and the route is as above). The request takes the key's scope — writes (KeyWrites), products, spendPerDay, lookToBook, reqPerSec, holdsPerHour, cancelsPerDay, expiresAt — and onBehalfOf, the Member the key acts for (default the caller; its principal and principalUserId). Minted by a session, the key is kind own and a member the request leaves out or sends null sets no key cap (the tenant's limits still govern), unchanged for people [assumption]; a session's onBehalfOf naming a Member other than the caller needs users:manage (else 403 PERMISSION_DENIED details.reason MISSING_PERMISSION) [assumption]. The key records the agent of the X-Agent header. NARROWING RULE (FEAT-034 §5.3, OD-AGT-1): an API key may mint, only while aikonic.keys.agent-mint.enabled is on, and only a key whose every dimension is equal or narrower than its own — scopes and suppliers a subset, environment the same, writes no higher on the KeyWrites ladder, products a subset, each numeric cap no higher, expiresAt no later. Either actorKind may mint: the device sign-up hands the agent a system key (OD-AGT-20). A cap the request leaves out or sends null is the parent's cap, never null where the parent has one [assumption]; the child keeps the parent's actorKind unless it asks for ai under a system parent (the narrower channel: the D-50 gate applies), and an ai parent asking for system is widening (dimension actorKind) [assumption]. Anything wider is 403 PERMISSION_DENIED details.reason SCOPE_WIDENING with details.dimensions naming each wider member (nextAction widen_scope_via_owner; three in an hour count as a soft velocity breach, FEAT-034 §12). The child is kind agt, mintedBy the parent's keyPrefix, and its principal is the parent's, fixed at mint: an onBehalfOf naming anyone else is 403 PERMISSION_DENIED details.reason PRINCIPAL_UNKNOWN, as is an onBehalfOf that is not an active Member of the tenant. A child never widens its ancestors' blast radius (FEAT-034 G4) [assumption; OD-AGT-6]: every call of a minted key also counts against each ancestor key's velocity and spend windows, and while an ancestor is PAUSED its descendants answer 423 KEY_PAUSED (resuming the ancestor resumes them), so minting N children never multiplies what one key may do. With aikonic.keys.agent-mint.enabled off an API key is refused 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED, as today (FEAT-034 AC-034-07 calls it SESSION_REQUIRED). Emits key.minted. MCP: the tool mint_api_key, on the same rule.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"erp-sync\",\n  \"scopes\": [\n    \"orders:read\"\n  ],\n  \"ipAllowlist\": [\n    \"203.0.113.0/24\"\n  ],\n  \"environment\": \"sandbox\",\n  \"actorKind\": \"system\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Mint a scoped API key — the secret is in this response and nowhere else, ever (15 §1.1) — agentNarrowKey",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys"
              ]
            },
            "description": "`POST /v1/keys` · operationId `mintApiKey`\n\nExample: The device sign-up key mints a hold-only hotels key for a sub-task, on the AI channel (narrowing rule)\n\nRequires keys:manage (session only, as built — an API key is 403 ACCESS_TOKEN_REQUIRED). The key's scopes must be a subset of the minting user's; the key is a service principal that inherits nothing from the user who minted it. Stored hashed: a lost key is revoked and re-minted, never recovered. A key can also be exchanged for a short-lived Bearer token at POST /v1/oauth/token (FEAT-020 phase C). While the tenant is SUSPENDED no key is minted or rotated (403 TENANT_SUSPENDED); revoke still works. actorKind (DR-40, default ai) sets the channel the key acts as: an ai key is the AI channel and needs a D-50 grant on money verbs; a system key is the SYSTEM channel for non-AI server integrations and skips the D-50 gate, so minting one also needs the tenant admin role (403 PERMISSION_DENIED details.reason SYSTEM_KEY_REQUIRES_ADMIN otherwise). A system key can never file an agent proposal (POST /v1/agent-proposals, AGENT_CREDENTIAL_REQUIRED). connect-v1 v1.46.0: suppliers (optional) narrows the key to a supplier allowlist on top of its scopes — see MintApiKeyRequest.suppliers for the validation and the runtime answers. It is fixed at mint and survives rotation; console sessions are never narrowed by it. connect-v1 1.57.0 (PROPOSAL, FEAT-034; the scope members are served when aikonic.keys.agent-mint.enabled is on, else a request carrying any of them is 501 NOT_IMPLEMENTED details.reason AGENT_KEYS_UNAVAILABLE [assumption] and the route is as above). The request takes the key's scope — writes (KeyWrites), products, spendPerDay, lookToBook, reqPerSec, holdsPerHour, cancelsPerDay, expiresAt — and onBehalfOf, the Member the key acts for (default the caller; its principal and principalUserId). Minted by a session, the key is kind own and a member the request leaves out or sends null sets no key cap (the tenant's limits still govern), unchanged for people [assumption]; a session's onBehalfOf naming a Member other than the caller needs users:manage (else 403 PERMISSION_DENIED details.reason MISSING_PERMISSION) [assumption]. The key records the agent of the X-Agent header. NARROWING RULE (FEAT-034 §5.3, OD-AGT-1): an API key may mint, only while aikonic.keys.agent-mint.enabled is on, and only a key whose every dimension is equal or narrower than its own — scopes and suppliers a subset, environment the same, writes no higher on the KeyWrites ladder, products a subset, each numeric cap no higher, expiresAt no later. Either actorKind may mint: the device sign-up hands the agent a system key (OD-AGT-20). A cap the request leaves out or sends null is the parent's cap, never null where the parent has one [assumption]; the child keeps the parent's actorKind unless it asks for ai under a system parent (the narrower channel: the D-50 gate applies), and an ai parent asking for system is widening (dimension actorKind) [assumption]. Anything wider is 403 PERMISSION_DENIED details.reason SCOPE_WIDENING with details.dimensions naming each wider member (nextAction widen_scope_via_owner; three in an hour count as a soft velocity breach, FEAT-034 §12). The child is kind agt, mintedBy the parent's keyPrefix, and its principal is the parent's, fixed at mint: an onBehalfOf naming anyone else is 403 PERMISSION_DENIED details.reason PRINCIPAL_UNKNOWN, as is an onBehalfOf that is not an active Member of the tenant. A child never widens its ancestors' blast radius (FEAT-034 G4) [assumption; OD-AGT-6]: every call of a minted key also counts against each ancestor key's velocity and spend windows, and while an ancestor is PAUSED its descendants answer 423 KEY_PAUSED (resuming the ancestor resumes them), so minting N children never multiplies what one key may do. With aikonic.keys.agent-mint.enabled off an API key is refused 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED, as today (FEAT-034 AC-034-07 calls it SESSION_REQUIRED). Emits key.minted. MCP: the tool mint_api_key, on the same rule.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"claude-code-quickstart\",\n  \"scopes\": [\n    \"search:hotels\",\n    \"orders:write\",\n    \"orders:read\"\n  ],\n  \"environment\": \"sandbox\",\n  \"actorKind\": \"ai\",\n  \"writes\": \"hold\",\n  \"products\": [\n    \"hotels\"\n  ],\n  \"lookToBook\": 500,\n  \"reqPerSec\": 10,\n  \"holdsPerHour\": 60,\n  \"cancelsPerDay\": 50,\n  \"expiresAt\": \"2027-01-25T09:12:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Narrow an API key's scope — never widen it (FEAT-034 §5.3) — narrow",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/keys/:keyPrefix",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys",
                ":keyPrefix"
              ],
              "variable": [
                {
                  "key": "keyPrefix",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`PATCH /v1/keys/{keyPrefix}` · operationId `updateApiKeyScope`\n\nExample: Hold only, hotels only, 5 requests a second, expiring sooner\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.keys.agent-mint.enabled is on, else 501 NOT_IMPLEMENTED details.reason AGENT_KEYS_UNAVAILABLE [assumption: the scoped-key routes ride the agent-mint flag; FEAT-034 §10 names none for them]). Changes the scope of a live key in place; it takes effect on the key's next request, no re-issue (FEAT-034 §14). Every member is optional and only the members sent change. Each dimension may only stay equal or get narrower than the key's current scope: writes lower on the ladder (KeyWrites), products and scopes a subset, suppliers a subset (an absent list meaning every supplier, so setting one narrows), a numeric cap lower or a cap set where there was none, expiresAt earlier or set where there was none. Anything wider is 403 PERMISSION_DENIED details.reason SCOPE_WIDENING with details.dimensions naming each wider member, and nothing changes; widening means minting a new key from a person's session. name is free. A REVOKED or EXPIRED key is 409 CONFLICT details.reason KEY_NOT_ACTIVE (a PAUSED key may be narrowed). Human session only, as every /v1/keys route (an API key is 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED); needs keys:manage, as the other key routes [assumption: FEAT-034 §6 says users:manage], or to be the user who minted the key. Audited as key.narrowed with the before and after scope. MCP: excluded.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"writes\": \"hold\",\n  \"products\": [\n    \"hotels\"\n  ],\n  \"reqPerSec\": 5,\n  \"expiresAt\": \"2026-12-31T00:00:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Revoke an API key now (FEAT-020 §5 flow 6)",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/keys/:keyPrefix",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys",
                ":keyPrefix"
              ],
              "variable": [
                {
                  "key": "keyPrefix",
                  "value": "<string>",
                  "description": "The key to revoke, by the prefix the key list shows."
                }
              ]
            },
            "description": "`DELETE /v1/keys/{keyPrefix}` · operationId `revokeApiKey`\n\nNeeds keys:manage (session only, as built — an API key is 403 ACCESS_TOKEN_REQUIRED). Idempotent — revoking a revoked key is a 204. A key of another tenant is a 404, indistinguishable from none (FEAT-017). Revoke is a compare-and-set on the key: when the key keeps changing under it (a rotation starting at the same moment) it re-reads up to three times, then answers 409 CONFLICT with details.reason KEY_CHANGED_CONCURRENTLY — read the key list and revoke again. connect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.keys.agent-mint.enabled is on, else session only as above): an API key (either actorKind) may revoke itself or a key it minted; any other key is 404, as a key of another tenant. A PAUSED key can be revoked. Emits key.revoked. MCP: the tool revoke_api_key, on the same rule.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Rotate an API key with an overlap window (15 §1.1)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/keys/:keyPrefix/rotate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys",
                ":keyPrefix",
                "rotate"
              ],
              "variable": [
                {
                  "key": "keyPrefix",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`POST /v1/keys/{keyPrefix}/rotate` · operationId `rotateApiKey`\n\nMints the replacement — same name, scopes and allowlist, its secret shown once here — and keeps the old key working until overlapUntil (default 24h, at most 7d), its calls carrying a Deprecation header and a Sunset naming when it stops. After that the old key is refused. A key already rotating, or revoked, is a 409: rotate the replacement. connect-v1 1.57.0 (PROPOSAL, FEAT-034 §5.3; served when aikonic.keys.agent-mint.enabled is on, else as above): the successor carries the parent's whole scope — kind, writes, products, the caps, principal, agent and mintedBy — with expiresAt = min(now + the key's lifetime, the minting key's expiresAt), so a child never outlives its parent [assumption: the lifetime restarts at rotation]; the old key's successorKeyPrefix names it, and after overlapUntil the old key reads state EXPIRED. A key with an expiresAt rotated by itself or by a Member session may rotate only within 7 days of it, else 409 CONFLICT details.reason ROTATION_TOO_EARLY (details.expiresAt); an Owner or Admin session may rotate at any time (an emergency rotation) [assumption]. The key itself (an API key presenting its own keyPrefix) may rotate itself while the flag is on; any other key is 404. Emits key.rotated. MCP: unchanged, excluded."
          },
          "response": []
        },
        {
          "name": "Pause an API key by hand, with a reason (FEAT-034 §5.4) — pause",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/keys/:keyPrefix:pause",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys",
                ":keyPrefix:pause"
              ],
              "variable": [
                {
                  "key": "keyPrefix",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`POST /v1/keys/{keyPrefix}:pause` · operationId `pauseApiKey`\n\nExample: An Owner pauses a key it suspects is looping\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.keys.agent-mint.enabled is on, else 501 NOT_IMPLEMENTED details.reason AGENT_KEYS_UNAVAILABLE [assumption: the scoped-key routes ride the agent-mint flag]). An Owner or Admin pauses a key by hand with a reason (1 to 500 characters, audited). From the next request every call with the key is 423 PERMISSION_DENIED details.reason KEY_PAUSED, nextAction stop (components/responses/KeyPaused); holds and orders already made are untouched and holds expire on their TTL. The key's state is PAUSED (status stays ACTIVE), pausedReason is the reason and pausedEvidence carries rule MANUAL. The velocity and loop rules of aikonic.keys.velocity.enabled pause a key the same way, without this route (FEAT-034 §5.4). Emits key.paused (webhook and notification). Human session only (an API key is 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED, as every /v1/keys route); Owner or Admin, else 403 PERMISSION_DENIED details.reason OWNER_OR_ADMIN_ONLY. A key that is not ACTIVE (already PAUSED, REVOKED or EXPIRED) is 409 CONFLICT details.reason KEY_NOT_ACTIVE. MCP: excluded, never a tool (FEAT-034 §6).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Investigating a hold loop in the quick-start script\",\n  \"category\": \"other\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Resume a paused API key, with a reason — a person only (FEAT-034 §5.4) — resume",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/keys/:keyPrefix:resume",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys",
                ":keyPrefix:resume"
              ],
              "variable": [
                {
                  "key": "keyPrefix",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`POST /v1/keys/{keyPrefix}:resume` · operationId `resumeApiKey`\n\nExample: The Owner fixed the script and resumes the key\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.keys.agent-mint.enabled is on, else 501 NOT_IMPLEMENTED details.reason AGENT_KEYS_UNAVAILABLE [assumption: the scoped-key routes ride the agent-mint flag]). An Owner or Admin resumes a PAUSED key with a reason (1 to 500 characters) and a category (false_positive, fixed, limits_changed, other); both are audited, and a false_positive resume within 10 minutes of the pause feeds the false-positive pause metric (FEAT-034 §13). The key is ACTIVE from its next request; pausedReason and pausedEvidence clear. Emits key.resumed (webhook and notification). Resume is never reachable by an agent (AC-034-10): an API key or service token is 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED, as every /v1/keys route [assumption: FEAT-034 calls it SESSION_REQUIRED; OD-AGT-16]; a Member or Developer session is 403 PERMISSION_DENIED details.reason OWNER_OR_ADMIN_ONLY. A key that is not PAUSED is 409 CONFLICT details.reason KEY_NOT_PAUSED. MCP: excluded; no resume tool exists on any toolset.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"The quick-start retry loop is fixed\",\n  \"category\": \"fixed\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "A key's counters and spend today against its scope (FEAT-034 §5.7)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/keys/:keyPrefix/usage",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "keys",
                ":keyPrefix",
                "usage"
              ],
              "variable": [
                {
                  "key": "keyPrefix",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`GET /v1/keys/{keyPrefix}/usage` · operationId `getApiKeyUsage`\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.keys.agent-mint.enabled is on, else 501 NOT_IMPLEMENTED details.reason AGENT_KEYS_UNAVAILABLE [assumption: the scoped-key routes ride the agent-mint flag]). Today's counters (UTC day) of one key — requests, errors, searches, bookings, holds, cancels, approvals and spend — beside the scope's limits, the soft breaches today (the third pauses the key, FEAT-034 §5.4) and the key's state. Replays with the same Idempotency-Key are not counted (F-02). A human session with keys:manage reads any key of the tenant; an API key reads only itself and the keys it minted (another key is 404, as a key of another tenant). The M8 Agent keys screen reads it. MCP: excluded."
          },
          "response": []
        },
        {
          "name": "The activation rail — every step with its state and what is still needed (FEAT-030 §5.3)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/activation",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation"
              ]
            },
            "description": "`GET /v1/activation` · operationId `getActivation`\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). One rail per tenant, the same answer from any device. Steps carry state todo | in_progress | needs_you | in_review | done | blocked; every needs_you step names in needs[] the exact field or document still missing. States are derived from facts (for example sandbox_booking from firstSandboxOrderAt), never set by hand. Order is a suggestion: steps 3 to 8 may run in parallel. tier is null while the tenant is TRIAL; a tenant made LIVE before FEAT-030 reads tier LIVE_ENTERPRISE. Needs tenant:read, which the Owner and Admin role bundles carry (OD-ENR-8); the users:manage fallback is retired; a human session or an API key (a sandbox key reads the rail of the tenant it belongs to). Step keys, states, tier and buildingIntent are x-extensible-enum: a client renders the values it knows and tolerates the rest. connect-v1 1.47.0 (FEAT-033), additive: the rail is derived per account type (accountType). A BUSINESS tenant keeps the nine steps above, unchanged. An INDIVIDUAL tenant has five: verify_email, sandbox_booking, identity_verification, terms, go_live — no business profile, no business verification, no money model, no wallet (an individual pays each booking by card through the payment provider's hosted checkout) and no API certification. identity_verification is done when an identity check (POST /v1/activation/identity-checks) is VERIFIED; its needs[] names what is missing (taxId). go_live is blocked until identity_verification and terms are done, and one Owner's sign-off is enough (GET /admin/v1/go-live requiredSignoffs 1). identityCheck carries the latest check, null until one ran; the full identity number is never returned. connect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b): flow says which rail the tenant is on (NINE_STEP, as above, or CAPPED) and every step carries required. On CAPPED the required steps are business_verification (GST register look-up, confirm, signatory check; hosted KYB the fallback), money_model (WALLET, CARD with a saved method, or the ops-routed models) and terms, plus fund_wallet only when the model is WALLET (verify_email is required too, always done at sign-up); every other step is required false: sandbox_booking and certification (recommended), business_profile (completed by the registration confirm on the India path; elsewhere the hosted KYB collects the company details) and go_live (done when the tenant turns live). The transition that completes the last required step activates the tenant itself (TRIAL to LIVE, actor system:capped-auto-live) with tier LIVE_CAPPED and a daily cap (limits; GET /v1/activation/limits), with no go-live signoff and no ops approval; go_live then reads done. A step in_review (a KYB case the provider reviews) never completes it. With the flag off every tenant reads NINE_STEP and nothing changes; LIVE_STANDARD and LIVE_ENTERPRISE stay on the OD-870 ops path. The CAPPED flow (connect-v1 1.51.0) applies to BUSINESS tenants only: an INDIVIDUAL tenant keeps the five-step rail of connect-v1 1.47.0 (FEAT-033) above, never reads flow CAPPED and is never activated by it. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2; served when aikonic.activation.capped-flow.enabled is on): the CAPPED rail lists, in this order, verify_email, business_profile, business_verification, terms, money_model, sandbox_booking (required false) and go_live; fund_wallet and certification are not listed on CAPPED. Every listed step but sandbox_booking is required. business_profile is done when country, businessStructure, industry, website (or noWebsite with productDescription), phone and monthlyVolume are set (PATCH /v1/activation/profile). business_verification is done only when the registration is confirmed (by GSTIN, or by scheme and number) or the hosted KYB documents are approved, the signatory matched (POST /v1/activation/signatory-checks) and the business people are submitted (PUT /v1/activation/business-people with owners, or noOwnerAt25Percent true). The CAPPED flow is for BUSINESS tenants only; a person selling in their own name signs up as an INDIVIDUAL account (FEAT-033) and is never on this rail. money_model is done for CARD with a saved method, for WALLET with an elected currency and a wallet balance above zero, and for OWN_ACCREDITATION or BYOC when the operator review (OpsApproval kind MONEY_MODEL_REVIEW) is approved; it reads in_review meanwhile. go_live is required and reads done when the tenant is LIVE: on CAPPED going live is now the Owner's explicit POST /v1/activation:go-live, which replaces the automatic promotion of 1.51.0 (no step transition activates the tenant by itself any more). workEmail says whether a work e-mail must be proved first (POST /v1/activation/work-email:send-code, then :confirm); go-live is refused while workEmail.required is true and workEmail.verifiedAt is null. Test mode (the sandbox twin and sandbox keys) is unaffected. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): on CAPPED, business_verification is the verification provider's verdict. It is done only when the provider's case is verified (with the registration confirmed where the country has a register, the signatory matched and the business people submitted); submitting the forms is not enough. businessVerification carries the case: status not_submitted leaves the step as round 2 reads it (what is still to fill), in_progress (submitted, the provider has not answered) reads in_progress, in_review reads in_review, needs_more reads needs_you with needs[] the outstanding requirement codes (businessVerification.outstanding), rejected reads blocked, verified reads done. The provider is routed per country (deployment settings aikonic.kyb.provider.<country>, default aikonic.kyb.provider; harness on DEV and SANDBOX only, stripe_connect, or a registered India KYC adapter). go_live stays refused with 409 CONFLICT PREREQUISITES_OPEN (details.open business_verification) until the verdict is verified; once verified, going live is immediate. A client re-reads this rail while businessVerification.status is in_progress or in_review. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18d go-live review; amended 2026-10-29, same version): going live on CAPPED is an operator review. POST /v1/activation:go-live opens it and goLiveReview carries it (GoLiveReview: status, submittedAt, decidedAt, expectedBy, note, and per product its status and checks business_details and test_calls); the tenant stays TRIAL until an operator approves. go_live reads in_review while the review is in_review, needs_you (needs[] go_live.changes_requested) on changes_requested, blocked (needs[] go_live.rejected) on rejected and done once approved (tier LIVE_CAPPED). It replaces \"once verified, going live is immediate\" above. A client re-reads this rail while goLiveReview.status is in_review. sandbox_booking is required on CAPPED (owner direction 2026-10-29): the test calls (search, reprice, orders, cancel in the sandbox) for every requested product must be seen before an operator can approve; the request itself may come first, and sandbox_booking's needs[] names what is missing (<product>.<verb>)."
          },
          "response": []
        },
        {
          "name": "Tell us about the business — rail step 3 (FEAT-030 §5.3, §7) — india",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "profile"
              ]
            },
            "description": "`PATCH /v1/activation/profile` · operationId `updateActivationProfile`\n\nExample: An Indian travel agency with its GSTIN\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). Partial update of the tenant's business profile: a member that is absent is unchanged. Step business_profile is done once businessType, country, website and volumeBand are all set. gstin and pan are accepted only when country is IN (400 VALIDATION details.reason INDIA_ONLY_FIELD otherwise); they feed FEAT-026 tax setup so they are asked once. Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs users:manage. Idempotency-Key required. Audited. connect-v1 1.51.0 (PROPOSAL, FEAT-030 §18b, OD-ENR-30): buildingIntent may be set here after sign-up (the Welcome screen's question); it reads back as ActivationState.buildingIntent and changes no step. Once a GST registration is confirmed (ActivationState.registrationConfirmedAt set), a body naming a gstin or pan different from the confirmed one is 409 CONFLICT details.reason REGISTRATION_LOCKED (details.field names it) and nothing is changed: a confirmed registration changes only through POST /v1/activation/business-registration:confirm. Sending the confirmed values again is a no-op for them. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2): the profile also takes businessStructure (the country's legal form label, a sole proprietorship being one of them), industry (with industryOther when it is OTHER), website or noWebsite true with productDescription (10 to 500 characters, required when noWebsite is true: 400 VALIDATION details.field productDescription otherwise), phone (E.164) and monthlyVolume (a band in a currency; the bands per currency are on MonthlyVolume). On the CAPPED flow step business_profile is done, and complete is true, when country, businessStructure, industry, website or noWebsite with productDescription, phone and monthlyVolume are set; businessType and volumeBand stay for the NINE_STEP rail and are not read on CAPPED. industryOther without industry OTHER, or website together with noWebsite true, is 400 VALIDATION with details.field naming the member. The website also sets the domain a work e-mail must be on (POST /v1/activation/work-email:send-code). With the capped flow off the new members are 400 VALIDATION details.field naming the member.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"businessType\": \"TRAVEL_AGENCY\",\n  \"country\": \"IN\",\n  \"website\": \"https://acme-travel.example\",\n  \"volumeBand\": \"FROM_10K_TO_100K\",\n  \"gstin\": \"27AAPFU0939F1ZV\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Tell us about the business — rail step 3 (FEAT-030 §5.3, §7) — buildingIntentOnly",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "profile"
              ]
            },
            "description": "`PATCH /v1/activation/profile` · operationId `updateActivationProfile`\n\nExample: The \"What are you building?\" answer, given on the Welcome screen (v1.51.0 PROPOSAL)\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). Partial update of the tenant's business profile: a member that is absent is unchanged. Step business_profile is done once businessType, country, website and volumeBand are all set. gstin and pan are accepted only when country is IN (400 VALIDATION details.reason INDIA_ONLY_FIELD otherwise); they feed FEAT-026 tax setup so they are asked once. Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs users:manage. Idempotency-Key required. Audited. connect-v1 1.51.0 (PROPOSAL, FEAT-030 §18b, OD-ENR-30): buildingIntent may be set here after sign-up (the Welcome screen's question); it reads back as ActivationState.buildingIntent and changes no step. Once a GST registration is confirmed (ActivationState.registrationConfirmedAt set), a body naming a gstin or pan different from the confirmed one is 409 CONFLICT details.reason REGISTRATION_LOCKED (details.field names it) and nothing is changed: a confirmed registration changes only through POST /v1/activation/business-registration:confirm. Sending the confirmed values again is a no-op for them. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2): the profile also takes businessStructure (the country's legal form label, a sole proprietorship being one of them), industry (with industryOther when it is OTHER), website or noWebsite true with productDescription (10 to 500 characters, required when noWebsite is true: 400 VALIDATION details.field productDescription otherwise), phone (E.164) and monthlyVolume (a band in a currency; the bands per currency are on MonthlyVolume). On the CAPPED flow step business_profile is done, and complete is true, when country, businessStructure, industry, website or noWebsite with productDescription, phone and monthlyVolume are set; businessType and volumeBand stay for the NINE_STEP rail and are not read on CAPPED. industryOther without industry OTHER, or website together with noWebsite true, is 400 VALIDATION with details.field naming the member. The website also sets the domain a work e-mail must be on (POST /v1/activation/work-email:send-code). With the capped flow off the new members are 400 VALIDATION details.field naming the member.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"buildingIntent\": \"BOOKING_SITE\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Tell us about the business — rail step 3 (FEAT-030 §5.3, §7) — companyRound2",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "profile"
              ]
            },
            "description": "`PATCH /v1/activation/profile` · operationId `updateActivationProfile`\n\nExample: A UK private limited company with a website (v1.63.0 PROPOSAL)\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). Partial update of the tenant's business profile: a member that is absent is unchanged. Step business_profile is done once businessType, country, website and volumeBand are all set. gstin and pan are accepted only when country is IN (400 VALIDATION details.reason INDIA_ONLY_FIELD otherwise); they feed FEAT-026 tax setup so they are asked once. Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs users:manage. Idempotency-Key required. Audited. connect-v1 1.51.0 (PROPOSAL, FEAT-030 §18b, OD-ENR-30): buildingIntent may be set here after sign-up (the Welcome screen's question); it reads back as ActivationState.buildingIntent and changes no step. Once a GST registration is confirmed (ActivationState.registrationConfirmedAt set), a body naming a gstin or pan different from the confirmed one is 409 CONFLICT details.reason REGISTRATION_LOCKED (details.field names it) and nothing is changed: a confirmed registration changes only through POST /v1/activation/business-registration:confirm. Sending the confirmed values again is a no-op for them. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2): the profile also takes businessStructure (the country's legal form label, a sole proprietorship being one of them), industry (with industryOther when it is OTHER), website or noWebsite true with productDescription (10 to 500 characters, required when noWebsite is true: 400 VALIDATION details.field productDescription otherwise), phone (E.164) and monthlyVolume (a band in a currency; the bands per currency are on MonthlyVolume). On the CAPPED flow step business_profile is done, and complete is true, when country, businessStructure, industry, website or noWebsite with productDescription, phone and monthlyVolume are set; businessType and volumeBand stay for the NINE_STEP rail and are not read on CAPPED. industryOther without industry OTHER, or website together with noWebsite true, is 400 VALIDATION with details.field naming the member. The website also sets the domain a work e-mail must be on (POST /v1/activation/work-email:send-code). With the capped flow off the new members are 400 VALIDATION details.field naming the member.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"country\": \"GB\",\n  \"businessStructure\": \"Private limited company\",\n  \"industry\": \"TRAVEL_AGENCY\",\n  \"website\": \"https://sampletravels.co.uk\",\n  \"phone\": \"+442071234567\",\n  \"monthlyVolume\": {\n    \"band\": \"BAND_2\",\n    \"currency\": \"GBP\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Tell us about the business — rail step 3 (FEAT-030 §5.3, §7) — soleProprietorNoWebsite",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "profile"
              ]
            },
            "description": "`PATCH /v1/activation/profile` · operationId `updateActivationProfile`\n\nExample: An Indian sole proprietorship with no website (v1.63.0 PROPOSAL)\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). Partial update of the tenant's business profile: a member that is absent is unchanged. Step business_profile is done once businessType, country, website and volumeBand are all set. gstin and pan are accepted only when country is IN (400 VALIDATION details.reason INDIA_ONLY_FIELD otherwise); they feed FEAT-026 tax setup so they are asked once. Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs users:manage. Idempotency-Key required. Audited. connect-v1 1.51.0 (PROPOSAL, FEAT-030 §18b, OD-ENR-30): buildingIntent may be set here after sign-up (the Welcome screen's question); it reads back as ActivationState.buildingIntent and changes no step. Once a GST registration is confirmed (ActivationState.registrationConfirmedAt set), a body naming a gstin or pan different from the confirmed one is 409 CONFLICT details.reason REGISTRATION_LOCKED (details.field names it) and nothing is changed: a confirmed registration changes only through POST /v1/activation/business-registration:confirm. Sending the confirmed values again is a no-op for them. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2): the profile also takes businessStructure (the country's legal form label, a sole proprietorship being one of them), industry (with industryOther when it is OTHER), website or noWebsite true with productDescription (10 to 500 characters, required when noWebsite is true: 400 VALIDATION details.field productDescription otherwise), phone (E.164) and monthlyVolume (a band in a currency; the bands per currency are on MonthlyVolume). On the CAPPED flow step business_profile is done, and complete is true, when country, businessStructure, industry, website or noWebsite with productDescription, phone and monthlyVolume are set; businessType and volumeBand stay for the NINE_STEP rail and are not read on CAPPED. industryOther without industry OTHER, or website together with noWebsite true, is 400 VALIDATION with details.field naming the member. The website also sets the domain a work e-mail must be on (POST /v1/activation/work-email:send-code). With the capped flow off the new members are 400 VALIDATION details.field naming the member.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"country\": \"IN\",\n  \"businessStructure\": \"Sole proprietorship\",\n  \"industry\": \"INDEPENDENT_ADVISOR\",\n  \"noWebsite\": true,\n  \"productDescription\": \"Holiday packages and flight bookings for families in Pune, sold over phone and chat.\",\n  \"phone\": \"+919876543210\",\n  \"monthlyVolume\": {\n    \"band\": \"BAND_1\",\n    \"currency\": \"INR\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Start or resume the business verification (KYB) session — rail step 4 (FEAT-030 §5.4) — request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/verification-sessions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "verification-sessions"
              ]
            },
            "description": "`POST /v1/activation/verification-sessions` · operationId `startVerificationSession`\n\nExample: Come back to the activation page when done\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-5, published in 1.18.0). The provider is per deployment, aikonic.kyb.provider: unset by default, so the route answers 501 NOT_IMPLEMENTED details.reason KYB_UNAVAILABLE; harness (the mock harness's fake provider, refused at boot above SANDBOX) is set on DEV and SANDBOX; the real provider waits on OD-ENR-2. Opens the provider's hosted session, or resumes the open one (same providerRef), and answers where to send the user: hostedUrl (a redirect) or embedToken (the provider's embedded component), with the status and the provider's outstanding list. Apiq prefills what it knows (legal name guess, domain, country) and stores only the provider reference, outcome, risk tier and timestamps, never a document. A tenant already verified is 409 CONFLICT details.reason ALREADY_VERIFIED. When the provider cannot be reached the answer is 502 UPSTREAM_UNAVAILABLE (retryable) and the rail shows step business_verification blocked; a deployment with no provider configured is 501 NOT_IMPLEMENTED details.reason KYB_UNAVAILABLE. Human session only; the caller must be an Owner (role OWNER or CUSTOMER_ADMIN, as OD-870), otherwise 403 PERMISSION_DENIED details.reason OWNER_ONLY. Idempotency-Key required. Audited.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"returnUrl\": \"https://console.aikonic.example/console/activate\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Check the person's identity (India PAN) — the individual rail's identity_verification step (FEAT-033) — inPan",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/identity-checks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "identity-checks"
              ]
            },
            "description": "`POST /v1/activation/identity-checks` · operationId `startIdentityCheck`\n\nExample: A personal PAN and the name as printed on the card\n\nconnect-v1 1.47.0 (FEAT-033, OD-906 proposed). Checks one identity number against the identity provider and answers the result at once (KYC-lite: no document, no image, no upload). The only scheme is IN_PAN: taxId.value must match ^[A-Z]{5}[0-9]{4}[A-Z]$ (400 VALIDATION details.reason TAX_ID_FORMAT otherwise; the server checks it, not a schema pattern, so other schemes can be added), and for an INDIVIDUAL account the fourth character must be P, a personal PAN (400 VALIDATION details.reason PERSONAL_PAN_REQUIRED). Another scheme is 400 VALIDATION details.reason UNSUPPORTED_SCHEME. fullName is the name exactly as on the PAN card; the provider compares it (nameMatched). A check the provider answers is a 200 whatever its outcome: VERIFIED ticks rail step identity_verification; FAILED (failureReason NAME_MISMATCH, NOT_FOUND or INACTIVE) leaves the step needs_you and the person may try again; PENDING means the provider answers later and GET /v1/activation shows the outcome. The latest check also reads back as ActivationState.identityCheck. The full number is never stored, logged or returned: the platform keeps the masked value (first five and last character), a keyed hash of the number (to recognise a repeat), the result, the name and the time — no document and no image. A tenant whose latest check is VERIFIED is 409 CONFLICT details.reason ALREADY_VERIFIED. The provider is per deployment, aikonic.identity-check.provider: unset by default, so the route answers 501 NOT_IMPLEMENTED details.reason IDENTITY_CHECK_UNAVAILABLE; harness (the mock harness's deterministic provider, refused at boot above SANDBOX, as the KYB harness) is set on DEV and SANDBOX; a real provider waits on OD-906. When the provider cannot be reached the answer is 502 UPSTREAM_UNAVAILABLE (retryable). Human session only; the caller must be an Owner (role OWNER or CUSTOMER_ADMIN, as OD-870), otherwise 403 PERMISSION_DENIED details.reason OWNER_ONLY. Idempotency-Key required: a replay answers the first check, the number is not sent to the provider twice. Audited, with the masked value only.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"taxId\": {\n    \"scheme\": \"IN_PAN\",\n    \"value\": \"ABCPE1234F\"\n  },\n  \"fullName\": \"Priya Sharma\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Choose how the tenant pays suppliers — rail step 5 (FEAT-030 §5.3, 08 §7) — wallet",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/money-model",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "money-model"
              ]
            },
            "description": "`POST /v1/activation/money-model` · operationId `electMoneyModel`\n\nExample: Prepaid wallet in USD\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). Elects WALLET (prepaid through Apiq's own credential sets; currency names the wallet's settlement currency and is required), OWN_ACCREDITATION (own IATA / ARC, BSP or ARC cash; the accreditation check routes to ops) or BYOC (bring your own credentials, 08 §7; routes to ops). A TRIAL tenant may elect again and the last election wins; once the tenant is LIVE a change is 409 CONFLICT details.reason MONEY_MODEL_LOCKED (an ops change). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required. Audited. connect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25): CARD, pay each booking with a saved card or UPI method. currency is not read. Step money_model is done once the tenant has a saved method (POST /v1/payments/methods), needs_you (needs[] payment_method) until then; orders then pay with payment.mode SAVED_METHOD. Where the flag is off CARD is 400 VALIDATION details.field model. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2, OD-ENR-34): OWN_ACCREDITATION takes accreditationNumber (the IATA, ARC or TIDS number, at most 20 characters) and BYOC takes contractReference (the supplier contract or account reference, at most 200 characters); on the CAPPED flow each is required for its model (400 VALIDATION details.field naming it) and the election opens an operator approval of kind MONEY_MODEL_REVIEW (OpsApproval; scope.tenantId the tenant, effect the model and the reference). Step money_model then reads in_review and MoneyModelElection.reviewState PENDING until an operator decides: APPROVED completes the step, REJECTED returns it to needs_you (elect again). Each field is ignored for the other models; on NINE_STEP it is recorded for the ops check that already routes there. On CAPPED, WALLET is done with an elected currency and a wallet balance above zero (fund_wallet is not listed on CAPPED) and CARD with a saved method.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"model\": \"WALLET\",\n  \"currency\": \"USD\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Choose how the tenant pays suppliers — rail step 5 (FEAT-030 §5.3, 08 §7) — card",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/money-model",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "money-model"
              ]
            },
            "description": "`POST /v1/activation/money-model` · operationId `electMoneyModel`\n\nExample: Pay each booking with a saved card or UPI method (v1.51.0 PROPOSAL)\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). Elects WALLET (prepaid through Apiq's own credential sets; currency names the wallet's settlement currency and is required), OWN_ACCREDITATION (own IATA / ARC, BSP or ARC cash; the accreditation check routes to ops) or BYOC (bring your own credentials, 08 §7; routes to ops). A TRIAL tenant may elect again and the last election wins; once the tenant is LIVE a change is 409 CONFLICT details.reason MONEY_MODEL_LOCKED (an ops change). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required. Audited. connect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25): CARD, pay each booking with a saved card or UPI method. currency is not read. Step money_model is done once the tenant has a saved method (POST /v1/payments/methods), needs_you (needs[] payment_method) until then; orders then pay with payment.mode SAVED_METHOD. Where the flag is off CARD is 400 VALIDATION details.field model. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2, OD-ENR-34): OWN_ACCREDITATION takes accreditationNumber (the IATA, ARC or TIDS number, at most 20 characters) and BYOC takes contractReference (the supplier contract or account reference, at most 200 characters); on the CAPPED flow each is required for its model (400 VALIDATION details.field naming it) and the election opens an operator approval of kind MONEY_MODEL_REVIEW (OpsApproval; scope.tenantId the tenant, effect the model and the reference). Step money_model then reads in_review and MoneyModelElection.reviewState PENDING until an operator decides: APPROVED completes the step, REJECTED returns it to needs_you (elect again). Each field is ignored for the other models; on NINE_STEP it is recorded for the ops check that already routes there. On CAPPED, WALLET is done with an elected currency and a wallet balance above zero (fund_wallet is not listed on CAPPED) and CARD with a saved method.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"model\": \"CARD\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Choose how the tenant pays suppliers — rail step 5 (FEAT-030 §5.3, 08 §7) — ownAccreditation",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/money-model",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "money-model"
              ]
            },
            "description": "`POST /v1/activation/money-model` · operationId `electMoneyModel`\n\nExample: Own IATA accreditation, its number for the operator review (v1.63.0 PROPOSAL)\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). Elects WALLET (prepaid through Apiq's own credential sets; currency names the wallet's settlement currency and is required), OWN_ACCREDITATION (own IATA / ARC, BSP or ARC cash; the accreditation check routes to ops) or BYOC (bring your own credentials, 08 §7; routes to ops). A TRIAL tenant may elect again and the last election wins; once the tenant is LIVE a change is 409 CONFLICT details.reason MONEY_MODEL_LOCKED (an ops change). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required. Audited. connect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25): CARD, pay each booking with a saved card or UPI method. currency is not read. Step money_model is done once the tenant has a saved method (POST /v1/payments/methods), needs_you (needs[] payment_method) until then; orders then pay with payment.mode SAVED_METHOD. Where the flag is off CARD is 400 VALIDATION details.field model. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2, OD-ENR-34): OWN_ACCREDITATION takes accreditationNumber (the IATA, ARC or TIDS number, at most 20 characters) and BYOC takes contractReference (the supplier contract or account reference, at most 200 characters); on the CAPPED flow each is required for its model (400 VALIDATION details.field naming it) and the election opens an operator approval of kind MONEY_MODEL_REVIEW (OpsApproval; scope.tenantId the tenant, effect the model and the reference). Step money_model then reads in_review and MoneyModelElection.reviewState PENDING until an operator decides: APPROVED completes the step, REJECTED returns it to needs_you (elect again). Each field is ignored for the other models; on NINE_STEP it is recorded for the ops check that already routes there. On CAPPED, WALLET is done with an elected currency and a wallet balance above zero (fund_wallet is not listed on CAPPED) and CARD with a saved method.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"model\": \"OWN_ACCREDITATION\",\n  \"accreditationNumber\": \"14312345\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Choose how the tenant pays suppliers — rail step 5 (FEAT-030 §5.3, 08 §7) — byoc",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/money-model",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "money-model"
              ]
            },
            "description": "`POST /v1/activation/money-model` · operationId `electMoneyModel`\n\nExample: Bring your own supplier contract, its reference for the operator review (v1.63.0 PROPOSAL)\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). Elects WALLET (prepaid through Apiq's own credential sets; currency names the wallet's settlement currency and is required), OWN_ACCREDITATION (own IATA / ARC, BSP or ARC cash; the accreditation check routes to ops) or BYOC (bring your own credentials, 08 §7; routes to ops). A TRIAL tenant may elect again and the last election wins; once the tenant is LIVE a change is 409 CONFLICT details.reason MONEY_MODEL_LOCKED (an ops change). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required. Audited. connect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25): CARD, pay each booking with a saved card or UPI method. currency is not read. Step money_model is done once the tenant has a saved method (POST /v1/payments/methods), needs_you (needs[] payment_method) until then; orders then pay with payment.mode SAVED_METHOD. Where the flag is off CARD is 400 VALIDATION details.field model. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2, OD-ENR-34): OWN_ACCREDITATION takes accreditationNumber (the IATA, ARC or TIDS number, at most 20 characters) and BYOC takes contractReference (the supplier contract or account reference, at most 200 characters); on the CAPPED flow each is required for its model (400 VALIDATION details.field naming it) and the election opens an operator approval of kind MONEY_MODEL_REVIEW (OpsApproval; scope.tenantId the tenant, effect the model and the reference). Step money_model then reads in_review and MoneyModelElection.reviewState PENDING until an operator decides: APPROVED completes the step, REJECTED returns it to needs_you (elect again). Each field is ignored for the other models; on NINE_STEP it is recorded for the ops check that already routes there. On CAPPED, WALLET is done with an elected currency and a wallet balance above zero (fund_wallet is not listed on CAPPED) and CARD with a saved method.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"model\": \"BYOC\",\n  \"contractReference\": \"SAMPLE-AIRLINE-NDC-2026-0042\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Accept the current terms version — rail step 8 (FEAT-030 §5.3) — code",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/terms:accept",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "terms:accept"
              ]
            },
            "description": "`POST /v1/activation/terms:accept` · operationId `acceptActivationTerms`\n\nExample: Accept with an authenticator code\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). termsVersion must equal GET /v1/activation terms.currentVersion; an older one is 409 CONFLICT details.reason TERMS_VERSION_STALE with details.currentVersion. Accepting the version already accepted is a 200 with the original acceptance. Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY) and step-up guarded with the body step-up pair of DR-53 / DR-54: exactly one of stepUpCode (a current code from the caller's MFA factor) or stepUpAssertion (a WebAuthn passkey assertion). A missing, wrong or doubled proof is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED, STEP_UP_LOCKED when the user is locked out, and STEP_UP_UNAVAILABLE [assumption] when the caller has no MFA factor to step up with (enrol one at /v1/auth/mfa; on the CAPPED flow, confirm with an e-mailed code instead, below). Idempotency-Key required. Audited (activation.terms_accepted). connect-v1 1.52.0 (PROPOSAL; CAPPED flow only, aikonic.activation.capped-flow.enabled; FEAT-030 §18b, OD-ENR-3): a third proof, emailCode — the 6-digit code POST /v1/activation/terms:send-code e-mailed to the signed-in Owner — so an Owner with no authenticator (every new sign-up) can accept. The body carries exactly one of stepUpCode, stepUpAssertion or emailCode; none or more than one is 403 STEP_UP_FAILED. A wrong, expired, voided (a newer code was sent) or never-sent emailCode is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED; the 5th wrong attempt spends the code and further attempts are STEP_UP_LOCKED until a new code is sent (the code lives 15 minutes, as the sign-up code). A code is spent by a successful acceptance. On the NINE_STEP flow emailCode is 400 VALIDATION details.field emailCode, details.reason FLOW_NOT_CAPPED (step up with stepUpCode or stepUpAssertion there); an emailCode that is not six digits is 400 VALIDATION details.field emailCode. On CAPPED, STEP_UP_UNAVAILABLE answers only a stepUpCode or stepUpAssertion from a caller with no factor; its message points at terms:send-code. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2, OD-ENR-3 owner direction 2026-10-28): on the CAPPED flow the acceptance is a click-wrap — the body may carry termsVersion and no proof at all (no stepUpCode, stepUpAssertion or emailCode); the Owner's human session is the proof (the e-mail was confirmed at sign-up), and the console sends termsVersion only, once the Owner has ticked \"I agree\". Every other rule stands: human session only, Owner only (OWNER_ONLY), termsVersion must be the current one (TERMS_VERSION_STALE), the version already accepted answers the original acceptance, Idempotency-Key required, audited. A proof that is sent anyway on CAPPED is still judged as above (exactly one, a wrong one 403 STEP_UP_FAILED), so a client of 1.52.0 keeps working. The NINE_STEP flow is unchanged: no proof there is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED (or STEP_UP_UNAVAILABLE for a caller with no factor). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version, OD-ENR-37): where the tenant's country is routed to the hosted onboarding provider (ActivationState.businessVerification.provider stripe_connect), the terms the Owner accepts include that provider's connected-account agreement, shown by the console as a fourth document beside the platform's own; the acceptance is forwarded to the tenant's connected account as its terms acceptance (the date, the caller's IP address and user agent), at once when the account exists and otherwise when the business verification opens it. Same request and answer; termsVersion still names the platform's version.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"termsVersion\": \"2026-10\",\n  \"stepUpCode\": \"000000\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Accept the current terms version — rail step 8 (FEAT-030 §5.3) — emailCode",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/terms:accept",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "terms:accept"
              ]
            },
            "description": "`POST /v1/activation/terms:accept` · operationId `acceptActivationTerms`\n\nExample: Accept with the code e-mailed by POST /v1/activation/terms:send-code (CAPPED flow, v1.52.0)\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). termsVersion must equal GET /v1/activation terms.currentVersion; an older one is 409 CONFLICT details.reason TERMS_VERSION_STALE with details.currentVersion. Accepting the version already accepted is a 200 with the original acceptance. Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY) and step-up guarded with the body step-up pair of DR-53 / DR-54: exactly one of stepUpCode (a current code from the caller's MFA factor) or stepUpAssertion (a WebAuthn passkey assertion). A missing, wrong or doubled proof is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED, STEP_UP_LOCKED when the user is locked out, and STEP_UP_UNAVAILABLE [assumption] when the caller has no MFA factor to step up with (enrol one at /v1/auth/mfa; on the CAPPED flow, confirm with an e-mailed code instead, below). Idempotency-Key required. Audited (activation.terms_accepted). connect-v1 1.52.0 (PROPOSAL; CAPPED flow only, aikonic.activation.capped-flow.enabled; FEAT-030 §18b, OD-ENR-3): a third proof, emailCode — the 6-digit code POST /v1/activation/terms:send-code e-mailed to the signed-in Owner — so an Owner with no authenticator (every new sign-up) can accept. The body carries exactly one of stepUpCode, stepUpAssertion or emailCode; none or more than one is 403 STEP_UP_FAILED. A wrong, expired, voided (a newer code was sent) or never-sent emailCode is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED; the 5th wrong attempt spends the code and further attempts are STEP_UP_LOCKED until a new code is sent (the code lives 15 minutes, as the sign-up code). A code is spent by a successful acceptance. On the NINE_STEP flow emailCode is 400 VALIDATION details.field emailCode, details.reason FLOW_NOT_CAPPED (step up with stepUpCode or stepUpAssertion there); an emailCode that is not six digits is 400 VALIDATION details.field emailCode. On CAPPED, STEP_UP_UNAVAILABLE answers only a stepUpCode or stepUpAssertion from a caller with no factor; its message points at terms:send-code. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2, OD-ENR-3 owner direction 2026-10-28): on the CAPPED flow the acceptance is a click-wrap — the body may carry termsVersion and no proof at all (no stepUpCode, stepUpAssertion or emailCode); the Owner's human session is the proof (the e-mail was confirmed at sign-up), and the console sends termsVersion only, once the Owner has ticked \"I agree\". Every other rule stands: human session only, Owner only (OWNER_ONLY), termsVersion must be the current one (TERMS_VERSION_STALE), the version already accepted answers the original acceptance, Idempotency-Key required, audited. A proof that is sent anyway on CAPPED is still judged as above (exactly one, a wrong one 403 STEP_UP_FAILED), so a client of 1.52.0 keeps working. The NINE_STEP flow is unchanged: no proof there is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED (or STEP_UP_UNAVAILABLE for a caller with no factor). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version, OD-ENR-37): where the tenant's country is routed to the hosted onboarding provider (ActivationState.businessVerification.provider stripe_connect), the terms the Owner accepts include that provider's connected-account agreement, shown by the console as a fourth document beside the platform's own; the acceptance is forwarded to the tenant's connected account as its terms acceptance (the date, the caller's IP address and user agent), at once when the account exists and otherwise when the business verification opens it. Same request and answer; termsVersion still names the platform's version.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"termsVersion\": \"2026-10\",\n  \"emailCode\": \"482913\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Accept the current terms version — rail step 8 (FEAT-030 §5.3) — cappedClickWrap",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/terms:accept",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "terms:accept"
              ]
            },
            "description": "`POST /v1/activation/terms:accept` · operationId `acceptActivationTerms`\n\nExample: Accept on the CAPPED flow with no proof — the Owner ticked \"I agree\" (v1.63.0 PROPOSAL)\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-2, published in 1.18.0). termsVersion must equal GET /v1/activation terms.currentVersion; an older one is 409 CONFLICT details.reason TERMS_VERSION_STALE with details.currentVersion. Accepting the version already accepted is a 200 with the original acceptance. Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY) and step-up guarded with the body step-up pair of DR-53 / DR-54: exactly one of stepUpCode (a current code from the caller's MFA factor) or stepUpAssertion (a WebAuthn passkey assertion). A missing, wrong or doubled proof is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED, STEP_UP_LOCKED when the user is locked out, and STEP_UP_UNAVAILABLE [assumption] when the caller has no MFA factor to step up with (enrol one at /v1/auth/mfa; on the CAPPED flow, confirm with an e-mailed code instead, below). Idempotency-Key required. Audited (activation.terms_accepted). connect-v1 1.52.0 (PROPOSAL; CAPPED flow only, aikonic.activation.capped-flow.enabled; FEAT-030 §18b, OD-ENR-3): a third proof, emailCode — the 6-digit code POST /v1/activation/terms:send-code e-mailed to the signed-in Owner — so an Owner with no authenticator (every new sign-up) can accept. The body carries exactly one of stepUpCode, stepUpAssertion or emailCode; none or more than one is 403 STEP_UP_FAILED. A wrong, expired, voided (a newer code was sent) or never-sent emailCode is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED; the 5th wrong attempt spends the code and further attempts are STEP_UP_LOCKED until a new code is sent (the code lives 15 minutes, as the sign-up code). A code is spent by a successful acceptance. On the NINE_STEP flow emailCode is 400 VALIDATION details.field emailCode, details.reason FLOW_NOT_CAPPED (step up with stepUpCode or stepUpAssertion there); an emailCode that is not six digits is 400 VALIDATION details.field emailCode. On CAPPED, STEP_UP_UNAVAILABLE answers only a stepUpCode or stepUpAssertion from a caller with no factor; its message points at terms:send-code. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2, OD-ENR-3 owner direction 2026-10-28): on the CAPPED flow the acceptance is a click-wrap — the body may carry termsVersion and no proof at all (no stepUpCode, stepUpAssertion or emailCode); the Owner's human session is the proof (the e-mail was confirmed at sign-up), and the console sends termsVersion only, once the Owner has ticked \"I agree\". Every other rule stands: human session only, Owner only (OWNER_ONLY), termsVersion must be the current one (TERMS_VERSION_STALE), the version already accepted answers the original acceptance, Idempotency-Key required, audited. A proof that is sent anyway on CAPPED is still judged as above (exactly one, a wrong one 403 STEP_UP_FAILED), so a client of 1.52.0 keeps working. The NINE_STEP flow is unchanged: no proof there is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED (or STEP_UP_UNAVAILABLE for a caller with no factor). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version, OD-ENR-37): where the tenant's country is routed to the hosted onboarding provider (ActivationState.businessVerification.provider stripe_connect), the terms the Owner accepts include that provider's connected-account agreement, shown by the console as a fourth document beside the platform's own; the acceptance is forwarded to the tenant's connected account as its terms acceptance (the date, the caller's IP address and user agent), at once when the account exists and otherwise when the business verification opens it. Same request and answer; termsVersion still names the platform's version.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"termsVersion\": \"2026-10\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "E-mail the signed-in Owner a code to accept the terms with — CAPPED flow only (FEAT-030 §18b)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/terms:send-code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "terms:send-code"
              ]
            },
            "description": "`POST /v1/activation/terms:send-code` · operationId `sendActivationTermsCode`\n\nconnect-v1 1.52.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-3). Sends a 6-digit code to the signed-in Owner's own e-mail address (the address they signed in with; never one taken from the request) so the Owner can accept the terms without an authenticator app: POST /v1/activation/terms:accept with emailCode. A new sign-up has no second factor, so on the CAPPED flow this is the step-up of the terms step. The code lives 15 minutes and allows at most 5 attempts, as the sign-up code; sending a new code voids the previous one. sentTo is the address masked (the first two characters of the local part, or the first one when it is shorter than three, then *** and the domain); expiresAt is when the code stops working; resendAfter is the earliest time another code may be sent (60 seconds after this one [assumption]). Served on the CAPPED flow only: a tenant whose GET /v1/activation flow is NINE_STEP is 409 CONFLICT details.reason FLOW_NOT_CAPPED (step up with stepUpCode or stepUpAssertion there). Rate limited per Owner as the identity code routes are (IdentityRateLimited): a send before resendAfter, or more than 5 sends in a 15-minute window [assumption], is 429 RATE_LIMITED details.reason SUBJECT_RATE_LIMITED, details.action terms-code-send, with Retry-After and details.retryAfterMs; nothing is sent. No request body (an empty JSON object is accepted and ignored). Human session only (an API key or a service token is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870, the terms acceptance rule). Idempotency-Key required: a replay answers the first 202 and sends nothing. Identity mail goes through SMTP (OD-ENR-7); a deployment without a sender is 501 NOT_IMPLEMENTED details.reason NO_VERIFICATION_SENDER. The code is never logged, audited or returned. Audited (activation.terms_code_sent, with sentTo masked). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2): no longer used by the console on the CAPPED flow — the terms are accepted there with termsVersion only (click-wrap, POST /v1/activation/terms:accept); kept for compatibility, and an emailCode it sent is still judged by terms:accept.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Look a GSTIN up in the GST register — proposed rail step business_verification (FEAT-030 §18b)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-registrations/:gstin",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-registrations",
                ":gstin"
              ],
              "variable": [
                {
                  "key": "gstin",
                  "value": "<string>",
                  "description": "India GSTIN, 15 characters, upper case"
                }
              ]
            },
            "description": "`GET /v1/activation/business-registrations/{gstin}` · operationId `lookUpBusinessRegistration`\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX — with a GST register provider configured, aikonic.gst-register.provider; FEAT-030 §18b, OD-ENR-24). Reads the registration a GSTIN names from the GST register and answers it for the person to confirm: legal and trade name, constitution, status, registration date, the company PAN and the principal place of business. Nothing is stored by the look-up; POST /v1/activation/business-registration:confirm stores it. India only: a tenant whose country (SignupVerifyRequest.country or BusinessProfile.country) is not IN, or not yet set, is 400 VALIDATION details.reason INDIA_ONLY_FIELD; a GSTIN that does not match the pattern is 400 VALIDATION details.field gstin. A well-formed GSTIN the register does not know is 404 NOT_FOUND details.reason GSTIN_NOT_FOUND. A CANCELLED or SUSPENDED registration is answered (200) so the screen can say why it cannot be used; confirming it is refused. status and constitution are x-extensible-enum. The register provider is per deployment: harness (the mock harness's fake register, refused at boot above SANDBOX) on DEV and SANDBOX; the real provider waits on OD-ENR-24. The capped flow off, or no provider configured, is 501 NOT_IMPLEMENTED (details.reason CAPPED_FLOW_UNAVAILABLE or GST_REGISTER_UNAVAILABLE); a provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable). Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs users:manage, as the profile it fills.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Look a business registration up by country and number — GSTIN, company number, trade licence, EIN, BN (FEAT-030 §18b) — gbCompany",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-registrations:look-up",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-registrations:look-up"
              ]
            },
            "description": "`POST /v1/activation/business-registrations:look-up` · operationId `lookUpBusinessRegistrationByNumber`\n\nExample: A UK company number\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX — with a register provider configured for the country; FEAT-030 §18b round 2, OD-ENR-31). Reads the registration a number names from the country's register and answers it for the person to confirm (POST /v1/activation/business-registration:confirm with scheme and number); nothing is stored. A read sent as POST so the number and the legal name stay out of the URL and the access logs. country picks the scheme: IN IN_GSTIN (the GST register, as GET /v1/activation/business-registrations/{gstin}, which stays), GB GB_COMPANY_NUMBER (the companies register), AE AE_TRADE_LICENCE (the licensing authority's register; jurisdiction names the emirate or free zone), US US_EIN (the IRS name and EIN match: legalName is required, 400 VALIDATION details.field legalName otherwise, and status reads ACTIVE when the name matches the EIN, else NOT_MATCHED; the IRS states no address and no registration date) and CA CA_BUSINESS_NUMBER (the federal business registry). The answer carries scheme, number and jurisdiction; gstin and pan are set on the India path only, and constitution, registeredOn and address only where the register states them. status is x-extensible-enum (ACTIVE, CANCELLED, SUSPENDED, DISSOLVED, EXPIRED, NOT_MATCHED): a registration that is not ACTIVE is answered (200) so the screen can say why, and confirming it is refused. country must be one of IN, GB, AE, US or CA and equal the tenant's country (ActivationState.country), else 400 VALIDATION details.field country; a number that does not match the scheme's format is 400 VALIDATION details.field number. A well-formed number the register does not know is 404 NOT_FOUND details.reason REGISTRATION_NOT_FOUND (an India GSTIN keeps GSTIN_NOT_FOUND). The capped flow off is 501 NOT_IMPLEMENTED details.reason CAPPED_FLOW_UNAVAILABLE; no register provider for the country is 501 details.reason REGISTER_UNAVAILABLE (GST_REGISTER_UNAVAILABLE for IN); a provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable). The provider is per deployment and country (aikonic.business-register.provider.<country> [assumption]; India keeps aikonic.gst-register.provider): harness (the mock harness's fake registers, refused at boot above SANDBOX) on DEV and SANDBOX; the real providers wait on OD-ENR-31. Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs users:manage, as the GSTIN look-up.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"country\": \"GB\",\n  \"number\": \"09876543\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Look a business registration up by country and number — GSTIN, company number, trade licence, EIN, BN (FEAT-030 §18b) — usEin",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-registrations:look-up",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-registrations:look-up"
              ]
            },
            "description": "`POST /v1/activation/business-registrations:look-up` · operationId `lookUpBusinessRegistrationByNumber`\n\nExample: A US EIN with the legal name the IRS matches it against\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX — with a register provider configured for the country; FEAT-030 §18b round 2, OD-ENR-31). Reads the registration a number names from the country's register and answers it for the person to confirm (POST /v1/activation/business-registration:confirm with scheme and number); nothing is stored. A read sent as POST so the number and the legal name stay out of the URL and the access logs. country picks the scheme: IN IN_GSTIN (the GST register, as GET /v1/activation/business-registrations/{gstin}, which stays), GB GB_COMPANY_NUMBER (the companies register), AE AE_TRADE_LICENCE (the licensing authority's register; jurisdiction names the emirate or free zone), US US_EIN (the IRS name and EIN match: legalName is required, 400 VALIDATION details.field legalName otherwise, and status reads ACTIVE when the name matches the EIN, else NOT_MATCHED; the IRS states no address and no registration date) and CA CA_BUSINESS_NUMBER (the federal business registry). The answer carries scheme, number and jurisdiction; gstin and pan are set on the India path only, and constitution, registeredOn and address only where the register states them. status is x-extensible-enum (ACTIVE, CANCELLED, SUSPENDED, DISSOLVED, EXPIRED, NOT_MATCHED): a registration that is not ACTIVE is answered (200) so the screen can say why, and confirming it is refused. country must be one of IN, GB, AE, US or CA and equal the tenant's country (ActivationState.country), else 400 VALIDATION details.field country; a number that does not match the scheme's format is 400 VALIDATION details.field number. A well-formed number the register does not know is 404 NOT_FOUND details.reason REGISTRATION_NOT_FOUND (an India GSTIN keeps GSTIN_NOT_FOUND). The capped flow off is 501 NOT_IMPLEMENTED details.reason CAPPED_FLOW_UNAVAILABLE; no register provider for the country is 501 details.reason REGISTER_UNAVAILABLE (GST_REGISTER_UNAVAILABLE for IN); a provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable). The provider is per deployment and country (aikonic.business-register.provider.<country> [assumption]; India keeps aikonic.gst-register.provider): harness (the mock harness's fake registers, refused at boot above SANDBOX) on DEV and SANDBOX; the real providers wait on OD-ENR-31. Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs users:manage, as the GSTIN look-up.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"country\": \"US\",\n  \"number\": \"12-3456789\",\n  \"legalName\": \"SAMPLE TRAVEL LLC\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Confirm the looked-up registration; it fills the business profile (FEAT-030 §18b) — confirm",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-registration:confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-registration:confirm"
              ]
            },
            "description": "`POST /v1/activation/business-registration:confirm` · operationId `confirmBusinessRegistration`\n\nExample: Confirm the registration the look-up answered\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-24). Re-reads the GSTIN from the register (never trusts a client copy) and, when its status is ACTIVE, stores it on the business profile: legalName, address, gstin and pan (the company PAN), with country IN and gstinConfirmedAt. Under the CAPPED flow this completes rail step business_profile (BusinessProfile.complete true) and opens the signatory check (POST /v1/activation/signatory-checks), which completes step business_verification. A registration that is not ACTIVE is 409 CONFLICT details.reason GSTIN_NOT_ACTIVE (details.status names it) and nothing is stored. A GSTIN already confirmed by ANOTHER tenant is 409 CONFLICT details.reason GSTIN_ALREADY_CLAIMED and nothing is stored; that business verifies through the hosted KYB session (POST /v1/activation/verification-sessions). Confirming the GSTIN already confirmed is a 200 with the profile as stored. Confirming a different GSTIN replaces the confirmed one: before the tenant is LIVE only an Owner may do it (403 PERMISSION_DENIED details.reason OWNER_ONLY otherwise) and it clears the signatory check, so step business_verification needs the check again; once the tenant is LIVE it is 409 CONFLICT details.reason REGISTRATION_LOCKED (an ops change). Errors as the look-up (400 INDIA_ONLY_FIELD, 404 GSTIN_NOT_FOUND, 501, 502). Human session only; needs users:manage (the Owner to replace). Idempotency-Key required. Audited (activation.registration_confirmed). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2): the body carries exactly one of gstin (the India path above) or scheme with number (a registration POST /v1/activation/business-registrations:look-up answered: IN_GSTIN, GB_COMPANY_NUMBER, AE_TRADE_LICENCE, US_EIN or CA_BUSINESS_NUMBER; for US_EIN also legalName, as the look-up); neither, both, or a scheme without a number is 400 VALIDATION details.field gstin (the backend enforces it, no oneOf). The register is re-read and the confirmed registration is stored on the profile (legalName, address when the register states it, registrationScheme, registrationNumber, and businessStructure from the register's legal form when the profile has none); ActivationState.registrationConfirmedAt is set for every scheme, registrationGstin and gstinConfirmedAt for IN_GSTIN only. A status other than ACTIVE is 409 CONFLICT details.reason GSTIN_NOT_ACTIVE and a number another tenant confirmed is 409 GSTIN_ALREADY_CLAIMED, for every scheme (the 1.51.0 reasons are kept so a client branches once; details.status names the status). An unknown number is 404 REGISTRATION_NOT_FOUND; no provider for the country is 501 REGISTER_UNAVAILABLE. On the CAPPED rail of 1.63.0 the confirm no longer completes business_profile by itself (GET /v1/activation): it completes the registration part of business_verification.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"gstin\": \"29AABCS1234F1Z5\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Confirm the looked-up registration; it fills the business profile (FEAT-030 §18b) — byNumber",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-registration:confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-registration:confirm"
              ]
            },
            "description": "`POST /v1/activation/business-registration:confirm` · operationId `confirmBusinessRegistration`\n\nExample: Confirm a UK company the look-up by number answered (v1.63.0 PROPOSAL)\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-24). Re-reads the GSTIN from the register (never trusts a client copy) and, when its status is ACTIVE, stores it on the business profile: legalName, address, gstin and pan (the company PAN), with country IN and gstinConfirmedAt. Under the CAPPED flow this completes rail step business_profile (BusinessProfile.complete true) and opens the signatory check (POST /v1/activation/signatory-checks), which completes step business_verification. A registration that is not ACTIVE is 409 CONFLICT details.reason GSTIN_NOT_ACTIVE (details.status names it) and nothing is stored. A GSTIN already confirmed by ANOTHER tenant is 409 CONFLICT details.reason GSTIN_ALREADY_CLAIMED and nothing is stored; that business verifies through the hosted KYB session (POST /v1/activation/verification-sessions). Confirming the GSTIN already confirmed is a 200 with the profile as stored. Confirming a different GSTIN replaces the confirmed one: before the tenant is LIVE only an Owner may do it (403 PERMISSION_DENIED details.reason OWNER_ONLY otherwise) and it clears the signatory check, so step business_verification needs the check again; once the tenant is LIVE it is 409 CONFLICT details.reason REGISTRATION_LOCKED (an ops change). Errors as the look-up (400 INDIA_ONLY_FIELD, 404 GSTIN_NOT_FOUND, 501, 502). Human session only; needs users:manage (the Owner to replace). Idempotency-Key required. Audited (activation.registration_confirmed). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2): the body carries exactly one of gstin (the India path above) or scheme with number (a registration POST /v1/activation/business-registrations:look-up answered: IN_GSTIN, GB_COMPANY_NUMBER, AE_TRADE_LICENCE, US_EIN or CA_BUSINESS_NUMBER; for US_EIN also legalName, as the look-up); neither, both, or a scheme without a number is 400 VALIDATION details.field gstin (the backend enforces it, no oneOf). The register is re-read and the confirmed registration is stored on the profile (legalName, address when the register states it, registrationScheme, registrationNumber, and businessStructure from the register's legal form when the profile has none); ActivationState.registrationConfirmedAt is set for every scheme, registrationGstin and gstinConfirmedAt for IN_GSTIN only. A status other than ACTIVE is 409 CONFLICT details.reason GSTIN_NOT_ACTIVE and a number another tenant confirmed is 409 GSTIN_ALREADY_CLAIMED, for every scheme (the 1.51.0 reasons are kept so a client branches once; details.status names the status). An unknown number is 404 REGISTRATION_NOT_FOUND; no provider for the country is 501 REGISTER_UNAVAILABLE. On the CAPPED rail of 1.63.0 the confirm no longer completes business_profile by itself (GET /v1/activation): it completes the registration part of business_verification.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"scheme\": \"GB_COMPANY_NUMBER\",\n  \"number\": \"09876543\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Check the signing Owner's PAN against the confirmed company's directors (FEAT-030 §18b) — director",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/signatory-checks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "signatory-checks"
              ]
            },
            "description": "`POST /v1/activation/signatory-checks` · operationId `checkSignatory`\n\nExample: The Owner's own PAN and the name on it\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-24). The Owner enters their own PAN and the name on it; the register provider checks it against the directors and signatories of the registration confirmed by POST /v1/activation/business-registration:confirm. DIRECTOR_MATCH completes rail step business_verification (no hosted KYB session needed); NOT_A_DIRECTOR is a 200 result, not an error: the step reads needs_you (needs[] signatory.pan) and the hosted KYB session (POST /v1/activation/verification-sessions) stays the fallback. result is x-extensible-enum. Before a registration is confirmed the check is 409 CONFLICT details.reason REGISTRATION_NOT_CONFIRMED. The PAN is sent to the provider and never stored or echoed: the answer carries panLast4 only [assumption: the DR-58 class of the check record]. A PAN that does not match the pattern is 400 VALIDATION details.field pan. The check is for the signed-in Owner only: when the Owner's name is known (AccessContext.givenName and familyName), nameOnPan must equal givenName + \" \" + familyName compared without regard to case or whitespace, else 400 VALIDATION details.reason SIGNATORY_NOT_SIGNED_IN_USER and nothing is sent to the provider. At most 5 checks per tenant per UTC day (replays of an Idempotency-Key do not count): the sixth is 429 RATE_LIMITED details.reason SIGNATORY_ATTEMPTS_EXCEEDED with Retry-After (seconds to the next 00:00 UTC) and details.retryAfterMs; step business_verification then reads needs_you (needs[] kyb.hosted) and the hosted KYB session is the path. Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870). Idempotency-Key required. Audited (activation.signatory_checked, with the result and panLast4). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2): method picks the check. PAN (the default when method is absent) is the check above and needs pan and nameOnPan (India). NAME_DOB needs dateOfBirth only: the signed-in Owner's name (AccessContext.givenName and familyName) and date of birth are checked against the directors, officers, members or managers the register names for the registration confirmed by scheme and number (GB, AE, US, CA). DIRECTOR_MATCH (GB, AE, CA) or OFFICER_MATCH (US, an officer or member named on the register) completes the signatory part of business_verification; NOT_A_DIRECTOR leaves it needs_you with hosted KYB the fallback. Exactly one method's fields: pan or nameOnPan with NAME_DOB, or dateOfBirth with PAN, is 400 VALIDATION details.field naming it, and a missing field of the method is 400 VALIDATION details.field naming it; NAME_DOB on an India registration is 400 VALIDATION details.field method (India checks by PAN). Either method needs a confirmed registration first (409 REGISTRATION_NOT_CONFIRMED, above). The date of birth is sent to the provider and not kept by this route (the business-people record holds the representative's). result also takes OFFICER_MATCH (x-extensible-enum); panLast4 is absent on NAME_DOB and method echoes the check made. The attempt limit above counts both methods.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"pan\": \"ABCPM1234K\",\n  \"nameOnPan\": \"Arjun Mehta\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Check the signing Owner's PAN against the confirmed company's directors (FEAT-030 §18b) — nameDob",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/signatory-checks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "signatory-checks"
              ]
            },
            "description": "`POST /v1/activation/signatory-checks` · operationId `checkSignatory`\n\nExample: The signed-in Owner's date of birth, checked with their name against the register (v1.63.0 PROPOSAL)\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-24). The Owner enters their own PAN and the name on it; the register provider checks it against the directors and signatories of the registration confirmed by POST /v1/activation/business-registration:confirm. DIRECTOR_MATCH completes rail step business_verification (no hosted KYB session needed); NOT_A_DIRECTOR is a 200 result, not an error: the step reads needs_you (needs[] signatory.pan) and the hosted KYB session (POST /v1/activation/verification-sessions) stays the fallback. result is x-extensible-enum. Before a registration is confirmed the check is 409 CONFLICT details.reason REGISTRATION_NOT_CONFIRMED. The PAN is sent to the provider and never stored or echoed: the answer carries panLast4 only [assumption: the DR-58 class of the check record]. A PAN that does not match the pattern is 400 VALIDATION details.field pan. The check is for the signed-in Owner only: when the Owner's name is known (AccessContext.givenName and familyName), nameOnPan must equal givenName + \" \" + familyName compared without regard to case or whitespace, else 400 VALIDATION details.reason SIGNATORY_NOT_SIGNED_IN_USER and nothing is sent to the provider. At most 5 checks per tenant per UTC day (replays of an Idempotency-Key do not count): the sixth is 429 RATE_LIMITED details.reason SIGNATORY_ATTEMPTS_EXCEEDED with Retry-After (seconds to the next 00:00 UTC) and details.retryAfterMs; step business_verification then reads needs_you (needs[] kyb.hosted) and the hosted KYB session is the path. Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870). Idempotency-Key required. Audited (activation.signatory_checked, with the result and panLast4). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18b round 2): method picks the check. PAN (the default when method is absent) is the check above and needs pan and nameOnPan (India). NAME_DOB needs dateOfBirth only: the signed-in Owner's name (AccessContext.givenName and familyName) and date of birth are checked against the directors, officers, members or managers the register names for the registration confirmed by scheme and number (GB, AE, US, CA). DIRECTOR_MATCH (GB, AE, CA) or OFFICER_MATCH (US, an officer or member named on the register) completes the signatory part of business_verification; NOT_A_DIRECTOR leaves it needs_you with hosted KYB the fallback. Exactly one method's fields: pan or nameOnPan with NAME_DOB, or dateOfBirth with PAN, is 400 VALIDATION details.field naming it, and a missing field of the method is 400 VALIDATION details.field naming it; NAME_DOB on an India registration is 400 VALIDATION details.field method (India checks by PAN). Either method needs a confirmed registration first (409 REGISTRATION_NOT_CONFIRMED, above). The date of birth is sent to the provider and not kept by this route (the business-people record holds the representative's). result also takes OFFICER_MATCH (x-extensible-enum); panLast4 is absent on NAME_DOB and method echoes the check made. The attempt limit above counts both methods.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"method\": \"NAME_DOB\",\n  \"dateOfBirth\": \"1984-05-12\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The business's representative and owners of 25 percent or more, as submitted (FEAT-030 §18b)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-people",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-people"
              ]
            },
            "description": "`GET /v1/activation/business-people` · operationId `getBusinessPeople`\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-31). The representative (the signed-in Owner acting for the business: job title, date of birth, home address and the last four characters of an identity number, never the full number) and the natural persons who own 25 percent or more, as last submitted with PUT; an empty record (representative null, owners empty, noOwnerAt25Percent false, submittedAt null) until then. Part of rail step business_verification on the CAPPED flow (GET /v1/activation). Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs users:manage, as the profile.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Submit the business's representative and owners — part of business_verification (FEAT-030 §18b) — resubmitWithIdNumber",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-people",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-people"
              ]
            },
            "description": "`PUT /v1/activation/business-people` · operationId `putBusinessPeople`\n\nExample: Resubmitted after needs_more — the full identity number the provider asked for (v1.63.0 PROPOSAL, amended 2026-10-29)\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-31). Replaces the whole record (BusinessPeople) and sets submittedAt. On the CAPPED flow business_verification is done only when the registration is confirmed (or the hosted KYB documents are approved), the signatory matched (POST /v1/activation/signatory-checks) and this record is submitted with representative set and owners non-empty or noOwnerAt25Percent true. owners holds at most 10 natural persons, each with sharePercent 25 to 100, together at most 100 (400 VALIDATION details.field owners); noOwnerAt25Percent true with owners non-empty is 400 VALIDATION details.field noOwnerAt25Percent. idNumberLast4 is the last four characters only (UK National Insurance, US SSN, UAE Emirates ID, Canadian SIN where asked); a longer value is 400 VALIDATION details.field representative.idNumberLast4 and is never stored; India leaves it absent (the PAN already checked stands for it). The date of birth and home address are personal data: kept for the KYB record only, never logged, and audited without their values (activation.business_people_submitted). Once the tenant is LIVE the record is changed by operations: 409 CONFLICT details.reason REGISTRATION_LOCKED. On the NINE_STEP flow 409 CONFLICT details.reason FLOW_NOT_CAPPED (the hosted KYB collects them there). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870). Idempotency-Key required. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): this PUT is \"Submit for verification\". It stores the record and opens the business verification case with the provider routed for the tenant's country (aikonic.kyb.provider.<country>, default aikonic.kyb.provider), prefilled from the confirmed registration, the business profile, the signatory result, the representative and the owners; a case already open is resumed with the changed data instead (one case per tenant). The pre-checks come first: where the country has a register (IN, GB, AE, US, CA) the registration must be confirmed (409 CONFLICT REGISTRATION_NOT_CONFIRMED) and the signatory matched (409 CONFLICT SIGNATORY_NOT_MATCHED), and nothing is stored or sent while either is open. business_verification turns done ONLY when the provider's verdict is verified; meanwhile ActivationState.businessVerification reads in_progress, in_review, needs_more (with the outstanding requirements) or rejected. After needs_more the Owner fixes the listed fields and PUTs the whole record again, which resumes the case. While the case is in_review a PUT is 409 CONFLICT VERIFICATION_IN_REVIEW, once verified 409 CONFLICT ALREADY_VERIFIED, once rejected 409 CONFLICT VERIFICATION_REJECTED (contact support); nothing changes. representative.idNumber (write-only) carries the full identity number only when the provider asks for it (requirement representative.id_number): it is sent to the provider and never stored, logged, audited or answered; idNumberLast4 is then set from it. Each owner may likewise carry dateOfBirth, address and a write-only idNumber when the provider asks for them (requirement fields owners[i].dateOfBirth, owners[i].address, owners[i].idNumber; none is needed at the first submission); an owner's idNumber is forwarded the same way and only its last four characters are kept (owners[i].idNumberLast4, read-only). No provider routed for the country is 501 NOT_IMPLEMENTED KYB_UNAVAILABLE (details.country); a provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable; the record is kept, the case is not opened; PUT again). The verdict arrives by the provider's signed callback or the periodic reconcile, never by this call; the console re-reads GET /v1/activation.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"representative\": {\n    \"jobTitle\": \"Director\",\n    \"dateOfBirth\": \"1984-05-12\",\n    \"homeAddress\": {\n      \"line1\": \"1 Sample Street\",\n      \"city\": \"Austin\",\n      \"region\": \"TX\",\n      \"postalCode\": \"78701\",\n      \"country\": \"US\"\n    },\n    \"idNumber\": \"000000000\"\n  },\n  \"owners\": [\n    {\n      \"fullName\": \"Arjun Mehta\",\n      \"sharePercent\": 100\n    }\n  ],\n  \"noOwnerAt25Percent\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Submit the business's representative and owners — part of business_verification (FEAT-030 §18b) — resubmitWithOwnerDetails",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-people",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-people"
              ]
            },
            "description": "`PUT /v1/activation/business-people` · operationId `putBusinessPeople`\n\nExample: Resubmitted after needs_more — the owners' date of birth, home address and identity number the provider asked for (v1.63.0 PROPOSAL, amended 2026-10-29)\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-31). Replaces the whole record (BusinessPeople) and sets submittedAt. On the CAPPED flow business_verification is done only when the registration is confirmed (or the hosted KYB documents are approved), the signatory matched (POST /v1/activation/signatory-checks) and this record is submitted with representative set and owners non-empty or noOwnerAt25Percent true. owners holds at most 10 natural persons, each with sharePercent 25 to 100, together at most 100 (400 VALIDATION details.field owners); noOwnerAt25Percent true with owners non-empty is 400 VALIDATION details.field noOwnerAt25Percent. idNumberLast4 is the last four characters only (UK National Insurance, US SSN, UAE Emirates ID, Canadian SIN where asked); a longer value is 400 VALIDATION details.field representative.idNumberLast4 and is never stored; India leaves it absent (the PAN already checked stands for it). The date of birth and home address are personal data: kept for the KYB record only, never logged, and audited without their values (activation.business_people_submitted). Once the tenant is LIVE the record is changed by operations: 409 CONFLICT details.reason REGISTRATION_LOCKED. On the NINE_STEP flow 409 CONFLICT details.reason FLOW_NOT_CAPPED (the hosted KYB collects them there). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870). Idempotency-Key required. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): this PUT is \"Submit for verification\". It stores the record and opens the business verification case with the provider routed for the tenant's country (aikonic.kyb.provider.<country>, default aikonic.kyb.provider), prefilled from the confirmed registration, the business profile, the signatory result, the representative and the owners; a case already open is resumed with the changed data instead (one case per tenant). The pre-checks come first: where the country has a register (IN, GB, AE, US, CA) the registration must be confirmed (409 CONFLICT REGISTRATION_NOT_CONFIRMED) and the signatory matched (409 CONFLICT SIGNATORY_NOT_MATCHED), and nothing is stored or sent while either is open. business_verification turns done ONLY when the provider's verdict is verified; meanwhile ActivationState.businessVerification reads in_progress, in_review, needs_more (with the outstanding requirements) or rejected. After needs_more the Owner fixes the listed fields and PUTs the whole record again, which resumes the case. While the case is in_review a PUT is 409 CONFLICT VERIFICATION_IN_REVIEW, once verified 409 CONFLICT ALREADY_VERIFIED, once rejected 409 CONFLICT VERIFICATION_REJECTED (contact support); nothing changes. representative.idNumber (write-only) carries the full identity number only when the provider asks for it (requirement representative.id_number): it is sent to the provider and never stored, logged, audited or answered; idNumberLast4 is then set from it. Each owner may likewise carry dateOfBirth, address and a write-only idNumber when the provider asks for them (requirement fields owners[i].dateOfBirth, owners[i].address, owners[i].idNumber; none is needed at the first submission); an owner's idNumber is forwarded the same way and only its last four characters are kept (owners[i].idNumberLast4, read-only). No provider routed for the country is 501 NOT_IMPLEMENTED KYB_UNAVAILABLE (details.country); a provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable; the record is kept, the case is not opened; PUT again). The verdict arrives by the provider's signed callback or the periodic reconcile, never by this call; the console re-reads GET /v1/activation.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"representative\": {\n    \"jobTitle\": \"Director\",\n    \"dateOfBirth\": \"1984-05-12\",\n    \"homeAddress\": {\n      \"line1\": \"1 Sample Street\",\n      \"city\": \"Austin\",\n      \"region\": \"TX\",\n      \"postalCode\": \"78701\",\n      \"country\": \"US\"\n    },\n    \"idNumberLast4\": \"0000\"\n  },\n  \"owners\": [\n    {\n      \"fullName\": \"Arjun Mehta\",\n      \"sharePercent\": 60,\n      \"dateOfBirth\": \"1984-05-12\",\n      \"address\": {\n        \"line1\": \"1 Sample Street\",\n        \"city\": \"Austin\",\n        \"region\": \"TX\",\n        \"postalCode\": \"78701\",\n        \"country\": \"US\"\n      },\n      \"idNumber\": \"000000000\"\n    },\n    {\n      \"fullName\": \"Kavya Rao\",\n      \"sharePercent\": 40,\n      \"dateOfBirth\": \"1988-03-21\",\n      \"address\": {\n        \"line1\": \"2 Sample Avenue\",\n        \"city\": \"Austin\",\n        \"region\": \"TX\",\n        \"postalCode\": \"78702\",\n        \"country\": \"US\"\n      },\n      \"idNumber\": \"000000001\"\n    }\n  ],\n  \"noOwnerAt25Percent\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Submit the business's representative and owners — part of business_verification (FEAT-030 §18b) — company",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-people",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-people"
              ]
            },
            "description": "`PUT /v1/activation/business-people` · operationId `putBusinessPeople`\n\nExample: A UK company's director and its two owners\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-31). Replaces the whole record (BusinessPeople) and sets submittedAt. On the CAPPED flow business_verification is done only when the registration is confirmed (or the hosted KYB documents are approved), the signatory matched (POST /v1/activation/signatory-checks) and this record is submitted with representative set and owners non-empty or noOwnerAt25Percent true. owners holds at most 10 natural persons, each with sharePercent 25 to 100, together at most 100 (400 VALIDATION details.field owners); noOwnerAt25Percent true with owners non-empty is 400 VALIDATION details.field noOwnerAt25Percent. idNumberLast4 is the last four characters only (UK National Insurance, US SSN, UAE Emirates ID, Canadian SIN where asked); a longer value is 400 VALIDATION details.field representative.idNumberLast4 and is never stored; India leaves it absent (the PAN already checked stands for it). The date of birth and home address are personal data: kept for the KYB record only, never logged, and audited without their values (activation.business_people_submitted). Once the tenant is LIVE the record is changed by operations: 409 CONFLICT details.reason REGISTRATION_LOCKED. On the NINE_STEP flow 409 CONFLICT details.reason FLOW_NOT_CAPPED (the hosted KYB collects them there). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870). Idempotency-Key required. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): this PUT is \"Submit for verification\". It stores the record and opens the business verification case with the provider routed for the tenant's country (aikonic.kyb.provider.<country>, default aikonic.kyb.provider), prefilled from the confirmed registration, the business profile, the signatory result, the representative and the owners; a case already open is resumed with the changed data instead (one case per tenant). The pre-checks come first: where the country has a register (IN, GB, AE, US, CA) the registration must be confirmed (409 CONFLICT REGISTRATION_NOT_CONFIRMED) and the signatory matched (409 CONFLICT SIGNATORY_NOT_MATCHED), and nothing is stored or sent while either is open. business_verification turns done ONLY when the provider's verdict is verified; meanwhile ActivationState.businessVerification reads in_progress, in_review, needs_more (with the outstanding requirements) or rejected. After needs_more the Owner fixes the listed fields and PUTs the whole record again, which resumes the case. While the case is in_review a PUT is 409 CONFLICT VERIFICATION_IN_REVIEW, once verified 409 CONFLICT ALREADY_VERIFIED, once rejected 409 CONFLICT VERIFICATION_REJECTED (contact support); nothing changes. representative.idNumber (write-only) carries the full identity number only when the provider asks for it (requirement representative.id_number): it is sent to the provider and never stored, logged, audited or answered; idNumberLast4 is then set from it. Each owner may likewise carry dateOfBirth, address and a write-only idNumber when the provider asks for them (requirement fields owners[i].dateOfBirth, owners[i].address, owners[i].idNumber; none is needed at the first submission); an owner's idNumber is forwarded the same way and only its last four characters are kept (owners[i].idNumberLast4, read-only). No provider routed for the country is 501 NOT_IMPLEMENTED KYB_UNAVAILABLE (details.country); a provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable; the record is kept, the case is not opened; PUT again). The verdict arrives by the provider's signed callback or the periodic reconcile, never by this call; the console re-reads GET /v1/activation.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"representative\": {\n    \"jobTitle\": \"Director\",\n    \"dateOfBirth\": \"1984-05-12\",\n    \"homeAddress\": {\n      \"line1\": \"1 Sample Lane\",\n      \"city\": \"London\",\n      \"region\": \"Greater London\",\n      \"postalCode\": \"N1 1AA\",\n      \"country\": \"GB\"\n    },\n    \"idNumberLast4\": \"456C\"\n  },\n  \"owners\": [\n    {\n      \"fullName\": \"Arjun Mehta\",\n      \"sharePercent\": 60\n    },\n    {\n      \"fullName\": \"Kavya Rao\",\n      \"sharePercent\": 40\n    }\n  ],\n  \"noOwnerAt25Percent\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Submit the business's representative and owners — part of business_verification (FEAT-030 §18b) — noOwnerAt25",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-people",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-people"
              ]
            },
            "description": "`PUT /v1/activation/business-people` · operationId `putBusinessPeople`\n\nExample: A company where no one owns 25 percent or more\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-31). Replaces the whole record (BusinessPeople) and sets submittedAt. On the CAPPED flow business_verification is done only when the registration is confirmed (or the hosted KYB documents are approved), the signatory matched (POST /v1/activation/signatory-checks) and this record is submitted with representative set and owners non-empty or noOwnerAt25Percent true. owners holds at most 10 natural persons, each with sharePercent 25 to 100, together at most 100 (400 VALIDATION details.field owners); noOwnerAt25Percent true with owners non-empty is 400 VALIDATION details.field noOwnerAt25Percent. idNumberLast4 is the last four characters only (UK National Insurance, US SSN, UAE Emirates ID, Canadian SIN where asked); a longer value is 400 VALIDATION details.field representative.idNumberLast4 and is never stored; India leaves it absent (the PAN already checked stands for it). The date of birth and home address are personal data: kept for the KYB record only, never logged, and audited without their values (activation.business_people_submitted). Once the tenant is LIVE the record is changed by operations: 409 CONFLICT details.reason REGISTRATION_LOCKED. On the NINE_STEP flow 409 CONFLICT details.reason FLOW_NOT_CAPPED (the hosted KYB collects them there). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870). Idempotency-Key required. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): this PUT is \"Submit for verification\". It stores the record and opens the business verification case with the provider routed for the tenant's country (aikonic.kyb.provider.<country>, default aikonic.kyb.provider), prefilled from the confirmed registration, the business profile, the signatory result, the representative and the owners; a case already open is resumed with the changed data instead (one case per tenant). The pre-checks come first: where the country has a register (IN, GB, AE, US, CA) the registration must be confirmed (409 CONFLICT REGISTRATION_NOT_CONFIRMED) and the signatory matched (409 CONFLICT SIGNATORY_NOT_MATCHED), and nothing is stored or sent while either is open. business_verification turns done ONLY when the provider's verdict is verified; meanwhile ActivationState.businessVerification reads in_progress, in_review, needs_more (with the outstanding requirements) or rejected. After needs_more the Owner fixes the listed fields and PUTs the whole record again, which resumes the case. While the case is in_review a PUT is 409 CONFLICT VERIFICATION_IN_REVIEW, once verified 409 CONFLICT ALREADY_VERIFIED, once rejected 409 CONFLICT VERIFICATION_REJECTED (contact support); nothing changes. representative.idNumber (write-only) carries the full identity number only when the provider asks for it (requirement representative.id_number): it is sent to the provider and never stored, logged, audited or answered; idNumberLast4 is then set from it. Each owner may likewise carry dateOfBirth, address and a write-only idNumber when the provider asks for them (requirement fields owners[i].dateOfBirth, owners[i].address, owners[i].idNumber; none is needed at the first submission); an owner's idNumber is forwarded the same way and only its last four characters are kept (owners[i].idNumberLast4, read-only). No provider routed for the country is 501 NOT_IMPLEMENTED KYB_UNAVAILABLE (details.country); a provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable; the record is kept, the case is not opened; PUT again). The verdict arrives by the provider's signed callback or the periodic reconcile, never by this call; the console re-reads GET /v1/activation.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"representative\": {\n    \"jobTitle\": \"Managing Director\",\n    \"dateOfBirth\": \"1979-09-30\",\n    \"homeAddress\": {\n      \"line1\": \"7 Sample Avenue\",\n      \"city\": \"Toronto\",\n      \"region\": \"Ontario\",\n      \"postalCode\": \"M5V 2T6\",\n      \"country\": \"CA\"\n    },\n    \"idNumberLast4\": \"0429\"\n  },\n  \"owners\": [],\n  \"noOwnerAt25Percent\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Submit the business's representative and owners — part of business_verification (FEAT-030 §18b) — soleProprietorIndia",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/business-people",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "business-people"
              ]
            },
            "description": "`PUT /v1/activation/business-people` · operationId `putBusinessPeople`\n\nExample: An Indian sole proprietorship; the proprietor owns it all\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-31). Replaces the whole record (BusinessPeople) and sets submittedAt. On the CAPPED flow business_verification is done only when the registration is confirmed (or the hosted KYB documents are approved), the signatory matched (POST /v1/activation/signatory-checks) and this record is submitted with representative set and owners non-empty or noOwnerAt25Percent true. owners holds at most 10 natural persons, each with sharePercent 25 to 100, together at most 100 (400 VALIDATION details.field owners); noOwnerAt25Percent true with owners non-empty is 400 VALIDATION details.field noOwnerAt25Percent. idNumberLast4 is the last four characters only (UK National Insurance, US SSN, UAE Emirates ID, Canadian SIN where asked); a longer value is 400 VALIDATION details.field representative.idNumberLast4 and is never stored; India leaves it absent (the PAN already checked stands for it). The date of birth and home address are personal data: kept for the KYB record only, never logged, and audited without their values (activation.business_people_submitted). Once the tenant is LIVE the record is changed by operations: 409 CONFLICT details.reason REGISTRATION_LOCKED. On the NINE_STEP flow 409 CONFLICT details.reason FLOW_NOT_CAPPED (the hosted KYB collects them there). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870). Idempotency-Key required. connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): this PUT is \"Submit for verification\". It stores the record and opens the business verification case with the provider routed for the tenant's country (aikonic.kyb.provider.<country>, default aikonic.kyb.provider), prefilled from the confirmed registration, the business profile, the signatory result, the representative and the owners; a case already open is resumed with the changed data instead (one case per tenant). The pre-checks come first: where the country has a register (IN, GB, AE, US, CA) the registration must be confirmed (409 CONFLICT REGISTRATION_NOT_CONFIRMED) and the signatory matched (409 CONFLICT SIGNATORY_NOT_MATCHED), and nothing is stored or sent while either is open. business_verification turns done ONLY when the provider's verdict is verified; meanwhile ActivationState.businessVerification reads in_progress, in_review, needs_more (with the outstanding requirements) or rejected. After needs_more the Owner fixes the listed fields and PUTs the whole record again, which resumes the case. While the case is in_review a PUT is 409 CONFLICT VERIFICATION_IN_REVIEW, once verified 409 CONFLICT ALREADY_VERIFIED, once rejected 409 CONFLICT VERIFICATION_REJECTED (contact support); nothing changes. representative.idNumber (write-only) carries the full identity number only when the provider asks for it (requirement representative.id_number): it is sent to the provider and never stored, logged, audited or answered; idNumberLast4 is then set from it. Each owner may likewise carry dateOfBirth, address and a write-only idNumber when the provider asks for them (requirement fields owners[i].dateOfBirth, owners[i].address, owners[i].idNumber; none is needed at the first submission); an owner's idNumber is forwarded the same way and only its last four characters are kept (owners[i].idNumberLast4, read-only). No provider routed for the country is 501 NOT_IMPLEMENTED KYB_UNAVAILABLE (details.country); a provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable; the record is kept, the case is not opened; PUT again). The verdict arrives by the provider's signed callback or the periodic reconcile, never by this call; the console re-reads GET /v1/activation.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"representative\": {\n    \"jobTitle\": \"Proprietor\",\n    \"dateOfBirth\": \"1990-01-15\",\n    \"homeAddress\": {\n      \"line1\": \"12 Sample Road, Kothrud\",\n      \"city\": \"Pune\",\n      \"region\": \"Maharashtra\",\n      \"postalCode\": \"411038\",\n      \"country\": \"IN\"\n    }\n  },\n  \"owners\": [\n    {\n      \"fullName\": \"Priya Sharma\",\n      \"sharePercent\": 100\n    }\n  ],\n  \"noOwnerAt25Percent\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "E-mail a code to a work address the Owner must prove before going live — CAPPED flow only (FEAT-030 §18b) — work",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/work-email:send-code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "work-email:send-code"
              ]
            },
            "description": "`POST /v1/activation/work-email:send-code` · operationId `sendWorkEmailCode`\n\nExample: A work address on the website's domain\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-32). The work-e-mail guardrail of the CAPPED flow, for every CAPPED BUSINESS tenant with no exception: when the sign-up e-mail is on a free-mail domain, or not on the domain of the profile's website, ActivationState.workEmail required is true and the Owner proves a work address before going live (POST /v1/activation:go-live is 409 CONFLICT details.reason WORK_EMAIL_REQUIRED until then). This sends a 6-digit code to email; POST /v1/activation/work-email:confirm proves it. Refused with 400 VALIDATION details.field email: details.reason FREE_MAIL_DOMAIN when the domain is on the maintained free-mail list (gmail.com, googlemail.com, the yahoo, outlook, hotmail and live domains, icloud.com, me.com, proton.me, protonmail.com, zoho.com, rediffmail.com, aol.com and gmx among them; the list is platform configuration, aikonic.activation.free-mail-domains [assumption], kept current by the platform and not part of the contract); details.reason DOMAIN_NOT_COMPANY when the profile has a website and the domain is neither the website's registrable domain nor a subdomain of it (with noWebsite true any domain not on the free-mail list is taken). The code lives 15 minutes and allows at most 5 attempts, and a new code voids the previous one, as the terms code; sentTo is the address masked as TermsCodeSent; resendAfter is 60 seconds after the send [assumption]. Rate limited per Owner as the identity code routes (IdentityRateLimited): 429 RATE_LIMITED details.reason SUBJECT_RATE_LIMITED, details.action work-email-code-send, with Retry-After. On the NINE_STEP flow 409 CONFLICT details.reason FLOW_NOT_CAPPED. Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required: a replay answers the first 202 and sends nothing. A deployment without an identity mail sender is 501 NOT_IMPLEMENTED details.reason NO_VERIFICATION_SENDER. The code is never logged, audited or returned; the send is audited as activation.work_email_code_sent with sentTo masked. Test mode (the sandbox twin and sandbox keys) is unaffected by the guardrail.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"arjun@sampletravels.co.uk\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Prove the work address with the e-mailed code — CAPPED flow only (FEAT-030 §18b) — confirm",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/work-email:confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "work-email:confirm"
              ]
            },
            "description": "`POST /v1/activation/work-email:confirm` · operationId `confirmWorkEmail`\n\nExample: The address and the code from the e-mail\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-32). email must be the address the latest code was sent to (400 VALIDATION details.field email otherwise) and code the 6 digits from that e-mail (400 VALIDATION details.field code when it is not six digits). A wrong, expired, voided or never-sent code is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED, and the 5th wrong attempt spends the code (STEP_UP_LOCKED until a new code is sent), as the terms code. On success the address is recorded as the tenant's verified work e-mail (ActivationState.workEmail.email and verifiedAt) and the guardrail is satisfied; the sign-in username does not change [assumption]. Confirming the address already verified is a 200 with the first verifiedAt. On the NINE_STEP flow 409 CONFLICT details.reason FLOW_NOT_CAPPED. Human session only; Owner only (403 OWNER_ONLY). Idempotency-Key required. Audited (activation.work_email_verified, the address masked).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"arjun@sampletravels.co.uk\",\n  \"code\": \"482913\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Go live, capped — the Owner asks to go live and operators review it (FEAT-030 §18b, §18d) — everyProduct",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation:go-live",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation:go-live"
              ]
            },
            "description": "`POST /v1/activation:go-live` · operationId `goLiveCapped`\n\nExample: No products named — every product of the tenant's sandbox capabilities\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-33). The Owner's explicit go-live on the CAPPED flow. It replaces the automatic promotion of 1.51.0: completing the last required step no longer activates the tenant by itself. When every required CAPPED step but go_live is done (verify_email, business_profile, business_verification, terms and money_model; a step in_review is not done) and the work e-mail is satisfied (ActivationState.workEmail.required false, or verifiedAt set), the tenant turns LIVE with tier LIVE_CAPPED and its daily cap (limits; GET /v1/activation/limits), actor the calling Owner, with no go-live signoff and no ops approval; step go_live then reads done, and the answer is the rail as GET /v1/activation reads it after the change. The lifecycle fact and events, and the tenant.live notification, are emitted as 1.51.0's promotion did. Refusals, all 409 CONFLICT and nothing changed: details.reason COUNTRY_NOT_LIVE (details.country; live bookings are not open for the tenant's country yet, ActivationState.liveAvailable false, checked first), then PREREQUISITES_OPEN (details.open lists the open step keys in rail order), WORK_EMAIL_REQUIRED (prove a work e-mail first, POST /v1/activation/work-email:send-code), ALREADY_LIVE (the tenant is LIVE already; a replay of the same Idempotency-Key answers the first answer instead) and FLOW_NOT_CAPPED on the nine-step rail (go live there with the signoffs of /admin/v1/go-live). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870). No request body (an empty JSON object is accepted and ignored). Idempotency-Key required. Audited (activation.went_live, with the tier and the cap). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): a business goes live ONLY once the verification provider's verdict is verified (ActivationState.businessVerification.status verified; business_verification done). While the case is not_submitted, in_progress, in_review, needs_more or rejected the refusal stays 409 CONFLICT PREREQUISITES_OPEN with business_verification in details.open (no new reason; a client reads businessVerification for why). Once verified, the Owner may ask to go live (the go-live review below; amended the same day, it replaces \"go-live is immediate\"). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18d go-live review, OD-ENR-39; amended 2026-10-29, same version): going live goes to an operator review. The call no longer turns the tenant LIVE: it opens a go-live review (ActivationState.goLiveReview, GoLiveReview, status in_review) for the products asked for (optional body GoLiveRequest.products, canonical product codes; absent means every product of the tenant's sandbox capabilities) and files an operator approval of kind GO_LIVE_REVIEW (OpsApproval; scope.key the review id), then answers 202 Accepted with the rail as GET /v1/activation reads it: tenant still TRIAL (tier null, liveAt null), step go_live in_review, a golive.in_review notification. Each product carries its checks (GoLiveCheck): business_details, by operator, and test_calls, by system (the tenant's own sandbox search, reprice, orders and cancel calls for that product). The test calls are required for the approval, not for this call: the Owner may ask before finishing them, the review opens, and an operator cannot approve until test_calls is passed for every requested product (OD-ENR-41); goLiveReview shows what is missing per product (verbsSeen) and step sandbox_booking names it in needs[]. Operators decide through the existing approvals queue, with the TENANT_ACTIVATE approver permission and step-up: approving approves every requested product (OD-ENR-40 option (a), all or nothing), turns the tenant LIVE, tier LIVE_CAPPED, with its daily cap (actor the approving operator), sets go_live done and emits tenant.live and golive.approved; rejecting sets the review changes_requested with the operator's note (go_live needs_you) and the Owner may call this again, which opens a new review. expectedBy is the answer the platform aims for, \"usually within one business day\" [assumption, OD-ENR-42]; each decision is e-mailed to the Owner and to the tenant's operations contact e-mail (GET /admin/v1/contact-emails operations; the Owner's address when unset, then sent once); test mode keeps working throughout. Refusals as above, plus 409 CONFLICT GO_LIVE_IN_REVIEW while a review is in_review (checked after ALREADY_LIVE), and 400 VALIDATION (details.field products) for a duplicate or a product not in the tenant's sandbox capabilities (details.reason PRODUCT_NOT_AVAILABLE); nothing is filed on a refusal. A replay of the same Idempotency-Key with the same body answers the first 202; the same key with other products is 409 IDEMPOTENCY_CONFLICT. Where the operator approvals queue (FEAT-024) is not running on the deployment the call is 501 NOT_IMPLEMENTED CAPPED_FLOW_UNAVAILABLE and nothing is filed, as POST /v1/activation/limit-requests. The 200 answer of round 2 (tenant LIVE at once) is not produced any more. Audited (activation.go_live_requested with the products; activation.went_live with the tier, the cap and the approval id when approved).",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Go live, capped — the Owner asks to go live and operators review it (FEAT-030 §18b, §18d) — flightsAndHotels",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation:go-live",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation:go-live"
              ]
            },
            "description": "`POST /v1/activation:go-live` · operationId `goLiveCapped`\n\nExample: Go live for flights and hotels only\n\nconnect-v1 1.63.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b round 2, OD-ENR-33). The Owner's explicit go-live on the CAPPED flow. It replaces the automatic promotion of 1.51.0: completing the last required step no longer activates the tenant by itself. When every required CAPPED step but go_live is done (verify_email, business_profile, business_verification, terms and money_model; a step in_review is not done) and the work e-mail is satisfied (ActivationState.workEmail.required false, or verifiedAt set), the tenant turns LIVE with tier LIVE_CAPPED and its daily cap (limits; GET /v1/activation/limits), actor the calling Owner, with no go-live signoff and no ops approval; step go_live then reads done, and the answer is the rail as GET /v1/activation reads it after the change. The lifecycle fact and events, and the tenant.live notification, are emitted as 1.51.0's promotion did. Refusals, all 409 CONFLICT and nothing changed: details.reason COUNTRY_NOT_LIVE (details.country; live bookings are not open for the tenant's country yet, ActivationState.liveAvailable false, checked first), then PREREQUISITES_OPEN (details.open lists the open step keys in rail order), WORK_EMAIL_REQUIRED (prove a work e-mail first, POST /v1/activation/work-email:send-code), ALREADY_LIVE (the tenant is LIVE already; a replay of the same Idempotency-Key answers the first answer instead) and FLOW_NOT_CAPPED on the nine-step rail (go live there with the signoffs of /admin/v1/go-live). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY; role OWNER or CUSTOMER_ADMIN as OD-870). No request body (an empty JSON object is accepted and ignored). Idempotency-Key required. Audited (activation.went_live, with the tier and the cap). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): a business goes live ONLY once the verification provider's verdict is verified (ActivationState.businessVerification.status verified; business_verification done). While the case is not_submitted, in_progress, in_review, needs_more or rejected the refusal stays 409 CONFLICT PREREQUISITES_OPEN with business_verification in details.open (no new reason; a client reads businessVerification for why). Once verified, the Owner may ask to go live (the go-live review below; amended the same day, it replaces \"go-live is immediate\"). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18d go-live review, OD-ENR-39; amended 2026-10-29, same version): going live goes to an operator review. The call no longer turns the tenant LIVE: it opens a go-live review (ActivationState.goLiveReview, GoLiveReview, status in_review) for the products asked for (optional body GoLiveRequest.products, canonical product codes; absent means every product of the tenant's sandbox capabilities) and files an operator approval of kind GO_LIVE_REVIEW (OpsApproval; scope.key the review id), then answers 202 Accepted with the rail as GET /v1/activation reads it: tenant still TRIAL (tier null, liveAt null), step go_live in_review, a golive.in_review notification. Each product carries its checks (GoLiveCheck): business_details, by operator, and test_calls, by system (the tenant's own sandbox search, reprice, orders and cancel calls for that product). The test calls are required for the approval, not for this call: the Owner may ask before finishing them, the review opens, and an operator cannot approve until test_calls is passed for every requested product (OD-ENR-41); goLiveReview shows what is missing per product (verbsSeen) and step sandbox_booking names it in needs[]. Operators decide through the existing approvals queue, with the TENANT_ACTIVATE approver permission and step-up: approving approves every requested product (OD-ENR-40 option (a), all or nothing), turns the tenant LIVE, tier LIVE_CAPPED, with its daily cap (actor the approving operator), sets go_live done and emits tenant.live and golive.approved; rejecting sets the review changes_requested with the operator's note (go_live needs_you) and the Owner may call this again, which opens a new review. expectedBy is the answer the platform aims for, \"usually within one business day\" [assumption, OD-ENR-42]; each decision is e-mailed to the Owner and to the tenant's operations contact e-mail (GET /admin/v1/contact-emails operations; the Owner's address when unset, then sent once); test mode keeps working throughout. Refusals as above, plus 409 CONFLICT GO_LIVE_IN_REVIEW while a review is in_review (checked after ALREADY_LIVE), and 400 VALIDATION (details.field products) for a duplicate or a product not in the tenant's sandbox capabilities (details.reason PRODUCT_NOT_AVAILABLE); nothing is filed on a refusal. A replay of the same Idempotency-Key with the same body answers the first 202; the same key with other products is 409 IDEMPOTENCY_CONFLICT. Where the operator approvals queue (FEAT-024) is not running on the deployment the call is 501 NOT_IMPLEMENTED CAPPED_FLOW_UNAVAILABLE and nothing is filed, as POST /v1/activation/limit-requests. The 200 answer of round 2 (tenant LIVE at once) is not produced any more. Audited (activation.go_live_requested with the products; activation.went_live with the tier, the cap and the approval id when approved).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"products\": [\n    \"FLT\",\n    \"HTL\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The capped tier's daily limit — cap, spent today and when it resets (FEAT-030 §18b)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/limits",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "limits"
              ]
            },
            "description": "`GET /v1/activation/limits` · operationId `getActivationLimits`\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-26). The tenant's LIVE tier and its daily spend cap, which applies to the orders of a LIVE_CAPPED tenant that is not a sandbox twin, on any rung: dailyCap (INR when the activation country is IN, else USD), usedToday (the tenant's non-cancelled orders since 00:00 UTC, converted to the cap currency at each order's FX snapshot) and resetAt (the next 00:00 UTC). An order that would take usedToday past dailyCap is refused 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED (POST /v1/orders). The cap is set per deployment by currency (aikonic.activation.capped.daily-cap.INR and .USD, minor units) until OD-ENR-26 decides it, and raised by an approved limit request (POST /v1/activation/limit-requests). A tenant whose tier carries no daily cap (TRIAL, LIVE_STANDARD, LIVE_ENTERPRISE) is 409 CONFLICT details.reason TENANT_NOT_CAPPED. tier is x-extensible-enum. Needs tenant:read, as GET /v1/activation; a human session or an API key."
          },
          "response": []
        },
        {
          "name": "The tenant's requests for a higher daily limit, newest first (FEAT-030 §18b)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/limit-requests?cursor=<string>&limit=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "limit-requests"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Opaque; the nextCursor of the previous page"
                },
                {
                  "key": "limit",
                  "value": "1",
                  "disabled": true,
                  "description": "Page size; default 20 [assumption]"
                }
              ]
            },
            "description": "`GET /v1/activation/limit-requests` · operationId `listLimitRequests`\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-27). Every limit request with its status (PENDING, APPROVED, REJECTED; x-extensible-enum) and decision time, newest first. Cursor-paged as GET /v1/wallets/{currency}/transactions: pass nextCursor back as cursor; a cursor this route never issued is 400 VALIDATION (details.field cursor). Needs tenant:read; a human session or an API key."
          },
          "response": []
        },
        {
          "name": "Ask for a higher daily limit — opens an ops approval (FEAT-030 §18b) — raise",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/activation/limit-requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "activation",
                "limit-requests"
              ]
            },
            "description": "`POST /v1/activation/limit-requests` · operationId `createLimitRequest`\n\nExample: Ask for ₹10,00,000 a day for the corporate travel season\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-27). Files a request to raise the daily cap of a LIVE_CAPPED tenant and opens an operator approval of kind TIER_LIMIT_RAISE (OpsApproval, FEAT-024 §7; scope.tenantId the tenant, scope.key the request id, effect the requested and current caps). Approving it raises the cap to requestedCap and marks the request APPROVED; rejecting it marks it REJECTED; either emits a limit.request_decided notification. One open request at a time: another while one is PENDING is 409 CONFLICT details.reason LIMIT_REQUEST_OPEN (details.requestId names it). A tenant whose tier carries no daily cap is 409 CONFLICT details.reason TENANT_NOT_CAPPED. dailyCap must be in the current cap's currency (400 VALIDATION details.reason CURRENCY_MISMATCH) and above the current cap (400 VALIDATION details.field dailyCap.amount). Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required. Audited (activation.limit_requested). 501 NOT_IMPLEMENTED details.reason CAPPED_FLOW_UNAVAILABLE also covers a deployment where the ops approval queue (FEAT-024) is not running, so no TIER_LIMIT_RAISE approval could be opened; nothing is filed.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"dailyCap\": {\n    \"amount\": 100000000,\n    \"currency\": \"INR\"\n  },\n  \"reason\": \"Corporate travel season\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Passive certification — the six integration checks observed from sandbox traffic (FEAT-030 §5.6)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/certification",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "certification"
              ]
            },
            "description": "`GET /v1/certification` · operationId `getCertification`\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-3, published in 1.18.0). The integration cert suite of 11 §15, evaluated continuously against the tenant's sandbox-twin traffic: idempotent_retry, price_change_handled, async_respected, webhook_verified, ambiguity_rule, deprecation_logged. A check is passed only with the requestIds of the traffic that proved it (AC-030-10); failed names the request that broke the rule (for example a blind retry after a price change). Rail step certification is done when every check is passed. Needs tenant:read, which the Owner and Admin role bundles carry (OD-ENR-8); the users:manage fallback is retired; a human session or an API key. Check keys and states are x-extensible-enum."
          },
          "response": []
        },
        {
          "name": "Run the checks not yet observed, through the sandbox scenario catalogue (FEAT-030 §5.6)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/certification:run-missing",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "certification:run-missing"
              ]
            },
            "description": "`POST /v1/certification:run-missing` · operationId `runMissingCertificationChecks`\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-3, published in 1.18.0). Drives the sandbox scenario catalogue (the Test header scenarios, components/parameters/SandboxTest) on the caller's sandbox twin for every check not yet passed, and answers 202 at once; read GET /v1/certification for the results. Checks already passed are listed and not re-run. Sandbox only, the same gate as the Test header: accepted only from a mo_test_ API key (or a service token of one) on a rung where aikonic.sandbox.simulation is on; anything else, including a production key, is 400 VALIDATION details.reason SANDBOX_ONLY and runs nothing. A human session is not a sandbox key (security declares the key schemes only). Needs orders:write [assumption: the run places sandbox orders for the tenant]. Idempotency-Key required.",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "X-Api-Key",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{apiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Where this tenant is on the way to certified and LIVE, and the next call to make (FEAT-034 §5.5)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/onboard",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "onboard"
              ]
            },
            "description": "`GET /v1/onboard` · operationId `getOnboard`\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.mcp.onboard.enabled is on, else 501 NOT_IMPLEMENTED details.reason ONBOARD_UNAVAILABLE). One read for an agent: the onboarding manifest of GET /.well-known/aikonic.json without its signature (manifest), the tenant's origin and sign-up agent (tenant), the activation rail exactly as GET /v1/activation answers it (rail, ActivationState: each step todo, in_progress, needs_you, in_review, done or blocked with the exact missing item in needs[]), passive certification as GET /v1/certification answers it (certification, the advisory checks included), and nextCall: the one request that moves the tenant forward now (operationId, method, path, headers, body, why, docs), or null when nothing is left for an agent to do (the remaining steps need the Owner). nextCall never names a human-session route; for a step only a person can do it names no call and the step reads needs_you. It walks the quick-start in order — search, reprice an offer the tenant's last search returned, hold it, book it (POST /v1/orders), cancel the order — with the refs the tenant's own sandbox calls produced, then runMissingCertificationChecks until every check passes. The quick-start and certification run on the device sign-up's SYSTEM key, which the D-50 approval gate does not stop (DR-40); only after certification passes does nextCall offer the narrowing mint of an ai child for the agent's MCP use (POST /v1/keys, actorKind ai), because an ai key's order writes wait for a person's D-50 approval (AC-034-16) [assumption: the order]. Needs tenant:read; a human session or an API key. The MCP tool onboard on /mcp/public returns this object unchanged."
          },
          "response": []
        },
        {
          "name": "Authorization server metadata (RFC 8414) for MCP clients (FEAT-031 §5.1, ADR-C10 §Phase 2 design D1)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/.well-known/oauth-authorization-server",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                ".well-known",
                "oauth-authorization-server"
              ]
            },
            "description": "`GET /.well-known/oauth-authorization-server` · operationId `getOAuthServerMetadata`\n\nThe discovery document of the Apiq authorization server, served at the API origin (the issuer, config aikonic.mcp.grants.issuer). An MCP client reads it after the AI plane's Protected Resource Metadata (RFC 9728, API-0011 §7) names this issuer. It advertises S256 as the only PKCE method, code as the only response type, Client ID Metadata Documents (client_id_metadata_document_supported, the registration the MCP authorization revision 2026-07-28 prefers) beside RFC 7591 dynamic registration (deprecated by that revision, served for older clients), and the RFC 9207 iss parameter on every authorization response. grant_types_supported lists authorization_code, refresh_token and the device_code grant; it deliberately omits client_credentials and token-exchange, which POST /v1/oauth/token serves to API keys and to the AI plane only. scopes_supported lists the scopes this rung admits: mcp:console:read and mcp:supplier:read wherever grants are on; mcp:console:write only where the platform write ceiling aikonic.mcp.grants.writes.enabled is on (ADR-C10 Phase 3, DR-68); mcp:operator:read only where operator grants are enabled and aikonic.ops.mfa.required is true (ADR-C10 Phase 4). Not envelope-shaped (see the identity tag). Where grants are off (aikonic.mcp.grants.enabled false: every rung above SANDBOX until OD-28 signs) the route answers 404 NOT_FOUND.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Start an MCP grant — OAuth 2.1 authorization code with PKCE (FEAT-031 §5.1, ADR-C10 §Phase 2 design D1, D8)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/authorize?response_type=code&client_id=<string>&redirect_uri=<uri>&code_challenge=<string>&code_challenge_method=S256&resource=<uri>&scope=<string>&state=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "authorize"
              ],
              "query": [
                {
                  "key": "response_type",
                  "value": "code",
                  "disabled": false,
                  "description": ""
                },
                {
                  "key": "client_id",
                  "value": "<string>",
                  "disabled": false,
                  "description": "A registered client id, or the https URL of the client's metadata document"
                },
                {
                  "key": "redirect_uri",
                  "value": "<uri>",
                  "disabled": false,
                  "description": ""
                },
                {
                  "key": "code_challenge",
                  "value": "<string>",
                  "disabled": false,
                  "description": "BASE64URL(SHA-256(code_verifier)), 43 characters (RFC 7636 §4.2)"
                },
                {
                  "key": "code_challenge_method",
                  "value": "S256",
                  "disabled": false,
                  "description": ""
                },
                {
                  "key": "resource",
                  "value": "<uri>",
                  "disabled": false,
                  "description": "The canonical URL of the MCP endpoint the grant is for (RFC 8707)"
                },
                {
                  "key": "scope",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Space-separated (RFC 6749 §3.3). mcp:console:read, optionally with mcp:console:write; mcp:supplier:read; or mcp:operator:read (connect-v1 1.26.0, ADR-C10 Phases 3 and 4)"
                },
                {
                  "key": "state",
                  "value": "<string>",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "`GET /v1/oauth/authorize` · operationId `oauthAuthorize`\n\nOpened in the human's browser by an MCP client. Checks run in this order. (1) client_id: a client registered at POST /v1/oauth/register, or an https URL naming a Client ID Metadata Document, which the platform fetches with an SSRF guard (https only, public addresses only, checked on the address actually connected to so DNS rebinding cannot reach an internal host, no redirects, at most 5 KB, 2 s, cached at most 24 h) and whose client_id must equal the URL. (2) redirect_uri: exact match against the registered or published list, except that for a registered http loopback IP literal (127.0.0.1 or [::1]) only the port may differ (RFC 8252 §7.3); scheme, host, path and query must still match, and the name localhost is never treated as loopback. If (1) or (2) fails the answer is a plain 400 and the browser is NEVER redirected. (3) response_type: code only. (4) code_challenge and code_challenge_method: S256 only; plain or absent is refused with reason PKCE_S256_REQUIRED. (5) resource (RFC 8707, required): exactly one of the configured MCP resources, the plane's /mcp/console, /mcp/supplier or /mcp/operator URL; any other value is invalid_target. The operator resource (ADR-C10 Phase 4) is invalid_target with reason OPERATOR_GRANTS_DISABLED where aikonic.mcp.grants.toolsets.operator.enabled is false and with reason OPERATOR_MFA_NOT_ENFORCED where aikonic.ops.mfa.required is false on the rung. (6) scope: absent (the resource's read scope), or a space-separated list from the resource's scopes. Console: mcp:console:read, plus mcp:console:write (ADR-C10 Phase 3, DR-68), which must come with mcp:console:read and is invalid_scope with reason WRITES_DISABLED where the platform write ceiling aikonic.mcp.grants.writes.enabled is false. Supplier: mcp:supplier:read only. Operator: mcp:operator:read only (reads only, ADR-C10 R3, OD-899). Any other scope, mcp:supplier:write and mcp:operator:write included, is invalid_scope. The write scope does not itself grant a write: the consent screen offers only the tools the tenant enabled and the human may use, every one unticked. (7) state: passed back unchanged. Refusals from (3) on are redirects to redirect_uri with error, error_description, state and iss (RFC 9207) only when that redirect_uri is a loopback IP literal or the client is a platform pre-registered (verified) client; for any other redirect_uri of an unverified client the refusal is a plain 400 with the same error and reason, so an anonymously registered client cannot turn this route into an open redirector to its own host (RFC 9700 §4.11.2). Requests are rate limited per client address (details.action oauth-authorize), which also bounds the metadata-document fetches an anonymous caller can cause. On success the platform records a pending grant request (10 minutes) and answers 302 to the consent screen of the realm the resource belongs to, {consent-base}/console/connect/authorize?request=<id>, {consent-base}/supplier/connect/authorize?request=<id> or {consent-base}/admin/connect/authorize?request=<id> (operator), where the signed-in human reads it (getMcpGrantRequest, supplierGetMcpGrantRequest or opsGetMcpGrantRequest) and approves or denies. Every client registered dynamically or by metadata document is UNVERIFIED on that screen. There is no implicit grant and no password grant. Off (404 NOT_FOUND) where aikonic.mcp.grants.enabled is false.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Dynamic client registration for MCP clients (RFC 7591; deprecated by MCP 2026-07-28, served for older clients) — loopback",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/register",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "register"
              ]
            },
            "description": "`POST /v1/oauth/register` · operationId `registerOAuthClient`\n\nExample: A desktop MCP client with a loopback redirect\n\nDEPRECATED by the MCP authorization specification revision 2026-07-28, which prefers Client ID Metadata Documents (pass the document's https URL as client_id instead); served for MCP clients that implement only RFC 7591. Public clients only: token_endpoint_auth_method is none and PKCE S256 is mandatory at authorize, so no client secret is ever issued. Every client registered here is UNVERIFIED on the consent screen (ADR-C10 §Decision item 4), and a client unused for 30 days is removed. redirect_uris must be https URLs or loopback IP literals (http://127.0.0.1 or http://[::1], any port, RFC 8252); a device-flow-only client (grant_types without authorization_code) may omit them. Rate limited per client address (details.action oauth-register). Not envelope-shaped (see the identity tag). Off (404 NOT_FOUND) where aikonic.mcp.grants.enabled is false.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"client_name\": \"Example IDE Assistant\",\n  \"redirect_uris\": [\n    \"http://127.0.0.1/callback\"\n  ],\n  \"grant_types\": [\n    \"authorization_code\",\n    \"refresh_token\"\n  ],\n  \"response_types\": [\n    \"code\"\n  ],\n  \"token_endpoint_auth_method\": \"none\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Start an MCP grant on a device without a browser (RFC 8628; console and supplier only) — supplier",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/device_authorization",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "device_authorization"
              ]
            },
            "description": "`POST /v1/oauth/device_authorization` · operationId `startDeviceAuthorization`\n\nExample: A headless agent asks to read the supplier toolset\n\nThe fallback for an MCP client that cannot open a browser (FEAT-031 §5.2). The client sends its client_id, the resource (the plane's /mcp/console or /mcp/supplier URL) and optionally the resource's read scope, and receives a device_code to poll POST /v1/oauth/token with and a user_code for the human. The human opens verification_uri (the realm's Connect page, {consent-base}/console/connect or {consent-base}/supplier/connect), signs in and types the code; there is deliberately no verification_uri_complete, so the code is always typed (ADR-C10 T2). user_code is 8 letters of the RFC 8628 §6.1 base-20 alphabet shown as XXXX-XXXX; it lives 10 minutes and serves one approval, and the device_code is redeemed for tokens once (a later poll is invalid_grant). Poll no faster than interval (5 s); polling faster answers slow_down. The operator resource is refused 400 invalid_target with reason DEVICE_FLOW_NOT_ALLOWED: the operator realm never offers a device flow, on every setting. scope is the resource's read scope, or for console mcp:console:read with mcp:console:write (ADR-C10 Phase 3; invalid_scope reason WRITES_DISABLED where the platform write ceiling is off); every other scope is invalid_scope. Off (404 NOT_FOUND) where aikonic.mcp.grants.enabled or aikonic.mcp.grants.device-flow.enabled is false.",
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "client_id",
                  "value": "mcpc_8Hn2kQ4rT7vW1xZ3bC5dF6",
                  "type": "text"
                },
                {
                  "key": "resource",
                  "value": "https://mcp.example/mcp/supplier",
                  "type": "text"
                },
                {
                  "key": "scope",
                  "value": "mcp:supplier:read",
                  "type": "text"
                }
              ]
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Revoke an MCP grant's refresh or access token (RFC 7009) — refresh",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/oauth/revoke",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "oauth",
                "revoke"
              ]
            },
            "description": "`POST /v1/oauth/revoke` · operationId `revokeOAuthToken`\n\nExample: The client disconnects and revokes its refresh token\n\nThe MCP client revokes the grant it holds by sending its refresh token (or a current access token) with its own client_id. Revoking either ends the WHOLE grant: its refresh token is refused at once and its access tokens within one access-token lifetime (10 minutes at most; the AI plane re-checks every token at most 60 s after its last check). The answer is 200 with an empty body whether or not the token was valid, already revoked or belongs to another client (RFC 7009 §2.2), so the route reveals nothing; a token of another client is not revoked. Only invalid_request (no token or no client_id) is refused. API keys and their client-credentials tokens are revoked through DELETE /v1/keys/{keyPrefix}, not here.",
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "token",
                  "value": "mcr_EXAMPLEONLYnotARealRefreshToken0000",
                  "type": "text"
                },
                {
                  "key": "token_type_hint",
                  "value": "refresh_token",
                  "type": "text"
                },
                {
                  "key": "client_id",
                  "value": "mcpc_8Hn2kQ4rT7vW1xZ3bC5dF6",
                  "type": "text"
                }
              ]
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "The consent screen of a pending console MCP grant (FEAT-031 §5.1, ADR-C10 §Phase 2 design D7)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/mcp/grant-requests/:requestId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "mcp",
                "grant-requests",
                ":requestId"
              ],
              "variable": [
                {
                  "key": "requestId",
                  "value": "<string>",
                  "description": "A pending MCP grant request id, from the consent redirect (?request=) or a user-code lookup. Opaque and unguessable: at least 128 bits from a cryptographic random source, never a sequential or monotonic id, because it is the only handle in the consent URL."
                }
              ]
            },
            "description": "`GET /v1/mcp/grant-requests/{requestId}` · operationId `getMcpGrantRequest`\n\nHUMAN CONSOLE SESSION ONLY: an API key, a service token or an MCP grant-derived token is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED. What the console's Approve agent screen shows: the client (name; VERIFIED only for a client the platform pre-registered, otherwise UNVERIFIED; the redirect host), the toolset, the scope with a plain description, the read operations of the toolset's generated allow-list (the x-mcp tools and resources the grant can reach, all pre-ticked), the grant lifetime and, for a device flow, the requesting address's country. A request of the supplier realm, an unknown id and an expired request are all 404 NOT_FOUND. Not envelope-shaped (see the identity tag).",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Find a pending console device-flow grant by the code the human typed (RFC 8628 §3.3) — typed",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/mcp/grant-requests:lookup",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "mcp",
                "grant-requests:lookup"
              ]
            },
            "description": "`POST /v1/mcp/grant-requests:lookup` · operationId `lookupMcpGrantRequest`\n\nExample: The code from the agent's screen\n\nHUMAN CONSOLE SESSION ONLY (a key, service token or grant-derived token is 403 PERMISSION_DENIED SESSION_REQUIRED). The console's Connect page posts the user_code the human typed; the code travels in the body so it never reaches a URL or an access log. Case and the dash are ignored. Answers the same McpGrantRequest as getMcpGrantRequest, flow DEVICE_CODE, with requesterIpCountry. An unknown, expired, spent or supplier-realm code is 404 NOT_FOUND details.reason USER_CODE_UNKNOWN (one answer, so codes cannot be probed). Lookups are rate limited per session and per client address: 429 RATE_LIMITED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userCode\": \"WDJB-MJHT\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The signed-in human approves an MCP grant for themselves (FEAT-031 §5.1, §5.2; ADR-C10 §Phase 2 design D5, D6) — allReads",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/mcp/grant-requests/:requestId:approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "mcp",
                "grant-requests",
                ":requestId:approve"
              ],
              "variable": [
                {
                  "key": "requestId",
                  "value": "<string>",
                  "description": "A pending MCP grant request id, from the consent redirect (?request=) or a user-code lookup. Opaque and unguessable: at least 128 bits from a cryptographic random source, never a sequential or monotonic id, because it is the only handle in the consent URL."
                }
              ]
            },
            "description": "`POST /v1/mcp/grant-requests/{requestId}:approve` · operationId `approveMcpGrantRequest`\n\nExample: Approve every read the request lists\n\nHUMAN CONSOLE SESSION ONLY: an API key, a service token and an MCP grant-derived token are 403 PERMISSION_DENIED details.reason SESSION_REQUIRED, so an agent can never approve its own grant. The grant is bound to the approving human and to the session (refresh family) the approval came from: logout, logout-all, a password change or reset, user disable, tenant SUSPENDED or OFFBOARDED and session reuse detection all end it. Its lifetime is the earlier of 8 hours and the bound session's expiry. toolset is required and must equal the request's (for a device flow the human selects it by hand); operations, if sent, narrows the grant to those x-mcp names, a non-empty subset of the request's operations (default: all of them); confirmOwnDevice true is required for a device flow. The tenant must be ACTIVE (a SUSPENDED tenant is 403 TENANT_SUSPENDED), the tenant's console toolset switch ON (getMcpSettings; OFF is 403 PERMISSION_DENIED details.reason TOOLSET_DISABLED) and grants enabled on the rung (else 403 PERMISSION_DENIED details.reason GRANTS_DISABLED). A request that is no longer PENDING is 409 CONFLICT details.reason WRONG_STATE. For an authorization-code request the answer carries redirectTo, the client's redirect_uri with code, state and iss, and the browser goes exactly there; the code is single-use and lives 60 s. For a device flow the answer is {approved: true} and the agent's next poll receives the tokens. A grant never reaches a route outside its toolset's read operations, and never one the human cannot reach now (ADR-C10 invariant 5). connect-v1 1.26.0 (ADR-C10 Phase 3, DR-68): writeOperations, a subset of the request's offered writeOperations, gives write consent for those console commit-lite tools; it needs the step-up proof (stepUpCode or stepUpAssertion; absent 403 STEP_UP_REQUIRED, wrong STEP_UP_FAILED). A ticked write runs only while the tenant keeps it ON, the platform and plane write ceilings are on and the human holds its permission; money and decisions stay unreachable. Emits mcp.grant.approved (internal event; a console (TENANT-realm) one is also delivered to its tenant as a webhook, 1.45.0, DR-78) and writes the audit row mcp.grant.approve (writeOperations, stepUp).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"toolset\": \"console\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The signed-in human approves an MCP grant for themselves (FEAT-031 §5.1, §5.2; ADR-C10 §Phase 2 design D5, D6) — narrowed",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/mcp/grant-requests/:requestId:approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "mcp",
                "grant-requests",
                ":requestId:approve"
              ],
              "variable": [
                {
                  "key": "requestId",
                  "value": "<string>",
                  "description": "A pending MCP grant request id, from the consent redirect (?request=) or a user-code lookup. Opaque and unguessable: at least 128 bits from a cryptographic random source, never a sequential or monotonic id, because it is the only handle in the consent URL."
                }
              ]
            },
            "description": "`POST /v1/mcp/grant-requests/{requestId}:approve` · operationId `approveMcpGrantRequest`\n\nExample: Approve two reads only\n\nHUMAN CONSOLE SESSION ONLY: an API key, a service token and an MCP grant-derived token are 403 PERMISSION_DENIED details.reason SESSION_REQUIRED, so an agent can never approve its own grant. The grant is bound to the approving human and to the session (refresh family) the approval came from: logout, logout-all, a password change or reset, user disable, tenant SUSPENDED or OFFBOARDED and session reuse detection all end it. Its lifetime is the earlier of 8 hours and the bound session's expiry. toolset is required and must equal the request's (for a device flow the human selects it by hand); operations, if sent, narrows the grant to those x-mcp names, a non-empty subset of the request's operations (default: all of them); confirmOwnDevice true is required for a device flow. The tenant must be ACTIVE (a SUSPENDED tenant is 403 TENANT_SUSPENDED), the tenant's console toolset switch ON (getMcpSettings; OFF is 403 PERMISSION_DENIED details.reason TOOLSET_DISABLED) and grants enabled on the rung (else 403 PERMISSION_DENIED details.reason GRANTS_DISABLED). A request that is no longer PENDING is 409 CONFLICT details.reason WRONG_STATE. For an authorization-code request the answer carries redirectTo, the client's redirect_uri with code, state and iss, and the browser goes exactly there; the code is single-use and lives 60 s. For a device flow the answer is {approved: true} and the agent's next poll receives the tokens. A grant never reaches a route outside its toolset's read operations, and never one the human cannot reach now (ADR-C10 invariant 5). connect-v1 1.26.0 (ADR-C10 Phase 3, DR-68): writeOperations, a subset of the request's offered writeOperations, gives write consent for those console commit-lite tools; it needs the step-up proof (stepUpCode or stepUpAssertion; absent 403 STEP_UP_REQUIRED, wrong STEP_UP_FAILED). A ticked write runs only while the tenant keeps it ON, the platform and plane write ceilings are on and the human holds its permission; money and decisions stay unreachable. Emits mcp.grant.approved (internal event; a console (TENANT-realm) one is also delivered to its tenant as a webhook, 1.45.0, DR-78) and writes the audit row mcp.grant.approve (writeOperations, stepUp).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"toolset\": \"console\",\n  \"operations\": [\n    \"list_agent_proposals\",\n    \"get_queue\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The signed-in human approves an MCP grant for themselves (FEAT-031 §5.1, §5.2; ADR-C10 §Phase 2 design D5, D6) — device",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/mcp/grant-requests/:requestId:approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "mcp",
                "grant-requests",
                ":requestId:approve"
              ],
              "variable": [
                {
                  "key": "requestId",
                  "value": "<string>",
                  "description": "A pending MCP grant request id, from the consent redirect (?request=) or a user-code lookup. Opaque and unguessable: at least 128 bits from a cryptographic random source, never a sequential or monotonic id, because it is the only handle in the consent URL."
                }
              ]
            },
            "description": "`POST /v1/mcp/grant-requests/{requestId}:approve` · operationId `approveMcpGrantRequest`\n\nExample: A device flow — the toolset picked by hand and the own-device confirmation ticked\n\nHUMAN CONSOLE SESSION ONLY: an API key, a service token and an MCP grant-derived token are 403 PERMISSION_DENIED details.reason SESSION_REQUIRED, so an agent can never approve its own grant. The grant is bound to the approving human and to the session (refresh family) the approval came from: logout, logout-all, a password change or reset, user disable, tenant SUSPENDED or OFFBOARDED and session reuse detection all end it. Its lifetime is the earlier of 8 hours and the bound session's expiry. toolset is required and must equal the request's (for a device flow the human selects it by hand); operations, if sent, narrows the grant to those x-mcp names, a non-empty subset of the request's operations (default: all of them); confirmOwnDevice true is required for a device flow. The tenant must be ACTIVE (a SUSPENDED tenant is 403 TENANT_SUSPENDED), the tenant's console toolset switch ON (getMcpSettings; OFF is 403 PERMISSION_DENIED details.reason TOOLSET_DISABLED) and grants enabled on the rung (else 403 PERMISSION_DENIED details.reason GRANTS_DISABLED). A request that is no longer PENDING is 409 CONFLICT details.reason WRONG_STATE. For an authorization-code request the answer carries redirectTo, the client's redirect_uri with code, state and iss, and the browser goes exactly there; the code is single-use and lives 60 s. For a device flow the answer is {approved: true} and the agent's next poll receives the tokens. A grant never reaches a route outside its toolset's read operations, and never one the human cannot reach now (ADR-C10 invariant 5). connect-v1 1.26.0 (ADR-C10 Phase 3, DR-68): writeOperations, a subset of the request's offered writeOperations, gives write consent for those console commit-lite tools; it needs the step-up proof (stepUpCode or stepUpAssertion; absent 403 STEP_UP_REQUIRED, wrong STEP_UP_FAILED). A ticked write runs only while the tenant keeps it ON, the platform and plane write ceilings are on and the human holds its permission; money and decisions stay unreachable. Emits mcp.grant.approved (internal event; a console (TENANT-realm) one is also delivered to its tenant as a webhook, 1.45.0, DR-78) and writes the audit row mcp.grant.approve (writeOperations, stepUp).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"toolset\": \"console\",\n  \"confirmOwnDevice\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The signed-in human approves an MCP grant for themselves (FEAT-031 §5.1, §5.2; ADR-C10 §Phase 2 design D5, D6) — withWrite",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/mcp/grant-requests/:requestId:approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "mcp",
                "grant-requests",
                ":requestId:approve"
              ],
              "variable": [
                {
                  "key": "requestId",
                  "value": "<string>",
                  "description": "A pending MCP grant request id, from the consent redirect (?request=) or a user-code lookup. Opaque and unguessable: at least 128 bits from a cryptographic random source, never a sequential or monotonic id, because it is the only handle in the consent URL."
                }
              ]
            },
            "description": "`POST /v1/mcp/grant-requests/{requestId}:approve` · operationId `approveMcpGrantRequest`\n\nExample: Approve the reads and one write, with a step-up code (connect-v1 1.26.0, ADR-C10 Phase 3)\n\nHUMAN CONSOLE SESSION ONLY: an API key, a service token and an MCP grant-derived token are 403 PERMISSION_DENIED details.reason SESSION_REQUIRED, so an agent can never approve its own grant. The grant is bound to the approving human and to the session (refresh family) the approval came from: logout, logout-all, a password change or reset, user disable, tenant SUSPENDED or OFFBOARDED and session reuse detection all end it. Its lifetime is the earlier of 8 hours and the bound session's expiry. toolset is required and must equal the request's (for a device flow the human selects it by hand); operations, if sent, narrows the grant to those x-mcp names, a non-empty subset of the request's operations (default: all of them); confirmOwnDevice true is required for a device flow. The tenant must be ACTIVE (a SUSPENDED tenant is 403 TENANT_SUSPENDED), the tenant's console toolset switch ON (getMcpSettings; OFF is 403 PERMISSION_DENIED details.reason TOOLSET_DISABLED) and grants enabled on the rung (else 403 PERMISSION_DENIED details.reason GRANTS_DISABLED). A request that is no longer PENDING is 409 CONFLICT details.reason WRONG_STATE. For an authorization-code request the answer carries redirectTo, the client's redirect_uri with code, state and iss, and the browser goes exactly there; the code is single-use and lives 60 s. For a device flow the answer is {approved: true} and the agent's next poll receives the tokens. A grant never reaches a route outside its toolset's read operations, and never one the human cannot reach now (ADR-C10 invariant 5). connect-v1 1.26.0 (ADR-C10 Phase 3, DR-68): writeOperations, a subset of the request's offered writeOperations, gives write consent for those console commit-lite tools; it needs the step-up proof (stepUpCode or stepUpAssertion; absent 403 STEP_UP_REQUIRED, wrong STEP_UP_FAILED). A ticked write runs only while the tenant keeps it ON, the platform and plane write ceilings are on and the human holds its permission; money and decisions stay unreachable. Emits mcp.grant.approved (internal event; a console (TENANT-realm) one is also delivered to its tenant as a webhook, 1.45.0, DR-78) and writes the audit row mcp.grant.approve (writeOperations, stepUp).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"toolset\": \"console\",\n  \"writeOperations\": [\n    \"claim_queue_item\"\n  ],\n  \"stepUpCode\": \"000000\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The signed-in human denies a pending MCP grant",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/mcp/grant-requests/:requestId:deny",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "mcp",
                "grant-requests",
                ":requestId:deny"
              ],
              "variable": [
                {
                  "key": "requestId",
                  "value": "<string>",
                  "description": "A pending MCP grant request id, from the consent redirect (?request=) or a user-code lookup. Opaque and unguessable: at least 128 bits from a cryptographic random source, never a sequential or monotonic id, because it is the only handle in the consent URL."
                }
              ]
            },
            "description": "`POST /v1/mcp/grant-requests/{requestId}:deny` · operationId `denyMcpGrantRequest`\n\nHUMAN CONSOLE SESSION ONLY (a key, service token or grant-derived token is 403 PERMISSION_DENIED SESSION_REQUIRED). For an authorization-code request the answer carries redirectTo, the client's redirect_uri with error=access_denied, state and iss; for a device flow it is {denied: true} and the agent's next poll answers access_denied. A request that is no longer PENDING is 409 CONFLICT details.reason WRONG_STATE. Emits mcp.grant.denied (internal event; a console (TENANT-realm) one is also delivered to its tenant as a webhook, 1.45.0, DR-78).",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Change the signed-in user's password; every OTHER session of the user is revoked (IMP-P2-002; BE-5) — change",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/password/change",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "password",
                "change"
              ]
            },
            "description": "`POST /v1/auth/password/change` · operationId `changePassword`\n\nExample: A new passphrase\n\nConsole session only (an access token): an API key is 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED. The credential is checked before the body is parsed. newPassword follows the password policy (12 to 1024 characters). A wrong currentPassword, or a locked account, is the same 403 PERMISSION_DENIED details.reason CURRENT_PASSWORD_INVALID, and a wrong currentPassword counts toward the login lockout (5 in 15 minutes). A newPassword equal to the current one is 400 VALIDATION details.reason PASSWORD_UNCHANGED. On success every other session family of the user is revoked (reason PASSWORD_CHANGED; their refresh is 401 SESSION_REVOKED) and the caller's own session stays. Rate-limited 60 per client IP and 10 per user. No Idempotency-Key. Plain JSON, not an envelope.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"currentPassword\": \"correct-horse-battery-staple\",\n  \"newPassword\": \"a brand new passphrase 2026\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Set the first password of an account that has none; every OTHER session of the user is revoked (FEAT-030, OD-ENR-15) — recentSession",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/password/set",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "password",
                "set"
              ]
            },
            "description": "`POST /v1/auth/password/set` · operationId `setFirstPassword`\n\nExample: No factor enrolled, signed in within 15 minutes\n\nconnect-v1 1.29.0. For an account created by FEAT-030 passwordless signup (E-17; OD-ENR-15 option a), which has no password until it sets one here; GET /v1/me answers passwordSet false for it. Console session only (an access token): an API key is 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED. The credential is checked before the body is parsed. An account that already has a password is 403 PERMISSION_DENIED details.reason PASSWORD_ALREADY_SET; it uses POST /v1/auth/password/change. Proof: when the user has an MFA factor enrolled, exactly one of stepUpCode and stepUpAssertion is required (DR-53, DR-54); neither, both or a wrong one is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED, and a user locked out of step-up is STEP_UP_LOCKED. With no factor enrolled the session must be recent: signed in no more than 15 minutes ago (the session's sign-in time; a refresh does not renew it), otherwise 403 PERMISSION_DENIED details.reason STEP_UP_REQUIRED, whose message tells the person to use forgot and reset (POST /v1/auth/password/forgot). newPassword follows the password policy (12 to 1024 characters). On success every other session family of the user is revoked (reason PASSWORD_SET; their refresh is 401 SESSION_REVOKED) and the caller's own session stays; POST /v1/auth/login and /v1/auth/sign-in then accept the password. Rate-limited 60 per client IP and 10 per user, as change is. No Idempotency-Key. Plain JSON, not an envelope. No new error code (DR-20): PASSWORD_ALREADY_SET and STEP_UP_REQUIRED are new details.reason values of PERMISSION_DENIED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"newPassword\": \"a brand new passphrase 2026\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Set the first password of an account that has none; every OTHER session of the user is revoked (FEAT-030, OD-ENR-15) — withStepUp",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/password/set",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "password",
                "set"
              ]
            },
            "description": "`POST /v1/auth/password/set` · operationId `setFirstPassword`\n\nExample: A factor is enrolled — its current code as the proof\n\nconnect-v1 1.29.0. For an account created by FEAT-030 passwordless signup (E-17; OD-ENR-15 option a), which has no password until it sets one here; GET /v1/me answers passwordSet false for it. Console session only (an access token): an API key is 403 PERMISSION_DENIED details.reason ACCESS_TOKEN_REQUIRED. The credential is checked before the body is parsed. An account that already has a password is 403 PERMISSION_DENIED details.reason PASSWORD_ALREADY_SET; it uses POST /v1/auth/password/change. Proof: when the user has an MFA factor enrolled, exactly one of stepUpCode and stepUpAssertion is required (DR-53, DR-54); neither, both or a wrong one is 403 PERMISSION_DENIED details.reason STEP_UP_FAILED, and a user locked out of step-up is STEP_UP_LOCKED. With no factor enrolled the session must be recent: signed in no more than 15 minutes ago (the session's sign-in time; a refresh does not renew it), otherwise 403 PERMISSION_DENIED details.reason STEP_UP_REQUIRED, whose message tells the person to use forgot and reset (POST /v1/auth/password/forgot). newPassword follows the password policy (12 to 1024 characters). On success every other session family of the user is revoked (reason PASSWORD_SET; their refresh is 401 SESSION_REVOKED) and the caller's own session stays; POST /v1/auth/login and /v1/auth/sign-in then accept the password. Rate-limited 60 per client IP and 10 per user, as change is. No Idempotency-Key. Plain JSON, not an envelope. No new error code (DR-20): PASSWORD_ALREADY_SET and STEP_UP_REQUIRED are new details.reason values of PERMISSION_DENIED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"newPassword\": \"a brand new passphrase 2026\",\n  \"stepUpCode\": \"000000\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The caller's own sessions (OD-IMP-P4-037)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/sessions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "sessions"
              ]
            },
            "description": "`GET /v1/auth/sessions` · operationId `listSessions`\n\nEvery signed-in session (refresh family) of the caller, current first. A console session only; an API key is 403 ACCESS_TOKEN_REQUIRED.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "End one of the caller's sessions",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/sessions/:sessionId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "sessions",
                ":sessionId"
              ],
              "variable": [
                {
                  "key": "sessionId",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`DELETE /v1/auth/sessions/{sessionId}` · operationId `endSession`\n\nEnds that refresh family only (there is no bulk delete; POST /v1/auth/logout-all signs out everywhere). Ending the current session is a sign-out. Idempotency-Key required.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The caller's MFA enrolment",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/mfa",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "mfa"
              ]
            },
            "description": "`GET /v1/auth/mfa` · operationId `mfaStatus`\n\nFactors and recovery-code count.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Start a TOTP enrolment — request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/mfa/totp",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "mfa",
                "totp"
              ]
            },
            "description": "`POST /v1/auth/mfa/totp` · operationId `mfaStartTotp`\n\nExample: Request\n\nIdempotency-Key required; the factor is pending until verified.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Authenticator app\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Confirm a pending TOTP factor with one code — request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/mfa/totp/:factorId:verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "mfa",
                "totp",
                ":factorId:verify"
              ],
              "variable": [
                {
                  "key": "factorId",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`POST /v1/auth/mfa/totp/{factorId}:verify` · operationId `mfaConfirmTotp`\n\nExample: Request\n\nIdempotency-Key required. The first factor also answers the recovery codes, once.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"000000\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "WebAuthn registration options — request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/mfa/webauthn/registration-options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "mfa",
                "webauthn",
                "registration-options"
              ]
            },
            "description": "`POST /v1/auth/mfa/webauthn/registration-options` · operationId `mfaWebauthnOptions`\n\nExample: Request\n\nSend {}. Idempotency-Key required.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Register a security key (WebAuthn) — request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/mfa/webauthn",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "mfa",
                "webauthn"
              ]
            },
            "description": "`POST /v1/auth/mfa/webauthn` · operationId `mfaRegisterWebauthn`\n\nExample: Request\n\nIdempotency-Key required.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"registrationId\": \"reg_01JA9N4\",\n  \"name\": \"YubiKey\",\n  \"credential\": {\n    \"id\": \"AbC1\",\n    \"rawId\": \"AbC1\",\n    \"type\": \"public-key\",\n    \"response\": {\n      \"clientDataJSON\": \"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIn0\",\n      \"attestationObject\": \"o2NmbXRkbm9uZQ\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Remove a factor",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/mfa/factors/:factorId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "mfa",
                "factors",
                ":factorId"
              ],
              "variable": [
                {
                  "key": "factorId",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`DELETE /v1/auth/mfa/factors/{factorId}` · operationId `mfaRemoveFactor`\n\nIdempotency-Key required. Removing the last factor where MFA is required is 409 CONFLICT details.reason MFA_REQUIRED.",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Regenerate recovery codes — request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/mfa/recovery-codes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "mfa",
                "recovery-codes"
              ]
            },
            "description": "`POST /v1/auth/mfa/recovery-codes` · operationId `mfaRegenerateRecoveryCodes`\n\nExample: Request\n\nSend {}. The previous set stops working. Idempotency-Key required.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Complete a sign-in challenge → session — request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/mfa/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "mfa",
                "verify"
              ]
            },
            "description": "`POST /v1/auth/mfa/verify` · operationId `mfaVerify`\n\nExample: Request\n\nOne proof: code, recoveryCode or webauthn. A wrong proof is 401 UNAUTHENTICATED details.reason MFA_FAILED; an expired challenge 401 MFA_CHALLENGE_EXPIRED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"challengeId\": \"mfa_01JA9P0\",\n  \"code\": \"000000\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "SSO discovery — email → the tenant's OIDC/SAML start URL (IMP-P4-067) — request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/auth/sso/discover",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "auth",
                "sso",
                "discover"
              ]
            },
            "description": "`POST /v1/auth/sso/discover` · operationId `ssoDiscover`\n\nExample: Request\n\nServer-side only (the web's sign-in action calls it). Enumeration-safe: an unknown domain is {mode: PASSWORD}. Rate-limited as the identity routes are.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"agent@acme-travel.example\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "The public profile of one agent by handle (FEAT-044 §5.5, §6)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Signature",
                "value": "<string>",
                "disabled": true,
                "description": "RFC 9421 Web Bot Auth: the signature value, labelled to match Signature-Input. Optional: an unsigned request is served as unverified (FEAT-044 §5.2). Present and wrong is 401 AGENT_SIGNATURE_INVALID."
              },
              {
                "key": "Signature-Input",
                "value": "<string>",
                "disabled": true,
                "description": "RFC 9421 Web Bot Auth: the covered components and parameters (created, keyid, tag=\"web-bot-auth\"). The signature must cover @authority or @target-uri, @method, @path and signature-agent, and content-digest on bodies; created may be at most 300 seconds old and 30 seconds in the future (DR-173)."
              },
              {
                "key": "Signature-Agent",
                "value": "<string>",
                "disabled": true,
                "description": "Web Bot Auth: the HTTPS URL of the signer, whose key directory is /.well-known/http-message-signatures-directory (200 only, no redirects, cached for at most one hour)."
              },
              {
                "key": "Agent-Version",
                "value": "<string>",
                "disabled": true,
                "description": "The version a registered agent runs. It counts only when it is a covered component of the signature (SHIP-feat044 A3); a covered REVOKED version is 401 AGENT_VERSION_REVOKED, and an absent or unknown version gives no ANS proof (tier at most T1)."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents:resolve?handle=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents:resolve"
              ],
              "query": [
                {
                  "key": "handle",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The agent handle, name@domain (DR-165): a hosted handle is name@<tenantSlug>.agents.aikonic.travel, an own-domain handle is name@your-domain."
                }
              ]
            },
            "description": "`GET /v1/agents:resolve` · operationId `resolveAgent`\n\nconnect-v1 1.64.0 (PROPOSAL, FEAT-044). Answers the public AgentPublicProfile of a registered agent: its handle, name, operator name and domain, tier, current version, protocols, key thumbprints, A2A card URL, certificate state and a public log of registry events. It never carries the principal, the tenant id or any traffic (FEAT-044 §14). A private agent (private true) and an unknown handle are the same 404 NOT_FOUND, so a handle cannot be probed. Anonymous: no credential is needed (security is empty); rate-limited per IP and per key, and an over-limit caller is 429 RATE_LIMITED. Web Bot Auth (RFC 9421 HTTP Message Signatures): a caller may sign the request with the Signature, Signature-Input and Signature-Agent headers, and a registered agent declares its version in Agent-Version, which counts only when it is a covered component of the signature (FEAT-044 §5.2, SHIP-feat044 A3). A request with no signature is not an error: it is served as unverified. A signature that is present and wrong is 401 AGENT_SIGNATURE_INVALID, and a covered Agent-Version that names a REVOKED version is 401 AGENT_VERSION_REVOKED. A signature created more than 300 seconds ago, or more than 30 seconds in the future, is invalid (DR-173).",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Search the travel agent directory (FEAT-044 §5.9, DR-170)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Signature",
                "value": "<string>",
                "disabled": true,
                "description": "RFC 9421 Web Bot Auth: the signature value, labelled to match Signature-Input. Optional: an unsigned request is served as unverified (FEAT-044 §5.2). Present and wrong is 401 AGENT_SIGNATURE_INVALID."
              },
              {
                "key": "Signature-Input",
                "value": "<string>",
                "disabled": true,
                "description": "RFC 9421 Web Bot Auth: the covered components and parameters (created, keyid, tag=\"web-bot-auth\"). The signature must cover @authority or @target-uri, @method, @path and signature-agent, and content-digest on bodies; created may be at most 300 seconds old and 30 seconds in the future (DR-173)."
              },
              {
                "key": "Signature-Agent",
                "value": "<string>",
                "disabled": true,
                "description": "Web Bot Auth: the HTTPS URL of the signer, whose key directory is /.well-known/http-message-signatures-directory (200 only, no redirects, cached for at most one hour)."
              },
              {
                "key": "Agent-Version",
                "value": "<string>",
                "disabled": true,
                "description": "The version a registered agent runs. It counts only when it is a covered component of the signature (SHIP-feat044 A3); a covered REVOKED version is 401 AGENT_VERSION_REVOKED, and an absent or unknown version gives no ANS proof (tier at most T1)."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agents:search?q=<string>&protocol=mcp&tier=T1&canHold=false&cursor=<string>&limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agents:search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Free text matched against the agent name, handle and capabilities."
                },
                {
                  "key": "protocol",
                  "value": "mcp",
                  "disabled": true,
                  "description": "Only agents that speak this protocol: mcp, a2a or http."
                },
                {
                  "key": "tier",
                  "value": "T1",
                  "disabled": true,
                  "description": "The lowest tier to list, T1 to T3; the default is T1 (a T0 agent is never listed, FEAT-044 §5.4)."
                },
                {
                  "key": "canHold",
                  "value": "false",
                  "disabled": true,
                  "description": "true lists only agents allowed to place holds (commit-lite tools); false lists only read-only agents; omitted lists both."
                },
                {
                  "key": "cursor",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Opaque cursor from the previous page nextCursor; omit for the first page."
                },
                {
                  "key": "limit",
                  "value": "20",
                  "disabled": true,
                  "description": "Rows per page, 1 to 50; the default is 20."
                }
              ]
            },
            "description": "`GET /v1/agents:search` · operationId `searchAgentDirectory`\n\nconnect-v1 1.64.0 (PROPOSAL, FEAT-044 §5.9). The travel-only directory (DR-170): public, non-private agents of T1 or above, paged by an opaque cursor. Each entry carries the handle, name, operator, tier, protocols, canHold, capabilities and outcomeScore, and never a principal, a tenant id or traffic. Served only while aikonic.agents.directory.enabled is on (Phase D, after Phase C); with it off the route answers 501 NOT_IMPLEMENTED. Anonymous (security is empty); rate-limited per IP and per key. Web Bot Auth (RFC 9421 HTTP Message Signatures): a caller may sign the request with the Signature, Signature-Input and Signature-Agent headers, and a registered agent declares its version in Agent-Version, which counts only when it is a covered component of the signature (FEAT-044 §5.2, SHIP-feat044 A3). A request with no signature is not an error: it is served as unverified. A signature that is present and wrong is 401 AGENT_SIGNATURE_INVALID, and a covered Agent-Version that names a REVOKED version is 401 AGENT_VERSION_REVOKED. A signature created more than 300 seconds ago, or more than 30 seconds in the future, is invalid (DR-173).",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "discovery",
      "description": "The public discovery documents an agent reads before it holds any credential: the signed onboarding manifest and the key set that verifies it.",
      "item": [
        {
          "name": "The signed onboarding manifest — environments, sign-up, key defaults, velocity, rail, certification (FEAT-034 §5.5)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/.well-known/aikonic.json",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                ".well-known",
                "aikonic.json"
              ]
            },
            "description": "`GET /.well-known/aikonic.json` · operationId `getAgentManifest`\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.manifest.enabled is on, else 404 NOT_FOUND). The one URL a coding agent is given: every environment's API and MCP endpoints, the sign-up routes and identities, the default key scopes for sandbox and LIVE_CAPPED agent keys, the velocity and loop thresholds (OD-AGT-6), the rail step keys, the certification checks (advisory ones apart), the scenario catalogue, products, the pricing summary, SDK package names and the llms.txt, openapi.json and MCP documentation URLs. Public data, no credential, Cache-Control public, max-age=3600. Signed (OD-AGT-7): jws is a detached-payload compact JWS (ES256) over the RFC 8785 canonical JSON of the document without jws, verifiable with the key of jws.kid at GET /.well-known/aikonic-jwks.json; an agent refuses a document that does not verify, and one past its expiresAt (issuedAt + 24 h [assumption]). The key set is served from the same origin, so it is no independent trust anchor: the kid thumbprints are also published out of band, in the SDKs and the docs, and an agent pins them (OD-AGT-7) [verify]. The signer is the platform KMS (aikonic.manifest.jws.signer kms; local is for DEV and SANDBOX and refused at boot above SANDBOX). If the signing key is unavailable the last signed document is served from cache and an alarm raised (FEAT-034 §12) until its expiresAt. Family API-0018. MCP: excluded.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "The public keys that verify the onboarding manifest (JWKS, RFC 7517)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/.well-known/aikonic-jwks.json",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                ".well-known",
                "aikonic-jwks.json"
              ]
            },
            "description": "`GET /.well-known/aikonic-jwks.json` · operationId `getAgentManifestKeys`\n\nconnect-v1 1.57.0 (PROPOSAL, FEAT-034; served when aikonic.manifest.enabled is on, else 404 NOT_FOUND). The JSON Web Key Set holding the public half of every manifest signing key in use. During a rotation (90 days, OD-AGT-7 default) two kids are live: the new one signs, the old one still verifies cached documents. Public data, Cache-Control public, max-age=3600. Family API-0018. MCP: excluded.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "devices",
      "description": "Register and remove the push-notification devices of a signed-in person's mobile apps; a human session only, never an API key.",
      "item": [
        {
          "name": "The signed-in user's own registered devices, newest first (FEAT-021 §18a)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/devices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "devices"
              ]
            },
            "description": "`GET /v1/devices` · operationId `listDevices`\n\nOnly the caller's own devices — never a colleague's, whatever the caller's permissions. The token is shown as its last six characters only. Human session only (the devices tag).",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Register this phone's push token for the signed-in user (FEAT-021 §18a) — iphone",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/devices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "devices"
              ]
            },
            "description": "`POST /v1/devices` · operationId `registerDevice`\n\nExample: An iPhone's APNs token\n\nHuman session only (the devices tag): an API key or a service token is 403 PERMISSION_DENIED, details.reason SESSION_REQUIRED. No permission is needed to register — what a device receives is decided when each push is sent: it gets an order's events only while its user holds orders:read (FEAT-021 §18a). Idempotent on (platform, token), so it takes no Idempotency-Key: registering a token this user already registered answers 200 with the same deviceId (appVersion refreshed); a token registered under another user, of this tenant or of another, moves to the caller — 201 with a new deviceId, and the earlier registration ends (its deviceId is a 404 to its former owner from then on). The token is stored, never logged and never returned: responses carry its last six characters only. A SUSPENDED tenant's users can still register, because push serves the post-booking servicing that suspension leaves up.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"platform\": \"APNS\",\n  \"token\": \"00112233445566778899aabbccddeeff00112233445566778899aabbcc3e2f1a\",\n  \"appVersion\": \"1.2.0 (42)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Register this phone's push token for the signed-in user (FEAT-021 §18a) — android",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/devices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "devices"
              ]
            },
            "description": "`POST /v1/devices` · operationId `registerDevice`\n\nExample: An Android phone's FCM registration token\n\nHuman session only (the devices tag): an API key or a service token is 403 PERMISSION_DENIED, details.reason SESSION_REQUIRED. No permission is needed to register — what a device receives is decided when each push is sent: it gets an order's events only while its user holds orders:read (FEAT-021 §18a). Idempotent on (platform, token), so it takes no Idempotency-Key: registering a token this user already registered answers 200 with the same deviceId (appVersion refreshed); a token registered under another user, of this tenant or of another, moves to the caller — 201 with a new deviceId, and the earlier registration ends (its deviceId is a 404 to its former owner from then on). The token is stored, never logged and never returned: responses carry its last six characters only. A SUSPENDED tenant's users can still register, because push serves the post-booking servicing that suspension leaves up.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"platform\": \"FCM\",\n  \"token\": \"fcm-example-registration-token-not-a-real-one-0000000000AbCdEf\",\n  \"appVersion\": \"1.2.0\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Stop pushing to one of the caller's devices — the apps call it on sign-out (FEAT-021 §18a)",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/devices/:deviceId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "devices",
                ":deviceId"
              ],
              "variable": [
                {
                  "key": "deviceId",
                  "value": "<string>",
                  "description": "The deviceId returned by POST /v1/devices (FEAT-021 §18a)"
                }
              ]
            },
            "description": "`DELETE /v1/devices/{deviceId}` · operationId `deleteDevice`\n\nThe device stops receiving pushes at once. A deviceId that is unknown, already deleted, moved to another user, or another user's is a 404, indistinguishable from none — an app signing out treats that 404 as done. No Idempotency-Key: deleting is idempotent in effect. Human session only (the devices tag).",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "shop",
      "description": "Search every product, poll for results, and read offer detail, schedules, seat maps, fare rules and ancillaries.",
      "item": [
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — flight",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: FLT — FlightSearchIntent (API-0001 §8)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FLT\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"slices\": [\n        {\n          \"origin\": \"GRU\",\n          \"destination\": \"MIA\",\n          \"date\": \"2026-11-10\"\n        }\n      ],\n      \"travelers\": [\n        {\n          \"type\": \"ADT\"\n        },\n        {\n          \"type\": \"CHD\",\n          \"age\": 9\n        }\n      ],\n      \"cabin\": \"ECONOMY\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — flightOutboundFirst",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: FLT — FlightSearchIntent, a round trip in journeyMode OUTBOUND_FIRST (v1.27.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FLT\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"slices\": [\n        {\n          \"origin\": \"SFO\",\n          \"destination\": \"BOM\",\n          \"date\": \"2026-12-18\"\n        },\n        {\n          \"origin\": \"BOM\",\n          \"destination\": \"SFO\",\n          \"date\": \"2027-01-06\"\n        }\n      ],\n      \"travelers\": [\n        {\n          \"type\": \"ADT\"\n        },\n        {\n          \"type\": \"ADT\"\n        }\n      ],\n      \"cabin\": \"ECONOMY\",\n      \"journeyMode\": \"OUTBOUND_FIRST\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — flightFiltered",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: FLT — FlightSearchIntent with search filters (v1.37.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FLT\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"slices\": [\n        {\n          \"origin\": \"JFK\",\n          \"destination\": \"LHR\",\n          \"date\": \"2026-12-02\"\n        },\n        {\n          \"origin\": \"LHR\",\n          \"destination\": \"JFK\",\n          \"date\": \"2026-12-09\",\n          \"cabin\": \"BUSINESS\"\n        }\n      ],\n      \"travelers\": [\n        {\n          \"type\": \"ADT\"\n        }\n      ],\n      \"cabin\": \"ECONOMY\",\n      \"carriers\": [\n        \"BA\",\n        \"AA\"\n      ],\n      \"refundableOnly\": true,\n      \"excludeBasicEconomy\": true,\n      \"fareTypes\": [\n        \"PUBLISHED\"\n      ]\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — stay",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: VR — StaySearchIntent, whole property, pets, child ages\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"VR\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"destination\": {\n        \"placeId\": \"mondee:place:miami\"\n      },\n      \"stay\": {\n        \"checkIn\": \"2026-11-10\",\n        \"checkOut\": \"2026-11-14\"\n      },\n      \"rooms\": [\n        {\n          \"adults\": 2,\n          \"childAges\": [\n            4\n          ],\n          \"pets\": 1\n        }\n      ],\n      \"unitType\": \"WHOLE_PROPERTY\",\n      \"petsAllowed\": true\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — ground",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: XFR — GroundSearchIntent, flight-linked pickup, luggage, child seat\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"XFR\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"waypoints\": [\n        {\n          \"type\": \"PICKUP\",\n          \"location\": {\n            \"iata\": \"GRU\"\n          },\n          \"flightLink\": {\n            \"carrier\": \"LA\",\n            \"flightNumber\": \"8084\",\n            \"date\": \"2026-11-10\"\n          }\n        },\n        {\n          \"type\": \"DROPOFF\",\n          \"location\": {\n            \"placeId\": \"ChIJ0WGkg4FEzpQRrlsz_whLqZs\"\n          }\n        }\n      ],\n      \"party\": {\n        \"adults\": 2,\n        \"childAges\": [\n          4\n        ]\n      },\n      \"luggage\": {\n        \"checked\": 3\n      },\n      \"serviceType\": \"TRANSFER\",\n      \"shared\": \"PRIVATE\",\n      \"childSeats\": [\n        {\n          \"childAge\": 4\n        }\n      ]\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — experience",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: EXP — ExperienceSearchIntent with a pickup point and guide language\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"EXP\",\n    \"currency\": \"EUR\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"destination\": {\n        \"placeId\": \"mondee:place:rome\"\n      },\n      \"dates\": {\n        \"from\": \"2026-11-12\",\n        \"to\": \"2026-11-12\"\n      },\n      \"party\": {\n        \"adults\": 2,\n        \"childAges\": [\n          9\n        ]\n      },\n      \"pickup\": {\n        \"mode\": \"PICKUP\",\n        \"point\": {\n          \"placeId\": \"mondee:place:rome-termini\"\n        }\n      },\n      \"guideLanguage\": \"en\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — event",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: EVT — ExperienceSearchIntent with event filters (section, quantity)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"EVT\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"dates\": {\n        \"from\": \"2026-11-20\",\n        \"to\": \"2026-11-20\"\n      },\n      \"party\": {\n        \"adults\": 2\n      },\n      \"event\": {\n        \"eventId\": \"mondee:event:tevo-2211907\",\n        \"quantity\": 2,\n        \"section\": \"114\"\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — insurance",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: INS — InsuranceSearchIntent, overseas single trip\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"INS\",\n    \"currency\": \"INR\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"trip\": {\n        \"start\": \"2026-11-10\",\n        \"end\": \"2026-11-20\",\n        \"origin\": \"IN\",\n        \"destinations\": [\n          \"US\"\n        ],\n        \"tripType\": \"SINGLE\"\n      },\n      \"insured\": [\n        {\n          \"travelerIndex\": 0,\n          \"ageAtDeparture\": 34\n        },\n        {\n          \"travelerIndex\": 1,\n          \"ageAtDeparture\": 9\n        }\n      ],\n      \"categories\": [\n        \"OVERSEAS\"\n      ]\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — airportService",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: APTSVC — AirportServiceSearchIntent, keyed to the flight\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"APTSVC\",\n    \"currency\": \"INR\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"flightLink\": {\n        \"carrier\": \"AI\",\n        \"flightNumber\": \"131\",\n        \"date\": \"2026-11-10\",\n        \"origin\": \"BOM\",\n        \"destination\": \"LHR\",\n        \"departureTerminal\": \"2\"\n      },\n      \"direction\": \"DEPARTURE\",\n      \"categories\": [\n        \"MEET_ASSIST\",\n        \"PORTER\"\n      ],\n      \"party\": {\n        \"adults\": 2,\n        \"childAges\": [\n          6\n        ]\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — lounge",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: LOUNGE — LoungeSearchIntent, voucher at a station\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"LOUNGE\",\n    \"currency\": \"INR\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"station\": {\n        \"airport\": \"DEL\",\n        \"terminal\": \"3\",\n        \"terminalType\": \"INTERNATIONAL\",\n        \"direction\": \"DEPARTURE\"\n      },\n      \"visitAt\": \"2026-11-10T20:00:00+05:30\",\n      \"party\": {\n        \"adults\": 2\n      },\n      \"entitlementModels\": [\n        \"VOUCHER\"\n      ]\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — visa",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: VISA — VisaSearchIntent, corridor IN → AE\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"VISA\",\n    \"currency\": \"INR\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"corridor\": {\n        \"nationality\": \"IN\",\n        \"residence\": \"IN\",\n        \"destinations\": [\n          \"AE\"\n        ],\n        \"purpose\": \"TOURISM\"\n      },\n      \"travel\": {\n        \"from\": \"2026-12-01\",\n        \"to\": \"2026-12-10\"\n      },\n      \"party\": {\n        \"adults\": 2\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — bus",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: BUS — BusSearchIntent, city to city on a date (v1.15.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"BUS\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"origin\": {\n        \"text\": \"Montreal\"\n      },\n      \"destination\": {\n        \"text\": \"Ottawa\"\n      },\n      \"departureDate\": \"2026-12-10\",\n      \"party\": {\n        \"adults\": 1,\n        \"childAges\": [\n          10\n        ],\n        \"seniorAges\": [\n          70\n        ]\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — checkin",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: CHK — CheckinSearchIntent, automated check-in on a booked flight (v1.21.0; code CHK since v1.22.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"CHK\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"flightLink\": {\n        \"carrier\": \"BA\",\n        \"flightNumber\": \"117\",\n        \"date\": \"2026-11-10\",\n        \"origin\": \"LHR\",\n        \"destination\": \"JFK\"\n      },\n      \"departureTime\": \"08:25\",\n      \"party\": {\n        \"adults\": 2\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — gift",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: GIFT — GiftSearchIntent, digital gift cards redeemable in the US, one face value (v1.31.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"GIFT\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"country\": \"US\",\n      \"category\": \"beauty\",\n      \"faceValue\": {\n        \"amount\": 1000,\n        \"currency\": \"USD\"\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — esim",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: ESIM — EsimSearchIntent, eSIM data plans for a week in Japan, 3 GB or more (v1.33.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"ESIM\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"esim\": {\n        \"country\": \"JP\",\n        \"minDataGb\": 3\n      },\n      \"dates\": {\n        \"from\": \"2026-11-10\",\n        \"to\": \"2026-11-16\"\n      },\n      \"party\": {\n        \"adults\": 1\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — parking",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: PARK — ParkingSearchIntent, airport parking at LAX from 1 to 3 June 2027, lot-local times (v1.55.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"PARK\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"location\": {\n        \"iata\": \"LAX\"\n      },\n      \"entryAt\": \"2027-06-01T09:00\",\n      \"exitAt\": \"2027-06-03T18:00\",\n      \"filters\": {\n        \"covered\": true\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — cruise",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: CRU — CruiseSearchIntent, ocean cruises of 5 to 9 nights in the first quarter of 2027 (v1.35.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"CRU\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"cruiseType\": \"OCEAN\",\n      \"departureWindow\": {\n        \"from\": \"2027-01-01\",\n        \"to\": \"2027-03-31\"\n      },\n      \"nights\": {\n        \"min\": 5,\n        \"max\": 9\n      },\n      \"regions\": [\n        \"mondee:cruise-region:8f3k2q\"\n      ],\n      \"party\": {\n        \"adults\": 2,\n        \"childAges\": []\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — package",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: PKG — PackageSearchIntent, a hotel + flight package for two adults and a child (v1.35.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"PKG\",\n    \"currency\": \"BRL\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"packageType\": \"HOTEL_FLIGHT\",\n      \"origin\": {\n        \"placeId\": \"mondee:place:sao-paulo-br\"\n      },\n      \"destination\": {\n        \"placeId\": \"mondee:place:fortaleza-br\"\n      },\n      \"departureDate\": \"2027-02-10\",\n      \"returnDate\": \"2027-02-17\",\n      \"party\": {\n        \"adults\": 2,\n        \"childAges\": [\n          7\n        ]\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — aicont",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: AICONT — AicontSearchIntent, the enriched attractions of one city, a free catalogue read (v1.39.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"AICONT\",\n    \"currency\": \"INR\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"operation\": \"list_services\",\n      \"arguments\": {\n        \"city\": \"Jaipur\",\n        \"category\": \"attractions\",\n        \"limit\": 20\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — diningByDestination",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: FNB — DiningSearchIntent by destination (v1.7.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"destination\": {\n        \"text\": \"San Francisco\",\n        \"countryCode\": \"US\"\n      },\n      \"date\": \"2026-11-12\",\n      \"time\": \"19:00\",\n      \"windowMinutes\": 90,\n      \"party\": {\n        \"adults\": 2\n      },\n      \"seating\": [\n        \"default\"\n      ]\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — diningByRestaurantIds",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: FNB — DiningSearchIntent for named restaurants, REST alias (v1.7.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"REST\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"restaurantIds\": [\n        \"prov:dining:100123\",\n        \"prov:dining:100456\"\n      ],\n      \"date\": \"2026-11-13\",\n      \"time\": \"20:00\",\n      \"party\": {\n        \"adults\": 4,\n        \"children\": 2\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — diningWithExperiences",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: FNB — DiningSearchIntent with experiences offered beside table slots (v1.7.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"destination\": {\n        \"text\": \"San Francisco\",\n        \"countryCode\": \"US\"\n      },\n      \"date\": \"2026-11-12\",\n      \"time\": \"19:00\",\n      \"party\": {\n        \"adults\": 2\n      },\n      \"includeExperiences\": true\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fan-out search entry for every product (03 §3 #1) — diningExperienceSelection",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search"
              ]
            },
            "description": "`POST /v1/search` · operationId `search`\n\nExample: FNB — slots for one experience with a per-diner price split (v1.7.0)\n\nAbsorbs /v1/calendar via intent.resultShape OFFERS | CALENDAR. Async-by-default products (FLT, CRU, PKG, DPKG, TOUR) and budget-exceeded fan-outs return the 202 form (03 §5, ADR-C04). Typed intents (IMP-P2-075): message.intent is validated against the schema the domain selects — FlightSearchIntent (FLT), StaySearchIntent (HTL, VR, DAYUSE), GroundSearchIntent (XFR, TXI, CAR), ExperienceSearchIntent (EXP, ATT, TOUR, EVT, MOV), InsuranceSearchIntent (INS), AirportServiceSearchIntent (APTSVC, FASTTRK), LoungeSearchIntent (LOUNGE), VisaSearchIntent (VISA), DiningSearchIntent (FNB, v1.7.0), BusSearchIntent (BUS, v1.15.0), CheckinSearchIntent (CHK, v1.21.0; code CHK since v1.22.0), GiftSearchIntent (GIFT, v1.31.0), EsimSearchIntent (ESIM, v1.33.0), CruiseSearchIntent (CRU, v1.35.0), PackageSearchIntent (PKG, v1.35.0), AicontSearchIntent (AICONT, v1.39.0), ParkingSearchIntent (PARK, v1.55.0). One request example per vertical below; FNB carries three (by destination, by restaurantIds, with experiences) and FLT two (the default COMPLETE journey, and flightOutboundFirst). Two-step round trip (v1.27.0): FlightSearchIntent.journeyMode OUTBOUND_FIRST answers outbound journeys (extension.air.journey) whose return is chosen afterwards through POST /v1/flights/returns; see that route's description. A supplier that cannot build them answers COMPLETE offers with a context.warnings[] entry JOURNEY_MODE_UNSUPPORTED. Flight search filters (v1.37.0): FlightSearchIntent carriers, alliance, refundableOnly, changeableOnly, excludeBasicEconomy, slices[].cabin and fareTypes are sent to the supplier; one a supplier cannot apply is left out of its search with a context.warnings[] entry SEARCH_FILTER_UNSUPPORTED (example flightFiltered).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"restaurantIds\": [\n        \"prov:dining:100123\"\n      ],\n      \"date\": \"2026-11-12\",\n      \"time\": \"19:00\",\n      \"party\": {\n        \"adults\": 3\n      },\n      \"includeExperiences\": true,\n      \"experienceSelection\": {\n        \"experienceId\": 900101,\n        \"version\": 3,\n        \"prices\": [\n          {\n            \"priceId\": 1,\n            \"count\": 2\n          },\n          {\n            \"priceId\": 2,\n            \"count\": 1\n          }\n        ]\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Async poll / paginate / filter surface for every async product (03 §3 #2, §5)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/search/:searchRef?cursor=<string>&filter=<string>&sort=<string>&view=SELECTED",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "search",
                ":searchRef"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Opaque; the message.nextCursor of the previous page (as served, BE-2 !64). A cursor this route did not issue is 400 VALIDATION details.field cursor; a valid cursor sent with a different sort or filter restarts at page one (not an error). With none of cursor, filter or sort the poll answers the whole current snapshot unpaged; with any of them it pages at a fixed 50 offers (no page-size parameter)."
                },
                {
                  "key": "filter",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Comma-separated key:value terms, ANDed: supplier:<name> (case-insensitive, the offer's fareSource.supplier), maxPrice:<minor units> / minPrice:<minor units> (run currency), refundable:true|false, holdable:true|false — e.g. supplier:EXAMPLEGDS,maxPrice:118400. Any other key or a malformed term is 400 VALIDATION details.field filter. Filtering never changes message.status."
                },
                {
                  "key": "sort",
                  "value": "<string>",
                  "disabled": true,
                  "description": "One key, - for descending: price | -price | expiresAt | -expiresAt. Absent: the run's stable order (planned supplier order, then landing order). Anything else is 400 VALIDATION details.field sort."
                },
                {
                  "key": "view",
                  "value": "SELECTED",
                  "disabled": true,
                  "description": "Where offer selection is on for the run's domain: SELECTED (one entry per item, the winner, its other offers in alternatives[]) or ALL (the flat list, every offer tagged with its item in selection). Absent: the view the tenant and platform policies resolve to. Any other value is 400 VALIDATION details.field context.selection.view. Ignored where selection is off."
                }
              ],
              "variable": [
                {
                  "key": "searchRef",
                  "value": "<string>",
                  "description": "Async search reference (03 §4; TTL 15 min after last poll [assumption])"
                }
              ]
            },
            "description": "`GET /v1/search/{searchRef}` · operationId `pollSearch`\n\nGeneralized from the flights-only sector poll. Filtering/sorting/pagination run against the server-side result store (14 §3). Same URL with Accept text/event-stream upgrades to SSE (D-78; polling is the guaranteed floor)."
          },
          "response": []
        },
        {
          "name": "Offer/product drill-down for all domains (03 §3 #4) — dining",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/detail",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "detail"
              ]
            },
            "description": "`POST /v1/detail` · operationId `detail`\n\nExample: FNB — the restaurant behind a dining offer: experiences, menus, reviews, payment (v1.7.0)\n\nAbsorbs /v1/options — accepts an optional selection refinement and may be called repeatedly to narrow (rooms to rate; cabin category to deck to cabin; date to timeslot). FLT (v1.27.0): on an OUTBOUND_FIRST offer whose journey is not complete, detail answers the offer as it stands with its extension.air.journey state; it does not list or choose return options. That is POST /v1/flights/returns.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"offerRef\": \"OF-D-5c11a9e0\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "FLT two-step round trip: list the returns of a chosen outbound, or choose one (v1.27.0) — listReturns",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/flights/returns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "flights",
                "returns"
              ]
            },
            "description": "`POST /v1/flights/returns` · operationId `flightReturns`\n\nExample: FLT — list the returns of an OUTBOUND_FIRST outbound offer (v1.27.0)\n\nThe second step of a round trip or multi-city search made with FlightSearchIntent.journeyMode OUTBOUND_FIRST (v1.27.0). A per-vertical FLT route in the style of the /v1/hotels/search alias (03 §1): the unified envelope, context.domain FLT, the same credentials, permission (search:flights) and tenant scoping as an FLT search; a foreign tenant's offerRef is 404 NOT_FOUND. Read-only: no Idempotency-Key, safe to retry; it never mints a repriceRef. LIST: message.intent {offerRef} of an offer whose extension.air.journey.complete is false. The platform asks the supplier for the options of slice journey.nextSliceIndex that go WITH this outbound (for SUP-AIR-EXAMPLE the itineraries of the next sector for this itinerary and fare) and answers 200 with message.offer (the outbound offer as it stands), message.returnOptions (AirReturnOption[], in the supplier's order), message.returnOptionsComplete and message.warnings. The list is the supplier's answer for THIS outbound; each price is the total journey price for the party after the tenant's pricing rules, never a supplier net, and priceDelta is against the outbound's \"from\" price. A key is valid only for this offerRef, only until the offer's expiresAt, and only from the latest listing: listing again may answer other keys and prices. When the supplier says there are no options for this outbound the answer is 200 with an empty list, returnOptionsComplete true and a warning NO_RETURN_OPTIONS; choose another outbound. CHOOSE: message.intent {offerRef, returnOption}. 200 with message.offer narrowed under the SAME offerRef, as the HTL room-rate narrowing keeps its offerRef; expiresAt is not extended. It carries every chosen slice in segments and slices and the journey price the supplier states for the combination (equal to the option's price, pricing.indicative false). Once every slice is chosen journey.complete is true and nextSliceIndex is absent, and /v1/reprice and /v1/orders treat the offer like a COMPLETE one; on a multi-city journey with slices left, complete stays false, nextSliceIndex moves on and the client lists again. Choosing again with another key of the latest listing replaces the choice; the last choice is what reprice prices. Errors use only the frozen codes, the reason in details.reason: 400 VALIDATION NOT_FLT_OFFER (the offerRef is not an FLT offer, or context.domain is not FLT); 404 NOT_FOUND RETURN_OPTION_UNKNOWN (a key this offer's latest listing did not answer, or one from before it expired; an unknown offerRef is the plain 404 NOT_FOUND); 409 OFFER_EXPIRED details.reason OFFER_EXPIRED (the offer is past expiresAt; search again); 409 CONFLICT JOURNEY_COMPLETE_ALREADY (the offer is complete: a COMPLETE search offer, or every slice already chosen; reprice it). Until the journey is complete, /v1/reprice and /v1/orders answer 409 CONFLICT details.reason JOURNEY_INCOMPLETE with details.nextSliceIndex.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FLT\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"offerRef\": \"OF-F-00000101\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "FLT two-step round trip: list the returns of a chosen outbound, or choose one (v1.27.0) — chooseReturn",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/flights/returns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "flights",
                "returns"
              ]
            },
            "description": "`POST /v1/flights/returns` · operationId `flightReturns`\n\nExample: FLT — choose one of the listed returns (v1.27.0)\n\nThe second step of a round trip or multi-city search made with FlightSearchIntent.journeyMode OUTBOUND_FIRST (v1.27.0). A per-vertical FLT route in the style of the /v1/hotels/search alias (03 §1): the unified envelope, context.domain FLT, the same credentials, permission (search:flights) and tenant scoping as an FLT search; a foreign tenant's offerRef is 404 NOT_FOUND. Read-only: no Idempotency-Key, safe to retry; it never mints a repriceRef. LIST: message.intent {offerRef} of an offer whose extension.air.journey.complete is false. The platform asks the supplier for the options of slice journey.nextSliceIndex that go WITH this outbound (for SUP-AIR-EXAMPLE the itineraries of the next sector for this itinerary and fare) and answers 200 with message.offer (the outbound offer as it stands), message.returnOptions (AirReturnOption[], in the supplier's order), message.returnOptionsComplete and message.warnings. The list is the supplier's answer for THIS outbound; each price is the total journey price for the party after the tenant's pricing rules, never a supplier net, and priceDelta is against the outbound's \"from\" price. A key is valid only for this offerRef, only until the offer's expiresAt, and only from the latest listing: listing again may answer other keys and prices. When the supplier says there are no options for this outbound the answer is 200 with an empty list, returnOptionsComplete true and a warning NO_RETURN_OPTIONS; choose another outbound. CHOOSE: message.intent {offerRef, returnOption}. 200 with message.offer narrowed under the SAME offerRef, as the HTL room-rate narrowing keeps its offerRef; expiresAt is not extended. It carries every chosen slice in segments and slices and the journey price the supplier states for the combination (equal to the option's price, pricing.indicative false). Once every slice is chosen journey.complete is true and nextSliceIndex is absent, and /v1/reprice and /v1/orders treat the offer like a COMPLETE one; on a multi-city journey with slices left, complete stays false, nextSliceIndex moves on and the client lists again. Choosing again with another key of the latest listing replaces the choice; the last choice is what reprice prices. Errors use only the frozen codes, the reason in details.reason: 400 VALIDATION NOT_FLT_OFFER (the offerRef is not an FLT offer, or context.domain is not FLT); 404 NOT_FOUND RETURN_OPTION_UNKNOWN (a key this offer's latest listing did not answer, or one from before it expired; an unknown offerRef is the plain 404 NOT_FOUND); 409 OFFER_EXPIRED details.reason OFFER_EXPIRED (the offer is past expiresAt; search again); 409 CONFLICT JOURNEY_COMPLETE_ALREADY (the offer is complete: a COMPLETE search offer, or every slice already chosen; reprice it). Until the journey is complete, /v1/reprice and /v1/orders answer 409 CONFLICT details.reason JOURNEY_INCOMPLETE with details.nextSliceIndex.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FLT\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"offerRef\": \"OF-F-00000101\",\n      \"returnOption\": \"ro_00000101_2\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Departures / sailings / showtimes — the calendar of time (03 §3 #6)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/schedules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "schedules"
              ]
            },
            "description": "`POST /v1/schedules` · operationId `schedules`\n\nBody: a skeleton built from the schema's required properties (the contract carries no example).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"<string>\"\n  },\n  \"message\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Canonical seat/berth/cabin/table grid with per-domain decorations (03 §3 #7, 06 §3)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/seatmap",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "seatmap"
              ]
            },
            "description": "`POST /v1/seatmap` · operationId `seatmap`\n\nBody: a skeleton built from the schema's required properties (the contract carries no example).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"<string>\"\n  },\n  \"message\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Pre-book ancillary catalog for a selection (03 §3 #8, 02 §6)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/ancillaries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ancillaries"
              ]
            },
            "description": "`POST /v1/ancillaries` · operationId `ancillaries`\n\nBody: a skeleton built from the schema's required properties (the contract carries no example).\n\nInput is a repriceRef or offerRef. Post-booking adds go via amend ADD_ANCILLARY.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"<string>\"\n  },\n  \"message\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Fare rules / cancellation policy / T&Cs for a selection, pre-book (03 §3 #9) — dining",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/rules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "rules"
              ]
            },
            "description": "`POST /v1/rules` · operationId `rules`\n\nExample: FNB — booking and cancellation policies of a dining slot (v1.7.0)\n\nDistinct from detail because rules are often a slower, supplier-billed call.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"offerRef\": \"OF-D-5c11a9e0\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "price",
      "description": "Confirm an offer's current price and hold it for a while before booking.",
      "item": [
        {
          "name": "The one firm-price verb; never cached (03 §3 #10) — dining",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/reprice",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reprice"
              ]
            },
            "description": "`POST /v1/reprice` · operationId `reprice`\n\nExample: FNB — re-read the slot; the price stays zero (v1.7.0)",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"offerRef\": \"OF-D-5c11a9e0\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The one firm-price verb; never cached (03 §3 #10) — diningExperience",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/reprice",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reprice"
              ]
            },
            "description": "`POST /v1/reprice` · operationId `reprice`\n\nExample: FNB — firm an experience slot with two tasting menus and one pairing (v1.7.0)",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"offerRef\": \"OF-D-7e02b4c1\",\n      \"experienceSelection\": {\n        \"experienceId\": 900101,\n        \"version\": 3,\n        \"prices\": [\n          {\n            \"priceId\": 1,\n            \"count\": 2\n          },\n          {\n            \"priceId\": 2,\n            \"count\": 1\n          }\n        ]\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Reserve without payment where capability exists (03 §3 #11) — dining",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/hold",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "hold"
              ]
            },
            "description": "`POST /v1/hold` · operationId `hold`\n\nExample: FNB — lock the slot (Example Dining slot lock, about five minutes) (v1.7.0)\n\nReturns holdRef + holdExpiresAt. TTLs range from ~90s (MOV) to multi-day (CRU); declared in capabilities and offer conditions. Expiry auto-releases + hold.expired event. While the tenant is SUSPENDED a new hold is refused 403 TENANT_SUSPENDED, with one exception: a same-key, same-body retry of a hold this tenant completed before the suspension is served its stored answer (Idempotency-Replayed true) — nothing new is started. A fresh, expired or still in-flight key is 403 TENANT_SUSPENDED, a different body under the key 409 IDEMPOTENCY_CONFLICT (FEAT-020 §18c).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"repriceRef\": \"RP-D-3f9a10\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Reserve without payment where capability exists (03 §3 #11) — diningExperience",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/hold",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "hold"
              ]
            },
            "description": "`POST /v1/hold` · operationId `hold`\n\nExample: FNB — lock the experience slot with the same price split (v1.7.0)\n\nReturns holdRef + holdExpiresAt. TTLs range from ~90s (MOV) to multi-day (CRU); declared in capabilities and offer conditions. Expiry auto-releases + hold.expired event. While the tenant is SUSPENDED a new hold is refused 403 TENANT_SUSPENDED, with one exception: a same-key, same-body retry of a hold this tenant completed before the suspension is served its stored answer (Idempotency-Replayed true) — nothing new is started. A fresh, expired or still in-flight key is 403 TENANT_SUSPENDED, a different body under the key 409 IDEMPOTENCY_CONFLICT (FEAT-020 §18c).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"repriceRef\": \"RP-D-4c2e81\",\n      \"experienceSelection\": {\n        \"experienceId\": 900101,\n        \"version\": 3,\n        \"prices\": [\n          {\n            \"priceId\": 1,\n            \"count\": 2\n          },\n          {\n            \"priceId\": 2,\n            \"count\": 1\n          }\n        ]\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Ask for more time on an ACTIVE hold where the supplier allows it (IMP-P2-083; an activity supplier) — extend",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/hold/:holdRef/extend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "hold",
                ":holdRef",
                "extend"
              ],
              "variable": [
                {
                  "key": "holdRef",
                  "value": "<string>",
                  "description": "The hold to extend, as the hold call returned it."
                }
              ]
            },
            "description": "`POST /v1/hold/{holdRef}/extend` · operationId `extendHold`\n\nExample: Ask for 20 more minutes\n\nAnswers the same HoldResponse with a later holdExpiresAt. The supplier decides the extension (OCTO extend; Example Activities re-hold); the platform never fakes one. A hold that cannot be extended is 409 CONFLICT details.reason EXTEND_NOT_SUPPORTED; an expired one is 409 HOLD_EXPIRED; same key, different body is 409 IDEMPOTENCY_CONFLICT. The new expiry is the min() of every supplier clock (CANONICAL-MODEL §2.7).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"EXP\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"requestedUntil\": \"2026-09-14T17:35:00Z\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Release a hold before it expires — free the inventory now — release",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/hold/:holdRef/release",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "hold",
                ":holdRef",
                "release"
              ],
              "variable": [
                {
                  "key": "holdRef",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`POST /v1/hold/{holdRef}/release` · operationId `releaseHold`\n\nExample: The traveller changed plans\n\nReleasing an unexpired hold the tenant owns gives the inventory back now, frees the RP- for a new hold and emits hold.expired with data.releaseOutcome. The supplier is told once, where it supports a release; otherwise the hold is released on the platform side and the answer says so. Idempotent: releasing a released or expired hold is a 200 with its state unchanged. A booked hold is 409 CONFLICT details.reason HOLD_SPENT. Idempotency-Key required.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"EXP\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"reason\": \"Traveller changed plans\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "config",
      "description": "Your commercial configuration: the markup rules applied to every price your customers see, versioned and simulated before you publish them.",
      "item": [
        {
          "name": "The tenant's published markup rule set + its version (FEAT-013 §6)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/config/rules?version=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "config",
                "rules"
              ],
              "query": [
                {
                  "key": "version",
                  "value": "1",
                  "disabled": true,
                  "description": "A prior immutable rule-set version to read (absent = current published)"
                }
              ]
            },
            "description": "`GET /v1/config/rules` · operationId `getRuleSet`\n\nTenant-scoped (tenant from the token, never the body — 07 §2). Returns the currently PUBLISHED immutable rule-set version, or a prior version when `version` is given (rollback = republish a prior version's rules, never edit-in-place — FEAT-013 §5). Every tenant has a platform-seeded zero-markup pass-through version at provisioning, so this never 404s without `version` (FEAT-013 §10). Only rules the caller's layer may see are returned — no endpoint exposes another layer's rules or any supplier net amount (FEAT-013 §6, §14). Like capabilities and webhooks this is a plain tenant-admin resource outside the {context, message} envelope. Requires rules:manage [PROPOSED, FEAT-013 §9]."
          },
          "response": []
        },
        {
          "name": "Publish a new immutable rule-set version (FEAT-013 §5, §6) — negativeMarginOverride",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/config/rules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "config",
                "rules"
              ]
            },
            "description": "`PUT /v1/config/rules` · operationId `publishRuleSet`\n\nExample: Publish over a simulation that found a negative-margin line — explicit override with reason (09 §9)\n\nMints version baseVersion+1 from the full rule list in the body; prior versions stay readable forever (archaeology, FEAT-013 §7). Optimistic concurrency: `baseVersion` must equal the current published version, otherwise 409 CONFLICT with details.currentVersion — re-read, re-simulate, re-publish. Simulate-before-publish: while the tenant flag rules.simulateRequired is on [PROPOSED default true, FEAT-013 §10] `simulationId` must name a simulation run on exactly this rule list, otherwise 409 CONFLICT (details.reason SIMULATION_REQUIRED). Two rules tying at the same layer/priority/specificity, or a lower layer widening a higher layer's cap, are 400 VALIDATION at publish time — never a runtime coin-flip (FEAT-013 §9, §12). Negative-margin guard (09 §9 failure flows, OD-340, OD-346): when the quoted simulation priced any line at a negative margin, publish is 409 CONFLICT (details.reason NEGATIVE_MARGIN_OVERRIDE_REQUIRED, with details.simulationId and details.negativeMarginLines) unless the body sets negativeMarginOverride true AND gives a reason; an override with no reason (and no note) is 400 VALIDATION details.reason REASON_REQUIRED. The reason and the override are recorded on the version and on config.rules_published, not echoed on RuleSet. Emits config.rules_published on the event envelope (FEAT-013 §8). Requires rules:manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"baseVersion\": 8,\n  \"simulationId\": \"sim_9e31b4\",\n  \"note\": \"Loss-leader HTL promo for the Q4 launch\",\n  \"reason\": \"Approved by revenue management for the Q4 launch week; margin restored in v10\",\n  \"negativeMarginOverride\": true,\n  \"rules\": [\n    {\n      \"ruleId\": \"rule_plat_passthrough\",\n      \"layer\": \"PLATFORM\",\n      \"scope\": {},\n      \"action\": {\n        \"kind\": \"MARKUP_PCT\",\n        \"value\": 0\n      },\n      \"priority\": 0\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Publish a new immutable rule-set version (FEAT-013 §5, §6) — raiseMarkup",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/config/rules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "config",
                "rules"
              ]
            },
            "description": "`PUT /v1/config/rules` · operationId `publishRuleSet`\n\nExample: Publish v8 on top of v7 after simulating it\n\nMints version baseVersion+1 from the full rule list in the body; prior versions stay readable forever (archaeology, FEAT-013 §7). Optimistic concurrency: `baseVersion` must equal the current published version, otherwise 409 CONFLICT with details.currentVersion — re-read, re-simulate, re-publish. Simulate-before-publish: while the tenant flag rules.simulateRequired is on [PROPOSED default true, FEAT-013 §10] `simulationId` must name a simulation run on exactly this rule list, otherwise 409 CONFLICT (details.reason SIMULATION_REQUIRED). Two rules tying at the same layer/priority/specificity, or a lower layer widening a higher layer's cap, are 400 VALIDATION at publish time — never a runtime coin-flip (FEAT-013 §9, §12). Negative-margin guard (09 §9 failure flows, OD-340, OD-346): when the quoted simulation priced any line at a negative margin, publish is 409 CONFLICT (details.reason NEGATIVE_MARGIN_OVERRIDE_REQUIRED, with details.simulationId and details.negativeMarginLines) unless the body sets negativeMarginOverride true AND gives a reason; an override with no reason (and no note) is 400 VALIDATION details.reason REASON_REQUIRED. The reason and the override are recorded on the version and on config.rules_published, not echoed on RuleSet. Emits config.rules_published on the event envelope (FEAT-013 §8). Requires rules:manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"baseVersion\": 7,\n  \"simulationId\": \"sim_8d20a1\",\n  \"note\": \"HTL USD markup 10% → 12%\",\n  \"rules\": [\n    {\n      \"ruleId\": \"rule_plat_passthrough\",\n      \"layer\": \"PLATFORM\",\n      \"scope\": {},\n      \"action\": {\n        \"kind\": \"MARKUP_PCT\",\n        \"value\": 0\n      },\n      \"priority\": 0\n    },\n    {\n      \"ruleId\": \"rule_tnt_htl_10pct\",\n      \"layer\": \"TENANT\",\n      \"scope\": {\n        \"domain\": \"HTL\",\n        \"currency\": \"USD\"\n      },\n      \"action\": {\n        \"kind\": \"MARKUP_PCT\",\n        \"value\": 1200\n      },\n      \"floor\": {\n        \"kind\": \"SUPPLIER_SELL_FLOOR\"\n      },\n      \"priority\": 100\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Price sample offers against a draft or published rule set — no side effects on prices (FEAT-013 §5) — draft",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/config/rules:simulate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "config",
                "rules:simulate"
              ]
            },
            "description": "`POST /v1/config/rules:simulate` · operationId `simulateRuleSet`\n\nExample: Simulate a draft 12% HTL markup against one inline sample\n\nThe ':simulate' custom-method path is FEAT-013 §6's own spelling; it is a literal path (no template), and every generator this contract feeds emits it verbatim. Envelope in/out per 03 §1: context.domain is the product of the samples. The candidate is either a draft rule list (intent.ruleSet.rules) or a stored version (intent.ruleSet.version); absent = the current published version. Each sample is priced under the candidate AND under the current published version so the editor can render old-vs-new plus floor-clamp counts. The returned simulationId is what PUT /v1/config/rules must quote to publish exactly this rule list. Samples are either a repriceRef (replayed from the RP- snapshot, FEAT-013 §5) or an inline base price supplied by the caller; the response never echoes a supplier net amount or floor value (kill-list, FEAT-013 §14). Instead of listing samples the caller may ask for the tenant's recent traffic: intent.sampleRecent {days 1..30, default 7; limit 1..200, default 50} replays that many recent RP- snapshots (OD-340); with nothing to replay the answer is 400 VALIDATION details.reason NO_RECENT_SAMPLES. summary.negativeMargin counts results priced below their base, which is what makes a later publish need negativeMarginOverride. Not a mutation, so no Idempotency-Key. Requires rules:manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"HTL\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"ruleSet\": {\n        \"rules\": [\n          {\n            \"ruleId\": \"rule_tnt_htl_10pct\",\n            \"layer\": \"TENANT\",\n            \"scope\": {\n              \"domain\": \"HTL\",\n              \"currency\": \"USD\"\n            },\n            \"action\": {\n              \"kind\": \"MARKUP_PCT\",\n              \"value\": 1200\n            },\n            \"floor\": {\n              \"kind\": \"SUPPLIER_SELL_FLOOR\"\n            },\n            \"priority\": 100\n          }\n        ]\n      },\n      \"samples\": [\n        {\n          \"sampleId\": \"s1\",\n          \"supplier\": \"EXAMPLEHOTELS4\",\n          \"basePrice\": {\n            \"amount\": 76555,\n            \"currency\": \"USD\",\n            \"fxApplied\": false,\n            \"fxSnapshotId\": \"fx-2026-09-25T09:00Z-01\"\n          },\n          \"sellFloor\": {\n            \"amount\": 90000,\n            \"currency\": \"USD\"\n          }\n        }\n      ]\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Price sample offers against a draft or published rule set — no side effects on prices (FEAT-013 §5) — sampleRecent",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/config/rules:simulate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "config",
                "rules:simulate"
              ]
            },
            "description": "`POST /v1/config/rules:simulate` · operationId `simulateRuleSet`\n\nExample: Simulate a draft against the last 7 days of this tenant's HTL reprices (OD-340)\n\nThe ':simulate' custom-method path is FEAT-013 §6's own spelling; it is a literal path (no template), and every generator this contract feeds emits it verbatim. Envelope in/out per 03 §1: context.domain is the product of the samples. The candidate is either a draft rule list (intent.ruleSet.rules) or a stored version (intent.ruleSet.version); absent = the current published version. Each sample is priced under the candidate AND under the current published version so the editor can render old-vs-new plus floor-clamp counts. The returned simulationId is what PUT /v1/config/rules must quote to publish exactly this rule list. Samples are either a repriceRef (replayed from the RP- snapshot, FEAT-013 §5) or an inline base price supplied by the caller; the response never echoes a supplier net amount or floor value (kill-list, FEAT-013 §14). Instead of listing samples the caller may ask for the tenant's recent traffic: intent.sampleRecent {days 1..30, default 7; limit 1..200, default 50} replays that many recent RP- snapshots (OD-340); with nothing to replay the answer is 400 VALIDATION details.reason NO_RECENT_SAMPLES. summary.negativeMargin counts results priced below their base, which is what makes a later publish need negativeMarginOverride. Not a mutation, so no Idempotency-Key. Requires rules:manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"HTL\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"ruleSet\": {\n        \"version\": 9\n      },\n      \"sampleRecent\": {\n        \"days\": 7,\n        \"limit\": 50\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "orders",
      "description": "Book an offer, then read, change and cancel the order.",
      "item": [
        {
          "name": "The one booking write (03 §7, ADR-C02) — dining",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders"
              ]
            },
            "description": "`POST /v1/orders` · operationId `createOrder`\n\nExample: FNB — book a held table; no payment (FREE) (v1.7.0)\n\nReplaces /v1/book, /v1/bookings (write) and /v1/taxi/book (12-month aliases, 18 §5). Book never re-quotes — price movement at commit is 409 PRICE_CHANGED, never a silent swap. Ambiguous supplier outcome is 202 AMBIGUOUS_RESULT with the order in PENDING_RECONCILIATION (06 §8, ADR-C07). While the tenant is SUSPENDED a new order is refused 403 TENANT_SUSPENDED, with one exception: a same-key, same-body retry of an order request this tenant completed before the suspension is served its stored answer (Idempotency-Replayed true) — nothing new is booked. A fresh, expired or still in-flight key is 403 TENANT_SUSPENDED, a different body under the key 409 IDEMPOTENCY_CONFLICT (FEAT-020 §18c). Payment check (OD-301 (b), OD-360, OD-361 (1)): when payments are served and message.intent.payment.paymentRef names an intent, the platform checks it covers THIS order before the write-ahead row and before any supplier byte. Anything but a funded intent for this tenant, this repriceRef and this total is 409 CONFLICT with details.field message.intent.payment.paymentRef and details.reason one of NOT_FOUND (unknown ref, or another tenant's — the same bytes for both), OTHER_QUOTE (the intent was created from a different repriceRef), AMOUNT_MISMATCH (details.detail AMOUNT or CURRENCY), WRONG_STATE (details.detail is the intent's state: CREATED, REQUIRES_ACTION, CANCELED, FAILED, or PENDING_RECONCILIATION while a payment operation is unresolved), REFUNDED, AUTH_EXPIRED (an uncaptured authorization past its validity), ALREADY_USED (another order already booked on this payment; that order is never named). Nothing was sent to a supplier and the Idempotency-Key is released, so the client fixes the payment and retries. retryable is true only for WRONG_STATE with detail PENDING_RECONCILIATION, and false for every other reason. A second order on a repriceRef that is already spent (OD-360 (b) spends the RP- at book) is 409 CONFLICT with details.field message.intent.repriceRef and details.reason REPRICE_REF_SPENT (OD-487); when a payment is attached the payment check answers first. A same-key retry of an order that already booked is answered from its stored result without re-checking. Two-step round trip (v1.27.0): an order on an OUTBOUND_FIRST FLT offer whose journey is not complete (extension.air.journey.complete false) is 409 CONFLICT with details.reason JOURNEY_INCOMPLETE and details.nextSliceIndex, not retryable; nothing is sent to a supplier and the Idempotency-Key is released. Choose the remaining slices through POST /v1/flights/returns and reprice first. Card-secured slots (v1.7.0, D3 / OD-875): message.intent.payment.paymentMethod carries a payment-method token the client obtained from Spreedly; the platform hands the supplier the token and never a card number or security code, and none can be sent: PaymentMethodToken has no such field and a card-number-shaped value anywhere is refused (INSTRUMENT_DATA_REFUSED). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to orders.create, the repriceRef and the order total; without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39). Wallet (v1.18.0, served since 1.18.1, FEAT-030 §5.5, §12): with message.intent.payment.mode WALLET the total is held on the tenant's wallet inside the write-ahead flow (F-03) before any supplier byte, settled at ticketing or confirmation and released when the supplier fails; an ambiguous outcome keeps the hold until reconciliation, never debiting twice. Too little available is 402 PAYMENT_FAILED with details.reason WALLET_INSUFFICIENT and details.available (Money), and no supplier is called (sandbox scenario Test wallet_insufficient). Where the wallet is off (aikonic.wallet.enabled, on at DEV and SANDBOX only) a WALLET order is refused the same way, with nothing available, and never booked unpaid. A tier cap (FEAT-030 §5.7, OD-ENR-1, only when auto-live is on; not built — capped LIVE waits on OD-ENR-1) is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap and details.resetAt. connect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b): TIER_CAP_REACHED is un-reserved. An order of a LIVE_CAPPED tenant that is not a sandbox twin (on any rung) whose total would take today's spend past the daily cap is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap, details.usedToday (Money) and details.resetAt, checked before any payment is taken, any wallet hold is placed or any supplier byte is sent; nothing is written. The cap currency is INR when the activation country is IN, else USD; usedToday counts the tenant's non-cancelled orders since 00:00 UTC, converted to the cap currency at each order's FX snapshot; resetAt is the next 00:00 UTC. The check is per order against today's confirmed and in-flight spend and is SOFT under concurrency: concurrent orders, each under the cap, may together exceed it by at most the orders in flight (a reservation-based hard cap is an option for OD-ENR-26). An order whose currency the FX snapshot cannot convert to the cap currency is 500 INTERNAL details.reason FX_SNAPSHOT_UNAVAILABLE (the money verbs' existing sub-reason; retryable) and nothing is charged, held or sent. A sandbox twin's orders and tenants of other tiers are never capped. With payment.mode SAVED_METHOD while aikonic.payment-methods.enabled is off the order is 501 NOT_IMPLEMENTED details.reason PAYMENT_METHODS_UNAVAILABLE. payment.mode SAVED_METHOD with payment.methodId charges a saved payment method (POST /v1/payments/methods); a decline is 402 PAYMENT_FAILED details.reason CARD_DECLINED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"holdRef\": \"HD-D-8b2c71\",\n      \"clientReference\": \"acme-din-0001\",\n      \"travelers\": [\n        {\n          \"type\": \"ADT\",\n          \"firstName\": \"Ada\",\n          \"lastName\": \"Example\",\n          \"email\": \"ada@example.test\",\n          \"phoneNumber\": \"+14155550100\",\n          \"lead\": true\n        }\n      ],\n      \"requiredInfoAnswers\": {\n        \"phoneCountry\": \"US\",\n        \"phone\": \"4155550100\",\n        \"specialRequest\": \"Window table if possible\",\n        \"restaurantEmailOptIn\": false,\n        \"opentableEmailOptIn\": false,\n        \"smsOptIn\": false\n      },\n      \"payment\": {\n        \"mode\": \"NONE\"\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The one booking write (03 §7, ADR-C02) — diningCardRequired",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders"
              ]
            },
            "description": "`POST /v1/orders` · operationId `createOrder`\n\nExample: FNB — book a card-secured slot with a Spreedly payment-method token, never a card number (D3, v1.7.0)\n\nReplaces /v1/book, /v1/bookings (write) and /v1/taxi/book (12-month aliases, 18 §5). Book never re-quotes — price movement at commit is 409 PRICE_CHANGED, never a silent swap. Ambiguous supplier outcome is 202 AMBIGUOUS_RESULT with the order in PENDING_RECONCILIATION (06 §8, ADR-C07). While the tenant is SUSPENDED a new order is refused 403 TENANT_SUSPENDED, with one exception: a same-key, same-body retry of an order request this tenant completed before the suspension is served its stored answer (Idempotency-Replayed true) — nothing new is booked. A fresh, expired or still in-flight key is 403 TENANT_SUSPENDED, a different body under the key 409 IDEMPOTENCY_CONFLICT (FEAT-020 §18c). Payment check (OD-301 (b), OD-360, OD-361 (1)): when payments are served and message.intent.payment.paymentRef names an intent, the platform checks it covers THIS order before the write-ahead row and before any supplier byte. Anything but a funded intent for this tenant, this repriceRef and this total is 409 CONFLICT with details.field message.intent.payment.paymentRef and details.reason one of NOT_FOUND (unknown ref, or another tenant's — the same bytes for both), OTHER_QUOTE (the intent was created from a different repriceRef), AMOUNT_MISMATCH (details.detail AMOUNT or CURRENCY), WRONG_STATE (details.detail is the intent's state: CREATED, REQUIRES_ACTION, CANCELED, FAILED, or PENDING_RECONCILIATION while a payment operation is unresolved), REFUNDED, AUTH_EXPIRED (an uncaptured authorization past its validity), ALREADY_USED (another order already booked on this payment; that order is never named). Nothing was sent to a supplier and the Idempotency-Key is released, so the client fixes the payment and retries. retryable is true only for WRONG_STATE with detail PENDING_RECONCILIATION, and false for every other reason. A second order on a repriceRef that is already spent (OD-360 (b) spends the RP- at book) is 409 CONFLICT with details.field message.intent.repriceRef and details.reason REPRICE_REF_SPENT (OD-487); when a payment is attached the payment check answers first. A same-key retry of an order that already booked is answered from its stored result without re-checking. Two-step round trip (v1.27.0): an order on an OUTBOUND_FIRST FLT offer whose journey is not complete (extension.air.journey.complete false) is 409 CONFLICT with details.reason JOURNEY_INCOMPLETE and details.nextSliceIndex, not retryable; nothing is sent to a supplier and the Idempotency-Key is released. Choose the remaining slices through POST /v1/flights/returns and reprice first. Card-secured slots (v1.7.0, D3 / OD-875): message.intent.payment.paymentMethod carries a payment-method token the client obtained from Spreedly; the platform hands the supplier the token and never a card number or security code, and none can be sent: PaymentMethodToken has no such field and a card-number-shaped value anywhere is refused (INSTRUMENT_DATA_REFUSED). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to orders.create, the repriceRef and the order total; without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39). Wallet (v1.18.0, served since 1.18.1, FEAT-030 §5.5, §12): with message.intent.payment.mode WALLET the total is held on the tenant's wallet inside the write-ahead flow (F-03) before any supplier byte, settled at ticketing or confirmation and released when the supplier fails; an ambiguous outcome keeps the hold until reconciliation, never debiting twice. Too little available is 402 PAYMENT_FAILED with details.reason WALLET_INSUFFICIENT and details.available (Money), and no supplier is called (sandbox scenario Test wallet_insufficient). Where the wallet is off (aikonic.wallet.enabled, on at DEV and SANDBOX only) a WALLET order is refused the same way, with nothing available, and never booked unpaid. A tier cap (FEAT-030 §5.7, OD-ENR-1, only when auto-live is on; not built — capped LIVE waits on OD-ENR-1) is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap and details.resetAt. connect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b): TIER_CAP_REACHED is un-reserved. An order of a LIVE_CAPPED tenant that is not a sandbox twin (on any rung) whose total would take today's spend past the daily cap is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap, details.usedToday (Money) and details.resetAt, checked before any payment is taken, any wallet hold is placed or any supplier byte is sent; nothing is written. The cap currency is INR when the activation country is IN, else USD; usedToday counts the tenant's non-cancelled orders since 00:00 UTC, converted to the cap currency at each order's FX snapshot; resetAt is the next 00:00 UTC. The check is per order against today's confirmed and in-flight spend and is SOFT under concurrency: concurrent orders, each under the cap, may together exceed it by at most the orders in flight (a reservation-based hard cap is an option for OD-ENR-26). An order whose currency the FX snapshot cannot convert to the cap currency is 500 INTERNAL details.reason FX_SNAPSHOT_UNAVAILABLE (the money verbs' existing sub-reason; retryable) and nothing is charged, held or sent. A sandbox twin's orders and tenants of other tiers are never capped. With payment.mode SAVED_METHOD while aikonic.payment-methods.enabled is off the order is 501 NOT_IMPLEMENTED details.reason PAYMENT_METHODS_UNAVAILABLE. payment.mode SAVED_METHOD with payment.methodId charges a saved payment method (POST /v1/payments/methods); a decline is 402 PAYMENT_FAILED details.reason CARD_DECLINED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"holdRef\": \"HD-D-91d0e4\",\n      \"clientReference\": \"acme-din-0002\",\n      \"travelers\": [\n        {\n          \"type\": \"ADT\",\n          \"firstName\": \"Ada\",\n          \"lastName\": \"Example\",\n          \"email\": \"ada@example.test\",\n          \"phoneNumber\": \"+14155550100\",\n          \"lead\": true\n        }\n      ],\n      \"requiredInfoAnswers\": {\n        \"phoneCountry\": \"US\"\n      },\n      \"payment\": {\n        \"mode\": \"PAYMENT_METHOD_TOKEN\",\n        \"paymentMethod\": {\n          \"provider\": \"SPREEDLY\",\n          \"token\": \"syn-spreedly-pm-token-0001\",\n          \"type\": \"credit_card\",\n          \"last4\": \"1111\",\n          \"brand\": \"visa\"\n        }\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The one booking write (03 §7, ADR-C02) — diningScaRetry",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders"
              ]
            },
            "description": "`POST /v1/orders` · operationId `createOrder`\n\nExample: FNB — retry after SCA_REQUIRED: the same token plus the 3-D Secure result (v1.7.0)\n\nReplaces /v1/book, /v1/bookings (write) and /v1/taxi/book (12-month aliases, 18 §5). Book never re-quotes — price movement at commit is 409 PRICE_CHANGED, never a silent swap. Ambiguous supplier outcome is 202 AMBIGUOUS_RESULT with the order in PENDING_RECONCILIATION (06 §8, ADR-C07). While the tenant is SUSPENDED a new order is refused 403 TENANT_SUSPENDED, with one exception: a same-key, same-body retry of an order request this tenant completed before the suspension is served its stored answer (Idempotency-Replayed true) — nothing new is booked. A fresh, expired or still in-flight key is 403 TENANT_SUSPENDED, a different body under the key 409 IDEMPOTENCY_CONFLICT (FEAT-020 §18c). Payment check (OD-301 (b), OD-360, OD-361 (1)): when payments are served and message.intent.payment.paymentRef names an intent, the platform checks it covers THIS order before the write-ahead row and before any supplier byte. Anything but a funded intent for this tenant, this repriceRef and this total is 409 CONFLICT with details.field message.intent.payment.paymentRef and details.reason one of NOT_FOUND (unknown ref, or another tenant's — the same bytes for both), OTHER_QUOTE (the intent was created from a different repriceRef), AMOUNT_MISMATCH (details.detail AMOUNT or CURRENCY), WRONG_STATE (details.detail is the intent's state: CREATED, REQUIRES_ACTION, CANCELED, FAILED, or PENDING_RECONCILIATION while a payment operation is unresolved), REFUNDED, AUTH_EXPIRED (an uncaptured authorization past its validity), ALREADY_USED (another order already booked on this payment; that order is never named). Nothing was sent to a supplier and the Idempotency-Key is released, so the client fixes the payment and retries. retryable is true only for WRONG_STATE with detail PENDING_RECONCILIATION, and false for every other reason. A second order on a repriceRef that is already spent (OD-360 (b) spends the RP- at book) is 409 CONFLICT with details.field message.intent.repriceRef and details.reason REPRICE_REF_SPENT (OD-487); when a payment is attached the payment check answers first. A same-key retry of an order that already booked is answered from its stored result without re-checking. Two-step round trip (v1.27.0): an order on an OUTBOUND_FIRST FLT offer whose journey is not complete (extension.air.journey.complete false) is 409 CONFLICT with details.reason JOURNEY_INCOMPLETE and details.nextSliceIndex, not retryable; nothing is sent to a supplier and the Idempotency-Key is released. Choose the remaining slices through POST /v1/flights/returns and reprice first. Card-secured slots (v1.7.0, D3 / OD-875): message.intent.payment.paymentMethod carries a payment-method token the client obtained from Spreedly; the platform hands the supplier the token and never a card number or security code, and none can be sent: PaymentMethodToken has no such field and a card-number-shaped value anywhere is refused (INSTRUMENT_DATA_REFUSED). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to orders.create, the repriceRef and the order total; without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39). Wallet (v1.18.0, served since 1.18.1, FEAT-030 §5.5, §12): with message.intent.payment.mode WALLET the total is held on the tenant's wallet inside the write-ahead flow (F-03) before any supplier byte, settled at ticketing or confirmation and released when the supplier fails; an ambiguous outcome keeps the hold until reconciliation, never debiting twice. Too little available is 402 PAYMENT_FAILED with details.reason WALLET_INSUFFICIENT and details.available (Money), and no supplier is called (sandbox scenario Test wallet_insufficient). Where the wallet is off (aikonic.wallet.enabled, on at DEV and SANDBOX only) a WALLET order is refused the same way, with nothing available, and never booked unpaid. A tier cap (FEAT-030 §5.7, OD-ENR-1, only when auto-live is on; not built — capped LIVE waits on OD-ENR-1) is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap and details.resetAt. connect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b): TIER_CAP_REACHED is un-reserved. An order of a LIVE_CAPPED tenant that is not a sandbox twin (on any rung) whose total would take today's spend past the daily cap is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap, details.usedToday (Money) and details.resetAt, checked before any payment is taken, any wallet hold is placed or any supplier byte is sent; nothing is written. The cap currency is INR when the activation country is IN, else USD; usedToday counts the tenant's non-cancelled orders since 00:00 UTC, converted to the cap currency at each order's FX snapshot; resetAt is the next 00:00 UTC. The check is per order against today's confirmed and in-flight spend and is SOFT under concurrency: concurrent orders, each under the cap, may together exceed it by at most the orders in flight (a reservation-based hard cap is an option for OD-ENR-26). An order whose currency the FX snapshot cannot convert to the cap currency is 500 INTERNAL details.reason FX_SNAPSHOT_UNAVAILABLE (the money verbs' existing sub-reason; retryable) and nothing is charged, held or sent. A sandbox twin's orders and tenants of other tiers are never capped. With payment.mode SAVED_METHOD while aikonic.payment-methods.enabled is off the order is 501 NOT_IMPLEMENTED details.reason PAYMENT_METHODS_UNAVAILABLE. payment.mode SAVED_METHOD with payment.methodId charges a saved payment method (POST /v1/payments/methods); a decline is 402 PAYMENT_FAILED details.reason CARD_DECLINED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"holdRef\": \"HD-D-91d0e4\",\n      \"clientReference\": \"acme-din-0002\",\n      \"travelers\": [\n        {\n          \"type\": \"ADT\",\n          \"firstName\": \"Ada\",\n          \"lastName\": \"Example\",\n          \"email\": \"ada@example.test\",\n          \"phoneNumber\": \"+14155550100\",\n          \"lead\": true\n        }\n      ],\n      \"requiredInfoAnswers\": {\n        \"phoneCountry\": \"US\"\n      },\n      \"payment\": {\n        \"mode\": \"PAYMENT_METHOD_TOKEN\",\n        \"paymentMethod\": {\n          \"provider\": \"SPREEDLY\",\n          \"token\": \"syn-spreedly-pm-token-0001\",\n          \"type\": \"credit_card\",\n          \"last4\": \"1111\",\n          \"brand\": \"visa\",\n          \"threeDs\": {\n            \"redirectUrl\": \"https://app.acme-travel.example/checkout/3ds-return\",\n            \"setupIntent\": \"syn-spreedly-setup-intent-0001\"\n          }\n        }\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The one booking write (03 §7, ADR-C02) — diningMpiData",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders"
              ]
            },
            "description": "`POST /v1/orders` · operationId `createOrder`\n\nExample: FNB — card authenticated by the client MPI: mpiData on the token (v1.7.0)\n\nReplaces /v1/book, /v1/bookings (write) and /v1/taxi/book (12-month aliases, 18 §5). Book never re-quotes — price movement at commit is 409 PRICE_CHANGED, never a silent swap. Ambiguous supplier outcome is 202 AMBIGUOUS_RESULT with the order in PENDING_RECONCILIATION (06 §8, ADR-C07). While the tenant is SUSPENDED a new order is refused 403 TENANT_SUSPENDED, with one exception: a same-key, same-body retry of an order request this tenant completed before the suspension is served its stored answer (Idempotency-Replayed true) — nothing new is booked. A fresh, expired or still in-flight key is 403 TENANT_SUSPENDED, a different body under the key 409 IDEMPOTENCY_CONFLICT (FEAT-020 §18c). Payment check (OD-301 (b), OD-360, OD-361 (1)): when payments are served and message.intent.payment.paymentRef names an intent, the platform checks it covers THIS order before the write-ahead row and before any supplier byte. Anything but a funded intent for this tenant, this repriceRef and this total is 409 CONFLICT with details.field message.intent.payment.paymentRef and details.reason one of NOT_FOUND (unknown ref, or another tenant's — the same bytes for both), OTHER_QUOTE (the intent was created from a different repriceRef), AMOUNT_MISMATCH (details.detail AMOUNT or CURRENCY), WRONG_STATE (details.detail is the intent's state: CREATED, REQUIRES_ACTION, CANCELED, FAILED, or PENDING_RECONCILIATION while a payment operation is unresolved), REFUNDED, AUTH_EXPIRED (an uncaptured authorization past its validity), ALREADY_USED (another order already booked on this payment; that order is never named). Nothing was sent to a supplier and the Idempotency-Key is released, so the client fixes the payment and retries. retryable is true only for WRONG_STATE with detail PENDING_RECONCILIATION, and false for every other reason. A second order on a repriceRef that is already spent (OD-360 (b) spends the RP- at book) is 409 CONFLICT with details.field message.intent.repriceRef and details.reason REPRICE_REF_SPENT (OD-487); when a payment is attached the payment check answers first. A same-key retry of an order that already booked is answered from its stored result without re-checking. Two-step round trip (v1.27.0): an order on an OUTBOUND_FIRST FLT offer whose journey is not complete (extension.air.journey.complete false) is 409 CONFLICT with details.reason JOURNEY_INCOMPLETE and details.nextSliceIndex, not retryable; nothing is sent to a supplier and the Idempotency-Key is released. Choose the remaining slices through POST /v1/flights/returns and reprice first. Card-secured slots (v1.7.0, D3 / OD-875): message.intent.payment.paymentMethod carries a payment-method token the client obtained from Spreedly; the platform hands the supplier the token and never a card number or security code, and none can be sent: PaymentMethodToken has no such field and a card-number-shaped value anywhere is refused (INSTRUMENT_DATA_REFUSED). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to orders.create, the repriceRef and the order total; without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39). Wallet (v1.18.0, served since 1.18.1, FEAT-030 §5.5, §12): with message.intent.payment.mode WALLET the total is held on the tenant's wallet inside the write-ahead flow (F-03) before any supplier byte, settled at ticketing or confirmation and released when the supplier fails; an ambiguous outcome keeps the hold until reconciliation, never debiting twice. Too little available is 402 PAYMENT_FAILED with details.reason WALLET_INSUFFICIENT and details.available (Money), and no supplier is called (sandbox scenario Test wallet_insufficient). Where the wallet is off (aikonic.wallet.enabled, on at DEV and SANDBOX only) a WALLET order is refused the same way, with nothing available, and never booked unpaid. A tier cap (FEAT-030 §5.7, OD-ENR-1, only when auto-live is on; not built — capped LIVE waits on OD-ENR-1) is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap and details.resetAt. connect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b): TIER_CAP_REACHED is un-reserved. An order of a LIVE_CAPPED tenant that is not a sandbox twin (on any rung) whose total would take today's spend past the daily cap is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap, details.usedToday (Money) and details.resetAt, checked before any payment is taken, any wallet hold is placed or any supplier byte is sent; nothing is written. The cap currency is INR when the activation country is IN, else USD; usedToday counts the tenant's non-cancelled orders since 00:00 UTC, converted to the cap currency at each order's FX snapshot; resetAt is the next 00:00 UTC. The check is per order against today's confirmed and in-flight spend and is SOFT under concurrency: concurrent orders, each under the cap, may together exceed it by at most the orders in flight (a reservation-based hard cap is an option for OD-ENR-26). An order whose currency the FX snapshot cannot convert to the cap currency is 500 INTERNAL details.reason FX_SNAPSHOT_UNAVAILABLE (the money verbs' existing sub-reason; retryable) and nothing is charged, held or sent. A sandbox twin's orders and tenants of other tiers are never capped. With payment.mode SAVED_METHOD while aikonic.payment-methods.enabled is off the order is 501 NOT_IMPLEMENTED details.reason PAYMENT_METHODS_UNAVAILABLE. payment.mode SAVED_METHOD with payment.methodId charges a saved payment method (POST /v1/payments/methods); a decline is 402 PAYMENT_FAILED details.reason CARD_DECLINED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"holdRef\": \"HD-D-91d0e4\",\n      \"travelers\": [\n        {\n          \"type\": \"ADT\",\n          \"firstName\": \"Ada\",\n          \"lastName\": \"Example\",\n          \"email\": \"ada@example.test\",\n          \"phoneNumber\": \"+14155550100\",\n          \"lead\": true\n        }\n      ],\n      \"payment\": {\n        \"mode\": \"PAYMENT_METHOD_TOKEN\",\n        \"paymentMethod\": {\n          \"provider\": \"SPREEDLY\",\n          \"token\": \"syn-spreedly-pm-token-0001\",\n          \"type\": \"credit_card\",\n          \"last4\": \"1111\",\n          \"brand\": \"visa\",\n          \"threeDs\": {\n            \"mpiData\": {\n              \"threeDSVersion\": \"2.2.0\",\n              \"eci\": \"05\",\n              \"cavv\": \"syn-cavv-placeholder-0001\",\n              \"dsTransId\": \"f25084f0-5b16-4c0a-ae5d-b24808a95e4b\"\n            }\n          }\n        }\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The one booking write (03 §7, ADR-C02) — diningSmsLink",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders"
              ]
            },
            "description": "`POST /v1/orders` · operationId `createOrder`\n\nExample: FNB — In-House card-secured slot: no token, Example Dining texts the diner a card link (smsOptIn true) (v1.7.0)\n\nReplaces /v1/book, /v1/bookings (write) and /v1/taxi/book (12-month aliases, 18 §5). Book never re-quotes — price movement at commit is 409 PRICE_CHANGED, never a silent swap. Ambiguous supplier outcome is 202 AMBIGUOUS_RESULT with the order in PENDING_RECONCILIATION (06 §8, ADR-C07). While the tenant is SUSPENDED a new order is refused 403 TENANT_SUSPENDED, with one exception: a same-key, same-body retry of an order request this tenant completed before the suspension is served its stored answer (Idempotency-Replayed true) — nothing new is booked. A fresh, expired or still in-flight key is 403 TENANT_SUSPENDED, a different body under the key 409 IDEMPOTENCY_CONFLICT (FEAT-020 §18c). Payment check (OD-301 (b), OD-360, OD-361 (1)): when payments are served and message.intent.payment.paymentRef names an intent, the platform checks it covers THIS order before the write-ahead row and before any supplier byte. Anything but a funded intent for this tenant, this repriceRef and this total is 409 CONFLICT with details.field message.intent.payment.paymentRef and details.reason one of NOT_FOUND (unknown ref, or another tenant's — the same bytes for both), OTHER_QUOTE (the intent was created from a different repriceRef), AMOUNT_MISMATCH (details.detail AMOUNT or CURRENCY), WRONG_STATE (details.detail is the intent's state: CREATED, REQUIRES_ACTION, CANCELED, FAILED, or PENDING_RECONCILIATION while a payment operation is unresolved), REFUNDED, AUTH_EXPIRED (an uncaptured authorization past its validity), ALREADY_USED (another order already booked on this payment; that order is never named). Nothing was sent to a supplier and the Idempotency-Key is released, so the client fixes the payment and retries. retryable is true only for WRONG_STATE with detail PENDING_RECONCILIATION, and false for every other reason. A second order on a repriceRef that is already spent (OD-360 (b) spends the RP- at book) is 409 CONFLICT with details.field message.intent.repriceRef and details.reason REPRICE_REF_SPENT (OD-487); when a payment is attached the payment check answers first. A same-key retry of an order that already booked is answered from its stored result without re-checking. Two-step round trip (v1.27.0): an order on an OUTBOUND_FIRST FLT offer whose journey is not complete (extension.air.journey.complete false) is 409 CONFLICT with details.reason JOURNEY_INCOMPLETE and details.nextSliceIndex, not retryable; nothing is sent to a supplier and the Idempotency-Key is released. Choose the remaining slices through POST /v1/flights/returns and reprice first. Card-secured slots (v1.7.0, D3 / OD-875): message.intent.payment.paymentMethod carries a payment-method token the client obtained from Spreedly; the platform hands the supplier the token and never a card number or security code, and none can be sent: PaymentMethodToken has no such field and a card-number-shaped value anywhere is refused (INSTRUMENT_DATA_REFUSED). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to orders.create, the repriceRef and the order total; without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39). Wallet (v1.18.0, served since 1.18.1, FEAT-030 §5.5, §12): with message.intent.payment.mode WALLET the total is held on the tenant's wallet inside the write-ahead flow (F-03) before any supplier byte, settled at ticketing or confirmation and released when the supplier fails; an ambiguous outcome keeps the hold until reconciliation, never debiting twice. Too little available is 402 PAYMENT_FAILED with details.reason WALLET_INSUFFICIENT and details.available (Money), and no supplier is called (sandbox scenario Test wallet_insufficient). Where the wallet is off (aikonic.wallet.enabled, on at DEV and SANDBOX only) a WALLET order is refused the same way, with nothing available, and never booked unpaid. A tier cap (FEAT-030 §5.7, OD-ENR-1, only when auto-live is on; not built — capped LIVE waits on OD-ENR-1) is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap and details.resetAt. connect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b): TIER_CAP_REACHED is un-reserved. An order of a LIVE_CAPPED tenant that is not a sandbox twin (on any rung) whose total would take today's spend past the daily cap is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap, details.usedToday (Money) and details.resetAt, checked before any payment is taken, any wallet hold is placed or any supplier byte is sent; nothing is written. The cap currency is INR when the activation country is IN, else USD; usedToday counts the tenant's non-cancelled orders since 00:00 UTC, converted to the cap currency at each order's FX snapshot; resetAt is the next 00:00 UTC. The check is per order against today's confirmed and in-flight spend and is SOFT under concurrency: concurrent orders, each under the cap, may together exceed it by at most the orders in flight (a reservation-based hard cap is an option for OD-ENR-26). An order whose currency the FX snapshot cannot convert to the cap currency is 500 INTERNAL details.reason FX_SNAPSHOT_UNAVAILABLE (the money verbs' existing sub-reason; retryable) and nothing is charged, held or sent. A sandbox twin's orders and tenants of other tiers are never capped. With payment.mode SAVED_METHOD while aikonic.payment-methods.enabled is off the order is 501 NOT_IMPLEMENTED details.reason PAYMENT_METHODS_UNAVAILABLE. payment.mode SAVED_METHOD with payment.methodId charges a saved payment method (POST /v1/payments/methods); a decline is 402 PAYMENT_FAILED details.reason CARD_DECLINED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"holdRef\": \"HD-D-a40c17\",\n      \"travelers\": [\n        {\n          \"type\": \"ADT\",\n          \"firstName\": \"Ada\",\n          \"lastName\": \"Example\",\n          \"email\": \"ada@example.test\",\n          \"phoneNumber\": \"+14155550100\",\n          \"lead\": true\n        }\n      ],\n      \"requiredInfoAnswers\": {\n        \"phoneCountry\": \"US\",\n        \"smsOptIn\": true\n      },\n      \"payment\": {\n        \"mode\": \"OPENTABLE_SMS_LINK\"\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The one booking write (03 §7, ADR-C02) — diningExperience",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders"
              ]
            },
            "description": "`POST /v1/orders` · operationId `createOrder`\n\nExample: FNB — book the experience with the price split; paid at the restaurant (v1.7.0)\n\nReplaces /v1/book, /v1/bookings (write) and /v1/taxi/book (12-month aliases, 18 §5). Book never re-quotes — price movement at commit is 409 PRICE_CHANGED, never a silent swap. Ambiguous supplier outcome is 202 AMBIGUOUS_RESULT with the order in PENDING_RECONCILIATION (06 §8, ADR-C07). While the tenant is SUSPENDED a new order is refused 403 TENANT_SUSPENDED, with one exception: a same-key, same-body retry of an order request this tenant completed before the suspension is served its stored answer (Idempotency-Replayed true) — nothing new is booked. A fresh, expired or still in-flight key is 403 TENANT_SUSPENDED, a different body under the key 409 IDEMPOTENCY_CONFLICT (FEAT-020 §18c). Payment check (OD-301 (b), OD-360, OD-361 (1)): when payments are served and message.intent.payment.paymentRef names an intent, the platform checks it covers THIS order before the write-ahead row and before any supplier byte. Anything but a funded intent for this tenant, this repriceRef and this total is 409 CONFLICT with details.field message.intent.payment.paymentRef and details.reason one of NOT_FOUND (unknown ref, or another tenant's — the same bytes for both), OTHER_QUOTE (the intent was created from a different repriceRef), AMOUNT_MISMATCH (details.detail AMOUNT or CURRENCY), WRONG_STATE (details.detail is the intent's state: CREATED, REQUIRES_ACTION, CANCELED, FAILED, or PENDING_RECONCILIATION while a payment operation is unresolved), REFUNDED, AUTH_EXPIRED (an uncaptured authorization past its validity), ALREADY_USED (another order already booked on this payment; that order is never named). Nothing was sent to a supplier and the Idempotency-Key is released, so the client fixes the payment and retries. retryable is true only for WRONG_STATE with detail PENDING_RECONCILIATION, and false for every other reason. A second order on a repriceRef that is already spent (OD-360 (b) spends the RP- at book) is 409 CONFLICT with details.field message.intent.repriceRef and details.reason REPRICE_REF_SPENT (OD-487); when a payment is attached the payment check answers first. A same-key retry of an order that already booked is answered from its stored result without re-checking. Two-step round trip (v1.27.0): an order on an OUTBOUND_FIRST FLT offer whose journey is not complete (extension.air.journey.complete false) is 409 CONFLICT with details.reason JOURNEY_INCOMPLETE and details.nextSliceIndex, not retryable; nothing is sent to a supplier and the Idempotency-Key is released. Choose the remaining slices through POST /v1/flights/returns and reprice first. Card-secured slots (v1.7.0, D3 / OD-875): message.intent.payment.paymentMethod carries a payment-method token the client obtained from Spreedly; the platform hands the supplier the token and never a card number or security code, and none can be sent: PaymentMethodToken has no such field and a card-number-shaped value anywhere is refused (INSTRUMENT_DATA_REFUSED). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to orders.create, the repriceRef and the order total; without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39). Wallet (v1.18.0, served since 1.18.1, FEAT-030 §5.5, §12): with message.intent.payment.mode WALLET the total is held on the tenant's wallet inside the write-ahead flow (F-03) before any supplier byte, settled at ticketing or confirmation and released when the supplier fails; an ambiguous outcome keeps the hold until reconciliation, never debiting twice. Too little available is 402 PAYMENT_FAILED with details.reason WALLET_INSUFFICIENT and details.available (Money), and no supplier is called (sandbox scenario Test wallet_insufficient). Where the wallet is off (aikonic.wallet.enabled, on at DEV and SANDBOX only) a WALLET order is refused the same way, with nothing available, and never booked unpaid. A tier cap (FEAT-030 §5.7, OD-ENR-1, only when auto-live is on; not built — capped LIVE waits on OD-ENR-1) is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap and details.resetAt. connect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b): TIER_CAP_REACHED is un-reserved. An order of a LIVE_CAPPED tenant that is not a sandbox twin (on any rung) whose total would take today's spend past the daily cap is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap, details.usedToday (Money) and details.resetAt, checked before any payment is taken, any wallet hold is placed or any supplier byte is sent; nothing is written. The cap currency is INR when the activation country is IN, else USD; usedToday counts the tenant's non-cancelled orders since 00:00 UTC, converted to the cap currency at each order's FX snapshot; resetAt is the next 00:00 UTC. The check is per order against today's confirmed and in-flight spend and is SOFT under concurrency: concurrent orders, each under the cap, may together exceed it by at most the orders in flight (a reservation-based hard cap is an option for OD-ENR-26). An order whose currency the FX snapshot cannot convert to the cap currency is 500 INTERNAL details.reason FX_SNAPSHOT_UNAVAILABLE (the money verbs' existing sub-reason; retryable) and nothing is charged, held or sent. A sandbox twin's orders and tenants of other tiers are never capped. With payment.mode SAVED_METHOD while aikonic.payment-methods.enabled is off the order is 501 NOT_IMPLEMENTED details.reason PAYMENT_METHODS_UNAVAILABLE. payment.mode SAVED_METHOD with payment.methodId charges a saved payment method (POST /v1/payments/methods); a decline is 402 PAYMENT_FAILED details.reason CARD_DECLINED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"holdRef\": \"HD-D-5f7a02\",\n      \"travelers\": [\n        {\n          \"type\": \"ADT\",\n          \"firstName\": \"Ada\",\n          \"lastName\": \"Example\",\n          \"email\": \"ada@example.test\",\n          \"phoneNumber\": \"+14155550100\",\n          \"lead\": true\n        }\n      ],\n      \"experienceSelection\": {\n        \"experienceId\": 900101,\n        \"version\": 3,\n        \"prices\": [\n          {\n            \"priceId\": 1,\n            \"count\": 2\n          },\n          {\n            \"priceId\": 2,\n            \"count\": 1\n          }\n        ]\n      },\n      \"requiredInfoAnswers\": {\n        \"phoneCountry\": \"US\",\n        \"phone\": \"4155550100\",\n        \"specialRequest\": \"One guest is vegetarian\",\n        \"restaurantEmailOptIn\": false,\n        \"opentableEmailOptIn\": false,\n        \"smsOptIn\": true\n      },\n      \"payment\": {\n        \"mode\": \"NONE\"\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The one booking write (03 §7, ADR-C02) — savedMethod",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders"
              ]
            },
            "description": "`POST /v1/orders` · operationId `createOrder`\n\nExample: HTL — pay with a saved card (payment.mode SAVED_METHOD, v1.51.0 PROPOSAL)\n\nReplaces /v1/book, /v1/bookings (write) and /v1/taxi/book (12-month aliases, 18 §5). Book never re-quotes — price movement at commit is 409 PRICE_CHANGED, never a silent swap. Ambiguous supplier outcome is 202 AMBIGUOUS_RESULT with the order in PENDING_RECONCILIATION (06 §8, ADR-C07). While the tenant is SUSPENDED a new order is refused 403 TENANT_SUSPENDED, with one exception: a same-key, same-body retry of an order request this tenant completed before the suspension is served its stored answer (Idempotency-Replayed true) — nothing new is booked. A fresh, expired or still in-flight key is 403 TENANT_SUSPENDED, a different body under the key 409 IDEMPOTENCY_CONFLICT (FEAT-020 §18c). Payment check (OD-301 (b), OD-360, OD-361 (1)): when payments are served and message.intent.payment.paymentRef names an intent, the platform checks it covers THIS order before the write-ahead row and before any supplier byte. Anything but a funded intent for this tenant, this repriceRef and this total is 409 CONFLICT with details.field message.intent.payment.paymentRef and details.reason one of NOT_FOUND (unknown ref, or another tenant's — the same bytes for both), OTHER_QUOTE (the intent was created from a different repriceRef), AMOUNT_MISMATCH (details.detail AMOUNT or CURRENCY), WRONG_STATE (details.detail is the intent's state: CREATED, REQUIRES_ACTION, CANCELED, FAILED, or PENDING_RECONCILIATION while a payment operation is unresolved), REFUNDED, AUTH_EXPIRED (an uncaptured authorization past its validity), ALREADY_USED (another order already booked on this payment; that order is never named). Nothing was sent to a supplier and the Idempotency-Key is released, so the client fixes the payment and retries. retryable is true only for WRONG_STATE with detail PENDING_RECONCILIATION, and false for every other reason. A second order on a repriceRef that is already spent (OD-360 (b) spends the RP- at book) is 409 CONFLICT with details.field message.intent.repriceRef and details.reason REPRICE_REF_SPENT (OD-487); when a payment is attached the payment check answers first. A same-key retry of an order that already booked is answered from its stored result without re-checking. Two-step round trip (v1.27.0): an order on an OUTBOUND_FIRST FLT offer whose journey is not complete (extension.air.journey.complete false) is 409 CONFLICT with details.reason JOURNEY_INCOMPLETE and details.nextSliceIndex, not retryable; nothing is sent to a supplier and the Idempotency-Key is released. Choose the remaining slices through POST /v1/flights/returns and reprice first. Card-secured slots (v1.7.0, D3 / OD-875): message.intent.payment.paymentMethod carries a payment-method token the client obtained from Spreedly; the platform hands the supplier the token and never a card number or security code, and none can be sent: PaymentMethodToken has no such field and a card-number-shaped value anywhere is refused (INSTRUMENT_DATA_REFUSED). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to orders.create, the repriceRef and the order total; without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39). Wallet (v1.18.0, served since 1.18.1, FEAT-030 §5.5, §12): with message.intent.payment.mode WALLET the total is held on the tenant's wallet inside the write-ahead flow (F-03) before any supplier byte, settled at ticketing or confirmation and released when the supplier fails; an ambiguous outcome keeps the hold until reconciliation, never debiting twice. Too little available is 402 PAYMENT_FAILED with details.reason WALLET_INSUFFICIENT and details.available (Money), and no supplier is called (sandbox scenario Test wallet_insufficient). Where the wallet is off (aikonic.wallet.enabled, on at DEV and SANDBOX only) a WALLET order is refused the same way, with nothing available, and never booked unpaid. A tier cap (FEAT-030 §5.7, OD-ENR-1, only when auto-live is on; not built — capped LIVE waits on OD-ENR-1) is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap and details.resetAt. connect-v1 1.51.0 (PROPOSAL; served when aikonic.activation.capped-flow.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b): TIER_CAP_REACHED is un-reserved. An order of a LIVE_CAPPED tenant that is not a sandbox twin (on any rung) whose total would take today's spend past the daily cap is 403 PERMISSION_DENIED details.reason TIER_CAP_REACHED with details.cap, details.usedToday (Money) and details.resetAt, checked before any payment is taken, any wallet hold is placed or any supplier byte is sent; nothing is written. The cap currency is INR when the activation country is IN, else USD; usedToday counts the tenant's non-cancelled orders since 00:00 UTC, converted to the cap currency at each order's FX snapshot; resetAt is the next 00:00 UTC. The check is per order against today's confirmed and in-flight spend and is SOFT under concurrency: concurrent orders, each under the cap, may together exceed it by at most the orders in flight (a reservation-based hard cap is an option for OD-ENR-26). An order whose currency the FX snapshot cannot convert to the cap currency is 500 INTERNAL details.reason FX_SNAPSHOT_UNAVAILABLE (the money verbs' existing sub-reason; retryable) and nothing is charged, held or sent. A sandbox twin's orders and tenants of other tiers are never capped. With payment.mode SAVED_METHOD while aikonic.payment-methods.enabled is off the order is 501 NOT_IMPLEMENTED details.reason PAYMENT_METHODS_UNAVAILABLE. payment.mode SAVED_METHOD with payment.methodId charges a saved payment method (POST /v1/payments/methods); a decline is 402 PAYMENT_FAILED details.reason CARD_DECLINED.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"HTL\",\n    \"currency\": \"INR\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"repriceRef\": \"RP-H-2ab91c\",\n      \"clientReference\": \"sample-0001\",\n      \"travelers\": [\n        {\n          \"type\": \"ADT\",\n          \"firstName\": \"Arjun\",\n          \"lastName\": \"Mehta\",\n          \"email\": \"arjun@sampletravels.in\",\n          \"phoneNumber\": \"+919800000000\",\n          \"lead\": true\n        }\n      ],\n      \"payment\": {\n        \"mode\": \"SAVED_METHOD\",\n        \"methodId\": \"PM-01JB2C4V\"\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Filter orders by clientReference, traveler, date, product, state (03 §3 #14)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                "search"
              ]
            },
            "description": "`POST /v1/orders/search` · operationId `searchOrders`\n\nBody: a skeleton built from the schema's required properties (the contract carries no example).\n\nPOST because filter objects outgrow query strings. The crash-recovery entry point (06 §8 step 6). Read-path failures (OD-P9-01; API-0005 §4, API-0006 §6, 03 §2.3): when the order store times out the answer is 502 UPSTREAM_UNAVAILABLE, retryable true (retry with backoff; nothing is written by a read); when the deployment has no order reader assembled it is 501 NOT_IMPLEMENTED, retryable false.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"<string>\"\n  },\n  \"message\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Order read (absorbs /v1/booking-detail and GET /v1/bookings/{ref} as aliases)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`GET /v1/orders/{orderRef}` · operationId `getOrder`\n\nRead-path failures (OD-P9-01; API-0005 §4, API-0006 §6, 03 §2.3): when the order store times out the answer is 502 UPSTREAM_UNAVAILABLE, retryable true (a read writes nothing, so a retry is safe); when the deployment has no order reader assembled it is 501 NOT_IMPLEMENTED, retryable false."
          },
          "response": []
        },
        {
          "name": "Vouchers, e-tickets, invoices, QR for an order (03 §3 #15)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/documents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "documents"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`GET /v1/orders/{orderRef}/documents` · operationId `listOrderDocuments`\n\nRead-path failures (OD-P9-01; API-0005 §4, API-0006 §6, 03 §2.3): when the order store times out the answer is 502 UPSTREAM_UNAVAILABLE, retryable true; when the deployment has no order reader assembled it is 501 NOT_IMPLEMENTED, retryable false."
          },
          "response": []
        },
        {
          "name": "Regenerate documents (async)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/documents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "documents"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/orders/{orderRef}/documents` · operationId `regenerateOrderDocuments`"
          },
          "response": []
        },
        {
          "name": "The booking's tickets, read live from the supplier (v1.16.0)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/tickets",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "tickets"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`GET /v1/orders/{orderRef}/tickets` · operationId `getOrderTickets`\n\nconnect-v1 1.16.0. Each ticket of the order as the supplier states it NOW — the passenger, the fare category, the seat, ISSUED or CANCELLED, the price the supplier displays (its own currency) and the ticket's own document. Read live on every call and never stored: the order holds no traveller (FEAT-F03 §14), so this answer is personal data handed to the caller, Cache-Control no-store. orders:read and the order's own tab, as GET /v1/orders/{orderRef}; another tenant's order is 404. 409 CONFLICT when the order holds no booked supplier reference yet (details.reason ORDER_NOT_TICKETED) or its supplier has no ticket read (TICKETS_NOT_READABLE). First supplier: SUP-BUS-EXAMPLE (GET /tickets/{id})."
          },
          "response": []
        },
        {
          "name": "The booked fare's rule text, read live from the supplier (v1.43.0)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/fare-rules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "fare-rules"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`GET /v1/orders/{orderRef}/fare-rules` · operationId `getOrderFareRules`\n\nconnect-v1 1.43.0. The airline's fare-rule text of the fare the order holds, one entry per flown segment in itinerary order — cancellation, change and refund terms as the airline files them (category text, verbatim and in the airline's own words, typically upper case). Read live from the supplier on every call and never stored; the text is the supplier's statement, not a quote: what a cancel or a change costs is answered by POST /v1/orders/{orderRef}/cancel-quote and the amend quote. orders:read and the order's own tab, as GET /v1/orders/{orderRef}; another tenant's order is 404. Cache-Control no-store. text is null on a segment the supplier stated no rules for. 409 CONFLICT when the order holds no booked supplier reference yet (details.reason ORDER_NOT_BOOKED) or its supplier has no fare-rule read (FARE_RULES_NOT_READABLE). First supplier: SUP-AIR-EXAMPLE (airbooking /farerules, keyed per segment from the PNR)."
          },
          "response": []
        },
        {
          "name": "Visa assistance: what this order sent to the visa provider (v1.34.0)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/visa-assistance",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "visa-assistance"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`GET /v1/orders/{orderRef}/visa-assistance` · operationId `listOrderVisaAssistance`\n\nconnect-v1 1.34.0. Every send this order made to the visa provider, newest first: the masked traveller, the countries and dates, the prepared application link and when. Read from the platform's own record; never calls the provider. An order that sent nothing answers an empty list. orders:read; another tenant's order is 404.",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "X-Api-Key",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{apiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Visa assistance: send a booked trip to the visa provider (v1.34.0) — oneTraveller",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/visa-assistance",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "visa-assistance"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/orders/{orderRef}/visa-assistance` · operationId `createOrderVisaAssistance`\n\nExample: One traveller, US passport, a trip to India\n\nconnect-v1 1.34.0. Hands the trip behind a booked order to the visa provider behind SUP-VIS-EXAMPLE (Example Visas), one trip per traveller, 1 to 20 travellers. The provider e-mails each traveller the visa and entry requirements for the trip with a link to apply, and answers one prepared application link per traveller, returned as applyUrl (the traveller applies and pays on the provider's site; no money passes through the platform). A person chooses to send it, per order, with the travellers' consent: nothing is sent automatically. Sent once under the Idempotency-Key; a replay answers the first result and never sends twice (the provider keeps no reference). The platform records each send (the traveller's first name, last initial and masked e-mail, the countries, dates, link and time) and never stores the full e-mail. Any domain's order once it holds a booked supplier reference; otherwise 400 VALIDATION details.reason ORDER_NOT_BOOKED, before any provider call. A trip the provider refuses is 400 VALIDATION details.reason VISA_TRIP_INVALID with the provider's lines in details.errors. Not provisioned or switched off is 503 PROVIDER_DOWN details.reason VISA_ASSISTANCE_NOT_PROVISIONED. orders:write; another tenant's order is 404.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"travellers\": [\n    {\n      \"firstName\": \"Test\",\n      \"lastName\": \"Traveller\",\n      \"email\": \"traveller@example.com\",\n      \"passportCountry\": \"US\"\n    }\n  ],\n  \"from\": \"US\",\n  \"to\": [\n    \"IN\"\n  ],\n  \"startDate\": \"2026-11-20T00:00:00Z\",\n  \"endDate\": \"2026-11-27T00:00:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "X-Api-Key",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{apiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The order's timeline — its events, oldest first, in the GET /v1/events vocabulary (03 §3 #26)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/events?cursor=<string>&type=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "events"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The nextCursor of the previous page; absent for the first page."
                },
                {
                  "key": "type",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Only events of this type, for example order.state_changed."
                }
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`GET /v1/orders/{orderRef}/events` · operationId `listOrderEvents`\n\nOne order's events, in the same WebhookEnvelope / EventPage shape and the same event-type catalog as GET /v1/events and webhook deliveries, so a timeline, the replay feed and a webhook receiver read one vocabulary. Reconciliation is not a separate event type: entering and leaving PENDING_RECONCILIATION are order.state_changed transitions carrying data.from and data.to. payment.* events that carry this orderRef belong on the timeline too. Oldest first; `cursor` continues where nextCursor left off, `type` filters to one event type, both with GET /v1/events' semantics. actor is optional, and actor.kind = ai is mandatory on AI-initiated events (the envelope law). Same permission as the order read: orders:read plus the tab permission of the order's domain; an absent or another tenant's order is a 404. Read-path failures (OD-P9-01; API-0005 §4, API-0006 §6, 03 §2.3): a `cursor` this route never issued is 400 VALIDATION (details.field cursor), not an empty page; when the order store times out the answer is 502 UPSTREAM_UNAVAILABLE, retryable true; when the deployment has no order reader assembled it is 501 NOT_IMPLEMENTED, retryable false."
          },
          "response": []
        },
        {
          "name": "Penalty preview before commitment (03 §3 #16, Example Activities mechanism) — dining",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/cancel-quote",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "cancel-quote"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/orders/{orderRef}/cancel-quote` · operationId `cancelQuoteOrder`\n\nExample: FNB — quote cancelling a table (free, or the card policy) (v1.7.0)",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {}\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Cancel; optional cancelQuoteRef guarantees the quoted penalty until validUntil (03 §3 #17) — dining",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "cancel"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/orders/{orderRef}/cancel` · operationId `cancelOrder`\n\nExample: FNB — cancel the table, consenting to the quoted zero penalty (v1.7.0)\n\nConsent-first cancel (DR-06): send intent.acceptedPenalty from the cancel-quote. D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to orders.cancel, the orderRef and the accepted penalty; without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"cancelQuoteRef\": \"CQ-D-5a7e20\",\n      \"acceptedPenalty\": {\n        \"amount\": 0,\n        \"currency\": \"USD\"\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Two-phase amend in one endpoint (03 §3 #18) — diningQuote",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/amend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "amend"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/orders/{orderRef}/amend` · operationId `amendOrder`\n\nExample: FNB — DATE_SHIFT quote: a new time and party size (v1.7.0)\n\nmode QUOTE returns amendRef + delta price; mode COMMIT with amendRef executes. Covers date shift, exchange, name correction, and post-booking ancillary adds (amendType ADD_ANCILLARY) plus document resubmission (VISA ADD_DOCUMENT). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to amend.commit, the orderRef and the amendment delta (COMMIT only; a QUOTE needs none); without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"mode\": \"QUOTE\",\n      \"amendType\": \"DATE_SHIFT\",\n      \"changes\": {\n        \"dateTime\": \"2026-11-12T20:30\",\n        \"partySize\": 4\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Two-phase amend in one endpoint (03 §3 #18) — diningQuoteDateAndTime",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/amend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "amend"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/orders/{orderRef}/amend` · operationId `amendOrder`\n\nExample: FNB — DATE_SHIFT quote with date + time instead of dateTime (v1.7.0)\n\nmode QUOTE returns amendRef + delta price; mode COMMIT with amendRef executes. Covers date shift, exchange, name correction, and post-booking ancillary adds (amendType ADD_ANCILLARY) plus document resubmission (VISA ADD_DOCUMENT). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to amend.commit, the orderRef and the amendment delta (COMMIT only; a QUOTE needs none); without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"mode\": \"QUOTE\",\n      \"amendType\": \"DATE_SHIFT\",\n      \"changes\": {\n        \"date\": \"2026-11-14\",\n        \"time\": \"19:15\"\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Two-phase amend in one endpoint (03 §3 #18) — diningCommit",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/amend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "amend"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/orders/{orderRef}/amend` · operationId `amendOrder`\n\nExample: FNB — commit the quoted amendment (v1.7.0)\n\nmode QUOTE returns amendRef + delta price; mode COMMIT with amendRef executes. Covers date shift, exchange, name correction, and post-booking ancillary adds (amendType ADD_ANCILLARY) plus document resubmission (VISA ADD_DOCUMENT). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to amend.commit, the orderRef and the amendment delta (COMMIT only; a QUOTE needs none); without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"mode\": \"COMMIT\",\n      \"amendType\": \"DATE_SHIFT\",\n      \"amendRef\": \"AM-D-61c0f2\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Two-phase amend in one endpoint (03 §3 #18) — diningQuoteCardSecured",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/amend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "amend"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/orders/{orderRef}/amend` · operationId `amendOrder`\n\nExample: FNB — DATE_SHIFT quote to a card-secured slot; the token is validated only, not kept (v1.7.0)\n\nmode QUOTE returns amendRef + delta price; mode COMMIT with amendRef executes. Covers date shift, exchange, name correction, and post-booking ancillary adds (amendType ADD_ANCILLARY) plus document resubmission (VISA ADD_DOCUMENT). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to amend.commit, the orderRef and the amendment delta (COMMIT only; a QUOTE needs none); without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"mode\": \"QUOTE\",\n      \"amendType\": \"DATE_SHIFT\",\n      \"changes\": {\n        \"dateTime\": \"2026-11-13T20:00\",\n        \"partySize\": 6\n      },\n      \"payment\": {\n        \"paymentMethod\": {\n          \"provider\": \"SPREEDLY\",\n          \"token\": \"syn-spreedly-pm-token-0001\",\n          \"type\": \"credit_card\",\n          \"last4\": \"1111\",\n          \"brand\": \"visa\"\n        }\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Two-phase amend in one endpoint (03 §3 #18) — diningCommitCardSecured",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/amend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "amend"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/orders/{orderRef}/amend` · operationId `amendOrder`\n\nExample: FNB — commit an amend to a card-secured slot: the token MUST be sent again on the COMMIT (v1.7.0)\n\nmode QUOTE returns amendRef + delta price; mode COMMIT with amendRef executes. Covers date shift, exchange, name correction, and post-booking ancillary adds (amendType ADD_ANCILLARY) plus document resubmission (VISA ADD_DOCUMENT). D-50 (12 §B2.4, OD-458): an AI-channel call must carry X-Approval-Token, a grant from POST /v1/approvals bound to amend.commit, the orderRef and the amendment delta (COMMIT only; a QUOTE needs none); without one it is 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED, and a spent, expired, unknown or mismatched grant is PERMISSION_DENIED with APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH. Single use, retried with the same Idempotency-Key. Human sessions and system keys (DR-40) are exempt; an ai key and every service token are the AI channel (DR-39).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\",\n    \"currency\": \"USD\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"mode\": \"COMMIT\",\n      \"amendType\": \"DATE_SHIFT\",\n      \"amendRef\": \"AM-D-7a1e33\",\n      \"payment\": {\n        \"paymentMethod\": {\n          \"provider\": \"SPREEDLY\",\n          \"token\": \"syn-spreedly-pm-token-0001\",\n          \"type\": \"credit_card\",\n          \"last4\": \"1111\",\n          \"brand\": \"visa\"\n        }\n      }\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Ask for a pre-signed upload slot for a FILE-typed requiredInfo answer (IMP-P2-077; 03 §10.4) — passportScan",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/uploads",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "uploads"
              ]
            },
            "description": "`POST /v1/uploads` · operationId `createUpload`\n\nExample: A passport scan for a VISA application (a visa supplier)\n\nThe client PUTs the file to the returned url with exactly the returned headers, then references uploadId in requiredInfoAnswers (or Traveler.documents[].uploadId). Slots are single-use, tenant-walled, time-boxed and scanned; a file over maxBytes or outside the FileAnswerSpec.accept list is refused 400 VALIDATION details.field sizeBytes or contentType. The object's data class is pinned from purpose (DATA-GOVERNANCE §2) and it is deleted when no order references it past expiresAt. Never a public URL, never bytes in an order body.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fileName\": \"passport.pdf\",\n  \"contentType\": \"application/pdf\",\n  \"sizeBytes\": 1834022,\n  \"purpose\": \"PASSPORT_SCAN\",\n  \"field\": \"passportScan\",\n  \"offerRef\": \"OF-V-2d81aa\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Ask for a pre-signed upload slot for a FILE-typed requiredInfo answer (IMP-P2-077; 03 §10.4) — eticket",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/uploads",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "uploads"
              ]
            },
            "description": "`POST /v1/uploads` · operationId `createUpload`\n\nExample: The airline e-ticket an airport-service handler needs (an airport-services supplier)\n\nThe client PUTs the file to the returned url with exactly the returned headers, then references uploadId in requiredInfoAnswers (or Traveler.documents[].uploadId). Slots are single-use, tenant-walled, time-boxed and scanned; a file over maxBytes or outside the FileAnswerSpec.accept list is refused 400 VALIDATION details.field sizeBytes or contentType. The object's data class is pinned from purpose (DATA-GOVERNANCE §2) and it is deleted when no order references it past expiresAt. Never a public URL, never bytes in an order body.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"fileName\": \"eticket-LA8084.pdf\",\n  \"contentType\": \"application/pdf\",\n  \"sizeBytes\": 210334,\n  \"purpose\": \"ETICKET\",\n  \"field\": \"eticketFile\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The state of an upload slot — whether the bytes landed and passed scanning",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/uploads/:uploadId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "uploads",
                ":uploadId"
              ],
              "variable": [
                {
                  "key": "uploadId",
                  "value": "<string>",
                  "description": "The upload slot to read, as the upload call returned it."
                }
              ]
            },
            "description": "`GET /v1/uploads/{uploadId}` · operationId `getUpload`"
          },
          "response": []
        },
        {
          "name": "Dining: add a partner note to a confirmed reservation (v1.30.0) — partnerReference",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/dining/orders/:orderRef/notes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dining",
                "orders",
                ":orderRef",
                "notes"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/dining/orders/{orderRef}/notes` · operationId `createDiningOrderNote`\n\nExample: A partner reference for the restaurant\n\nAdds a partner note to the restaurant reservation behind a dining (FNB) order, as the restaurant sees it on the reservation (Example Dining Create Partner Reservation Note v2: POST /v2/booking/{rid}/reservations/{rid}-{confirmation}/notes). A partner note is not a guest-profile note and cannot change notes written by the restaurant's staff. At most 5,000 characters, not blank. Sent once under the Idempotency-Key; a replay answers the first result. An order of another domain, or a dining order on a supplier that has no partner notes, is 400 VALIDATION details.reason DINING_ORDER_ACTION_UNSUPPORTED, before any supplier call. A supplier that has not granted the call to the platform answers 403 at the supplier, surfaced as 403 PERMISSION_DENIED details.reason SUPPLIER_FORBIDDEN (as the partner routes).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"note\": \"Booked through Apiq; the partner reference is MO-ri1IdksWcCAWsPPncUh-mw.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "X-Api-Key",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{apiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Dining: change or delete a partner note (v1.30.0) — change",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/dining/orders/:orderRef/notes/:noteId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dining",
                "orders",
                ":orderRef",
                "notes",
                ":noteId"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                },
                {
                  "key": "noteId",
                  "value": "<string>",
                  "description": "The note id POST /v1/dining/orders/{orderRef}/notes answered"
                }
              ]
            },
            "description": "`PATCH /v1/dining/orders/{orderRef}/notes/{noteId}` · operationId `updateDiningOrderNote`\n\nExample: Change the note\n\nReplaces the text of a partner note this platform added to the reservation behind a dining (FNB) order (Example Dining Update Partner Reservation Note v2: PATCH /v2/booking/{rid}/reservations/{rid}-{confirmation}/notes). An empty note deletes it. The noteId is the one POST /v1/dining/orders/{orderRef}/notes answered; a note id that is not this partner's is refused by the supplier (404 NOT_FOUND). At most 5,000 characters. Sent once under the Idempotency-Key. Same 400 and 403 rules as the create.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"note\": \"Window table, please.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "X-Api-Key",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{apiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Dining: change or delete a partner note (v1.30.0) — delete",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/dining/orders/:orderRef/notes/:noteId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dining",
                "orders",
                ":orderRef",
                "notes",
                ":noteId"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                },
                {
                  "key": "noteId",
                  "value": "<string>",
                  "description": "The note id POST /v1/dining/orders/{orderRef}/notes answered"
                }
              ]
            },
            "description": "`PATCH /v1/dining/orders/{orderRef}/notes/{noteId}` · operationId `updateDiningOrderNote`\n\nExample: Delete the note (empty text)\n\nReplaces the text of a partner note this platform added to the reservation behind a dining (FNB) order (Example Dining Update Partner Reservation Note v2: PATCH /v2/booking/{rid}/reservations/{rid}-{confirmation}/notes). An empty note deletes it. The noteId is the one POST /v1/dining/orders/{orderRef}/notes answered; a note id that is not this partner's is refused by the supplier (404 NOT_FOUND). At most 5,000 characters. Sent once under the Idempotency-Key. Same 400 and 403 rules as the create.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"note\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "X-Api-Key",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{apiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Dining: confirm the reservation with the restaurant (v1.30.0)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/dining/orders/:orderRef/confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "dining",
                "orders",
                ":orderRef",
                "confirm"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`POST /v1/dining/orders/{orderRef}/confirm` · operationId `confirmDiningOrder`\n\nTells the restaurant that the diner confirms the reservation behind a dining (FNB) order (Example Dining Partner Reservation Confirmation V2: POST /v2/booking/{rid}/reservations/{rid}-{confirmation}/confirm, no body). The order's state does not change; the supplier records the confirmation on the reservation. Sent once under the Idempotency-Key. Same 400 and 403 rules as the note routes.",
            "auth": {
              "type": "apikey",
              "apikey": [
                {
                  "key": "key",
                  "value": "X-Api-Key",
                  "type": "string"
                },
                {
                  "key": "value",
                  "value": "{{apiKey}}",
                  "type": "string"
                },
                {
                  "key": "in",
                  "value": "header",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "enquiries",
      "description": "Send a lead to a supplier that takes enquiries instead of bookings; nothing is priced, held or paid.",
      "item": [
        {
          "name": "List the tenant's enquiries, newest first, cursor-paged (v1.7.0)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/enquiries?cursor=<string>&limit=25&domain=FNB&status=SUBMITTED&createdAfter=2026-10-01T00:00:00Z",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enquiries"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Opaque; the message.nextCursor of the previous page"
                },
                {
                  "key": "limit",
                  "value": "25",
                  "disabled": true,
                  "description": "Page size (1-100); absent = 25"
                },
                {
                  "key": "domain",
                  "value": "FNB",
                  "disabled": true,
                  "description": "Only this domain (code or alias)"
                },
                {
                  "key": "status",
                  "value": "SUBMITTED",
                  "disabled": true,
                  "description": "Only enquiries in this state (SUBMITTED, UNCONFIRMED, FAILED)"
                },
                {
                  "key": "createdAfter",
                  "value": "2026-10-01T00:00:00Z",
                  "disabled": true,
                  "description": "Only enquiries created after this instant"
                }
              ]
            },
            "description": "`GET /v1/enquiries` · operationId `listEnquiries`\n\nAdded in connect-v1 v1.7.0 (WEB lane). The tenant's enquiries, newest first. Filters: domain (canonical code or alias), status (the enquiry state: SUBMITTED, UNCONFIRMED, FAILED) and createdAfter. Pages by an opaque cursor: pass message.nextCursor back as ?cursor= with the same filters; absent on the last page. A cursor this route did not issue is 400 VALIDATION details.field cursor. Requires orders:read. The contact is never listed."
          },
          "response": []
        },
        {
          "name": "Send a lead to a lead-only supplier: private dining and other enquiries (v1.7.0) — privateDining",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/enquiries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enquiries"
              ]
            },
            "description": "`POST /v1/enquiries` · operationId `createEnquiry`\n\nExample: FNB — an Example Dining private-dining lead for 24 guests\n\nAdded in connect-v1 v1.7.0 (dining proposal §5.5, OD-874). An enquiry is a lead, not a booking: nothing is priced, held or paid, and no order is created. The platform validates the lead the way the supplier would, sends it ONCE and answers the enquiry with its state. SUBMITTED carries the supplier's lead id as supplierReference; UNCONFIRMED means the supplier did not answer, the lead may have reached the venue and is never re-sent (warning <SUPPLIER>_LEAD_UNCONFIRMED). Requires orders:write and the domain tab entitlement (search:dining for FNB). Idempotency-Key is required; a replay answers the stored 201. Personal data in contact travels to the supplier and is never echoed.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"FNB\"\n  },\n  \"message\": {\n    \"intent\": {\n      \"productId\": \"prov:dining:100123\",\n      \"contact\": {\n        \"firstName\": \"Ada\",\n        \"lastName\": \"Example\",\n        \"email\": \"ada@example.test\",\n        \"phoneNumber\": \"+14155550100\"\n      },\n      \"eventDate\": \"2026-12-04\",\n      \"eventTime\": \"19:00\",\n      \"partySize\": 24,\n      \"eventType\": \"CorporateEvent\",\n      \"flexibleDate\": false,\n      \"clientReference\": \"acme-pd-0042\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Read one enquiry (v1.7.0)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/enquiries/:enquiryRef",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enquiries",
                ":enquiryRef"
              ],
              "variable": [
                {
                  "key": "enquiryRef",
                  "value": "EN-4KD-8QT-2ZP",
                  "description": "EN-XXX-XXX-XXX from POST /v1/enquiries"
                }
              ]
            },
            "description": "`GET /v1/enquiries/{enquiryRef}` · operationId `getEnquiry`\n\nAdded in connect-v1 v1.7.0. The enquiry as POST /v1/enquiries answered it, with its current state. Another tenant's ref and an unknown ref are the same 404 NOT_FOUND. Requires orders:read."
          },
          "response": []
        }
      ]
    },
    {
      "name": "payments",
      "description": "Pay for an order with a card tokenized by the payment provider, and confirm the payment.",
      "item": [
        {
          "name": "Create a payment intent from a repriceRef (03 §8), or on the tenant's own provider account (v1.49.0) — fromReprice",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "OPTIONAL on intent create (API-0010 §5 [PROPOSED]; making it required would be a breaking change, OD-299) — except with paymentSource (v1.49.0), where it is REQUIRED (400 VALIDATION details.reason IDEMPOTENCY_KEY_REQUIRED) because it keys the provider's own idempotency. When sent it is honoured exactly as the 03 §7 key: 48h TTL, same body replays the original response with Idempotency-Replayed true, a different body is 409 IDEMPOTENCY_CONFLICT. A replay returns the STORED answer, so its `checkout` (the hosted session an intent may carry) may since have been consumed or expired: re-read the intent (GET /v1/payments/{paymentId}) for the live session (OD-447)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/intent",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "intent"
              ]
            },
            "description": "`POST /v1/payments/intent` · operationId `createPaymentIntent`\n\nExample: On the platform's account — the amount derives from the repriceRef (03 §8)\n\nReturns PSP client secret or Pix QR payload. Provider behind the seam is config (D-26). With paymentSource (v1.49.0, PLAT-FINSUP phase 2) the intent runs on the tenant's OWN account at the provider, registered as a tenant-owned credential set: the caller states amount, sends no repriceRef, and must send an Idempotency-Key; the intent never pays an Apiq order and the response carries paymentSource instead of fork. Production external API never accepts PAN (18 §6); SANDBOX/UAT accept a card sealed to the platform key on POST /v1/orders (payment.mode ENCRYPTED_CARD, v1.36.0, PLAT-CARDENV, OD-904), never on this route. CREDIT_LINE and BR installments ride the same intent object. A deployment with no payment provider configured (provider=none) answers 501 NOT_IMPLEMENTED on every payment route (OD-295).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"repriceRef\": \"RP-H-2ab91c\",\n  \"method\": \"CARD\",\n  \"captureModel\": \"MANUAL_CAPTURE\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Create a payment intent from a repriceRef (03 §8), or on the tenant's own provider account (v1.49.0) — onTenantOwnAccount",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "OPTIONAL on intent create (API-0010 §5 [PROPOSED]; making it required would be a breaking change, OD-299) — except with paymentSource (v1.49.0), where it is REQUIRED (400 VALIDATION details.reason IDEMPOTENCY_KEY_REQUIRED) because it keys the provider's own idempotency. When sent it is honoured exactly as the 03 §7 key: 48h TTL, same body replays the original response with Idempotency-Replayed true, a different body is 409 IDEMPOTENCY_CONFLICT. A replay returns the STORED answer, so its `checkout` (the hosted session an intent may carry) may since have been consumed or expired: re-read the intent (GET /v1/payments/{paymentId}) for the live session (OD-447)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/intent",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "intent"
              ]
            },
            "description": "`POST /v1/payments/intent` · operationId `createPaymentIntent`\n\nExample: On the tenant's own Example Pay account — the caller states the amount (v1.49.0)\n\nReturns PSP client secret or Pix QR payload. Provider behind the seam is config (D-26). With paymentSource (v1.49.0, PLAT-FINSUP phase 2) the intent runs on the tenant's OWN account at the provider, registered as a tenant-owned credential set: the caller states amount, sends no repriceRef, and must send an Idempotency-Key; the intent never pays an Apiq order and the response carries paymentSource instead of fork. Production external API never accepts PAN (18 §6); SANDBOX/UAT accept a card sealed to the platform key on POST /v1/orders (payment.mode ENCRYPTED_CARD, v1.36.0, PLAT-CARDENV, OD-904), never on this route. CREDIT_LINE and BR installments ride the same intent object. A deployment with no payment provider configured (provider=none) answers 501 NOT_IMPLEMENTED on every payment route (OD-295).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": {\n    \"amount\": 24500,\n    \"currency\": \"EUR\"\n  },\n  \"method\": \"CARD\",\n  \"captureModel\": \"MANUAL_CAPTURE\",\n  \"paymentSource\": {\n    \"supplier\": \"SUP-PAY-EXAMPLE\",\n    \"credentialSet\": \"TEN-7f3a:SUP-PAY-EXAMPLE:own:0a1b2c3d\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Confirm an intent → paymentRef usable in orders (03 §8)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Test",
                "value": "<string>",
                "disabled": true,
                "description": "Sandbox scenario simulation (BE-7, !72): price_changed | sold_out | ambiguous | hold_expired | payment_declined | supplier_timeout | partial_search | duplicate_order (case-insensitive). Accepted only from a mo_test_ API key on a rung where aikonic.sandbox.simulation is on (DEV and SANDBOX by default; UAT and PROD refuse to boot with it on); anything else is 400 VALIDATION details.reason TEST_HEADER_NOT_ALLOWED, an unknown value 400 UNKNOWN_TEST_SCENARIO (details.accepted). Read on every travel verb before the permission wall and idempotency; a scenario sent on a verb it does not apply to has no effect. Every simulated error carries details.simulated true and details.scenario. price_changed (reprice, hold, orders) → 409 PRICE_CHANGED; sold_out → 422 SOLD_OUT; hold_expired (orders) → 409 HOLD_EXPIRED; duplicate_order (orders) → 409 DUPLICATE_ORDER existingOrderRef MO-SIM-DUPLICATE; payment_declined (orders, POST /v1/payments/{paymentId}/confirm) → 402 PAYMENT_FAILED; supplier_timeout → 502 UPSTREAM_UNAVAILABLE; partial_search (search) → two SUPPLIER_DROPPED warnings; ambiguous (orders) → 202 PENDING_RECONCILIATION on an MO-SIM- ref. wallet_insufficient (v1.18.0, served since 1.18.1, FEAT-030 WP-3; orders with payment.mode WALLET, matched exact-case) → 402 PAYMENT_FAILED details.reason WALLET_INSUFFICIENT."
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Actor-Kind",
                "value": "<string>",
                "disabled": true,
                "description": "The channel that initiated a money verb: ai marks an AI-initiated call, which must then carry X-Approval-Token (D-50, 12 §B2.4, API-0010 §5). NOT a trust signal (OD-305): the platform binds the actor kind to the credential — an AI principal's call is AI whatever this header says, and omitting it never exempts a call from the D-50 gate. The header is accepted for compatibility and can only mark a call ai, never un-mark one. New routes (POST /v1/approvals, /v1/agent-proposals) do not take it."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/:paymentId/confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId",
                "confirm"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`POST /v1/payments/{paymentId}/confirm` · operationId `confirmPayment`\n\nBody: a skeleton built from the schema's required properties (the contract carries no example).",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "3DS challenge completion callback (03 §8)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/3ds-callback",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "3ds-callback"
              ]
            },
            "description": "`POST /v1/payments/3ds-callback` · operationId `threeDsCallback`\n\nBody: a skeleton built from the schema's required properties (the contract carries no example).",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Capture an authorized payment, fully or partially — capture-on-ticketing (API-0010 §2 [PROPOSED], OD-308) — partial",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Actor-Kind",
                "value": "<string>",
                "disabled": true,
                "description": "The channel that initiated a money verb: ai marks an AI-initiated call, which must then carry X-Approval-Token (D-50, 12 §B2.4, API-0010 §5). NOT a trust signal (OD-305): the platform binds the actor kind to the credential — an AI principal's call is AI whatever this header says, and omitting it never exempts a call from the D-50 gate. The header is accepted for compatibility and can only mark a call ai, never un-mark one. New routes (POST /v1/approvals, /v1/agent-proposals) do not take it."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/:paymentId/capture",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId",
                "capture"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<string>",
                  "description": "The paymentId returned by POST /v1/payments/intent (API-0010 §3)"
                }
              ]
            },
            "description": "`POST /v1/payments/{paymentId}/capture` · operationId `capturePayment`\n\nExample: Capture USD 500.00 of an authorized USD 842.10\n\nCaptures up to the authorized remainder. amount absent = capture the whole remainder. Partial and multiple captures follow the provider's capability flags. Captures serialize per payment on its state machine: a capture beyond the authorized remainder, or of an intent that is not AUTHORIZED / PARTIALLY_CAPTURED, is 409 CONFLICT — never an over-capture (a PARTIALLY_REFUNDED payment has nothing left to capture, so it is INVALID_STATE too) (API-0010 §5). Idempotency-Key is REQUIRED on this new operation. From an AI channel (X-Actor-Kind ai) the D-50 approval token is required (API-0010 §5). The answer is provisional until the provider's webhook lands; the confirmation signal is payment.captured (API-0010 §3/§6). Harness provider only today (DR-26). A post-booking operation on an intent that already exists: it stays up for a SUSPENDED tenant and, on the production deployment, for a tenant whose activation has not been approved (07 §5, FEAT-020 §12, §18b), so its 403 is PaymentForbidden — never TENANT_SUSPENDED or TENANT_NOT_LIVE.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": {\n    \"amount\": 50000,\n    \"currency\": \"USD\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Void an authorized, uncaptured payment — release the hold (API-0010 §2, OD-306 (c), OD-497)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Actor-Kind",
                "value": "<string>",
                "disabled": true,
                "description": "The channel that initiated a money verb: ai marks an AI-initiated call, which must then carry X-Approval-Token (D-50, 12 §B2.4, API-0010 §5). NOT a trust signal (OD-305): the platform binds the actor kind to the credential — an AI principal's call is AI whatever this header says, and omitting it never exempts a call from the D-50 gate. The header is accepted for compatibility and can only mark a call ai, never un-mark one. New routes (POST /v1/approvals, /v1/agent-proposals) do not take it."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/:paymentId/void",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId",
                "void"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<string>",
                  "description": "The paymentId returned by POST /v1/payments/intent (API-0010 §3)"
                }
              ]
            },
            "description": "`POST /v1/payments/{paymentId}/void` · operationId `voidPayment`\n\nCancels the authorization of an intent that holds funds but has captured nothing: the intent becomes CANCELED and the provider releases the hold. An intent that has captured any money is 409 CONFLICT — refund it instead (POST /v1/payments/{paymentId}/refund); an intent already CANCELED answers 200 with its state unchanged. Idempotency-Key is REQUIRED. D-50 (DR-39): an AI-channel caller must carry X-Approval-Token, a grant from POST /v1/approvals bound to payments.void, the paymentId and the authorized amount; human sessions and system keys (DR-40) are exempt. The answer is provisional until the provider's webhook lands; the confirmation signal is payment.canceled. Harness provider only today (DR-26). A post-booking operation on an intent that already exists: it stays up for a SUSPENDED tenant and, on the production deployment, for a tenant whose activation has not been approved (07 §5, FEAT-020 §12, §18b), so its 403 is PaymentForbidden — never TENANT_SUSPENDED or TENANT_NOT_LIVE."
          },
          "response": []
        },
        {
          "name": "Execute a provider-level refund of captured money (API-0010 §2/§6 [PROPOSED], OD-308) — partial",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "X-Actor-Kind",
                "value": "<string>",
                "disabled": true,
                "description": "The channel that initiated a money verb: ai marks an AI-initiated call, which must then carry X-Approval-Token (D-50, 12 §B2.4, API-0010 §5). NOT a trust signal (OD-305): the platform binds the actor kind to the credential — an AI principal's call is AI whatever this header says, and omitting it never exempts a call from the D-50 gate. The header is accepted for compatibility and can only mark a call ai, never un-mark one. New routes (POST /v1/approvals, /v1/agent-proposals) do not take it."
              },
              {
                "key": "X-Approval-Token",
                "value": "<string>",
                "disabled": true,
                "description": "The D-50 approval grant from POST /v1/approvals (approvalToken; opaque) presented on the retry after a 403 whose details.reason is APPROVAL_REQUIRED. Redeemed by the payment verbs and by POST /v1/orders (orders.create), cancel (orders.cancel) and amend COMMIT (amend.commit) (DR-39). Human sessions and system keys (DR-40) are exempt. Single use, ~2 min TTL, bound to tenant + action + resource + amount; any drift fails closed and burns the token (API-0010 §5)."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/:paymentId/refund",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId",
                "refund"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<string>",
                  "description": "The paymentId returned by POST /v1/payments/intent (API-0010 §3)"
                }
              ]
            },
            "description": "`POST /v1/payments/{paymentId}/refund` · operationId `refundPayment`\n\nExample: Refund USD 150.00 of the captured amount\n\nThe provider-execution layer that the order's cancel and amend verbs drive, plus console goodwill refunds — never a second client-facing refund truth: the order stays the money-back source of truth and order.refund_issued its event (03 §3 #19, API-0010 §6). amount absent = refund everything captured and not yet refunded. A refund beyond the captured-and-unrefunded amount is 409 CONFLICT, and on a cancelled order so is a refund beyond the order's committed cancellation refund (captured minus penalty, OD-707). A refund that leaves part of the captured amount unrefunded moves a CAPTURED payment (or a PARTIALLY_CAPTURED one whose authorization has lapsed) to PARTIALLY_REFUNDED, which accepts further refunds; the refund that brings cumulative refunds to the captured amount moves it to REFUNDED (terminal). A PARTIALLY_CAPTURED payment that can still capture keeps that state after a partial refund (refundedAmount shows it). payment.refunded is unchanged: one event per refund, amount = the rise. Idempotency-Key is REQUIRED on this new operation; from an AI channel the D-50 approval token is required. Harness provider only today (DR-26). A post-booking operation on an intent that already exists: it stays up for a SUSPENDED tenant and, on the production deployment, for a tenant whose activation has not been approved (07 §5, FEAT-020 §12, §18b), so its 403 is PaymentForbidden — never TENANT_SUSPENDED or TENANT_NOT_LIVE.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": {\n    \"amount\": 15000,\n    \"currency\": \"USD\"\n  },\n  \"reason\": \"GOODWILL\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Open a hosted checkout for an intent; the caller never handles card data (API-0010 §11, 15 §2 SAQ-A) — appReturn",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/:paymentId/checkout-session",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId",
                "checkout-session"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<string>",
                  "description": "The paymentId returned by POST /v1/payments/intent (API-0010 §3)"
                }
              ]
            },
            "description": "`POST /v1/payments/{paymentId}/checkout-session` · operationId `createCheckoutSession`\n\nExample: Return to the agent app by its aikonic:// scheme\n\nMints a single-use hosted checkout session for an existing intent and answers the URL to open in the system browser. The hosted page collects the payment at the provider and confirms the intent there, so neither the calling app nor this platform ever sees a PAN (15 §2, SAQ-A). Only an intent in CREATED or REQUIRES_ACTION can get a session; any other state is 409 CONFLICT. A session expires after ~15 minutes (deployment property aikonic.payments.checkout.session-ttl, default PT15M) and is spent by the first submit on the hosted page. An intent has at most ONE active session: a new session (a new Idempotency-Key) voids the previous one, and a confirm of the intent by any other route voids it too. While a session is active, the intent's answers carry it as `checkout`. returnUrl, when sent, must be an aikonic:// URL or an https URL whose origin exactly equals one of the deployment's configured origins (aikonic.payments.checkout.allowed-return-origins, default empty); anything else is 400 VALIDATION with details.reason RETURN_URL_NOT_ALLOWED — never an open redirect. When the hosted page finishes it redirects to returnUrl with `paymentId` and `status` query parameters; `status` is advisory, and the caller re-reads the intent (GET /v1/payments/{paymentId}) before acting on it. payment.captured and payment.failed fire as for any intent (API-0009). Idempotency-Key is REQUIRED: the same key and body within 48h replay the original answer (Idempotency-Replayed true) — including its expiresAt, so a replayed session may already be expired or spent; create a new one with a new key. Permission is that of a NEW payment: payments:write plus the tenant's standing, so a SUSPENDED tenant is refused TENANT_SUSPENDED and, on the production deployment, a tenant whose activation is not approved is refused PERMISSION_DENIED TENANT_NOT_LIVE (403, as on intent create). No D-50 approval token: the money is committed by the human on the provider's page, and an AI channel never holds payments:write (15 §1.5). A deployment with no payment provider (provider=none) answers 501 NOT_IMPLEMENTED; a provider that has no hosted checkout, or the harness provider above DEV, answers 501 NOT_IMPLEMENTED with details.reason HOSTED_CHECKOUT_UNSUPPORTED (OD-447). The harness provider's hosted page is DEV-only and is not an operation of this contract (API-0010 §11).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"returnUrl\": \"aikonic://payment\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Open a hosted checkout for an intent; the caller never handles card data (API-0010 §11, 15 §2 SAQ-A) — noReturn",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/:paymentId/checkout-session",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId",
                "checkout-session"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<string>",
                  "description": "The paymentId returned by POST /v1/payments/intent (API-0010 §3)"
                }
              ]
            },
            "description": "`POST /v1/payments/{paymentId}/checkout-session` · operationId `createCheckoutSession`\n\nExample: No returnUrl — the hosted page ends on its own result screen\n\nMints a single-use hosted checkout session for an existing intent and answers the URL to open in the system browser. The hosted page collects the payment at the provider and confirms the intent there, so neither the calling app nor this platform ever sees a PAN (15 §2, SAQ-A). Only an intent in CREATED or REQUIRES_ACTION can get a session; any other state is 409 CONFLICT. A session expires after ~15 minutes (deployment property aikonic.payments.checkout.session-ttl, default PT15M) and is spent by the first submit on the hosted page. An intent has at most ONE active session: a new session (a new Idempotency-Key) voids the previous one, and a confirm of the intent by any other route voids it too. While a session is active, the intent's answers carry it as `checkout`. returnUrl, when sent, must be an aikonic:// URL or an https URL whose origin exactly equals one of the deployment's configured origins (aikonic.payments.checkout.allowed-return-origins, default empty); anything else is 400 VALIDATION with details.reason RETURN_URL_NOT_ALLOWED — never an open redirect. When the hosted page finishes it redirects to returnUrl with `paymentId` and `status` query parameters; `status` is advisory, and the caller re-reads the intent (GET /v1/payments/{paymentId}) before acting on it. payment.captured and payment.failed fire as for any intent (API-0009). Idempotency-Key is REQUIRED: the same key and body within 48h replay the original answer (Idempotency-Replayed true) — including its expiresAt, so a replayed session may already be expired or spent; create a new one with a new key. Permission is that of a NEW payment: payments:write plus the tenant's standing, so a SUSPENDED tenant is refused TENANT_SUSPENDED and, on the production deployment, a tenant whose activation is not approved is refused PERMISSION_DENIED TENANT_NOT_LIVE (403, as on intent create). No D-50 approval token: the money is committed by the human on the provider's page, and an AI channel never holds payments:write (15 §1.5). A deployment with no payment provider (provider=none) answers 501 NOT_IMPLEMENTED; a provider that has no hosted checkout, or the harness provider above DEV, answers 501 NOT_IMPLEMENTED with details.reason HOSTED_CHECKOUT_UNSUPPORTED (OD-447). The harness provider's hosted page is DEV-only and is not an operation of this contract (API-0010 §11).",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The public key a card is sealed to for payment.mode ENCRYPTED_CARD (v1.36.0, SANDBOX/UAT)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/card-key",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "card-key"
              ]
            },
            "description": "`GET /v1/payments/card-key` · operationId `getCardEnvelopeKey`\n\nconnect-v1 1.36.0 (PLAT-CARDENV §4.1; SANDBOX/UAT only, OD-904). The RSA public key, as a JWK, that POST /v1/orders payment.encryptedCard is sealed to (EncryptedCardEnvelope). Public data, Cache-Control max-age=3600; after a rotation the previous kid still opens for 24 hours. Any authenticated caller, no extra scope. The channel switched off — every PROD deployment — answers 501 NOT_IMPLEMENTED details.reason CARD_PAYMENT_NOT_ENABLED."
          },
          "response": []
        },
        {
          "name": "Read a payment intent — what a client re-reads after a hosted checkout returns (API-0010 §3, §11)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/:paymentId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                ":paymentId"
              ],
              "variable": [
                {
                  "key": "paymentId",
                  "value": "<string>",
                  "description": "The paymentId returned by POST /v1/payments/intent (API-0010 §3)"
                }
              ]
            },
            "description": "`GET /v1/payments/{paymentId}` · operationId `getPaymentIntent`\n\nThe intent's current answer: the same PaymentIntentResponse the create, capture and refund routes answer, with `checkout` while a hosted checkout session is active. This is the read a client makes after the hosted page redirects back; the redirect's `status` query parameter is advisory and never authority. A read of an intent that already exists: it stays up for a SUSPENDED tenant and for a tenant whose production activation is not approved, like capture and refund, so its 403 is only PERMISSION_DENIED for a credential without payments:write (15 §1.3). An unknown id and another tenant's id are the same 404. A synchronous state is provisional until the provider's webhook lands (API-0010 §1)."
          },
          "response": []
        },
        {
          "name": "The tenant's saved cards and UPI methods, default first (FEAT-030 §18b)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/methods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "methods"
              ]
            },
            "description": "`GET /v1/payments/methods` · operationId `listPaymentMethods`\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25). The payment methods saved for the tenant, the default first, then newest first. A method is a PSP token and display facts only (brand, last4, a masked VPA); a card number, security code or full VPA is never stored or answered (OD-875, 15 §2). kind is x-extensible-enum. Where the flag is off the route is 501 NOT_IMPLEMENTED details.reason PAYMENT_METHODS_UNAVAILABLE. Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs payments:write [assumption: one permission for the whole family, read included, because a method spends money].",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Save a card (as a PSP token) or a UPI method (FEAT-030 §18b) — card",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/methods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "methods"
              ]
            },
            "description": "`POST /v1/payments/methods` · operationId `addPaymentMethod`\n\nExample: A card, as the harness PSP's success token (brand and last4 come from the PSP)\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25, OD-875). kind CARD takes token, a payment-method token the client obtained from the PSP's hosted fields (Spreedly in production, OD-875; the harness PSP on DEV and SANDBOX, whose test tokens are tok_harness_success and tok_harness_decline); kind UPI takes vpa. A card number, security code or any card-number-shaped value is refused 400 VALIDATION details.reason INSTRUMENT_DATA_REFUSED and nothing is stored. The token is verified with the PSP before it is saved [assumption: a zero-amount authorisation]; a declined token is 402 PAYMENT_FAILED details.reason CARD_DECLINED (details.declineCode) and nothing is saved. The first method saved becomes the default. With the money model CARD (POST /v1/activation/money-model) rail step money_model is done once one method is saved. brand and last4 are derived by the server from the PSP token, never sent by the client. Human session only; Owner only, as the money model (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required: a replay returns the original method and saves nothing twice. Audited (payments.method_added, never the token). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): the card provider and the UPI provider are deployment settings (aikonic.payment-methods.card-provider harness or stripe; aikonic.payment-methods.upi-provider harness or razorpay; harness on DEV and SANDBOX only). With the card provider stripe the client first calls POST /v1/payments/methods:setup, confirms the card with the card provider's browser library (its hosted card fields: the card number never reaches Apiq) and then sends kind CARD with token the card provider's payment-method id (pm_ followed by the id) the confirmed setup carries; the server retrieves it from the card provider, takes brand and last4 from it, keeps it attached to the tenant's own customer at the card provider and stores the id only. A payment method the card provider does not know, or one attached to another customer, is 400 VALIDATION details.field token. With the UPI provider razorpay the vpa is validated with the provider before it is saved; a VPA the provider says does not exist or cannot receive is 400 VALIDATION details.reason VPA_INVALID (details.field vpa) and nothing is saved. A provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable, nothing saved).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"CARD\",\n  \"token\": \"tok_harness_success\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Save a card (as a PSP token) or a UPI method (FEAT-030 §18b) — upi",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/methods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "methods"
              ]
            },
            "description": "`POST /v1/payments/methods` · operationId `addPaymentMethod`\n\nExample: A UPI method\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25, OD-875). kind CARD takes token, a payment-method token the client obtained from the PSP's hosted fields (Spreedly in production, OD-875; the harness PSP on DEV and SANDBOX, whose test tokens are tok_harness_success and tok_harness_decline); kind UPI takes vpa. A card number, security code or any card-number-shaped value is refused 400 VALIDATION details.reason INSTRUMENT_DATA_REFUSED and nothing is stored. The token is verified with the PSP before it is saved [assumption: a zero-amount authorisation]; a declined token is 402 PAYMENT_FAILED details.reason CARD_DECLINED (details.declineCode) and nothing is saved. The first method saved becomes the default. With the money model CARD (POST /v1/activation/money-model) rail step money_model is done once one method is saved. brand and last4 are derived by the server from the PSP token, never sent by the client. Human session only; Owner only, as the money model (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required: a replay returns the original method and saves nothing twice. Audited (payments.method_added, never the token). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): the card provider and the UPI provider are deployment settings (aikonic.payment-methods.card-provider harness or stripe; aikonic.payment-methods.upi-provider harness or razorpay; harness on DEV and SANDBOX only). With the card provider stripe the client first calls POST /v1/payments/methods:setup, confirms the card with the card provider's browser library (its hosted card fields: the card number never reaches Apiq) and then sends kind CARD with token the card provider's payment-method id (pm_ followed by the id) the confirmed setup carries; the server retrieves it from the card provider, takes brand and last4 from it, keeps it attached to the tenant's own customer at the card provider and stores the id only. A payment method the card provider does not know, or one attached to another customer, is 400 VALIDATION details.field token. With the UPI provider razorpay the vpa is validated with the provider before it is saved; a VPA the provider says does not exist or cannot receive is 400 VALIDATION details.reason VPA_INVALID (details.field vpa) and nothing is saved. A provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable, nothing saved).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"UPI\",\n  \"vpa\": \"sampletravels@okicici\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Save a card (as a PSP token) or a UPI method (FEAT-030 §18b) — stripeCard",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/methods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "methods"
              ]
            },
            "description": "`POST /v1/payments/methods` · operationId `addPaymentMethod`\n\nExample: A card confirmed with the card provider's browser library — its payment-method id, never card data (v1.63.0 round 3)\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25, OD-875). kind CARD takes token, a payment-method token the client obtained from the PSP's hosted fields (Spreedly in production, OD-875; the harness PSP on DEV and SANDBOX, whose test tokens are tok_harness_success and tok_harness_decline); kind UPI takes vpa. A card number, security code or any card-number-shaped value is refused 400 VALIDATION details.reason INSTRUMENT_DATA_REFUSED and nothing is stored. The token is verified with the PSP before it is saved [assumption: a zero-amount authorisation]; a declined token is 402 PAYMENT_FAILED details.reason CARD_DECLINED (details.declineCode) and nothing is saved. The first method saved becomes the default. With the money model CARD (POST /v1/activation/money-model) rail step money_model is done once one method is saved. brand and last4 are derived by the server from the PSP token, never sent by the client. Human session only; Owner only, as the money model (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required: a replay returns the original method and saves nothing twice. Audited (payments.method_added, never the token). connect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version): the card provider and the UPI provider are deployment settings (aikonic.payment-methods.card-provider harness or stripe; aikonic.payment-methods.upi-provider harness or razorpay; harness on DEV and SANDBOX only). With the card provider stripe the client first calls POST /v1/payments/methods:setup, confirms the card with the card provider's browser library (its hosted card fields: the card number never reaches Apiq) and then sends kind CARD with token the card provider's payment-method id (pm_ followed by the id) the confirmed setup carries; the server retrieves it from the card provider, takes brand and last4 from it, keeps it attached to the tenant's own customer at the card provider and stores the id only. A payment method the card provider does not know, or one attached to another customer, is 400 VALIDATION details.field token. With the UPI provider razorpay the vpa is validated with the provider before it is saved; a VPA the provider says does not exist or cannot receive is 400 VALIDATION details.reason VPA_INVALID (details.field vpa) and nothing is saved. A provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable, nothing saved).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"CARD\",\n  \"token\": \"pm_example0000000000000001\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Start saving a card with the card provider — a SetupIntent for the hosted card fields (FEAT-030 §18c) — card",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/methods:setup",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "methods:setup"
              ]
            },
            "description": "`POST /v1/payments/methods:setup` · operationId `createPaymentMethodSetup`\n\nExample: Start saving a card\n\nconnect-v1 1.63.0 (PROPOSAL, FEAT-030 §18c round 3; amended 2026-10-29, same version; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; OD-ENR-25, OD-875). The first half of saving a card. With the card provider stripe (aikonic.payment-methods.card-provider) the server creates a card setup for off-session use on the tenant's own customer at the card provider (one customer per tenant, created on first use) and answers provider STRIPE, setupRef (the setup id), clientSecret (the setup's client secret, handed to the card provider's browser library only; never logged or audited) and publishableKey. The console mounts the card provider's hosted card fields with them, confirms the card in the browser (3-D Secure included) and then saves it with POST /v1/payments/methods, kind CARD, token the confirmed payment-method id; the card number never reaches Apiq. With the harness card provider (DEV and SANDBOX only) the answer is provider HARNESS, publishableKey null, and the console keeps the harness test tokens (tok_harness_success, tok_harness_decline). kind is CARD (UPI needs no setup). Each call starts a new setup; an unused one simply expires (expiresAt when the provider states it). Human session only; Owner only, as the saved methods (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required: a replay answers the original setup. A card provider that cannot be reached is 502 UPSTREAM_UNAVAILABLE (retryable). Audited (payments.method_setup_started, never the client secret).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"CARD\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Remove a saved payment method (FEAT-030 §18b)",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/methods/:methodId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "methods",
                ":methodId"
              ],
              "variable": [
                {
                  "key": "methodId",
                  "value": "<string>",
                  "description": "A saved payment method's id (SavedPaymentMethod.id)"
                }
              ]
            },
            "description": "`DELETE /v1/payments/methods/{methodId}` · operationId `removePaymentMethod`\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25). Deletes the method and its PSP token reference. Removing the default makes the newest remaining method the default [assumption]. Removing the last method of a tenant on the CARD money model: before the tenant is LIVE it is allowed and rail step money_model returns to needs_you (needs[] payment_method); once the tenant is LIVE it is 409 CONFLICT details.reason LAST_METHOD_IN_USE (add another method first). An unknown id and another tenant's id are the same 404 NOT_FOUND. Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required. Audited (payments.method_removed).",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "Make a saved payment method the default (FEAT-030 §18b)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/methods/:methodId:make-default",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "methods",
                ":methodId:make-default"
              ],
              "variable": [
                {
                  "key": "methodId",
                  "value": "<string>",
                  "description": "A saved payment method's id (SavedPaymentMethod.id)"
                }
              ]
            },
            "description": "`POST /v1/payments/methods/{methodId}:make-default` · operationId `setDefaultPaymentMethod`\n\nconnect-v1 1.51.0 (PROPOSAL; served when aikonic.payment-methods.enabled is on — off by default, on at DEV and SANDBOX; FEAT-030 §18b, OD-ENR-25). The ':make-default' literal-suffix custom method. The default is what a SAVED_METHOD order charges when it names no methodId [assumption], and what the console's review-and-pay step preselects. Making the default the default again is a 200 no-op. An unknown id and another tenant's id are the same 404 NOT_FOUND. Human session only; Owner only (403 PERMISSION_DENIED details.reason OWNER_ONLY). Idempotency-Key required. Audited (payments.method_default_set).",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "The payment provider's asynchronous notification — the async source of truth for money state (API-0010 §1, OD-308) — harnessCaptured",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/psp-webhook/:provider",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "psp-webhook",
                ":provider"
              ],
              "variable": [
                {
                  "key": "provider",
                  "value": "<string>",
                  "description": "The payment-provider key behind the seam (D-26), e.g. harness"
                }
              ]
            },
            "description": "`POST /v1/payments/psp-webhook/{provider}` · operationId `receivePspWebhook`\n\nExample: A harness-provider capture notification\n\nProvider keys served (BE-7 !72): harness, razorpay (x-razorpay-signature over the raw body; dedupe on x-razorpay-event-id; payment.authorized, payment.captured and order.paid trigger a provider read, every other event is recorded only) and payu (a signed form callback; a non-PAID status is recorded only). Called by the payment provider, not by a tenant, so it carries no platform credential (security is empty). Instead its signature is verified server-side against the provider's configured webhook secret before anything is read: a missing, invalid or stale signature, or a provider with no verification config, is 401 UNAUTHENTICATED and nothing is applied (MH-15 bad-signature half; the edge accepts nothing it cannot verify). A verified notification drives the PaymentIntent state machine and is surfaced to tenants as payment.captured / payment.failed / payment.refunded / payment.canceled (API-0009). Redelivery is normal: a notification already seen is 200 DUPLICATE with zero new state transitions (MH-15 dedupe), never a 409, so a provider does not retry forever. The body is the provider's own payload and is deliberately untyped here. Harness provider only today (DR-26).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"evt_h_7Qp1\",\n  \"type\": \"payment.captured\",\n  \"paymentRef\": \"pi_h_91c4\",\n  \"amount\": 84210,\n  \"currency\": \"USD\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "The payment provider's notification about payments on ONE tenant's own account (v1.49.0, PLAT-FINSUP phase 2) — stripeCheckoutCompleted",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/payments/psp-webhook/:provider/:sourceRef",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments",
                "psp-webhook",
                ":provider",
                ":sourceRef"
              ],
              "variable": [
                {
                  "key": "provider",
                  "value": "<string>",
                  "description": "The payment-provider key, e.g. stripe"
                },
                {
                  "key": "sourceRef",
                  "value": "<string>",
                  "description": "The tenant's credential set id the endpoint was registered for"
                }
              ]
            },
            "description": "`POST /v1/payments/psp-webhook/{provider}/{sourceRef}` · operationId `receivePspSourceWebhook`\n\nExample: An Example Pay checkout.session.completed event about the tenant's own account (abridged)\n\nThe endpoint a tenant registers at its own provider account (provider key stripe; sourceRef is the tenant's credential set id). No platform credential (security is empty): the signature, made with THAT account's own webhook signing secret (the set's webhookSecret field), is the credential, verified before anything is read — a missing, invalid or stale signature, an unknown source or a revoked set is 401 UNAUTHENTICATED and nothing is applied. A verified notification applies to that tenant's payments on that credential set only: one tenant's secret can never move another tenant's payment, nor a payment on the platform's account (such a reference answers 400 VALIDATION details.reason UNKNOWN_PAYMENT_REF). Redelivery is 200 DUPLICATE.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"evt_1Q2w3E4r5T6y\",\n  \"object\": \"event\",\n  \"type\": \"checkout.session.completed\",\n  \"data\": {\n    \"object\": {\n      \"id\": \"cs_test_a1B2c3D4\",\n      \"object\": \"checkout.session\",\n      \"client_reference_id\": \"stck_5f0c2a9e\",\n      \"payment_status\": \"paid\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "response": []
        },
        {
          "name": "Mint a D-50 approval grant for one AI-channel money action (API-0010 §5, Q3) — confirmPayment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/approvals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "approvals"
              ]
            },
            "description": "`POST /v1/approvals` · operationId `mintApprovalGrant`\n\nExample: A traveller approves the AI desk's USD 842.10 payment in the white-label sheet\n\nD-50: AI proposes, never commits. A money-touching action an AI caller asks for (booking, cancel, amend commit, payment confirm / capture / void / refund) is refused 403 PERMISSION_DENIED details.reason APPROVAL_REQUIRED until a HUMAN approves it in a UI the platform controls (12 §B2.4, 15 §1.4). This route is where that human's approval becomes a grant: it mints a single-use token of about 2 minutes, bound to the tenant, the action, the resource and the exact amount, which the AI caller then presents as X-Approval-Token on the verb it binds, retried with the SAME Idempotency-Key. Any drift at redemption (another resource, another amount, another action) burns the token: 403 PERMISSION_DENIED APPROVAL_TOKEN_MISMATCH. HUMAN SESSION ONLY: an API key or a service token from POST /v1/oauth/token is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED whatever its scopes, because the API surface never mints approval tokens for machine callers (OD-403, 15 §1.4). An MCP grant-derived token (connect-v1 1.26.0) is likewise 403 PERMISSION_DENIED details.reason SESSION_REQUIRED, details.credential mcp-grant: an agent acting for a human is never that human's session (DR-38, DR-39). The model can never mint. The minter must hold the permission the action needs: orders:write for orders.create, orders:cancel for orders.cancel, orders:amend for amend.commit, payments:write for payments.confirm, payments.capture, payments.void and payments.refund. The token value is returned once (Cache-Control no-store) and never logged; approvalTokenId is what audit rows and the approved action carry (13 §1.1). The console's POST /admin/v1/approvals (API-0012 §3.9) is the console spelling of the same mint (OD-458). The caller's actor kind is bound to its credential, never to a request header (OD-305). A mint is NOT replayed: re-sending a mint under an Idempotency-Key that already minted does not re-serve the token (a token value is returned exactly once). It answers 409 CONFLICT details.reason APPROVAL_ALREADY_MINTED, meaning mint again under a new key. The same key from a different user is 409 IDEMPOTENCY_CONFLICT.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"payments.confirm\",\n  \"resourceRef\": \"pi_7Hq2\",\n  \"amount\": {\n    \"amount\": 84210,\n    \"currency\": \"USD\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "wallets",
      "description": "Your prepaid wallet, one per settlement currency: its balance, its movements and top-ups.",
      "item": [
        {
          "name": "The tenant's wallets, one per settlement currency (FEAT-030 §5.5)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/wallets?environment=PRODUCTION",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "wallets"
              ],
              "query": [
                {
                  "key": "environment",
                  "value": "PRODUCTION",
                  "disabled": true,
                  "description": "Which side of the sandbox/production tenant pair (07 §5) to read or act on. An API key is bound to its own side (mo_test_ = SANDBOX, mo_live_ = PRODUCTION): absent means that side, and naming the other is 400 VALIDATION details.reason ENVIRONMENT_MISMATCH. A human session may name either; absent means SANDBOX while the tenant is TRIAL and PRODUCTION once it is LIVE [assumption]."
                }
              ]
            },
            "description": "`GET /v1/wallets` · operationId `listWallets`\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-4, published in 1.18.0; sandbox twin only in this phase). The wallet is on where aikonic.wallet.enabled is: DEV and SANDBOX; off by default and refused at boot above SANDBOX. Where it is off the wallet routes still answer: an empty list here, 404 NOT_FOUND for one wallet, 501 WALLET_TOPUP_UNAVAILABLE on a top-up. Every amount is Money (integer minor units + ISO-4217). balance, held and available are sums of ledger legs (F-08), never a stored number: available = balance - held. environment selects the side of the tenant pair (07 §5); see components/parameters/WalletEnvironmentQuery. Needs finance:read [PROPOSED]; a human session or an API key (a key may hold finance:read). An empty list when no wallet exists (money model not WALLET)."
          },
          "response": []
        },
        {
          "name": "One wallet — balance, held, available, funding instructions (FEAT-030 §5.5)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/wallets/:currency?environment=PRODUCTION",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "wallets",
                ":currency"
              ],
              "query": [
                {
                  "key": "environment",
                  "value": "PRODUCTION",
                  "disabled": true,
                  "description": "Which side of the sandbox/production tenant pair (07 §5) to read or act on. An API key is bound to its own side (mo_test_ = SANDBOX, mo_live_ = PRODUCTION): absent means that side, and naming the other is 400 VALIDATION details.reason ENVIRONMENT_MISMATCH. A human session may name either; absent means SANDBOX while the tenant is TRIAL and PRODUCTION once it is LIVE [assumption]."
                }
              ],
              "variable": [
                {
                  "key": "currency",
                  "value": "USD",
                  "description": "The wallet's settlement currency, ISO-4217 (one wallet per tenant per currency, FEAT-030 §5.5)"
                }
              ]
            },
            "description": "`GET /v1/wallets/{currency}` · operationId `getWallet`\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-4, published in 1.18.0). As one row of GET /v1/wallets. No wallet in that currency is 404 NOT_FOUND. Needs finance:read [PROPOSED]; a human session or an API key."
          },
          "response": []
        },
        {
          "name": "The wallet's movements, newest first — one row per ledger leg (FEAT-030 §5.5, F-08)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/wallets/:currency/transactions?environment=PRODUCTION&type=<string>&orderRef=<string>&cursor=<string>&limit=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "wallets",
                ":currency",
                "transactions"
              ],
              "query": [
                {
                  "key": "environment",
                  "value": "PRODUCTION",
                  "disabled": true,
                  "description": "Which side of the sandbox/production tenant pair (07 §5) to read or act on. An API key is bound to its own side (mo_test_ = SANDBOX, mo_live_ = PRODUCTION): absent means that side, and naming the other is 400 VALIDATION details.reason ENVIRONMENT_MISMATCH. A human session may name either; absent means SANDBOX while the tenant is TRIAL and PRODUCTION once it is LIVE [assumption]."
                },
                {
                  "key": "type",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Only legs of this type (WALLET_TOPUP | WALLET_HOLD | WALLET_SETTLE | WALLET_RELEASE | WALLET_FEE)"
                },
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Only the legs of this order"
                },
                {
                  "key": "cursor",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Opaque; the nextCursor of the previous page"
                },
                {
                  "key": "limit",
                  "value": "1",
                  "disabled": true,
                  "description": "Page size; default 20 [assumption]"
                }
              ],
              "variable": [
                {
                  "key": "currency",
                  "value": "USD",
                  "description": "The wallet's settlement currency, ISO-4217 (one wallet per tenant per currency, FEAT-030 §5.5)"
                }
              ]
            },
            "description": "`GET /v1/wallets/{currency}/transactions` · operationId `listWalletTransactions`\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-4, published in 1.18.0). One row per ledger leg: WALLET_TOPUP (credit), WALLET_HOLD (moves funds to held at booking), WALLET_SETTLE (debit at ticketing or confirmation, clears the hold), WALLET_RELEASE (clears a hold when the booking fails) and WALLET_FEE (debit). Rows are never edited; a correction is a new leg. Newest first; cursor-paged as GET /v1/webhooks/{webhookId}/deliveries: pass nextCursor back as cursor with the same filters; a cursor this route never issued is 400 VALIDATION (details.field cursor). type is x-extensible-enum. Needs finance:read [PROPOSED]; a human session or an API key. No wallet in that currency is 404 NOT_FOUND."
          },
          "response": []
        },
        {
          "name": "Set or clear the low-balance threshold (FEAT-030 §5.5) — thousand",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/wallets/:currency/alerts?environment=PRODUCTION",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "wallets",
                ":currency",
                "alerts"
              ],
              "query": [
                {
                  "key": "environment",
                  "value": "PRODUCTION",
                  "disabled": true,
                  "description": "Which side of the sandbox/production tenant pair (07 §5) to read or act on. An API key is bound to its own side (mo_test_ = SANDBOX, mo_live_ = PRODUCTION): absent means that side, and naming the other is 400 VALIDATION details.reason ENVIRONMENT_MISMATCH. A human session may name either; absent means SANDBOX while the tenant is TRIAL and PRODUCTION once it is LIVE [assumption]."
                }
              ],
              "variable": [
                {
                  "key": "currency",
                  "value": "USD",
                  "description": "The wallet's settlement currency, ISO-4217 (one wallet per tenant per currency, FEAT-030 §5.5)"
                }
              ]
            },
            "description": "`PUT /v1/wallets/{currency}/alerts` · operationId `putWalletAlerts`\n\nExample: Alert below 1,000.00 USD\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-4, published in 1.18.0). lowBalanceThreshold is Money in the wallet's own currency (another currency is 400 VALIDATION details.reason CURRENCY_MISMATCH), or null to clear it. When available falls below it the platform e-mails the Owners and emits wallet.low_balance once per crossing. Needs finance:write [PROPOSED], which is in no key's default grant. No wallet in that currency is 404 NOT_FOUND. Idempotency-Key required. Audited.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lowBalanceThreshold\": {\n    \"amount\": 100000,\n    \"currency\": \"USD\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Top up the wallet by card — simulated in sandbox only in this phase (FEAT-030 §5.5) — simulated",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/wallets/:currency/top-ups?environment=PRODUCTION",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "wallets",
                ":currency",
                "top-ups"
              ],
              "query": [
                {
                  "key": "environment",
                  "value": "PRODUCTION",
                  "disabled": true,
                  "description": "Which side of the sandbox/production tenant pair (07 §5) to read or act on. An API key is bound to its own side (mo_test_ = SANDBOX, mo_live_ = PRODUCTION): absent means that side, and naming the other is 400 VALIDATION details.reason ENVIRONMENT_MISMATCH. A human session may name either; absent means SANDBOX while the tenant is TRIAL and PRODUCTION once it is LIVE [assumption]."
                }
              ],
              "variable": [
                {
                  "key": "currency",
                  "value": "USD",
                  "description": "The wallet's settlement currency, ISO-4217 (one wallet per tenant per currency, FEAT-030 §5.5)"
                }
              ]
            },
            "description": "`POST /v1/wallets/{currency}/top-ups` · operationId `createWalletTopUp`\n\nExample: Add 500.00 USD to the sandbox wallet\n\nconnect-v1 1.18.1 (served; FEAT-030 WP-4, published in 1.18.0). In this phase the top-up is SIMULATED: it is served only for the sandbox side of the tenant pair (environment SANDBOX) on a rung where aikonic.sandbox.simulation is on, credits the wallet at once with one WALLET_TOPUP leg (plus a WALLET_FEE leg when fee is non-zero) and answers simulated true; no card is charged and paymentMethod is not read. Anywhere else (environment PRODUCTION, or aikonic.wallet.enabled off) it is 501 NOT_IMPLEMENTED details.reason WALLET_TOPUP_UNAVAILABLE and nothing moves; real card top-ups through the payments path (OD-875 tokenisation) and bank transfers wait on OD-ENR-4. amount must be positive and in the wallet's currency (400 VALIDATION details.reason CURRENCY_MISMATCH otherwise), and at most the simulated maximum (default 1,000,000.00 in the wallet's currency, configurable per deployment): above it the answer is 400 VALIDATION details.field amount.amount, details.reason AMOUNT_TOO_LARGE, details.maximum (a Money) and nothing is credited. Human session only (an API key is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED); needs finance:write [PROPOSED]. Idempotency-Key REQUIRED: a replay returns the original top-up and credits nothing twice.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": {\n    \"amount\": 50000,\n    \"currency\": \"USD\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "reference",
      "description": "Reference data and static content: places, airports and other lookups, and what each product supports today.",
      "item": [
        {
          "name": "The only lookup mechanism (03 §3 #21, 05)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "If-None-Match",
                "value": "<string>",
                "disabled": true,
                "description": "Conditional GET against the per-type-version ETag; unchanged → 304 (API-0008 §2/§3)"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/lookup/:type?q=<string>&cursor=<string>&limit=1&filter.country=<string>&filter.role=<string>&filter.kind=<string>&filter.timezone=<string>&filter.metroArea=<string>&filter.currency=<string>&filter.area=<string>&filter.packageType=HOTEL_FLIGHT&filter.origin=<string>&filter.destination=<string>&locale=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "lookup",
                ":type"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "<string>",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "cursor",
                  "value": "<string>",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "limit",
                  "value": "1",
                  "disabled": true,
                  "description": "Page size; default 20, max 100 [assumption, API-0008 §2 / §10 Q5]"
                },
                {
                  "key": "filter.country",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Typed field filter (API-0008 §2 `filter.<field>`). Each lookup type accepts its own set of filter fields; a field the type does not declare is 400 VALIDATION with the accepted `filter.*` names in details.accepted. Alternatives are joined with `|` (filter.country=BR|AR). The fields declared here are the ones a registered type accepts today; others are type-specific and follow the same `filter.<field>` form."
                },
                {
                  "key": "filter.role",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Place role, e.g. origin|destination — absorbs the legacy origins/destinations routes (API-0008 §2)"
                },
                {
                  "key": "filter.kind",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Record kind within a type, e.g. city (places)"
                },
                {
                  "key": "filter.timezone",
                  "value": "<string>",
                  "disabled": true,
                  "description": "IANA time zone (airports)"
                },
                {
                  "key": "filter.metroArea",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Metro-area code grouping several airports (airports)"
                },
                {
                  "key": "filter.currency",
                  "value": "<string>",
                  "disabled": true,
                  "description": "ISO-4217 currency (countries)"
                },
                {
                  "key": "filter.area",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Area grouping of a code list (amenity codes)"
                },
                {
                  "key": "filter.packageType",
                  "value": "HOTEL_FLIGHT",
                  "disabled": true,
                  "description": "Package type (package-origins, package-destinations, package-calendar)"
                },
                {
                  "key": "filter.origin",
                  "value": "<string>",
                  "disabled": true,
                  "description": "A package-origins id (package-destinations, package-calendar)"
                },
                {
                  "key": "filter.destination",
                  "value": "<string>",
                  "disabled": true,
                  "description": "A package-destinations id (package-calendar)"
                },
                {
                  "key": "locale",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Localized names; fallback chain pt-BR → pt → en (05 §5, API-0008 §2)"
                }
              ],
              "variable": [
                {
                  "key": "type",
                  "value": "<string>",
                  "description": "Lookup type key per 05 §3 (locations, airports, suppliers, currencies, amenity codes)"
                }
              ]
            },
            "description": "`GET /v1/lookup/{type}` · operationId `lookup`\n\nExisting five lookup routes become type values. Tenant-filtered; suppliers directory is type suppliers."
          },
          "response": []
        },
        {
          "name": "Bulk + delta export of one lookup type as NDJSON (API-0008 §2/§3, 05 §6)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/lookup/:type/export?since=0&locale=<string>&asOf=0&expires=0&sig=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "lookup",
                ":type",
                "export"
              ],
              "query": [
                {
                  "key": "since",
                  "value": "0",
                  "disabled": true,
                  "description": "Per-type monotonic version from a previous export's asOfVersion; absent = full snapshot. As built, a date-versioned dataset (version yyyyMMdd) also accepts an ISO-8601 date or instant here, read as \"changed after the day before\" (OD-340); the schema stays integer so the change is additive, and a typed client sends the version. A version ahead of the dataset is 400 VALIDATION details.reason SINCE_AHEAD; an instant on a dataset that is not date-versioned is 400 VALIDATION SINCE_INSTANT_UNSUPPORTED."
                },
                {
                  "key": "locale",
                  "value": "<string>",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "asOf",
                  "value": "0",
                  "disabled": true,
                  "description": "DOWNLOAD only — the asOfVersion the signed link was issued at; set by the platform in `url`, never by hand"
                },
                {
                  "key": "expires",
                  "value": "0",
                  "disabled": true,
                  "description": "DOWNLOAD only — link expiry, epoch seconds; set by the platform in `url`"
                },
                {
                  "key": "sig",
                  "value": "<string>",
                  "disabled": true,
                  "description": "DOWNLOAD only — the link's HMAC-SHA-256 signature (base64url); its presence makes the GET a download"
                }
              ],
              "variable": [
                {
                  "key": "type",
                  "value": "<string>",
                  "description": "Registered lookup type key (05 §2)"
                }
              ]
            },
            "description": "`GET /v1/lookup/{type}/export` · operationId `lookupExport`\n\nThe offline-sync mechanism, in two calls to this one route. ISSUE (no `sig`): without `since` it answers a handle to a full snapshot as of `asOfVersion`; with `since` a handle to only the records changed or tombstoned after it (a tombstone is a LookupRecord with deprecated true and, on a merge, successorId — the reference.entity_merged re-key protocol, 05 §4). DOWNLOAD: the handle's `url` is this same route on the platform's own origin with the query parameters `asOf`, `expires` and `sig` (plus `since` and `locale` as issued); a GET on it answers the NDJSON file itself, one LookupRecord per line, content type application/x-ndjson, header X-Export-As-Of-Version. The link is HMAC-signed and lives 15 minutes, and it is NOT presigned: the download still needs the caller's credential, like every lookup (OD-450 option (a); a bearer-less link needs an object store, OD-347). A tampered link is 403 PERMISSION_DENIED details.reason EXPORT_URL_INVALID, an expired one EXPORT_URL_EXPIRED, and a dataset that moved since the link was issued 400 VALIDATION EXPORT_SNAPSHOT_SUPERSEDED (issue a new export). Tenant-filtered like every lookup; answered with Cache-Control no-store. Built as of OD-297 — the earlier \"501 as built\" note is retired (OD-340). A `since` older than the delta-retention horizon needs a full re-export — the spec's [PROPOSED] 410 GONE is not in the 03 §9 registry (API-0008 §10 Q2), so it is not declared here."
          },
          "response": []
        },
        {
          "name": "Hydrate one reference record by canonical id (API-0008 §2, 05 §4)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "If-None-Match",
                "value": "<string>",
                "disabled": true,
                "description": ""
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/lookup/:type/:id?locale=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "lookup",
                ":type",
                ":id"
              ],
              "query": [
                {
                  "key": "locale",
                  "value": "<string>",
                  "disabled": true,
                  "description": ""
                }
              ],
              "variable": [
                {
                  "key": "type",
                  "value": "<string>",
                  "description": "Registered lookup type key (05 §2)"
                },
                {
                  "key": "id",
                  "value": "<string>",
                  "description": "Canonical id mondee:{type}:{id}, or a prov- provisional alias (05 §4)"
                }
              ]
            },
            "description": "`GET /v1/lookup/{type}/{id}` · operationId `lookupRecord`\n\nOne-record form of the lookup mechanism. Canonical ids are mondee:{type}:{id}; prov- ids stay permanent aliases after promotion, so clients never re-key. A tenant-scoped record outside the caller's entitlement is a 404 (existence hidden, API-0008 §4)."
          },
          "response": []
        },
        {
          "name": "Static content catalog sync + delta feed (03 §3 #22, 05 §6, 06 §7)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/content",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "content"
              ]
            },
            "description": "`POST /v1/content` · operationId `syncContent`\n\nBody: a skeleton built from the schema's required properties (the contract carries no example).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"context\": {\n    \"domain\": \"<string>\"\n  },\n  \"message\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The load-bearing discovery doc (04, ADR-C03)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "If-None-Match",
                "value": "<string>",
                "disabled": true,
                "description": "The ETag of the document the client holds; a match is 304 Not Modified"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/capabilities?domain=<string>&supplier=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "capabilities"
              ],
              "query": [
                {
                  "key": "domain",
                  "value": "<string>",
                  "disabled": true,
                  "description": ""
                },
                {
                  "key": "supplier",
                  "value": "<string>",
                  "disabled": true,
                  "description": ""
                }
              ]
            },
            "description": "`GET /v1/capabilities` · operationId `getCapabilities`\n\nGenerated from the domain registry + supplier registry + tenant entitlements + credential health — never authored. Omission means not entitled; PLANNED means blocked on contract (D-21); a production 501 anywhere is a capabilities bug. ETag + Cache-Control max-age=300, private; a matching If-None-Match is 304 with no body (API-0007 §5). Every verb key is stated with a status (NOT_APPLICABLE or PLANNED, never absence); omission applies to products only (04 §2 rules 2 and 6)."
          },
          "response": []
        }
      ]
    },
    {
      "name": "events",
      "description": "Subscribe to webhooks and replay the events your account has received.",
      "item": [
        {
          "name": "List webhook subscriptions",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks"
              ]
            },
            "description": "`GET /v1/webhooks` · operationId `listWebhooks`"
          },
          "response": []
        },
        {
          "name": "Subscribe to signed webhook deliveries (03 §3 #26)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks"
              ]
            },
            "description": "`POST /v1/webhooks` · operationId `createWebhook`\n\nBody: a skeleton built from the schema's required properties (the contract carries no example).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"<uri>\",\n  \"events\": []\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Read one webhook subscription (API-0009 §2); signingSecret is never re-returned",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhookId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhookId"
              ],
              "variable": [
                {
                  "key": "webhookId",
                  "value": "<string>",
                  "description": "The subscription to read, as creating it returned it."
                }
              ]
            },
            "description": "`GET /v1/webhooks/{webhookId}` · operationId `getWebhook`"
          },
          "response": []
        },
        {
          "name": "Remove a webhook subscription",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhookId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhookId"
              ],
              "variable": [
                {
                  "key": "webhookId",
                  "value": "<string>",
                  "description": ""
                }
              ]
            },
            "description": "`DELETE /v1/webhooks/{webhookId}` · operationId `deleteWebhook`"
          },
          "response": []
        },
        {
          "name": "Rotate a subscription's signing secret with a dual-secret overlap window (API-0009 §3/§5, 12 §A17) — oneHourOverlap",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhookId/rotate-secret",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhookId",
                "rotate-secret"
              ],
              "variable": [
                {
                  "key": "webhookId",
                  "value": "<string>",
                  "description": "The webhookId returned by POST /v1/webhooks (API-0009 §3)"
                }
              ]
            },
            "description": "`POST /v1/webhooks/{webhookId}/rotate-secret` · operationId `rotateWebhookSecret`\n\nExample: Keep the old secret signing for one hour\n\nMints a new signing secret and returns it ONCE, in this response — like signingSecret at creation, it is never re-returned. During the overlap window every delivery is signed with BOTH secrets (one v1= value per secret in Mondee-Signature), so a receiver can switch without dropping a delivery; at previousSecretExpiresAt the old secret stops signing. overlapSeconds absent = the platform default (86400 [PROPOSED], 12 §A17 names the overlap, not its length). Rotating again while a window is open ends the older window immediately: at most two secrets sign at once. Idempotency-Key is REQUIRED (a replay returns the original response, the same new secret, rather than minting a third). Unknown or cross-tenant webhookId is 404 (existence hidden).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"overlapSeconds\": 3600\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "The stored delivery-attempt log of one subscription (API-0009 §7, 12 §A17)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhookId/deliveries?state=<string>&eventId=<string>&cursor=<string>&limit=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhookId",
                "deliveries"
              ],
              "query": [
                {
                  "key": "state",
                  "value": "<string>",
                  "disabled": true,
                  "description": "PENDING | DELIVERED | DEAD_LETTERED | CANCELLED — filter by delivery state"
                },
                {
                  "key": "eventId",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Only the delivery of this event"
                },
                {
                  "key": "cursor",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The nextCursor of the previous page; absent for the first page."
                },
                {
                  "key": "limit",
                  "value": "1",
                  "disabled": true,
                  "description": "Page size; default 20 [assumption]"
                }
              ],
              "variable": [
                {
                  "key": "webhookId",
                  "value": "<string>",
                  "description": "The webhookId returned by POST /v1/webhooks (API-0009 §3)"
                }
              ]
            },
            "description": "`GET /v1/webhooks/{webhookId}/deliveries` · operationId `listWebhookDeliveries`\n\nThe tenant-visible debugging surface: one row per (subscription, event) delivery with its state and every attempt — timestamp, HTTP status, latency, a bounded response snippet (never a header, the secret or the request body). Newest first; cursor-paged. At most 50 attempts are kept per delivery (the attempts counter keeps counting). A DEAD_LETTERED event stays recoverable through GET /v1/events — the replay cursor is the redrive (API-0009 §5). Unknown or cross-tenant webhookId is 404 (existence hidden)."
          },
          "response": []
        },
        {
          "name": "Cursor-based event replay — the recover-from-missed-webhooks path (03 §3 #26)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/events?cursor=<string>&type=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "events"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The nextCursor of the previous page; absent for the first page."
                },
                {
                  "key": "type",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Only events of this type, for example order.state_changed."
                }
              ]
            },
            "description": "`GET /v1/events` · operationId `listEvents`"
          },
          "response": []
        }
      ]
    },
    {
      "name": "agents",
      "description": "Proposals an AI agent submits for a signed-in person to approve or reject; an agent proposes, a person commits.",
      "item": [
        {
          "name": "An agent submits an L1 proposal — the uniform reviewable envelope (FEAT-019 §6, OD-75) — rebooking",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agent-proposals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agent-proposals"
              ]
            },
            "description": "`POST /v1/agent-proposals` · operationId `submitAgentProposal`\n\nExample: AG-22 proposes a rebooking after a schedule change (money-touching, L1)\n\nFEAT-019 §6's uniform L1 envelope, published for non-console callers so a proposal lives in the platform rather than in the proposing process (OD-75 option (a)): it survives a restart, every AI-plane replica sees it, its expiry is enforced once, and the console renders it. Called by an AGENT service principal — an API key or a POST /v1/oauth/token service token holding agents:propose (FEAT-019 §9: each agent is its own least-privilege principal). The platform stamps actor {kind: ai, agentId} from that credential; the body's agentId must be the credential's agent, otherwise 403 PERMISSION_DENIED details.reason AGENT_MISMATCH. A system key (DR-40) or a human session filing is 403 PERMISSION_DENIED details.reason AGENT_CREDENTIAL_REQUIRED: a system key can never file a proposal or act as the AI plane. Actor kind is bound to the credential, never to a request header (OD-305). A paused agent is 409 CONFLICT details.reason AGENT_PAUSED (D-54 kill switch, honoured here and not only in the agent runtime, FEAT-019 §14). The proposal is OPEN until a human approves or rejects it or it expires (default 48h, capped at the idempotency window, FEAT-019 §7); nothing is executed at submission. A money-touching proposal has requiresApprovalToken true and amount set (12 §B2.5, no tenant override): its approve needs a D-50 grant from POST /v1/approvals. Emits agent.proposal.created. FEAT-019 is still draft: this shape follows the console form already published (ConsoleAgentProposal, API-0012) so there is one envelope, not two (OD-457).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"agentId\": \"agt_rebook_22\",\n  \"type\": \"REBOOKING\",\n  \"title\": \"Book the 18:40 departure for the disrupted traveller\",\n  \"summary\": \"The 14:10 was cancelled by the carrier; the 18:40 has the same fare family and two seats together.\",\n  \"resource\": {\n    \"type\": \"reprice\",\n    \"ref\": \"RP-F-7c3d20\"\n  },\n  \"requiresApprovalToken\": true,\n  \"approvalAction\": \"orders.create\",\n  \"amount\": {\n    \"amount\": 84210,\n    \"currency\": \"USD\"\n  },\n  \"execution\": {\n    \"action\": \"orders.create\",\n    \"domain\": \"FLT\",\n    \"currency\": \"USD\",\n    \"intent\": {\n      \"repriceRef\": \"RP-F-7c3d20\"\n    }\n  },\n  \"evidence\": [\n    {\n      \"kind\": \"EVENT\",\n      \"ref\": \"evt_01J9ZJ4\",\n      \"label\": \"order.schedule_changed\"\n    }\n  ],\n  \"changes\": [\n    {\n      \"path\": \"items[0].segments[0].departure\",\n      \"before\": \"2026-10-02T14:10:00-05:00\",\n      \"after\": \"2026-10-02T18:40:00-05:00\"\n    }\n  ],\n  \"projectedEffect\": {\n    \"description\": \"Card on file charged for the new booking\",\n    \"amountDelta\": {\n      \"amount\": 84210,\n      \"currency\": \"USD\"\n    }\n  },\n  \"undoPlan\": \"Cancel the new order inside the free-cancellation window\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Read one proposal — the reviewable object (FEAT-019 §6, §7)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/agent-proposals/:proposalId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agent-proposals",
                ":proposalId"
              ],
              "variable": [
                {
                  "key": "proposalId",
                  "value": "<string>",
                  "description": "An agent proposal id (FEAT-019 §7)"
                }
              ]
            },
            "description": "`GET /v1/agent-proposals/{proposalId}` · operationId `getAgentProposal`\n\nAny caller of the tenant holding agents:propose or a human session with agents:act reads it; another tenant's proposal is 404 like an unknown one. EXPIRED is reported as soon as expiresAt has passed, whether or not a sweep has run; only an OPEN proposal expires, never EXECUTING or EXECUTION_UNKNOWN."
          },
          "response": []
        },
        {
          "name": "A human approves an OPEN proposal; it executes once (FEAT-019 §6, D-50) — withGrant",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agent-proposals/:proposalId/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agent-proposals",
                ":proposalId",
                "approve"
              ],
              "variable": [
                {
                  "key": "proposalId",
                  "value": "<string>",
                  "description": "An agent proposal id (FEAT-019 §7)"
                }
              ]
            },
            "description": "`POST /v1/agent-proposals/{proposalId}/approve` · operationId `approveProposal`\n\nExample: Approve a money-touching proposal with the D-50 grant minted for it\n\nHUMAN SESSION ONLY, with agents:act: an agent's proposal can never be approved by another agent (FEAT-019 §9) — an API key or a service token is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED. An MCP grant-derived token (connect-v1 1.26.0) is likewise 403 PERMISSION_DENIED details.reason SESSION_REQUIRED, details.credential mcp-grant: an agent acting for a human is never that human's session (DR-38, DR-39). A proposal with requiresApprovalToken true needs approvalToken, a D-50 grant from POST /v1/approvals bound to its action, resource and amount; without one it is 400 VALIDATION (details.field approvalToken), and a spent, expired, unknown or mismatched grant is 403 PERMISSION_DENIED with details.reason APPROVAL_TOKEN_USED | _EXPIRED | _INVALID | _MISMATCH (the /v1 dialect, OD-327). Preconditions are re-validated at approval time (FEAT-019 §12). What the human approves is the proposal's server-derived executionSummary; if the stored execution no longer matches the approved resource or amount, nothing executes and the answer is 409 CONFLICT details.reason EXECUTION_MISMATCH. A proposal that is not OPEN (EXECUTING, APPROVED, REJECTED, EXPIRED, EXECUTION_UNKNOWN) is 409 CONFLICT details.reason WRONG_STATE with details.state; a paused agent 409 CONFLICT AGENT_PAUSED. The same Idempotency-Key replays the stored answer, so the action executes at most once. Emits agent.proposal.approved. Approve claims the proposal (OPEN to EXECUTING) and runs its verb once. A definitive failure proven NOT dispatched (PRICE_CHANGED, OFFER_EXPIRED, a 402 PAYMENT_FAILED before dispatch, a supplier refusal that was not sent) returns the proposal to OPEN, gives the D-50 grant back and answers the verb's own error. Any failure that may have dispatched makes it EXECUTION_UNKNOWN: terminal for the API, the grant stays spent, the verb is never retried, and the desk resolves it. A stale EXECUTING (a crash mid-run) is likewise resolved by the desk to EXECUTION_UNKNOWN. Expiry applies only to OPEN: an EXECUTING or EXECUTION_UNKNOWN proposal never expires. Any failure after the claim that is not proven undispatched answers 500 INTERNAL details.reason EXECUTION_UNKNOWN, never a retryable INTERNAL. The ambiguous answer carries details.proposalId and emits agent.proposal.execution_unknown so the desk reconciles it; do not re-approve, read GET /v1/agent-proposals/{proposalId}.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"approvalToken\": \"apt_Vb2m9QxR1sLk4TzW8cN0aYhE5uJ3pD7fG6iO2qA1bC4\",\n  \"reason\": \"Customer confirmed by phone\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "A human rejects an OPEN proposal (FEAT-019 §6) — reject",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Mandatory on mutations (03 §7): **48h TTL** (fixed by DR-16, closing OD-01); replay with same body-hash returns the original response with Idempotency-Replayed true; same key + different body is 409 IDEMPOTENCY_CONFLICT; in-flight duplicate is 409 IDEMPOTENCY_IN_PROGRESS."
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/agent-proposals/:proposalId/reject",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "agent-proposals",
                ":proposalId",
                "reject"
              ],
              "variable": [
                {
                  "key": "proposalId",
                  "value": "<string>",
                  "description": "An agent proposal id (FEAT-019 §7)"
                }
              ]
            },
            "description": "`POST /v1/agent-proposals/{proposalId}/reject` · operationId `rejectProposal`\n\nExample: Reject with a reason\n\nHUMAN SESSION ONLY, with agents:act (an API key or service token is 403 PERMISSION_DENIED details.reason SESSION_REQUIRED). Nothing executes; the reason is kept on the proposal and on agent.proposal.rejected. A proposal that is not OPEN is 409 CONFLICT details.reason WRONG_STATE. An MCP grant-derived token (connect-v1 1.26.0) is likewise 403 PERMISSION_DENIED details.reason SESSION_REQUIRED, details.credential mcp-grant: an agent acting for a human is never that human's session (DR-38, DR-39).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Customer prefers a refund to a rebooking\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{accessToken}}",
                  "type": "string"
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "enrichment",
      "description": "Read-only travel data such as flight emissions and status, ratings, weather, holidays and entry requirements; it never blocks a booking.",
      "item": [
        {
          "name": "CO2e per passenger for flights and typical emissions for markets (API-0008 §11; a data provider) — oneFlight",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/enrichment/flight-emissions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enrichment",
                "flight-emissions"
              ]
            },
            "description": "`POST /v1/enrichment/flight-emissions` · operationId `getFlightEmissions`\n\nExample: One leg, economy\n\nPOST because lists outgrow query strings (the /v1/orders/search precedent). Served from cache or the pre-warmed store first; a provider call runs only under the kind's budget. Cache-Control max-age follows the SPI §6 ladder (7 days; invalidated on a model-version change). Scope data:read.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"flights\": [\n    {\n      \"operatingCarrier\": \"LA\",\n      \"flightNumber\": \"8084\",\n      \"origin\": \"GRU\",\n      \"destination\": \"MIA\",\n      \"departureDate\": \"2026-11-10\"\n    }\n  ],\n  \"cabin\": \"ECONOMY\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Operational status of a flight, one record per operated leg (API-0008 §11; a data provider, a data provider)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/enrichment/flight-status?carrier=<string>&flightNumber=<string>&date=2026-11-10&origin=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enrichment",
                "flight-status"
              ],
              "query": [
                {
                  "key": "carrier",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The airline, as its two-character IATA code."
                },
                {
                  "key": "flightNumber",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The flight number without the airline code."
                },
                {
                  "key": "date",
                  "value": "2026-11-10",
                  "disabled": false,
                  "description": "The departure date, local to the departure airport."
                },
                {
                  "key": "origin",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The departure airport, as its IATA code, to narrow a multi-leg flight to one leg."
                }
              ]
            },
            "description": "`GET /v1/enrichment/flight-status` · operationId `getFlightStatus`\n\nCache TTL is dynamic by proximity to departure (SPI §6). Never changes order state; the order-scoped view is GET /v1/orders/{orderRef}/enrichment. Scope data:read."
          },
          "response": []
        },
        {
          "name": "On-time statistics for a flight number or a route (API-0008 §11)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/enrichment/flight-ratings?carrier=<string>&flightNumber=<string>&origin=<string>&destination=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enrichment",
                "flight-ratings"
              ],
              "query": [
                {
                  "key": "carrier",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The airline, as its two-character IATA code."
                },
                {
                  "key": "flightNumber",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The flight number without the airline code; send it, or origin and destination for a route."
                },
                {
                  "key": "origin",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The departure airport, as its IATA code, for a route."
                },
                {
                  "key": "destination",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The arrival airport, as its IATA code, for a route."
                }
              ]
            },
            "description": "`GET /v1/enrichment/flight-ratings` · operationId `getFlightRatings`"
          },
          "response": []
        },
        {
          "name": "Cabin amenities of a flight, with confidence (API-0008 §11)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/enrichment/flight-amenities?carrier=<string>&flightNumber=<string>&date=2026-11-10&cabin=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enrichment",
                "flight-amenities"
              ],
              "query": [
                {
                  "key": "carrier",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The airline, as its two-character IATA code."
                },
                {
                  "key": "flightNumber",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The flight number without the airline code."
                },
                {
                  "key": "date",
                  "value": "2026-11-10",
                  "disabled": true,
                  "description": "The departure date, local to the departure airport."
                },
                {
                  "key": "cabin",
                  "value": "<string>",
                  "disabled": true,
                  "description": "Only this cabin, for example ECONOMY or BUSINESS; absent means every cabin."
                }
              ]
            },
            "description": "`GET /v1/enrichment/flight-amenities` · operationId `getFlightAmenities`"
          },
          "response": []
        },
        {
          "name": "The flown track of a flight, one record per operated leg that has taken off (API-0008 §11; a data provider)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/enrichment/flight-track?carrier=<string>&flightNumber=<string>&date=2026-11-10&origin=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enrichment",
                "flight-track"
              ],
              "query": [
                {
                  "key": "carrier",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The airline, as its two-character IATA code."
                },
                {
                  "key": "flightNumber",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The flight number without the airline code."
                },
                {
                  "key": "date",
                  "value": "2026-11-10",
                  "disabled": false,
                  "description": "The departure date, local to the departure airport."
                },
                {
                  "key": "origin",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The departure airport, as its IATA code, to narrow a multi-leg flight to one leg."
                }
              ]
            },
            "description": "`GET /v1/enrichment/flight-track` · operationId `getFlightTrack`\n\nTime-ordered positions of the aircraft for each operated leg of the flight that has taken off, and its latest position while the leg is airborne, for a client to draw on its own map. A leg that has not taken off, is cancelled, or lies outside the provider's window has no track: data is empty and warnings carry ENRICHMENT_UNAVAILABLE. Cache TTL is short while a leg is airborne; a landed leg's track is final (SPI §6). No map image is served. Never changes order state. Scope data:read."
          },
          "response": []
        },
        {
          "name": "Door-to-door time and distance per leg, or an origins × destinations matrix (API-0008 §11; a data provider) — oneLeg",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/enrichment/route-estimates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enrichment",
                "route-estimates"
              ]
            },
            "description": "`POST /v1/enrichment/route-estimates` · operationId `getRouteEstimates`\n\nExample: Airport to hotel by car, historical traffic",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"legs\": [\n    {\n      \"legRef\": \"OI-2/0\",\n      \"origin\": {\n        \"iata\": \"GRU\"\n      },\n      \"destination\": {\n        \"placeId\": \"mondee:place:sp-hotel-unique\"\n      }\n    }\n  ],\n  \"modes\": [\n    \"CAR\"\n  ],\n  \"departAt\": \"2026-11-11T08:10:00-03:00\",\n  \"traffic\": \"HISTORICAL\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Current, forecast or historical weather at a place (API-0008 §11; a data provider)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/enrichment/weather?place=<string>&from=2026-11-10&to=2026-11-10&kind=CURRENT",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enrichment",
                "weather"
              ],
              "query": [
                {
                  "key": "place",
                  "value": "<string>",
                  "disabled": false,
                  "description": "A mondee:place:… id or an IATA code"
                },
                {
                  "key": "from",
                  "value": "2026-11-10",
                  "disabled": true,
                  "description": "First day of the forecast or history (inclusive)."
                },
                {
                  "key": "to",
                  "value": "2026-11-10",
                  "disabled": true,
                  "description": "Last day of the forecast or history (inclusive)."
                },
                {
                  "key": "kind",
                  "value": "CURRENT",
                  "disabled": true,
                  "description": "CURRENT for now, FORECAST for coming days or HISTORICAL for past days."
                }
              ]
            },
            "description": "`GET /v1/enrichment/weather` · operationId `getWeather`"
          },
          "response": []
        },
        {
          "name": "Public holidays and long weekends for a country (API-0008 §11; a data provider)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/enrichment/holidays?country=<string>&subdivision=<string>&from=2026-11-10&to=2026-11-10&include=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enrichment",
                "holidays"
              ],
              "query": [
                {
                  "key": "country",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The country, as its ISO 3166-1 alpha-2 code."
                },
                {
                  "key": "subdivision",
                  "value": "<string>",
                  "disabled": true,
                  "description": "A state or region, as its ISO 3166-2 code, to add its own holidays."
                },
                {
                  "key": "from",
                  "value": "2026-11-10",
                  "disabled": true,
                  "description": "First day of the range (inclusive)."
                },
                {
                  "key": "to",
                  "value": "2026-11-10",
                  "disabled": true,
                  "description": "Last day of the range (inclusive)."
                },
                {
                  "key": "include",
                  "value": "<string>",
                  "disabled": true,
                  "description": "LONG_WEEKENDS to add the platform-computed long weekends; CELLS (planned) to add one HolidayCalendar.cells[] entry per day"
                }
              ]
            },
            "description": "`GET /v1/enrichment/holidays` · operationId `getHolidays`"
          },
          "response": []
        },
        {
          "name": "Documents a traveller needs for a corridor; never an eligibility verdict (API-0008 §11; a data provider)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/enrichment/entry-requirements?origin=<string>&destination=<string>&nationality=<string>&residence=<string>&purpose=<string>&stayDays=0&age=0&transport=<string>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "enrichment",
                "entry-requirements"
              ],
              "query": [
                {
                  "key": "origin",
                  "value": "<string>",
                  "disabled": false,
                  "description": "Where the traveller departs from, as a country code (ISO 3166-1 alpha-2)."
                },
                {
                  "key": "destination",
                  "value": "<string>",
                  "disabled": false,
                  "description": "Where the traveller enters, as a country code (ISO 3166-1 alpha-2)."
                },
                {
                  "key": "nationality",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The traveller's nationality, as a country code (ISO 3166-1 alpha-2)."
                },
                {
                  "key": "residence",
                  "value": "<string>",
                  "disabled": false,
                  "description": "The traveller's country of residence, as a country code (ISO 3166-1 alpha-2), when it differs from the nationality."
                },
                {
                  "key": "purpose",
                  "value": "<string>",
                  "disabled": true,
                  "description": "The reason for the trip, for example TOURISM, BUSINESS, TRANSIT, STUDY or WORK."
                },
                {
                  "key": "stayDays",
                  "value": "0",
                  "disabled": true,
                  "description": "How many days the traveller stays."
                },
                {
                  "key": "age",
                  "value": "0",
                  "disabled": true,
                  "description": "The traveller's age in years, where requirements differ for minors."
                },
                {
                  "key": "transport",
                  "value": "<string>",
                  "disabled": true,
                  "description": "How the traveller arrives at the destination."
                }
              ]
            },
            "description": "`GET /v1/enrichment/entry-requirements` · operationId `getEntryRequirements`\n\nTenant-scoped, Cache-Control private, TTL at most 24 hours. nationality and residence are personal attributes: never logged beside an order ref. The answer's shownAt is what an order keeps as evidence (CANONICAL-MODEL §5 P4). Scope data:read."
          },
          "response": []
        },
        {
          "name": "The trip-page aggregate for one order — flight status from the watch store, weather, entry requirements, route estimates (API-0008 §11)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/orders/:orderRef/enrichment",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "orders",
                ":orderRef",
                "enrichment"
              ],
              "variable": [
                {
                  "key": "orderRef",
                  "value": "<string>",
                  "description": "MO-XXX-XXX-XXX (D-48); BK- retained as read alias"
                }
              ]
            },
            "description": "`GET /v1/orders/{orderRef}/enrichment` · operationId `getOrderEnrichment`\n\nSame permission as the order read; flight status is read from the watch store, never a live provider call (SPI §4.1)."
          },
          "response": []
        },
        {
          "name": "Account-level emissions and \"carbon saved\" over a period, from order-commit snapshots (API-0008 §11)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sustainability/summary?from=2026-11-10&to=2026-11-10",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sustainability",
                "summary"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-11-10",
                  "disabled": false,
                  "description": "First day of the period (inclusive)."
                },
                {
                  "key": "to",
                  "value": "2026-11-10",
                  "disabled": false,
                  "description": "Last day of the period (inclusive)."
                }
              ]
            },
            "description": "`GET /v1/sustainability/summary` · operationId `getCarbonSummary`"
          },
          "response": []
        }
      ]
    }
  ]
}
