# ai.ohmyfin/banking-intelligence

> Cross-border payment & banking intelligence for AI agents: SWIFT/BIC, IBAN, sanctions, FX, tracking.

- name: ai.ohmyfin/banking-intelligence
- version: 1.0.0
- connection class: R0 (autonomous)
- trust: 76/100 flags: no-repository
- quality: 66/100 (needs work)
- owner: not claimed
- descriptor: https://api.protogrid.dev/v1/servers/ai.ohmyfin%2Fbanking-intelligence
- tools: https://api.protogrid.dev/v1/servers/ai.ohmyfin%2Fbanking-intelligence/tools

**An agent can connect right now, no human step.** Remote endpoint, no authentication, reachable on the last probe.

## Connection (mcpServers)

```json
{
  "mcpServers": {
    "banking-intelligence": {
      "type": "http",
      "url": "https://mcp.ohmyfin.ai/mcp"
    }
  }
}
```


## Trust

- hygiene: 80
- liveness: 100
- freshness: 65
- provenance: 55
- drivers: +dns-namespace +website +reachable +uptime-100% ~updated-130d-ago -no-repository
- Derived from observable signals (official registry feed and our own credential-free probes); no code audit performed.

## Quality 66/100 (needs work)

### protocol: 75 (weight 25)

- warn `protocol.modern`: newest supported version is 2025-11-25; 2026-07-28 not supported, older versions are deprecated until 2027-07-28
- n/a `protocol.stateless`: only applies to 2026-07-28 servers
- pass `protocol.transport`: streamable HTTP
- n/a `protocol.list_ttl`: only applies to 2026-07-28 servers

### authorization: n/a (weight 15)

- n/a `auth.prm`, `auth.as_metadata`, `auth.cimd`: no remote uses OAuth
- n/a `auth.secret_in_url`: no templated URL

### tool hygiene: 79 (weight 30)

- pass `tools.descriptions`: every tool has a description
- warn `tools.description_length`: 21 tools have descriptions over 1,024 characters, which crowds the context
- pass `tools.schemas`: every tool has an object input schema
- pass `tools.annotations`: 34 of 34 tools declare readOnlyHint or destructiveHint
- warn `tools.directory_hints`: 5 of 34 tools declare all four hints; missing: country_banking_rules (destructiveHint, idempotentHint); fx_rate_history (destructiveHint, idempotentHint); payment_cutoff_times (destructiveHint, idempotentHint); …
- warn `tools.token_cost`: about 21,278 tokens to load every tool
- pass `tools.api_dump`: tools are not a one-to-one API dump

### stability: 25 (weight 15)

- warn `stability.changes`: 39 tool changes in 30 days
- fail `stability.rug_pull`: 1 tools kept their name but changed most of their description in 30 days; review before trusting them

### dependencies: n/a (weight 15)

- n/a `deps.known_vulns`, `deps.mcp_sdk_version`, `deps.resolvable`: no npm or PyPI package

