generated: '2026-09-03' method: searched source: >- openapi/wealth-reader-api-for-ai.yaml ; https://www.wealthreader.com/docs/en/flow.md ; https://www.wealthreader.com/docs/en/iframe-integration-1-of-2-frontend.md ; https://www.wealthreader.com/docs/en/iframe-integration-2-of-2-backend.md ; https://github.com/Wealth-Reader/wealthreader-api-docs/blob/main/api-reference/endpoints.md docs: https://www.wealthreader.com/docs/en/introduction/ auth_style: summary: Static api_key as a form field; end-user bank credentials custodied behind an opaque token. see: authentication/wealth-reader-authentication.yml request: content_type: application/x-www-form-urlencoded exception: >- The Cards (real time) tag and the batch tag use application/json. The rest of the API is form-encoded — unusual for a 2026 fintech API and worth knowing before generating a client. methods: GET and POST only. No PUT, PATCH or DELETE anywhere in the contract. response_envelope: shape: '{success: boolean, payload: object|array, error: {code, message}, statistics: {...}}' status_semantics: >- HTTP status is a weak signal. A failed bank read commonly returns HTTP 200 with success:false and an integer error.code. Branch on success and error.code, not on the status line. The Cards (real time) endpoints break this convention and do use real HTTP statuses (400/401/409/429) with string codes. always_present: - statistics.SESSION - statistics.execution_time idempotency: supported: true mechanisms: - surface: 'POST /cards/enrollments/ (cardsEnrollmentsCreate)' key: '(api_key, email)' header: null scope: while the enrollment is still pending and unexpired retention: ttl_minutes, default 20, range 1-60 behaviour: >- Repeating the call for the same (api_key, email) returns the same request rather than creating a second one. If the email is already linked to the calling customer it returns status "active" directly; if linked to a different customer it returns 409. - surface: iframe / OAuth callback delivery key: operation_id header: null scope: per operation, generated by the integrator (crypto.randomUUID in the provider's own example) behaviour: >- The provider instructs the CONSUMER to enforce idempotency: "Treat operation_id as idempotent: a repeated delivery must not create two operations in your system." This is a documented contract obligation, but it is enforced on the integrator's side, not the API's. - surface: cards webhook delivery key: delivery_id header: X-WR-Delivery scope: per delivery, 32 hex characters behaviour: >- Retries resend the exact same body with the same delivery_id, so a consumer can deduplicate across the 1 min / 5 min / 30 min / 2 h / 24 h retry ladder. - surface: 'POST /cards/webhook/ (cardsWebhookRegister)' key: null behaviour: >- Effectively an upsert. Omitting webhook_url leaves the stored URL untouched, which is how the secret is rotated without changing the URL; sending null disables webhooks for that customer. no_idempotency_key_header: true gap: >- There is no Idempotency-Key request header anywhere in the contract, and the primary read (POST /entities/) has no idempotency mechanism at all — it is a live bank navigation, and every call bills as products read. An agent must not blind-retry it. pagination: styles: - surface: 'GET /cards/transactions/' style: cursor params: cursor: since_id size: limit defaults: limit: 500 max_limit: 1000 date_from: today minus 3 days date_to: today response_field: payload.next_since_id exhausted_when: next_since_id is null ordering: ascending transaction id - surface: 'POST /tokens/' style: page-number params: page: page page_size: 500 default_page: 1 response_field: null note: No total or next-page field is returned; the caller pages until a short block comes back. - surface: 'POST /entities/' style: none note: >- The primary read returns the whole normalised payload in one response. Volume is bounded by date_from/date_to and product_types, not by pagination. consistency: >- Two different pagination idioms in one API, and no Link header or RFC 8288 anywhere. filtering: product_types: >- Comma-separated product filter on POST /entities/ — user_information, accounts, portfolios, cards, receipts, loans, deposits, leases, insurances, factoring, confirming, properties, invoices, files, contracts. Requesting a type outside the licence returns error code 2. only_balances: Boolean shortcut that returns balances instead of full product detail. required_products_schema: Selects specific accounts or cards with per-product configuration. date_range: date_from (past, transactions) and date_to (future only, for loan and confirming). expansion: field: fetch_transaction_details style: boolean opt-in, not sparse-fieldset or ?expand= cost: >- Explicitly warned about in the contract: enabling it performs one or more extra navigations PER TRANSACTION, growing with transaction volume, and "will inevitably and significantly increase the execution time". Requires a dedicated environment. Results land under additional_info on each transaction. identifiers: uuid: >- Every product and transaction carries a `uuid` (40 hex characters) that is stable across reads, which is what makes incremental sync possible without diffing on amount and date. entity_code: Institution slug — bbva, caixabank, abanca — sent as `code` and echoed in statistics.code. session: statistics.SESSION, the support correlation id. The provider asks for it in every ticket. operation_id: Integrator-generated, bridges the frontend widget operation and the backend callback. tracing: request_id_header: null correlation: statistics.SESSION in the response body note: >- No X-Request-Id or traceparent header. Correlation is body-level only, which means a failed request that never produced a body cannot be correlated at all. versioning: scheme: semver on the specification document current: 8.1.7 in_url: false in_header: false note: >- The version lives in info.version of the OpenAPI. There is no /v1/ path segment, no version header and no version query parameter — a client cannot pin a version, and the server cannot tell which version a client was built against. see: lifecycle/wealth-reader-lifecycle.yml localization: spec: >- api.yaml carries x-i18n blocks rendering the reference in 17 languages; api-for-ai.yaml is the same specification with those blocks stripped, published specifically for code generation and agents. That split is deliberate provider work and it is rare. runtime: '?lang=en on /error-codes/ and /warning-codes/; Spanish is the default.' rate_limit_signaling: headers: none see: rate-limits/wealth-reader-rate-limits.yml errors: see: errors/wealth-reader-problem-types.yml format: custom integer codes with a fatal flag; not RFC 9457 dry_run_mode: supported: partial detail: >- There is no dry-run flag on the API. What exists instead is mock data: the tokens MOCKDATA, MOCKOTP and MOCKLOGINKO on POST /entities/ return a canned OK response, an OTP challenge and a login error respectively without touching a real bank. See sandbox/wealth-reader-sandbox.yml. The sibling allwr-toolkit CLI/MCP is dry-run BY DEFAULT, which is the stronger posture, but it operates ALL WR rather than this API. reversibility: applicability: mixed read_surface_note: >- The bank-data core of this API is read-only by design and by regulatory scope — Wealth Reader is authorised for account information services (AIS) under PSD2 and never initiates payments. For POST /entities/, POST /batch/*, GET /cards/transactions/ and the catalogue reads, reversibility is `na`: there is nothing to take back. The cost of a mistaken call is billing (each read bills as products) and end-user friction (a repeated failed login walks the user towards a bank-side lockout), not an irreversible state change. grade: documented grade_rationale: >- Three of the four write surfaces have a real, named reversal operation, and the provider documents them. None of them states a WINDOW inside which the reversal works, so this cannot be graded `verified`. No window has been invented here. write_surfaces: - operation: revokeToken path: POST /tokens/revoke/ action: Revokes a custodied bank credential token. reversal: none reversal_window: null note: >- This IS the reversal for tokenisation. It is itself irreversible — the docs say a revoked token cannot be un-revoked; error code 9999 ("Token invalidated by client request") is permanent and "the client must generate a new token", which requires the end user to re-authenticate at their bank. An agent should treat revocation as one-way. - operation: reasignToken path: POST /tokens/reasign/ action: Moves a token from api_key_source to api_key_target. reversal: reasignToken with the two api_keys swapped reversal_window: null confidence: derived note: >- The inverse call is symmetric and obviously available, but the docs never state it as a reversal or bound it in time. Recorded as derived, not as a documented guarantee. - operation: cardsWebhookRegister path: POST /cards/webhook/ action: Registers or rotates the real-time cards webhook URL and secret. reversal: 'POST /cards/webhook/ with webhook_url: null' reversal_window: null note: >- Sending null disables webhooks for that customer, which reverses the registration. The SECRET rotation is NOT reversible: a new webhook_secret is returned exactly once and the previous one cannot be retrieved again, so rotating without capturing the response permanently breaks signature verification. Retries already in flight when the secret rotates are signed with the NEW secret. - operation: cardsEnrollmentsCreate path: POST /cards/enrollments/ action: Pre-registers an employee email in pending status. reversal: none published reversal_window: ttl_minutes (default 20, range 1-60) after which a pending enrollment expires on its own note: >- No cancel or delete operation is published for a pending enrollment. The only unwind is waiting out ttl_minutes. Once confirmed, revokeUser (POST /user/revoke/) exists on the Advanced tag but the docs do not state that it reverses an enrollment, so that link is not asserted here. - operation: addDomain path: POST /domains/ action: Associates a widget domain with a callback URL and api_key. reversal: none published in the API reversal_window: null note: >- The docs point to the client area at https://www.wealthreader.com/clients/ to "edit or test your domains" — a human console, not an API operation. There is no removeDomain. what_would_raise_this_to_verified: - A stated window for un-revoking or re-issuing a token without a fresh end-user bank login. - A published cancel operation for a pending cards enrollment. - A documented retention period for a rotated webhook_secret.