- tools: 34, about 21,278 tokens to load them all
- tool set hash: 3ec90cb307598606b4c80b42be75a312
- tools last changed: 2026-10-18T04:33:36.496Z
- badge: [![protogrid quality](https://protogrid.dev/badge/ai.ohmyfin/banking-intelligence.svg)](https://protogrid.dev/servers/ai.ohmyfin/banking-intelligence)
- Checks run on what our credential-free, read-only probes observe; tools are never called and no code audit is performed.

### Tool changes

- 2026-10-18 `settlement_eta` changed (description)
  - before: BETA. Estimate when a SWIFT payment will arrive: a corpus-grounded arrival window with an honest tail, computed from real completed payments we have tracked, projected onto the currency's banking calendar. This estimator is in BETA and still calibrating. Say so when you present a number: call it an estimate or a typical window, never a commitment, and never let a user plan an irreversible decision (a cutoff, a contractual settlement date) on it without that caveat. The payload carries beta=true while this holds. Two modes: - Forward (default): "when will it land" — returns P50/P90/P95 arrival dates, sample size, confidence, competing non-arrival risk, delay-risk factors, and (where validated) the most likely correspondent route. - Reverse: pass arrive_by_date (YYYY-MM-DD) — returns the latest send date such that arrival by that day is likely ("send by Thursday to land by month-end"). INPUT DISCIPLINE (important): - Mid-flight payment: pass ONLY the uetr (from TrackingContext or track_payment). The server resolves the current status, currency and elapsed time deterministically from the tracking record. NEVER compute elapsed_business_days yourself. - Pre-trade question ("how long will a USD wire from X to Y take?"): pass currency + sender_bic/receiver_bic (8 or 11 chars, or bank names). current_status / elapsed_business_days are for this path only. Reading the answer honestly (relay these to the user): - basis.n is the sample size and confidence reflects it; when confidence is "low", present the window as a rough range, never a promise. - route.confirmed=false means the route is INFERRED from settlement instructions on file, not confirmed by GPI — say so. - basis.route_adjusted=true means we hold no completed payments for this exact pair and the window was lifted to a route-composed estimate: the SSI-implied correspondent chain (route.intermediaries hops) with typical processing time per hop. Present it as a route-based estimate, not as observed statistics, and never quote the faster currency-pool average alongside it as if corridor-specific. - mode="outlier" means the payment is already slower than ~90% of similar payments: stop quoting a window, explain the usual manual causes (compliance review, repair/RFI, missing cover) and pivot to the stuck-payment diagnostic flow. - non_arrival.p_reject is the share of similar payments that were returned or rejected rather than delivered. - "Delivered" (ACCC) means delivered to the beneficiary bank per GPI; funds can become usable in the account slightly later. Available on every surface to any caller with an active subscription.
  - after: BETA. Estimate when a SWIFT payment will arrive: a corpus-grounded arrival window with an honest tail, computed from real completed payments we have tracked, projected onto the currency's banking calendar. This estimator is in BETA and still calibrating. Say so when you present a number: call it an estimate or a typical window, never a commitment, and never let a user plan an irreversible decision (a cutoff, a contractual settlement date) on it without that caveat. The payload carries beta=true while this holds. Two modes: - Forward (default): "when will it land" — returns P50/P90/P95 arrival dates, sample size, confidence, competing non-arrival risk, delay-risk factors, and (where validated) the most likely correspondent route. - Reverse: pass arrive_by_date (YYYY-MM-DD) — returns the latest send date such that arrival by that day is likely ("send by Thursday to land by month-end"). INPUT DISCIPLINE (important): - Mid-flight payment: pass ONLY the uetr (from TrackingContext or track_payment). The server resolves the current status, currency and elapsed time deterministically from the tracking record. NEVER compute elapsed_business_days yourself. - Pre-trade question ("how long will a USD wire from X to Y take?"): pass currency + sender_bic/receiver_bic (8 or 11 chars, or bank names). current_status / elapsed_business_days are for this path only. Reading the answer honestly (relay these to the user): - basis.n is the sample size and confidence reflects it; when confidence is "low", present the window as a rough range, never a promise. - basis.level says whose payments basis.n counts: `corridor` this bank pair, `sender_ccy` / `receiver_ccy` this sending / receiving bank in this currency, `currency` every bank in this currency, `global` every tracked payment in every currency. basis.currency is the PAYMENT's currency, not the pool's: a global answer is never "<currency> payments". basis.pool says it in words where it matters. - basis.refined_by names a thin cohort blended into that pool; basis.n is the pool's size, not its. The return rate can be that cohort's own (non_arrival.based_on): never attach basis.n to it. - route.confirmed=false means the route is INFERRED from settlement instructions on file, not confirmed by GPI — say so. - basis.route_adjusted=true means we hold no completed payments for this exact pair and the window was lifted to a route-composed estimate: the SSI-implied correspondent chain (route.intermediaries hops) with typical processing time per hop. Present it as a route-based estimate, not as observed statistics, and never quote the faster currency-pool average alongside it as if corridor-specific. - mode="outlier" means the payment is already slower than ~90% of similar payments: stop quoting a window, explain the usual manual causes (compliance review, repair/RFI, missing cover) and pivot to the stuck-payment diagnostic flow. - non_arrival.p_reject is the share of similar payments that were returned or rejected rather than delivered; read non_arrival.based_on when it is there. - "Delivered" (ACCC) means delivered to the beneficiary bank per GPI; funds can become usable in the account slightly later. Available on every surface to any caller with an active subscription.
- 2026-10-14 `gpi_status_codes` changed (description)
  - before: Explain SWIFT GPI tracking status codes and provide stuck-payment investigation guidance. USE THIS TOOL FIRST whenever the user reports a payment that is stuck, delayed, not arriving, held, pending, rejected, or otherwise not behaving as expected. It is the primary diagnostic entrypoint for payment investigation — calling with a specific code returns a full investigation playbook (common delay causes, recommended actions, GPI SLA timeframes, escalation steps). Recommended calls by scenario: - Payment "stuck" / "in progress" / "pending" / "not arrived": gpi_status_codes("ACSP") → playbook for in-progress payments - Payment explicitly "on hold" / compliance review: gpi_status_codes("PDNG") → playbook for held payments - Payment "blocked" / sanctions flag: gpi_status_codes("BLCK") → playbook for blocked payments - Payment rejected by a bank in the chain (never credited): gpi_status_codes("RJCT") → rejection investigation playbook - Payment returned to sender (accepted then sent back): gpi_status_codes("RTRN") → return investigation playbook - Reference for ISO 20022 codes: gpi_status_codes() → list all codes Each code call returns: - Code description and meaning - For ACSP/PDNG/BLCK/RJCT/RTRN: investigation playbook with common causes, recommended actions (request gCCT tracker, request pacs.002/pacs.004 reason code, verify beneficiary details, escalate via MT199, etc.), and common ISO 20022 reason codes (AC01, AC04, AG01, RR01-RR04, etc.) when applicable - Child reason codes (e.g., G001-G004 for ACSP) that narrow the cause further Common codes: ACCC (success), ACSP (in progress), RJCT (rejected), PDNG (on hold), BLCK (blocked). GPI reason codes (G000-G004) qualify ACSP with more detail (e.g. G001 = cover payment sent, G002 = forwarded to next agent). Examples: gpi_status_codes("ACSP") # stuck-payment diagnostic playbook gpi_status_codes("G001") # detail on a specific reason code gpi_status_codes() # full reference list
  - after: Explain SWIFT GPI tracking status codes and provide stuck-payment investigation guidance. USE THIS TOOL FIRST whenever the user reports a payment that is stuck, delayed, not arriving, held, pending, rejected, or otherwise not behaving as expected. It is the primary diagnostic entrypoint for payment investigation — calling with a specific code returns a full investigation playbook (common delay causes, recommended actions, GPI SLA timeframes, escalation steps). Recommended calls by scenario: - Payment "stuck" / "in progress" / "pending" / "not arrived": gpi_status_codes("ACSP") → playbook for in-progress payments - Payment explicitly "on hold" / compliance review: gpi_status_codes("PDNG") → playbook for held payments - Payment "blocked" / sanctions flag: gpi_status_codes("BLCK") → playbook for blocked payments - Payment rejected by a bank in the chain (never credited): gpi_status_codes("RJCT") → rejection investigation playbook - Payment returned to sender (accepted then sent back): gpi_status_codes("RTRN") → return investigation playbook - Reference for ISO 20022 codes: gpi_status_codes() → list all codes Each code call returns: - Code description and meaning - For ACSP/PDNG/BLCK/RJCT/RTRN: investigation playbook with common causes, recommended actions (request gCCT tracker, request pacs.002/pacs.004 reason code, verify beneficiary details, escalate via MT199, etc.), and common ISO 20022 reason codes (AC01, AC04, AG01, RR01-RR04, etc.) when applicable - Child reason codes (e.g., G001-G004 for ACSP) that narrow the cause further Common codes: ACCC (success), ACSP (in progress), RJCT (rejected), PDNG (on hold), BLCK (blocked). GPI reason codes (G000-G004) qualify ACSP with more detail (e.g. G001 = passed on to a bank that is not a gpi member, G002 = credit pending, may not be same day). Examples: gpi_status_codes("ACSP") # stuck-payment diagnostic playbook gpi_status_codes("G001") # detail on a specific reason code gpi_status_codes() # full reference list
- 2026-10-10 `tracking_history` changed (description)
  - before: Show how a SWIFT payment's tracking results changed over time. Returns the DISTINCT tracking results recorded for a payment (by UETR or reference), deduplicated so ten identical re-tracks collapse to one entry while any change — a new last-update, a status change, or new bank data — appears as its own entry. Each entry includes what was ENTERED when the search was run (amount, currency, date) alongside the banks that reported data and their confirmed amount / value date. WHEN TO USE THIS: - The user says the page shows different data than you see, or asks why a bank line (e.g. JP Morgan) "disappeared" or a value date differs. - You need to reconcile an amount discrepancy. Correspondent banks such as JP Morgan return their confirmation ONLY when the tracked amount exactly matches the payment, so a search run with the wrong amount silently drops their line. Comparing entries here — same UETR, different entered amounts, different bank data — is how you spot that the amount was the problem. - Before concluding "the record was consolidated" or "the bank stopped reporting", check the history: the earlier result you're being asked about is usually still here, under a different entered amount. Each bank entry's `source` names the tracking SOURCE that reported it (e.g. "Standard Chartered"), not the bank at a step of the payment; track_payment names the same data by the bank at each step, so the two names differ. Only results for the current user (plus system tracks with no owner) are returned; other users' searches of the same UETR are never shown. Requires an API key with an active FI subscription.
  - after: Show how a SWIFT payment's tracking results changed over time. Returns the DISTINCT tracking results recorded for a payment (by UETR or reference), deduplicated so ten identical re-tracks collapse to one entry while any change — a new last-update, a status change, or new bank data — appears as its own entry. Each entry includes what was ENTERED when the search was run (amount, currency, date) alongside the banks that reported data and their confirmed amount / value date. WHEN TO USE THIS: - The user says the page shows different data than you see, or asks why a bank line (e.g. JP Morgan) "disappeared" or a value date differs. - You need to reconcile an amount or date discrepancy. JP Morgan returns its line only when the entered date and amount are close to its own record (seen: value dates up to 3 days apart, amounts mostly within 1%), so a search run with a wrong date or amount can silently drop its line. When it does answer, `amount_exact_match` is false if its value date OR its amount differs from what was entered, despite the name, and `amount_exact_match_detail` names which. Comparing entries here — same UETR, different entered dates or amounts, different bank data — is how you spot which entry was the problem. - Before concluding "the record was consolidated" or "the bank stopped reporting", check the history: the earlier result you're being asked about is usually still here, under a different entered amount. Each bank entry's `source` names the tracking SOURCE that reported it (e.g. "Standard Chartered"), not the bank at a step of the payment; track_payment names the same data by the bank at each step, so the two names differ. Only results for the current user (plus system tracks with no owner) are returned; other users' searches of the same UETR are never shown. Requires an API key with an active FI subscription.
- 2026-10-07 `company_registries` changed (title, annotations)
- 2026-10-07 `company_search_result` changed (title, description, inputSchema, annotations)
  - before: EXPERIMENTAL — Retrieve cached company search results by search ID. Every company_search_person and company_search_company call returns a search_id. Use this tool to retrieve those results again without re-running the search. Args: search_id: The search_id from a previous company search response. api_key: Your Ohmyfin API key (prod-...). Can also be passed via KEY header or Authorization: Bearer header. Examples: company_search_result("abc123-def456")
  - after: EXPERIMENTAL — Retrieve cached company search results by search ID. Every company_search_person and company_search_company call returns a search_id. Use this tool to retrieve those results again without re-running the search. Examples: company_search_result("abc123-def456")
- 2026-10-07 `company_search_company` changed (title, description, inputSchema, annotations)
  - before: EXPERIMENTAL — Search company registries for a company with its officers and shareholders. Find company registrations across worldwide registries, including directors, officers, and beneficial owners (PSC/shareholders). Every entity found is automatically screened against sanctions lists. You MUST specify at least one jurisdiction. "ALL" is not supported. Available jurisdictions: AM, AT, AU, BR, CA, CH, CZ, DE, DK, EE, FI, FR, IE, IL, IS, LT, LV, NL, NO, PL, SG, UK, XX. Call company_registries() for the live list — this one can go stale. XX is GLEIF LEI, a GLOBAL registry rather than a country. Reach for it whenever the company sits outside the national registries above — a supplier in Hong Kong, mainland China, the US or the UAE. Hits carry an LEI, a registered address and a search.gleif.org URL the user can open. A jurisdiction NOT on that list is dropped silently by the backend: you get total_results 0 with status "completed" and no error. That means the company was never searched for — it is NOT evidence that it is unregistered or fake, and saying so to someone checking a counterparty before wiring money is the most damaging thing this tool can do. Check `jurisdictions_not_searched` and `coverage_warning` in the response before you report an empty result. Args: name: Company name to search for. jurisdictions: Country codes to search (required, e.g. ["UK"]). "ALL" is not supported — specify individual countries. include_sanctions_check: Auto-screen results against sanctions DB (default: true). include_officers: Include directors and officers (default: true). include_shareholders: Include PSC/beneficial owners (default: true). include_only_active: Filter to active companies only (default: false). api_key: Your Ohmyfin API key (prod-...). Can also be passed via KEY header or Authorization: Bearer header. Examples: company_search_company("Equinor", jurisdictions=["NO"]) company_search_company("Acme Corp", jurisdictions=["UK", "DE"], include_only_active=True)
  - after: EXPERIMENTAL — Search company registries for a company with its officers and shareholders. Find company registrations across worldwide registries, including directors, officers, and beneficial owners (PSC/shareholders). Every entity found is automatically screened against sanctions lists. You MUST specify at least one jurisdiction. "ALL" is not supported. Available jurisdictions: AM, AT, AU, BR, CA, CH, CZ, DE, DK, EE, FI, FR, IE, IL, IS, LT, LV, NL, NO, PL, SG, UK, XX. Call company_registries() for the live list — this one can go stale. XX is GLEIF LEI, a GLOBAL registry rather than a country. Reach for it whenever the company sits outside the national registries above — a supplier in Hong Kong, mainland China, the US or the UAE. Hits carry an LEI, a registered address and a search.gleif.org URL the user can open. A jurisdiction NOT on that list is dropped silently by the backend: you get total_results 0 with status "completed" and no error. That means the company was never searched for — it is NOT evidence that it is unregistered or fake, and saying so to someone checking a counterparty before wiring money is the most damaging thing this tool can do. Check `jurisdictions_not_searched` and `coverage_warning` in the response before you report an empty result. Examples: company_search_company("Equinor", jurisdictions=["NO"]) company_search_company("Acme Corp", jurisdictions=["UK", "DE"], include_only_active=True)
- 2026-10-07 `company_search_person` changed (title, description, inputSchema, annotations)
  - before: EXPERIMENTAL — Search company registries for a person's directorships, officer roles, and shareholdings. Searches worldwide company registries to find where a person holds director, officer, or shareholder positions. Every person and company found is automatically screened against sanctions lists. You MUST specify at least one jurisdiction. "ALL" is not supported. Available jurisdictions: AM, AT, AU, BR, CA, CH, CZ, DE, DK, EE, FI, FR, IE, IL, IS, LT, LV, NL, NO, PL, SG, UK, XX. Call company_registries() for the live list — this one can go stale. XX is GLEIF LEI, a GLOBAL registry rather than a country. Use it for anyone connected to a company outside the national registries above. A jurisdiction NOT on that list is dropped silently by the backend: you get total_results 0 with status "completed" and no error. That means the person was never searched for — it is NOT evidence they hold no roles. Check `jurisdictions_not_searched` and `coverage_warning` in the response before you report an empty result to the user. Args: name: Person name to search for. jurisdictions: Country codes to search (required, e.g. ["UK", "NO"]). "ALL" is not supported — specify individual countries. include_sanctions_check: Auto-screen results against sanctions DB (default: true). include_inactive_roles: Include resigned/ceased roles (default: true). api_key: Your Ohmyfin API key (prod-...). Can also be passed via KEY header or Authorization: Bearer header. Examples: company_search_person("John Smith", jurisdictions=["UK", "NO"]) company_search_person("Jane Doe", jurisdictions=["DE"])
  - after: EXPERIMENTAL — Search company registries for a person's directorships, officer roles, and shareholdings. Searches worldwide company registries to find where a person holds director, officer, or shareholder positions. Every person and company found is automatically screened against sanctions lists. You MUST specify at least one jurisdiction. "ALL" is not supported. Available jurisdictions: AM, AT, AU, BR, CA, CH, CZ, DE, DK, EE, FI, FR, IE, IL, IS, LT, LV, NL, NO, PL, SG, UK, XX. Call company_registries() for the live list — this one can go stale. XX is GLEIF LEI, a GLOBAL registry rather than a country. Use it for anyone connected to a company outside the national registries above. A jurisdiction NOT on that list is dropped silently by the backend: you get total_results 0 with status "completed" and no error. That means the person was never searched for — it is NOT evidence they hold no roles. Check `jurisdictions_not_searched` and `coverage_warning` in the response before you report an empty result to the user. Examples: company_search_person("John Smith", jurisdictions=["UK", "NO"]) company_search_person("Jane Doe", jurisdictions=["DE"])
- 2026-10-07 `banks_using_correspondent` changed (title, description, inputSchema, annotations)
  - before: Reverse SSI lookup — find banks that use a given correspondent for a currency. Given a correspondent BIC, currency, and origin country, returns the banks in that country that have a declared nostro at the correspondent for that currency. Inverse of ssi_lookup. Returns only swift + name per bank — to retrieve the account number, intermediary chain, or other SSI details for a specific bank from the result list, call ssi_lookup(bank_swift, currency) on it. Country and currency are required (not optional) — both bound the result set and the query is rejected without them. Requires an API key with an active PRO, VIP, or FI subscription. Tight per-account daily caps apply (5/day on PRO, 10/day on VIP/FI/trial). Args: correspondent_swift: BIC of the correspondent bank (e.g. "IRVTUS3N"). currency: ISO 4217 (e.g. "USD"). country: ISO 3166-1 alpha-2 of the client banks (e.g. "AE"). name_prefix: Optional prefix on bank name (e.g. "AL"). page: 1–4. Defaults to 1. api_key: Your Ohmyfin API key (prod-...). Can also be passed via KEY header or Authorization: Bearer header. Examples: banks_using_correspondent("IRVTUS3N", "USD", "AE") banks_using_correspondent("CITIUS33", "USD", "SA", name_prefix="AL")
  - after: Reverse SSI lookup — find banks that use a given correspondent for a currency. Given a correspondent BIC, currency, and origin country, returns the banks in that country that have a declared nostro at the correspondent for that currency. Inverse of ssi_lookup. Returns only swift + name per bank — to retrieve the account number, intermediary chain, or other SSI details for a specific bank from the result list, call ssi_lookup(bank_swift, currency) on it. Country and currency are required (not optional) — both bound the result set and the query is rejected without them. Requires an API key with an active PRO, VIP, or FI subscription. Examples: banks_using_correspondent("IRVTUS3N", "USD", "AE") banks_using_correspondent("CITIUS33", "USD", "SA", name_prefix="AL")
- 2026-10-07 `ssi_lookup` changed (title, description, inputSchema, annotations)
  - before: Look up correspondent banking / settlement instructions (SSI) for a bank. Returns the correspondent banks (nostro accounts) that a given bank uses to settle payments in a specific currency, including account numbers (when available) and intermediary chains. Essential for payment routing and pre-validation. Each correspondent is annotated with a clearing_note indicating whether it can clear the currency directly (located in a home country for that currency) or needs its own correspondent. If the note suggests a further lookup, call ssi_lookup on the correspondent's SWIFT code to find the full clearing chain. IMPORTANT — known data gaps to respect: - Account numbers may be empty for some/all correspondents. The response surfaces an `account_availability_note` in those cases. Do NOT invent account numbers. Use swift_lookup() to find the bank's own published correspondent banks page when accounts are missing. - WHICH CORRESPONDENT: read `correspondent_selection`, not the array order. It gives the COMPLETE set of BICs for each flow (customer MT103 vs interbank MT202) and for whether the correspondent clears the currency itself, each with a count. The correspondents are returned in STORAGE order, which is not a ranking. Naming a subset ("principally X and Y", "route via X") invents a preference this feed does not hold. Name every BIC in the matching set, or give its count: which one is used is the SENDING bank's choice among the ones it can already reach, not the beneficiary bank's, and not ours to guess from bank size or reputation. `is_preferred` is `null` on almost every row because the flag is genuinely unrecorded (11 rows in the entire corpus carry it); where it IS set, `correspondent_selection.bank_flagged_preference` names it and you should lead with that. - `intermediaries` is `null` — not `[]` — when this correspondent's onward chain is not recorded, which is 98% of rows. `null` means NOT ESTABLISHED, never zero hops: do not read it as a direct chain and do not count it. Whether a further hop is needed is answered by each correspondent's `clearing_note`, which says either that it clears the currency itself or that a further hop is expected and gives the ssi_lookup call that resolves it. When the chain IS recorded the array is populated and `intermediaries_note` names the hops in order. - Asset category per correspondent is COMMERCIAL (for customer MT103 credit transfers) or FINANCIAL (for bank-own-account / interbank MT202/pacs.009 settlements). Read `asset_categories` (the full list) rather than the single `asset_category`, which shows the commercial view only: one entry is one BIC+account and the same account is often published under BOTH categories, so the single field can never establish what an account may NOT be used for. The `asset_category_note` summarises the split — match the listed correspondents to the user's flow type (customer payment vs treasury/interbank). - If `correspondents` is EMPTY, we have no SSI on file for that bank/currency. The response carries a `no_ssi_note` (no SSI in any currency) or `requested_currency_unavailable_note` (SSI on file for other currencies only). This is a coverage gap, NOT a finding that the bank has no correspondents. Do NOT name a correspondent for the missing currency from training data — surface the `published_ssi_document` / the bank's website and tell the user to confirm SSI with the bank. Always inspect the response's top-level `next_steps` array — it chains the swift_lookup / country_banking_rules / bank_holidays calls that complete a settlement-instruction answer. COVERAGE IS KNOWABLE BEFORE YOU CALL, AND FOR FREE. swift_lookup returns a `settlement_instructions` block on every bank: `on_file: true` with a currency count means this lookup will answer, `false` means we hold none in any currency, null means it has not been established. Do not treat "might be a coverage gap" as a reason to ski
  - after: Look up correspondent banking / settlement instructions (SSI) for a bank. Returns the correspondent banks (nostro accounts) that a given bank uses to settle payments in a specific currency, including account numbers (when available) and intermediary chains. Essential for payment routing and pre-validation. Each correspondent is annotated with a clearing_note indicating whether it can clear the currency directly (located in a home country for that currency) or needs its own correspondent. If the note suggests a further lookup, call ssi_lookup on the correspondent's SWIFT code to find the full clearing chain. IMPORTANT — known data gaps to respect: - Account numbers may be empty for some/all correspondents. The response surfaces an `account_availability_note` in those cases. Do NOT invent account numbers. Use swift_lookup() to find the bank's own published correspondent banks page when accounts are missing. - WHICH CORRESPONDENT: read `correspondent_selection`, not the array order. It gives the COMPLETE set of BICs for each flow (customer MT103 vs interbank MT202) and for whether the correspondent clears the currency itself, each with a count. The correspondents are returned in STORAGE order, which is not a ranking. Naming a subset ("principally X and Y", "route via X") invents a preference this feed does not hold. Name every BIC in the matching set, or give its count: which one is used is the SENDING bank's choice among the ones it can already reach, not the beneficiary bank's, and not ours to guess from bank size or reputation. `is_preferred` is `null` on almost every row because the flag is genuinely unrecorded (11 rows in the entire corpus carry it); where it IS set, `correspondent_selection.bank_flagged_preference` names it and you should lead with that. - `intermediaries` is `null` — not `[]` — when this correspondent's onward chain is not recorded, which is 98% of rows. `null` means NOT ESTABLISHED, never zero hops: do not read it as a direct chain and do not count it. Whether a further hop is needed is answered by each correspondent's `clearing_note`, which says either that it clears the currency itself or that a further hop is expected and gives the ssi_lookup call that resolves it. When the chain IS recorded the array is populated and `intermediaries_note` names the hops in order. - Asset category per correspondent is COMMERCIAL (for customer MT103 credit transfers) or FINANCIAL (for bank-own-account / interbank MT202/pacs.009 settlements). Read `asset_categories` (the full list) rather than the single `asset_category`, which shows the commercial view only: one entry is one BIC+account and the same account is often published under BOTH categories, so the single field can never establish what an account may NOT be used for. The `asset_category_note` summarises the split — match the listed correspondents to the user's flow type (customer payment vs treasury/interbank). - If `correspondents` is EMPTY, we have no SSI on file for that bank/currency. The response carries a `no_ssi_note` (no SSI in any currency) or `requested_currency_unavailable_note` (SSI on file for other currencies only). This is a coverage gap, NOT a finding that the bank has no correspondents. Do NOT name a correspondent for the missing currency from training data — surface the `published_ssi_document` / the bank's website and tell the user to confirm SSI with the bank. Always inspect the response's top-level `next_steps` array — it chains the swift_lookup / country_banking_rules / bank_holidays calls that complete a settlement-instruction answer. COVERAGE IS KNOWABLE BEFORE YOU CALL, AND FOR FREE. swift_lookup returns a `settlement_instructions` block on every bank: `on_file: true` with a currency count means this lookup will answer, `false` means we hold none in any currency, null means it has not been established. Do not treat "might be a coverage gap" as a reason to ski
- 2026-10-07 `tracking_history` changed (title, description, inputSchema, annotations)
  - before: Show how a SWIFT payment's tracking results changed over time. Returns the DISTINCT tracking results recorded for a payment (by UETR or reference), deduplicated so ten identical re-tracks collapse to one entry while any change — a new last-update, a status change, or new bank data — appears as its own entry. Each entry includes what was ENTERED when the search was run (amount, currency, date) alongside the banks that reported data and their confirmed amount / value date. WHEN TO USE THIS: - The user says the page shows different data than you see, or asks why a bank line (e.g. JP Morgan) "disappeared" or a value date differs. - You need to reconcile an amount discrepancy. Correspondent banks such as JP Morgan return their confirmation ONLY when the tracked amount exactly matches the payment, so a search run with the wrong amount silently drops their line. Comparing entries here — same UETR, different entered amounts, different bank data — is how you spot that the amount was the problem. - Before concluding "the record was consolidated" or "the bank stopped reporting", check the history: the earlier result you're being asked about is usually still here, under a different entered amount. Each bank entry's `source` names the tracking SOURCE that reported it (e.g. "Standard Chartered"), not the bank at a step of the payment; track_payment names the same data by the bank at each step, so the two names differ. Only results for the current user (plus system tracks with no owner) are returned; other users' searches of the same UETR are never shown. Requires an API key with an active FI subscription. Args: uetr: UETR (UUID v4) of the payment. Strongly preferred. reference: Sender's reference (MT103 Field 20) — used when no UETR.
  - after: Show how a SWIFT payment's tracking results changed over time. Returns the DISTINCT tracking results recorded for a payment (by UETR or reference), deduplicated so ten identical re-tracks collapse to one entry while any change — a new last-update, a status change, or new bank data — appears as its own entry. Each entry includes what was ENTERED when the search was run (amount, currency, date) alongside the banks that reported data and their confirmed amount / value date. WHEN TO USE THIS: - The user says the page shows different data than you see, or asks why a bank line (e.g. JP Morgan) "disappeared" or a value date differs. - You need to reconcile an amount discrepancy. Correspondent banks such as JP Morgan return their confirmation ONLY when the tracked amount exactly matches the payment, so a search run with the wrong amount silently drops their line. Comparing entries here — same UETR, different entered amounts, different bank data — is how you spot that the amount was the problem. - Before concluding "the record was consolidated" or "the bank stopped reporting", check the history: the earlier result you're being asked about is usually still here, under a different entered amount. Each bank entry's `source` names the tracking SOURCE that reported it (e.g. "Standard Chartered"), not the bank at a step of the payment; track_payment names the same data by the bank at each step, so the two names differ. Only results for the current user (plus system tracks with no owner) are returned; other users' searches of the same UETR are never shown. Requires an API key with an active FI subscription.

Full history: https://api.protogrid.dev/v1/servers/ai.ohmyfin%2Fbanking-intelligence/changes

## Remotes

- https://mcp.ohmyfin.ai/mcp (streamable-http, auth none, reachable true, uptime30d 1)

## Packages

_none_

## Tools (34)

- `bank_holidays`: Get bank/public holidays for a country with payment impact analysis.

Returns all public holidays plus a 'payment_impact' section that shows:
- Whether today is a business day or holiday in this country
- Upcoming holidays in the next 14 days
- Recent holidays in the last 14 days — for diagnosing a payment that is
  ALREADY stuck ("in progress for N days", "sent X days ago"). A recent
  holiday only counts if BOTH hold: it falls INSIDE the payment's own
  window (on or after the send date), AND its 'costs_a_business_day' is
  true. One that predates the send date is irrelevant, and one on the
  country's banking weekend closed nothing that was open — neither may be
  subtracted or given to the user as a cause. An empty list affirmatively
  means no recent holiday explains the delay — do not invent one from
  training data.
- Every holiday entry (upcoming, recent, and next_holiday_after_today)
  carries 'costs_a_business_day'. Roughly one holiday date in six lands on
  its own country's weekend and shortens nothing; check the flag before
  quoting a holiday as a delay, a closure or a reason a window was short.
- elapsed_business_days_by_send_date — the AUTHORITATIVE elapsed
  business-day count keyed by send date (weekends + this country's
  holidays already excluded). Use it verbatim instead of hand-counting.
- Next business day and how many consecutive non-business days remain
This context helps determine if holidays are causing payment delays.

Examples:
  bank_holidays("US")
  bank_holidays("DE", 2026)
  bank_holidays("GB", 2025)
- `banks_using_correspondent`: Reverse SSI lookup — find banks that use a given correspondent for a currency.

Given a correspondent BIC, currency, and origin country, returns the banks
in that country that have a declared nostro at the correspondent for that
currency. Inverse of ssi_lookup.

Returns only swift + name per bank — to retrieve the account number,
intermediary chain, or other SSI details for a specific bank from the
result list, call ssi_lookup(bank_swift, currency) on it.

Country and currency are required (not optional) — both bound the result
set and the query is rejected without them.

Requires an API key with an active PRO, VIP, or FI subscription.

Examples:
  banks_using_correspondent("IRVTUS3N", "USD", "AE")
  banks_using_correspondent("CITIUS33", "USD", "SA", name_prefix="AL")
- `company_registries`: EXPERIMENTAL — List available company registries and supported jurisdictions.

Returns the list of company registries that can be searched,
along with the jurisdiction codes you can use in
company_search_person and company_search_company. This is the LIVE
list and outranks the codes named in those two tools' descriptions.

Any country code not returned here has no registry behind it: a search
naming it comes back empty and "completed", which does not mean the
company is unregistered. `XX` (GLEIF LEI) is global and is the fallback
for those jurisdictions.

No API key required.

Examples:
  company_registries()
- `company_search_company`: EXPERIMENTAL — Search company registries for a company with its officers and shareholders.

Find company registrations across worldwide registries, including
directors, officers, and beneficial owners (PSC/shareholders).
Every entity found is automatically screened against sanctions lists.

You MUST specify at least one jurisdiction. "ALL" is not supported.
Available jurisdictions: AM, AT, AU, BR, CA, CH, CZ, DE, DK, EE, FI,
FR, IE, IL, IS, LT, LV, NL, NO, PL, SG, UK, XX. Call
company_registries() for the live list — this one can go stale.

XX is GLEIF LEI, a GLOBAL registry rather than a country. Reach for it
whenever the company sits outside the national registries above — a
supplier in Hong Kong, mainland China, the US or the UAE. Hits carry an
LEI, a registered address and a search.gleif.org URL the user can open.

A jurisdiction NOT on that list is dropped silently by the backend: you
get total_results 0 with status "completed" and no error. That means the
company was never searched for — it is NOT evidence that it is
unregistered or fake, and saying so to someone checking a counterparty
before wiring money is the most damaging thing this tool can do. Check
`jurisdictions_not_searched` and `coverage_warning` in the response
before you report an empty result.

Examples:
  company_search_company("Equinor", jurisdictions=["NO"])
  company_search_company("Acme Corp", jurisdictions=["UK", "DE"], include_only_active=True)
- `company_search_person`: EXPERIMENTAL — Search company registries for a person's directorships, officer roles, and shareholdings.

Searches worldwide company registries to find where a person holds
director, officer, or shareholder positions. Every person and company
found is automatically screened against sanctions lists.

You MUST specify at least one jurisdiction. "ALL" is not supported.
Available jurisdictions: AM, AT, AU, BR, CA, CH, CZ, DE, DK, EE, FI,
FR, IE, IL, IS, LT, LV, NL, NO, PL, SG, UK, XX. Call
company_registries() for the live list — this one can go stale.

XX is GLEIF LEI, a GLOBAL registry rather than a country. Use it for
anyone connected to a company outside the national registries above.

A jurisdiction NOT on that list is dropped silently by the backend: you
get total_results 0 with status "completed" and no error. That means the
person was never searched for — it is NOT evidence they hold no roles.
Check `jurisdictions_not_searched` and `coverage_warning` in the response
before you report an empty result to the user.

Examples:
  company_search_person("John Smith", jurisdictions=["UK", "NO"])
  company_search_person("Jane Doe", jurisdictions=["DE"])
- `company_search_result`: EXPERIMENTAL — Retrieve cached company search results by search ID.

Every company_search_person and company_search_company call returns
a search_id. Use this tool to retrieve those results again without
re-running the search.

Examples:
  company_search_result("abc123-def456")
- `country_banking_rules`: Get banking rules and requirements for a country.

Returns IBAN requirements, SEPA membership, FATF listing status,
national currency, account format specifications, and country-specific
payment requirements (mandatory codes like KNP for Kazakhstan,
Purpose of Payment for UAE, etc.).

The `fatf_listing` block is the authoritative answer to "is this country
grey-listed / black-listed / under FATF increased monitoring". Both FATF
public lists are held in full, so a `not_listed` status is a positive
determination and not missing data. Use it instead of training data for any
FATF question, and note that the coarse `fatf` field is a separate, weaker
signal about regional-body membership that says nothing about listing.

Everything here is COUNTRY-level. `currency` is the country's national
currency, not the denomination of any beneficiary account — never pair it
with the payment currency to diagnose a currency mismatch (see
`currency_note` in the response).

Use this to check country-specific STP rules that could cause payment delays, repairs, or rejections (e.g., missing purpose codes, regulatory fields).

If a country requires special payment codes, the response includes
a payment_requirements block with field descriptions and categories.
Use country_payment_codes to look up specific code values.

Examples:
  country_banking_rules("DE")
  country_banking_rules("KZ")   # includes KNP requirement info
  country_banking_rules("AE")   # includes Purpose of Payment info
- `country_export_controls`: Look up export control restrictions for a specific country.

Returns embargo status, sanctioned programs, control reasons, and
restriction details across jurisdictions (US EAR, EU, UN, etc.)
for the given country. Response also includes a payment_jurisdiction_note
explaining when each listed restriction actually applies to a payment
(US controls only bind when there's a US nexus, etc.).

IMPORTANT: Each jurisdiction's controls only bind a payment when the
payment has a nexus to that jurisdiction. Use the jurisdiction filter
when you know the payment's actual jurisdictional touchpoints (sender
country, clearing currency, intermediary banks). For a CHF/EUR payment
with no US bank in the chain, US export controls are informational only
— do NOT cite them as compliance blockers without confirming a US nexus.

Examples:
  country_export_controls("RU")          # Russia — heavily embargoed
  country_export_controls("CN")          # China — partial restrictions
  country_export_controls("DE")          # Germany — minimal controls
  country_export_controls("RU", "US")    # Russia, US jurisdiction only

Use case: 'What export restrictions apply to shipping to Russia?'
- `country_payment_codes`: Look up country-specific payment codes (KNP, purpose codes, etc.).

Use country_banking_rules first to see which code types a country
requires (in the payment_requirements block), then use this tool
to find the right code value.

Examples:
  country_payment_codes("KZ", "knp", "transport")
  country_payment_codes("KZ", "knp", "insurance")
  country_payment_codes("AE", "purpose_code", "trade")
  country_payment_codes("KZ", "knp")   # all codes (large response)
- `eccn_lookup`: Look up an Export Control Classification Number (ECCN).

Pure reference tool — returns classification details, controlled
jurisdictions, and license requirements for the given ECCN.

ECCNs are alphanumeric codes (e.g. "5A001") used under export control
regimes (US EAR, EU Dual-Use Regulation, Wassenaar Arrangement) to
classify items that may require an export license.

Examples:
  eccn_lookup("5A001")   # Telecommunications security equipment
  eccn_lookup("3A001")   # Electronic components
  eccn_lookup("1C351")   # Human pathogens, zoonoses, toxins
- `export_controls_screen`: Screen goods for export-control restrictions to a destination country.

Combines the goods classification with the destination's restriction status
and returns whether a license is required, the risk level, applicable
license policies (e.g. presumption of denial), control reasons (NS, MT, NP,
CB, AT), and proliferation/dual-use flags. Identify the goods by ANY of:
ECCN, HS code, or a free-text description (English or Russian).

IMPORTANT — jurisdiction nexus: each jurisdiction's controls only bind a
payment/shipment when there is a nexus to that jurisdiction (US EAR binds
US persons, USD-clearing, and US-origin items; EU/UK/JP bind their persons,
currencies, and origin). Use jurisdiction="ALL" for a comprehensive
multi-jurisdiction view, or pick the one matching the actual touchpoints.

IMPORTANT, prefer eccn or hs_code: goods_description is a fallback: the
classifier matches it lexically, so a vague description still returns one
specific HS code and it is usually the wrong one ("industrial machinery"
returns bakery and pasta machinery). When you pass a description only, the
result carries classification_basis and classification_confidence_note;
read them, never quote the inferred code back to the user as their HS code
or ECCN, and ask for the code on their invoice or export declaration. The
destination findings (embargo, transshipment risk, screening duties) are
NOT affected by that doubt, so report them normally.

Provide at least one of eccn / hs_code / goods_description.

Examples:
  export_controls_screen("RU", eccn="3A001")                       # electronics → Russia
  export_controls_screen("CN", eccn="3A090")                       # advanced computing → China
  export_controls_screen("IR", goods_description="industrial valves")
  export_controls_screen("RU", goods_description="drone", jurisdiction="ALL")
  export_controls_screen("DE", hs_code="854231")                   # → Germany (allied)

Use case: 'Can we ship integrated circuits to Russia?'
- `federal_register_changes`: Get recent US regulatory changes from BIS and OFAC.

Returns Federal Register publications including entity list updates,
rule changes, country policy shifts, and new sanctions programs.

Examples:
  federal_register_changes()                          # Last 30 days, all
  federal_register_changes(agency="OFAC", days=7)     # OFAC changes this week
  federal_register_changes(category="entity_list", severity="critical")

Use case: 'Any new entity list additions affecting China?'
- `fx_rate`: Get the latest available reference (mid-market) exchange rate for a pair.

Rates are the official ECB euro foreign-exchange reference rates where the
ECB publishes the currency; pairs whose currency the ECB does not cover
(e.g. VND, NGN, PKR, KZT, MAD) fall back to a market data feed. ALWAYS
check the `source` field before describing provenance: "ecb" = official
ECB reference rate; "market" = indicative mid-market rate, NOT an ECB
fixing — never call it "the ECB rate". The `source_note` field in the
result states this explicitly. Coverage is wide (~60 currencies) but NOT
universal — some currencies (e.g. CLP, COP, PEN) have no rate on file at
all. When a leg is missing, the error names exactly which currency is
uncovered: relay that we hold no rate rather than supplying one from your
own knowledge. Non-EUR pairs are
computed as cross-rates via EUR (e.g., USD/GBP = EUR/GBP / EUR/USD), so
they are indicative mid-rates, not dealable/executable rates.

BOTH currencies are required. Always pass the exact pair you intend.
There is no implicit default pair: a call that omits or mis-names a
currency returns a "missing required argument" error rather than a
silently-wrong rate. Never assume EUR/USD when the user asked about a
different pair such as USD/VND.

Examples:
  fx_rate("EUR", "USD")
  fx_rate("GBP", "JPY")
  fx_rate("USD", "VND")
- `fx_rate_history`: Get the historical reference exchange-rate series for a currency pair.

Returns `series` (the daily rates) plus the metadata needed to describe it
honestly. READ `series_coverage` BEFORE CHARACTERISING THE PERIOD. `days`
is the window we look back over, NOT a promise of how much history exists:
our series start at different dates per currency, so a 365-day request
routinely returns five months. `series_coverage.first_date`/`last_date` are
what the numbers actually span, and `series_coverage.truncated` is true
when that is shorter than you asked for. Say "the ~N months we hold", never
"over the past year", and never fill the gap from your own knowledge.

CHECK `source` BEFORE ATTRIBUTING PROVENANCE. "ecb" = official ECB euro
reference fixings, weekdays only. "market" = an indicative market data feed
for a currency the ECB does not publish (AED, QAR, SAR, KWD, NGN, PKR, VND,
KZT, RUB, UAH and ~20 more). A market series is NOT an ECB series and must
never be described as one. Where a pair mixes the two, `series_coverage`
reports the dates lost to aligning them.

`peg_context` appears when either currency is pegged, including when the
peg is against some third currency: it names the anchor and the pair whose
movement you are really looking at. Take the peg date from there rather
than from memory.

An uncovered pair returns an `error` naming the missing leg instead of an
empty series. Report that we hold no history rather than describing the
rate as stable or range-bound.

BOTH currencies are required. Always pass the exact pair you intend.
There is no implicit EUR/USD default: an omitted or mis-named currency
errors rather than returning the wrong pair's history.

Examples:
  fx_rate_history("EUR", "USD", 30)
  fx_rate_history("GBP", "CHF", 365)
- `fx_timing_advisor`: Get FX trading windows for FX execution timing and spread / rate optimization.

Returns market sessions and liquidity windows for a currency. Use this
to understand:
- **Rate optimization** (primary, reliable use): higher liquidity means
  tighter spreads and better rates. Execute during peak windows to minimize
  conversion costs.
- **Delay diagnosis** (use with care): the FX market session is when a
  currency TRADES. It is NOT a guaranteed processing schedule for an inbound
  foreign-currency payment that the beneficiary bank converts on arrival.
  Conversion timing is beneficiary-bank-specific (some convert in real time
  during the session, others batch once or twice daily), so do NOT tell the
  user a payment is "held until the next session" and do not quote specific
  hold durations ("adds X hours", "overnight delay"); those are bank policy
  and are not in our data. For the binding delivery-side cutoff that gates the
  converted local-currency leg, call country_banking_rules(destination) and
  read local_clearing.systems. When a currency is restricted, this tool's own
  output carries an inbound_processing_note with the accurate framing to quote.

Pass a currency code to get its optimal window, or omit to get
all market sessions and overlap windows.

Examples:
  fx_timing_advisor("EUR")
  fx_timing_advisor("JPY")
  fx_timing_advisor("INR")  # Check INR conversion windows
  fx_timing_advisor()
- `fx_volatility`: Get realized FX volatility for a currency pair, and size the FX risk on an exposure held to a future date.

Computes 30-day and 90-day annualized volatility from historical
ECB reference rates (standard deviation of daily log returns,
annualized by sqrt(252)). Returns a qualitative bucket:
LOW (<5%), MEDIUM (5-15%), HIGH (15-25%), VERY_HIGH (>25%),
PEGGED (currency peg — near-zero volatility, e.g., USD/AED, USD/HKD).

Also returns practical daily/weekly movement estimates and a
settlement_risk_note explaining what the volatility means over a
typical T+2 settlement period — use these to advise users on FX
risk for their specific payment.

PASS horizon_days WHENEVER THE USER'S EXPOSURE RUNS PAST SETTLEMENT.
It returns a `horizon` block: the volatility scaled to that horizon as an
actual rate band at 1 and 2 sigma, which end of the band hurts a payer
versus a receiver, and what the band does and does not tell them about
hedging. Use it for questions shaped like:
  - "should I hedge / lock in / take a forward for <future period>?"
  - "how far could <pair> move by <date>?"
  - "what rate should I budget for next year?"
  - "I have invoices in <currency> through 2027 — what is my risk?"
  - any exposure not settling within a few days.
Count the calendar days from today to the date the exposure ends and pass
that. Rough is fine — the band moves with the square root of time, so a
month either way barely changes it.

Read `sample_depth` before quoting any figure: this is REALISED volatility
from a short history, not implied volatility, and the sample may be shorter
than the horizon asked about (`horizon.beyond_sample`). Say so.

IMPORTANT — the band is the range of FUTURE SPOT. It is not a rate anyone
can transact at, and the width of the band is NOT the cost of a hedge. A
forward is priced off the interest-rate differential between the two
currencies, which we do not hold and must not guess or recall from memory.
Relay `horizon.hedge_cost_note` rather than inventing forward points, a
carry figure, or a "typical" hedging cost. Never state a forward rate.

Examples:
  fx_volatility("EUR", "USD")
  fx_volatility("USD", "TRY")
  fx_volatility("GBP", "JPY", 506)   # exposure running to end-2027
  fx_volatility("EUR", "PLN", 90)    # invoice settling in a quarter
- `goods_classify`: Classify goods for export control from a description (or HS code).

Bilingual (English / Russian, auto-detected) goods classifier. Returns the
best-matching HS code (with EN+RU descriptions), related ECCNs, control
reasons (NS, MT, NP, CB, AT...), an export-control level (high/medium/low/
none), a confidence score, and alternative matches for review.

This is destination-agnostic — it identifies WHAT the goods are and whether
they are controlled in principle. To get the license decision FOR A SPECIFIC
destination, pass the result into export_controls_screen.

IMPORTANT, the matcher is lexical, and confidence scores the strength of
the string match, not the correctness of the classification: "equipment"
returns semiconductor manufacturing equipment at confidence 1.0. Treat the
code as a suggestion for narrowing the question. When no hs_code was
supplied the result carries classification_basis and
classification_confidence_note; read them before quoting any code, and ask
the user for the HS code or ECCN on their shipping documentation.

Examples:
  goods_classify("uranium centrifuge")                 # → HS 840120, ECCN 0B001
  goods_classify("центрифуга для обогащения урана")    # Russian query, same result
  goods_classify("semiconductor manufacturing equipment")
  goods_classify("", hs_code="840120")                 # direct HS lookup

Use case: 'Is a semiconductor lithography machine export-controlled?'
- `gpi_status_codes`: Explain SWIFT GPI tracking status codes and provide stuck-payment investigation guidance.

USE THIS TOOL FIRST whenever the user reports a payment that is stuck,
delayed, not arriving, held, pending, rejected, or otherwise not
behaving as expected. It is the primary diagnostic entrypoint for
payment investigation — calling with a specific code returns a
full investigation playbook (common delay causes, recommended
actions, GPI SLA timeframes, escalation steps).

Recommended calls by scenario:
  - Payment "stuck" / "in progress" / "pending" / "not arrived":
      gpi_status_codes("ACSP")   → playbook for in-progress payments
  - Payment explicitly "on hold" / compliance review:
      gpi_status_codes("PDNG")   → playbook for held payments
  - Payment "blocked" / sanctions flag:
      gpi_status_codes("BLCK")   → playbook for blocked payments
  - Payment rejected by a bank in the chain (never credited):
      gpi_status_codes("RJCT")   → rejection investigation playbook
  - Payment returned to sender (accepted then sent back):
      gpi_status_codes("RTRN")   → return investigation playbook
  - Reference for ISO 20022 codes:
      gpi_status_codes()         → list all codes

Each code call returns:
  - Code description and meaning
  - For ACSP/PDNG/BLCK/RJCT/RTRN: investigation playbook with common
    causes, recommended actions (request gCCT tracker, request
    pacs.002/pacs.004 reason code, verify beneficiary details,
    escalate via MT199, etc.), and common ISO 20022 reason codes
    (AC01, AC04, AG01, RR01-RR04, etc.) when applicable
  - Child reason codes (e.g., G001-G004 for ACSP) that narrow the
    cause further

Common codes: ACCC (success), ACSP (in progress), RJCT (rejected),
PDNG (on hold), BLCK (blocked). GPI reason codes (G000-G004) qualify
ACSP with more detail (e.g. G001 = passed on to a bank that is not a
gpi member, G002 = credit pending, may not be same day).

Examples:
  gpi_status_codes("ACSP")   # stuck-payment diagnostic playbook
  gpi_status_codes("G001")   # detail on a specific reason code
  gpi_status_codes()         # full reference list
- `hs_code_lookup`: Reverse-lookup an HS code → mapped export-control classifications (ECCNs).

For customs brokers / shippers who have an HS (Harmonized System) code and
need to know which export-control classifications may apply. Returns the
mapped ECCNs with confidence levels, control reasons, sensitivity, and the
governing international regime (Wassenaar, MTCR, NSG, etc.).

A 4-digit HS heading is accepted, but mappings are richest at the 6-digit
subheading level (e.g. "854231" rather than "8542"). An empty mapping list
means no export-control mapping is on file for that code — it is NOT a
guarantee the goods are uncontrolled; confirm with goods_classify or a
formal classification.

Examples:
  hs_code_lookup("854231")          # semiconductors → 3A001 / 3A090 ...
  hs_code_lookup("841112")          # turbojet engines → 9A001 ...
  hs_code_lookup("854231", "US")    # US mappings only

Use case: 'What export controls might apply to HS code 854231?'
- `iban_validate`: Validate an IBAN and identify the institution that holds the account.

Performs format check, country-specific length check, and
ISO 7064 mod-97 checksum verification. Also returns COUNTRY-level
banking rules for the IBAN's country prefix (national currency,
SEPA status, expected format).

CROSS-CHECKS THE BENEFICIARY BANK. Where the country's IBAN registry
mask defines the bank identifier as four alpha characters (GB, NL, IE,
RO, PK, MT, JO, QA, KW and others), `bank_identifier.resolved_institution`
names the institution that actually holds the account, read out of the
IBAN itself. Call this whenever the user supplies an IBAN AND names a
beneficiary bank or BIC — if the two disagree, that mismatch is a far
better explanation for a rejected or returned payment than anything you
can infer, and it is invisible without this call. Everywhere else the
bank identifier is a NATIONAL bank/sort code (the German BLZ, Italian
ABI, French code banque): `bank_identifier.code` is those characters,
`is_bic_prefix` is false, and no institution is named — quote the code
when telling the user what to check, never a bank name derived from it.

A FAILURE IS NOT ALWAYS A CHECKSUM FAILURE. An IBAN of the right length
whose characters break the country's mask (a letter where the country
requires a digit — O for 0, I for 1) is refused BEFORE mod-97 runs, with
`format_violations` naming the position. Report the position and the
character; do not tell the user the check digits are wrong.

`valid: true` means the check digits are right and NOTHING MORE — not
that the account exists, is open, or belongs to the named beneficiary or
the named bank. Never rule out the account details on the strength of it
when diagnosing a failed payment (see `verification_note`).

An IBAN encodes country + bank + account number and carries NO
currency information. `country_currency` is the country's national
currency, NOT this account's denomination — never infer a currency
mismatch or a "resend in X" recommendation from it (see
`currency_note` in the response).

Examples:
  iban_validate("DE89370400440532013000")
  iban_validate("GB29 NWBK 6016 1331 9268 19")
- `is_business_day_check`: Check if a specific date is a business day in a country.

Accounts for weekends (country-specific) and public holidays.
Returns whether the date is a business day, and if not, why
(weekend or specific holiday name) and the next business day.

The response carries a `today` block with the server's real current date.
Resolve any relative date in the user's question ("the 20th", "next
Friday") against that, not against your own sense of today. A check_date
already in the past also returns `date_anchor_warning` — heed it: a
mis-resolved year flips the answer outright (2025-07-20 is a Sunday,
2026-08-20 is a Thursday).

Examples:
  is_business_day_check("US", "2026-12-25")
  is_business_day_check("DE", "2026-03-12")
  is_business_day_check("GB", "2026-01-01")
- `mcp_register`: Register for an Ohmyfin API key to use paid tools.

Creates an account and sends a 6-digit verification code to your
email. After receiving the code, call mcp_verify to complete
registration and get your API key.

By setting accept_terms to true, you confirm acceptance of the
Ohmyfin Terms & Conditions (https://ohmyfin.ai/terms) on behalf
of your operator, including the API/MCP access terms (Section 3A),
sanctions screening terms (Section 3B), and financial data
disclaimer (Section 3C).

Examples:
  mcp_register("agent@example.com", "Acme Corp", true)
- `mcp_verify`: Verify your email and receive your API key.

After calling mcp_register, check your email for the 6-digit code
and pass it here. On success, returns your production and test
API keys. You must subscribe at ohmyfin.ai/subscription to
activate paid tools.

Examples:
  mcp_verify("agent@example.com", "123456")
- `payment_cutoff_times`: Get payment system cutoff times for major clearing systems.

Covers RTGS (T2 — formerly TARGET2, CHAPS, Fedwire, BOJ-NET, SIC),
net settlement (CHIPS, BACS), SEPA schemes (SCT, SCT Inst, OCT Inst,
SDD Core, SDD B2B), FX settlement (CLS, FXYCS), and other systems
(CIPS, SPEI, FAST).

For same-day EUR guidance: filter by currency="EUR" to retrieve all
SEPA schemes plus T2 in one call — the scheme-level view is usually
what treasurers need. Underlying CSMs (TIPS, RT1, EURO1, STEP2) are
referenced in scheme notes.

DST-observing systems also carry `season_now` and `operative_cutoff_today`
fields computed for the current date. cutoff_utc/cutoff_local are the
STANDARD-TIME (winter) values; summer_offset holds the DST value. Quote the
cutoff that `operative_cutoff_today` points at for TODAY's season — do not
default to the winter figure when DST is currently in force (e.g. the T2
customer cutoff is 15:00 UTC in summer, not the 16:00 UTC winter value).

Examples:
  payment_cutoff_times(system="T2")
  payment_cutoff_times(currency="EUR")
  payment_cutoff_times(currency="USD")
  payment_cutoff_times()
- `payment_method_compare`: Compare payment methods and investigate fee deductions for a country pair.

Evaluates SEPA vs SWIFT vs domestic options. Also explains SWIFT charge
options (OUR/SHA/BEN) and fee investigation — use this when the beneficiary
received less than expected to understand where the money went and which
MT103 fields reveal each deduction. Returns cost, speed, requirements,
charge options, and step-by-step fee investigation guidance.

Examples:
  payment_method_compare("DE", "FR")   # Both SEPA — will recommend SCT
  payment_method_compare("US", "TR")   # Non-SEPA — will recommend SWIFT
  payment_method_compare("GB", "GB")   # Domestic — will show CHAPS/FPS
  payment_method_compare("US", "VN")   # Fee investigation — why beneficiary got less
- `sanctions_screen`: Screen a name against global sanctions and watchlists.

Checks the name against 300+ sanctions, designation and watchlists
worldwide, including US OFAC (SDN and non-SDN), EU, UK OFSI, Canada,
Switzerland, Australia, New Zealand, Japan, Israel and national lists.
Returns matching entities with similarity scores. The response says how
many lists were actually searched (lists_searched); report THAT, and do not
present a fixed per-jurisdiction table of "clear" rows, which asserts a
per-list result the screen does not return and understates the coverage.

For a company or an individual the screen covers every list, including
adverse media, PEP and debarment registers. For a BANK or other financial
institution it returns sanctions DESIGNATIONS only: a warning-list entry
naming a bank is usually a clone-firm alert about fraudsters impersonating
it, and the feed carries nothing that tells the two apart.

When the name resolves in our bank directory, each designation is also
cross-referenced against that institution's record (country, entity type,
and the name or ALIAS that earned the fuzzy score) and contradicted rows are
removed. ALWAYS read the `verification` block, which is on every response:
`applied: false` means nothing was cross-referenced and the rows are raw
feed output — either the name is not in our bank directory, or the subject
is a company or individual, which has no directory record to check against.
Never report an `applied: false` result as verified, and never report an
empty one as verified-clear. Screening a bank by BIC, or calling
swift_lookup, gets a verified answer.

A row surviving that cross-reference is NOT the same as a row the
cross-reference supported. Each verified row carries `adjudication`:
`corroborated` means the check backed it and it is a designation against
this institution; `not_corroborated` means it survived the false-positive
floor but nothing tied it to this institution — typically
`country_conflict: true`, the designated entity being domiciled elsewhere.
`is_false_positive: false` is only that floor test and is never a finding;
read `adjudication` instead. When NOTHING is corroborated the response
carries `verification_gate.applied: true` and `recommended_action` has been
lowered from BLOCK to REVIEW: report a possible match needing identity
confirmation, do not reinstate BLOCK from the row-level `action` fields,
and do not report the institution as clear either — every row is still in
`matches` and the open question is which legal entity the counterparty is.

`verification_gate` is on every bank response whose verdict asserts a
finding (BLOCK, REVIEW, MONITOR or INFORM), and it asks one question: does
that verdict survive the evidence this payload actually publishes. A
REVIEW, MONITOR or INFORM over an EMPTY `matches` list is not a weaker
BLOCK. This screen publishes designations only for a financial institution,
so the rows that carried such a verdict are the non-designation ones
(warning lists, adverse media, PEP, debarment) it withholds — counted in
`non_designation_rows_withheld` and never listed. Those responses come back
`recommended_action: CLEAR` with `verification_gate.applied: true`, meaning
no DESIGNATION matched. Say exactly that, and keep the scope with it: it is
never a clean bill across every list. The gate stands down, and the verdict
stays, when our own verification removed a designation as a false positive
(`designations_filtered_as_false_positives`), when the directory record is
not clean, or when the row that set the verdict was off-page.

On an unverified response every row also carries `query_match`, listing
which of the screened words appear in that row's own name or aliases and
which do not. Nothing is removed on account of it. Weigh it against the
score: a row sharing one word out of four with the query is usually a
different entity, and its action is that entity's action, not a verdict on
the party screened. Absence is not proof — non-Latin aliases contribute no
words, and a transliterated designation of the right party can show words
missing — but where every query word is present, take the row at face value.
Present words a row holds only inside a web address, e-mail address, @handle
or labelled channel are named again in `query_words_only_in_contact_details`:
they name an address or channel of the listed party, not the listed party,
so that row is not taken at face value on their account.

The response also carries `coverage_gate`, set by the screening engine: how
much of the name you screened actually appears on the rows that are
blocking. When `applied` is true the payload-level `recommended_action` was
lowered from BLOCK to REVIEW, because no blocking row carries more than
half of the distinctive words you screened. Report a possible match that
needs identity confirmation, NOT a designation, and do not reinstate BLOCK
from the row-level `action` fields. Nothing was removed: every row the feed
returned is still in `matches`, with its own score and action intact. When
`applied` is false the verdict stands and `reason` says why. A `reason` of
`identifier_query` means the name screened was a registration or tax
number that matched a designation's own identifier: that is a match on
identity, not on wording, so report the BLOCK as it stands.

Examples:
  sanctions_screen("Acme Trading Ltd")
  sanctions_screen("John Smith", threshold=90)
  sanctions_screen("First Abu Dhabi Bank", subject_type="bank")
  sanctions_screen("Acme Trading Ltd", api_key="prod-abc123...")
- `settlement_eta`: BETA. Estimate when a SWIFT payment will arrive: a corpus-grounded
arrival window with an honest tail, computed from real completed payments
we have tracked, projected onto the currency's banking calendar.

This estimator is in BETA and still calibrating. Say so when you present a
number: call it an estimate or a typical window, never a commitment, and
never let a user plan an irreversible decision (a cutoff, a contractual
settlement date) on it without that caveat. The payload carries beta=true
while this holds.

Two modes:
- Forward (default): "when will it land" — returns P50/P90/P95 arrival
  dates, sample size, confidence, competing non-arrival risk, delay-risk
  factors, and (where validated) the most likely correspondent route.
- Reverse: pass arrive_by_date (YYYY-MM-DD) — returns the latest send
  date such that arrival by that day is likely ("send by Thursday to
  land by month-end").

INPUT DISCIPLINE (important):
- Mid-flight payment: pass ONLY the uetr (from TrackingContext or
  track_payment). The server resolves the current status, currency and
  elapsed time deterministically from the tracking record. NEVER compute
  elapsed_business_days yourself.
- Pre-trade question ("how long will a USD wire from X to Y take?"):
  pass currency + sender_bic/receiver_bic (8 or 11 chars, or bank names).
  current_status / elapsed_business_days are for this path only.

Reading the answer honestly (relay these to the user):
- basis.n is the sample size and confidence reflects it; when confidence
  is "low", present the window as a rough range, never a promise.
- basis.level says whose payments basis.n counts: `corridor` this bank
  pair, `sender_ccy` / `receiver_ccy` this sending / receiving bank in
  this currency, `currency` every bank in this currency, `global` every
  tracked payment in every currency. basis.currency is the PAYMENT's
  currency, not the pool's: a global answer is never "<currency>
  payments". basis.pool says it in words where it matters.
- basis.refined_by names a thin cohort blended into that pool;
  basis.n is the pool's size, not its. The return rate can be that
  cohort's own (non_arrival.based_on): never attach basis.n to it.
- route.confirmed=false means the route is INFERRED from settlement
  instructions on file, not confirmed by GPI — say so.
- basis.route_adjusted=true means we hold no completed payments for this
  exact pair and the window was lifted to a route-composed estimate:
  the SSI-implied correspondent chain (route.intermediaries hops) with
  typical processing time per hop. Present it as a route-based estimate,
  not as observed statistics, and never quote the faster currency-pool
  average alongside it as if corridor-specific.
- mode="outlier" means the payment is already slower than ~90% of similar
  payments: stop quoting a window, explain the usual manual causes
  (compliance review, repair/RFI, missing cover) and pivot to the
  stuck-payment diagnostic flow.
- non_arrival.p_reject is the share of similar payments that were
  returned or rejected rather than delivered; read non_arrival.based_on
  when it is there.
- "Delivered" (ACCC) means delivered to the beneficiary bank per GPI;
  funds can become usable in the account slightly later.

Available on every surface to any caller with an active subscription.
- `ssi_lookup`: Look up correspondent banking / settlement instructions (SSI) for a bank.

Returns the correspondent banks (nostro accounts) that a given bank
uses to settle payments in a specific currency, including account
numbers (when available) and intermediary chains. Essential for
payment routing and pre-validation.

Each correspondent is annotated with a clearing_note indicating
whether it can clear the currency directly (located in a home
country for that currency) or needs its own correspondent.
If the note suggests a further lookup, call ssi_lookup on the
correspondent's SWIFT code to find the full clearing chain.

IMPORTANT — known data gaps to respect:
- Account numbers may be empty for some/all correspondents. The
  response surfaces an `account_availability_note` in those cases.
  Do NOT invent account numbers. Use swift_lookup() to find the
  bank's own published correspondent banks page when accounts
  are missing.
- WHICH CORRESPONDENT: read `correspondent_selection`, not the array order.
  It gives the COMPLETE set of BICs for each flow (customer MT103 vs
  interbank MT202) and for whether the correspondent clears the currency
  itself, each with a count. The correspondents are returned in STORAGE
  order, which is not a ranking. Naming a subset ("principally X and Y",
  "route via X") invents a preference this feed does not hold. Name every
  BIC in the matching set, or give its count: which one is used is the
  SENDING bank's choice among the ones it can already reach, not the
  beneficiary bank's, and not ours to guess from bank size or reputation.
  `is_preferred` is `null` on almost every row because the flag is
  genuinely unrecorded (11 rows in the entire corpus carry it); where it IS
  set, `correspondent_selection.bank_flagged_preference` names it and you
  should lead with that.
- `intermediaries` is `null` — not `[]` — when this correspondent's onward
  chain is not recorded, which is 98% of rows. `null` means NOT ESTABLISHED,
  never zero hops: do not read it as a direct chain and do not count it.
  Whether a further hop is needed is answered by each correspondent's
  `clearing_note`, which says either that it clears the currency itself or
  that a further hop is expected and gives the ssi_lookup call that resolves
  it. When the chain IS recorded the array is populated and
  `intermediaries_note` names the hops in order.
- Asset category per correspondent is COMMERCIAL (for customer
  MT103 credit transfers) or FINANCIAL (for bank-own-account /
  interbank MT202/pacs.009 settlements). Read `asset_categories`
  (the full list) rather than the single `asset_category`, which
  shows the commercial view only: one entry is one BIC+account and
  the same account is often published under BOTH categories, so the
  single field can never establish what an account may NOT be used
  for. The `asset_category_note` summarises the split — match the
  listed correspondents to the user's flow type (customer payment
  vs treasury/interbank).
- If `correspondents` is EMPTY, we have no SSI on file for that
  bank/currency. The response carries a `no_ssi_note` (no SSI in any
  currency) or `requested_currency_unavailable_note` (SSI on file for
  other currencies only). This is a coverage gap, NOT a finding that the
  bank has no correspondents. Do NOT name a correspondent for the missing
  currency from training data — surface the `published_ssi_document` /
  the bank's website and tell the user to confirm SSI with the bank.

Always inspect the response's top-level `next_steps` array — it
chains the swift_lookup / country_banking_rules / bank_holidays
calls that complete a settlement-instruction answer.

COVERAGE IS KNOWABLE BEFORE YOU CALL, AND FOR FREE. swift_lookup returns a
`settlement_instructions` block on every bank: `on_file: true` with a
currency count means this lookup will answer, `false` means we hold none in
any currency, null means it has not been established. Do not treat "might be
a coverage gap" as a reason to skip the call and describe the routing from
memory — ask swift_lookup, then read the answer here.

Requires an API key with an active FI subscription.
To get started: call mcp_register → mcp_verify → subscribe to
an FI plan at https://ohmyfin.ai/subscription.

Examples:
  ssi_lookup("DEUTDEFF", "USD")                  # Deutsche Bank USD correspondents
  ssi_lookup("HSBCHKHH", "EUR")                  # HSBC HK EUR correspondents
  ssi_lookup("DEUTDEFF", "USD", api_key="prod-abc123...")
- `swift_lookup`: Search banks and financial institutions by name, SWIFT/BIC code, or country.

Covers both SWIFT-connected banks and non-SWIFT financial institutions
(e-money issuers, payment processors, MFOs, brokerages, VASPs, etc.).

Returns: SWIFT/BIC code (if any), name, city, country, institution type,
GPI membership, a coarse sanctions FLAG across 7 hard-sanctions watchlists
(OFAC SDN, EU, UK, CA, CH, AU, NZ — see sanctions_note; this is NOT a full
screen, use sanctions_screen for a compliance verdict), and enriched bank
profile when available.

EVERY BANK COMES BACK SAYING WHETHER WE HOLD ITS CORRESPONDENT CHAIN.
Read `settlement_instructions` on each bank: `on_file: true` with a
`currencies_on_file` count means we hold that BIC8's actual correspondent
BIC, nostro account number and national clearing ID, and `read_with` is the
exact ssi_lookup call that returns them. `on_file: false` means we hold none
in any currency — a gap in our data, not a finding about the bank. A null is
"not established yet" and is neither. This is the answer to "which
intermediary bank do I put on the instruction?", and it is a fact we either
have or do not have — never one to recall from training data.

A BIC IDENTIFIES AN OFFICE, NOT A BRAND, AND THE DIFFERENCE IS PRICED.
A name search returns ONE representative office per bank, elected by BIC
convention rather than by relevance to the payment, and `office_note` says
so whenever the bank holds more than one. Published tariffs, correspondent
chains and settlement instructions are filed per BIC, so the choice changes
the answer: transfer_cost("COBADEFF") returns Commerzbank's published 0.15%
sending fee and transfer_cost("COBADEBB") refuses for want of a filed
tariff, and both of those are Commerzbank AG in Germany. So:
  - If the user named a CITY, put it in the query — "Commerzbank Frankfurt"
    resolves to the Frankfurt office, and the plain name cannot.
  - If they did not, ask which BIC is on their statement or payment
    instruction before pricing or routing, and say which office you used.
  - Never present a representative office's BIC as "the bank's BIC".

Examples:
  swift_lookup("DEUTDEFF")              # exact BIC lookup
  swift_lookup("Deutsche Bank")         # search by name
  swift_lookup("Commerzbank Frankfurt") # bank + city -> that office's BIC
  swift_lookup("TBC PAY")              # find non-SWIFT payment processor
  swift_lookup("bank", country="KZ")    # explore banks in a country
  swift_lookup("Halyk", country="KZ")   # find specific bank in country
  swift_lookup("Bank Mandiri", country="Indonesia")  # full country name OK
- `swift_message_reference`: Look up SWIFT message types — MT (FIN) and MX (ISO 20022).

Pass a specific type to get full details, or omit to list all types.
Covers customer payments (MT103, pacs.008), FI transfers (MT202,
pacs.009), trade finance (MT700, MT760), cash management (MT940,
camt.053), and payment status (pacs.002).

Also use this tool to answer questions about where specific payment
fields live — e.g., where the UETR sits in an MT103 (Field 121, Block 3
header), where charges appear (71A/71F/71G), or which fields carry
routing info (56/57). MT103 and pacs.008 responses include a
`tracing_note` explaining UETR recovery for customers who only have
a reference number.

Examples:
  swift_message_reference("MT103")
  swift_message_reference("pacs.008")
  swift_message_reference()
- `tracking_history`: Show how a SWIFT payment's tracking results changed over time.

Returns the DISTINCT tracking results recorded for a payment (by UETR or
reference), deduplicated so ten identical re-tracks collapse to one entry
while any change — a new last-update, a status change, or new bank data —
appears as its own entry. Each entry includes what was ENTERED when the
search was run (amount, currency, date) alongside the banks that reported
data and their confirmed amount / value date.

WHEN TO USE THIS:
  - The user says the page shows different data than you see, or asks why a
    bank line (e.g. JP Morgan) "disappeared" or a value date differs.
  - You need to reconcile an amount or date discrepancy. JP Morgan returns
    its line only when the entered date and amount are close to its own
    record (seen: value dates up to 3 days apart, amounts mostly within 1%),
    so a search run with a wrong date or amount can silently drop its line.
    When it does answer, `amount_exact_match` is false if its value date OR
    its amount differs from what was entered, despite the name, and
    `amount_exact_match_detail` names which. Comparing entries here — same
    UETR, different entered dates or amounts, different bank data — is how
    you spot which entry was the problem.
  - Before concluding "the record was consolidated" or "the bank stopped
    reporting", check the history: the earlier result you're being asked
    about is usually still here, under a different entered amount.

Each bank entry's `source` names the tracking SOURCE that reported it (e.g.
"Standard Chartered"), not the bank at a step of the payment; track_payment
names the same data by the bank at each step, so the two names differ.

Only results for the current user (plus system tracks with no owner) are
returned; other users' searches of the same UETR are never shown.

Requires an API key with an active FI subscription.
- `track_payment`: Track a SWIFT payment by UETR or reference number.

Basic SWIFT payment tracking enriched by data from certain banks in
the correspondent chain. Returns the overall payment status and,
when available, per-bank details showing which banks reported
information about this payment.

IMPORTANT — every trace needs four things:
  amount, currency, date, and an identifier (uetr, or reference when
  there is no UETR). amount, currency and date are required
  parameters, on the UETR path too: there is no UETR-only lookup, so
  never tell the user the UETR alone is enough to run one. Ask for
  whatever is missing before calling, and never guess a value.

IMPORTANT — UETR vs Reference:
  The UETR (Unique End-to-End Transaction Reference) is a UUID
  assigned to every SWIFT gpi payment. Tracking by UETR succeeds
  ~80% of the time. Tracking by reference number alone succeeds
  less than 1% of the time because most banks only index by UETR.

  → Always provide the UETR if available.
  → The reference number is Field 20 of the MT103 (or the
    equivalent <InstrId>/<EndToEndId> in pacs.008). It is the
    sender's transaction reference. Still valuable — provide it
    alongside the UETR when you have both.

WHEN THE USER HAS ONLY A REFERENCE AND NO UETR
("how do I find / trace my payment?", "I have a reference number
but no UETR, where is it?"):
  This is exactly the scenario this tool can attempt — do NOT answer
  from general knowledge. A reference-based trace cannot be run from
  the reference alone; you MUST first collect three things from the user:
    1. amount   — the exact amount as sent
    2. currency — ISO 4217 (e.g. "USD")
    3. date     — the send date (within the last 90 days)
  Then call track_payment(reference=..., amount=..., currency=...,
  date=...). State the expectation up front: reference-only tracing
  succeeds less than 1% of the time.
  In parallel, tell the user how to recover the UETR for a reliable
  (~80%) trace: ask the SENDING bank for the MT103 confirmation — the
  UETR is in Block 3, tag {121:} (a UUID v4), stored by every
  gpi-enabled bank against the payment. Re-run with uetr= once they
  have it. (swift_message_reference("MT103") returns the full
  field/UETR-recovery reference if you need to cite specifics.)

IMPORTANT — Interpreting bank details:
  Each entry in the 'details' array represents a bank that reported
  data about this payment. The bank could be the SENDER, the
  BENEFICIARY, or ANY INTERMEDIARY/CORRESPONDENT in the chain.
  Do NOT assume a bank is an intermediary just because it appears
  in the list — we only know the payment passed through that bank.
  The bank's role is only known when it self-reports via push API
  (indicated by a non-null 'role' field).

Requires an API key with an active FI subscription.
To get started: call mcp_register → mcp_verify → subscribe to
an FI plan at https://ohmyfin.ai/subscription.

Returns a dict with:
    status: Overall payment status — one of:
        "success"     — delivered to the beneficiary when the gpi code
                        ACCC backs it; otherwise one bank reported its
                        own leg completed (status_explanation says which)
        "in progress" — payment is being processed (may update)
        "returned"    — payment was canceled/returned after processing (final)
        "rejected"    — payment was refused (final)
        "on hold"     — temporarily held, e.g. compliance review
        "future"      — scheduled for a future value date
        "unknown"     — no tracking data available yet
    status_raw: ISO 20022 status code (ACCC/ACSP/RJCT/PDNG) or null
    status_reason: ISO 20022 reason code at PAYMENT level, or null. null
             is common and does NOT mean no reason code was reported —
             most feeds report the qualifier per bank instead, see below.
    reason_codes_reported_by_banks: Present whenever any bank line reports
             a "STATUS/REASON" qualifier (ACSP/G003, RJCT/MS03). Each entry
             is decoded to its name and meaning. This is what answers "is
             anything pending / held / rejected / flagged on my payment",
             not status_reason.
    lastupdate: Date of last status change (YYYY-MM-DD) or null
    details: Array of bank-level tracking entries (see role_explanation
             in each entry for how to interpret the bank's role)
    not_found_guidance: Present only when nothing was found — concrete
             next steps (UETR recovery, exact-match checks). Relay these
             to the user instead of improvising; a miss on a
             reference-only trace is the expected outcome and does NOT
             mean the payment failed.

Examples:
  track_payment(uetr="eb6305c8-0710-4e41-84ad-f58db3083e82",
                amount=15000, currency="USD", date="2026-03-10")
  track_payment(uetr="eb6305c8-0710-4e41-84ad-f58db3083e82",
                reference="FT2603100123",
                amount=15000, currency="USD", date="2026-03-10")
  track_payment(reference="FT2603100123",
                amount=5000, currency="EUR", date="12.03.2026")
- `transfer_cost`: BETA. Estimate what a cross-border payment will COST, split by WHO
PAYS: the sending bank's published fee (the sender's side), what
correspondents deduct in transit and what the beneficiary's own bank
charges to credit it (the beneficiary's side), and what actually lands.

This estimator is in BETA. Present every number as a typical case and a
high case, never as a quote, and never let a user commit to a contractual
amount on it. The payload carries beta=true while this holds.

HOW TO READ THE ANSWER (relay these honestly):
- `answered=false` means we REFUSED. The most common reason is that we
  hold no published tariff rule for the sending bank, in which case there
  is deliberately no total and no "recipient receives" figure. Say we do
  not know what that bank charges. Do NOT add up the parts yourself and
  present a total: treating the unknown fee as zero is the exact defect
  this tool was built to remove.
- The correspondent fee is a RANGE (`p50` typical, `p90` high case), not a
  point. The spread is real: SWIFT tracking never reveals whether a
  payment was sent OUR, SHA or BEN, so a single cohort mixes all three.
- THREE FEES, THREE DIFFERENT PAYERS, AND THEY ARE NOT INTERCHANGEABLE.
  `sending_fee` is billed to the SENDER by their own bank.
  `correspondent_fee` comes out of the payment in transit, so the
  BENEFICIARY bears it. `beneficiary_fee` is what the RECEIVING bank
  charges its own customer to credit the payment, so the beneficiary bears
  that too - and it is frequently the largest of the three (measured
  2026-08-22 on one live corridor: 35.26 USD of sender-side cost against a
  245.68 USD beneficiary bank fee). Never quote one of them as "the cost",
  and never call the beneficiary bank's fee a correspondent charge.
  `total` is the sending fee plus the transit deduction; `total_both_sides`
  adds the beneficiary bank's fee and is the all-in figure.
- WHERE EACH NUMBER COMES FROM. The correspondent fee is OBSERVED, from
  payments we have tracked. `beneficiary_fee.source` is `tariff` (or
  `tariff_fallback`, see below) and never `observed`: a beneficiary bank
  deducts after the last bank that reports to GPI, so no tracking data can
  see it, and we read it off that bank's published incoming tariff
  instead. Say which is which when the user leans on a figure.
- `beneficiary_fee.known=false` means WE HOLD NO INCOMING TARIFF for that
  bank (we hold one for roughly two thirds of beneficiary banks). Its
  charge is then missing from every figure, `total_both_sides` is null,
  and `recipient_receives.typical` is an UPPER BOUND -
  `recipient_receives.beneficiary_fee_known` says so. Do not fill that gap
  with a zero, a guess or a typical figure; say the receiving bank's own
  charge is not included and point the user at that bank's tariff.
- `beneficiary_fee.applies=false` under OUR / OUR-OUR: the instruction says
  the sender covers every downstream charge, so the bank claims it back
  rather than taking it off the credit. The figure is reported but NOT
  subtracted. Our data ends before the account is credited, so we can
  neither confirm nor refute that it was honoured on a given payment.
- `beneficiary_fee.segment` says which of the bank's incoming price lists
  was read. `segment_fallback=true` means the account type asked for had
  no usable schedule so the other one answered - which can only happen
  when `beneficiary_segment` was NOT supplied, i.e. when we were assuming
  the beneficiary matches the sender. Say that you assumed it.
- `beneficiary_fee.reason='other_segment_only'` means you DID supply
  `beneficiary_segment`, and that bank publishes an incoming tariff for
  the other account type only (`beneficiary_fee.other_segment` names it).
  We decline to quote it. Do NOT report this as "we hold no tariff for
  that bank": we hold one, for a different kind of account. Tell the user
  which, because it is often the useful half of the answer.
- `basis.n` is how many observed payments back the correspondent figure and
  `confidence` reflects it. At "low", present the range as rough.
- `basis.level` says how specific the evidence is: `corridor` is this
  correspondent into this destination country, `correspondent` is that
  bank overall, and `currency` or `global` mean we hold nothing specific
  and are quoting a pool. Say so when it is a pool.
- ON A REFUSAL `basis` IS NULL, and the same two figures are still on each
  entry of `correspondent_fee.legs[]` as `level` and `n`. Read them there.
  Do not read a missing `basis` as corridor-specific evidence: on a
  measured DE->AM screen the legs said `level: "currency", n: 146`, a
  currency-wide pool, and the answer described it as a single well-priced
  hop because the top-level key was absent.
- `assumptions` is a list of plain sentences explaining what shaped the
  number (SEPA, OUR honoured, PSD2, a modelled BEN uplift, a stale
  tariff). Relay the ones that matter to the user's question.
- under OUR the correspondent leg carries `our_breach`: the measured share
  of OUR payments that lose a charge in transit anyway, and what that
  costs. p50 is 0 and p90 is that loss. Quote BOTH - "the beneficiary
  should receive the full amount, and in about 7% of the OUR payments we
  can follow end to end they do not" - never the p50 alone as a promise.
- `chain.status` = `no_chain` means the pair settles on local rails (SEPA,
  domestic, same banking group) with NO correspondent deduction at all.

IMPORTANT ON CHARGE TYPE: charge_type is an INPUT and is never inferred
from tracking. OUR is a real instruction and usually holds - of 150
payments whose own MT103 declared OUR and which we could follow from the
instructed amount to the settled one, 139 reached the beneficiary intact,
against 6 of 52 under SHA. It is NOT a guarantee: the other 11 lost a flat
correspondent charge in transit, and we find no evidence that this depends
on the destination country or on a US correspondent being in the chain, so
do not tell a user that OUR is safe everywhere except the US. BEN is
materially more expensive than SHA and our high case models it rather than
measuring it. If the user has not said which they will use, ask, or state
which one you assumed.

Pass `beneficiary_bic` whenever the user knows the receiving bank: without
it there is no correspondent chain to price and no beneficiary bank to
read a tariff from, so the answer is the sending fee alone and no total.

`customer_segment` selects which side of the SENDING bank's published price
list is read. It is not cosmetic: of 30 banks publishing both schedules, 9
of the 17 that answered on both quote a different fee, one of them 220 PLN
for a company against free for a person. It defaults to `individual` here;
pass `business` when the payer is a company, and say which you assumed.

`beneficiary_segment` does the same for the RECEIVING side, which is a
different bank's price list and not a restatement of the sender's. Of 120
banks publishing both schedules, 28 quote a different incoming fee
(measured 2026-08-24), and it runs both ways: Hipotekarna banka (HBBAMEPG)
credits a 100,000 EUR payment free of charge to a company and takes 0.1%
of it from a person, while Nordea charges a person 60 SEK and a company
250. Omit it and we assume the beneficiary matches the sender, which is
what this tool did before 2026-08-24 - so if you omit it, say you assumed
it. Supply it when the user has told you who is being paid, and prefer
asking over guessing when the amount makes the difference material.

Available to any caller with an active subscription.
- `value_date`: Calculate the value/settlement date for a payment.

Determines when a payment will settle based on:
- Source and destination country holiday calendars
- Weekend conventions (Sat/Sun or Fri/Sat)
- Currency center holidays (if FX conversion involved)
- Settlement convention (T+0, T+1, T+2)

Examples:
  value_date("US", "DE")
  value_date("US", "DE", "fx_spot", "USD", "EUR")
  value_date("DE", "FR", "sepa")
  value_date("US", "US", "wire", from_date="2026-07-03")
