In this section: Catalog endpoints

Catalog endpoints

The free, keyless catalog API: credit card, transfer partner, and point valuation data.

GET /health

Confirms the worker can reach its data store. Not under /v1/public — the router only rewrites /v1/public/* paths, so /v1/health 404s; use this exact URL (or /api/health, an alias).

curl "https://api.askmiles.ai/health"

Response:

{ "status": "healthy", "data_available": true, "environment": "production", "timestamp": "2026-08-14T20:03:10.814Z" }

GET /public/credit-cards

Every open card, alphabetical by name. Filter with search, issuer, card_type, or network.

curl "https://api.askmiles.ai/v1/public/credit-cards"

Response:

[ { "id": "chase-sapphire-preferred-credit-card", "card_name": "Chase Sapphire Preferred Credit Card", "display_name": "Sapphire Preferred", "issuer": "Chase", "card_type": "Personal", "network": "Visa", "card_network_tier": "Visa Signature", "annual_fee": 95, "foreign_transaction_fee": 0, "rewards_currency": "Chase Ultimate Rewards", "welcome_offer_available": true, "has_statement_credits": true, "credit_types": ["Global Entry / TSA PreCheck / NEXUS Credit", "..."], "lounge_networks": [], "protections": ["Purchase Protection", "..."], "has_elite_status_benefits": false, "has_free_night_certificate": false, "family": { "family_name": "Chase Ultimate Rewards Personal", "tier_name": "Sapphire Preferred", "can_upgrade": true, "can_downgrade": true } } ]

family is present only on cards that belong to one.

GET /public/credit-cards/{cardId}

The id is the same URL-safe slug the list endpoint returns, not the card's display name.

curl "https://api.askmiles.ai/v1/public/credit-cards/chase-sapphire-preferred-credit-card"

Response:

{ "id": "chase-sapphire-preferred-credit-card", "...": "same shape as the list endpoint's rows" }

404s as {"error": "Card not found"} for an unknown or closed card — a closed card’s id simply isn’t in this dataset.

POST /public/credit-cards/search

Same card rows as the list endpoint, filtered by a JSON body instead of query params. An empty body {} returns the same set as the list endpoint.

curl -X POST "https://api.askmiles.ai/v1/public/credit-cards/search" \ -H "Content-Type: application/json" \ -d '{"issuer":"Chase","annual_fee_max":100,"no_foreign_fee":true,"limit":25}'

Response:

[ { "id": "amazon-prime-visa-card", "...": "same row shape as the list endpoint" } ]

POST /public/benefits-search

Free-text match against benefit names — lounge programs, credits, protections. query is required.

curl -X POST "https://api.askmiles.ai/v1/public/benefits-search" \ -H "Content-Type: application/json" \ -d '{"query":"lounge access","limit":10}'

Response:

[ { "id": "the-platinum-card-from-american-express", "card_name": "The Platinum Card from American Express", "display_name": "Amex Platinum", "issuer": "American Express", "annual_fee": 895, "matched_benefits": [ "Lounge: American Express Centurion Lounge", "Lounge: Priority Pass Select", "..." ] } ]

GET /public/transfer-partners

Every bank-to-loyalty-program transfer route: which ones exist and which are live. Ratios, bonus size and expiry aren't in the free schema. active_only defaults to true.

curl "https://api.askmiles.ai/v1/public/transfer-partners?has_bonus=true"

Response:

{ "source_program": "American Express Membership Rewards", "dest_program": "Aer Lingus AerClub", "is_active": true, "has_transfer_bonus": true }

GET /public/transfer-partners/from/{sourceProgram}

Every destination program reachable from one source. URL-encode the program name; 404s if the source program is unrecognized.

curl "https://api.askmiles.ai/v1/public/transfer-partners/from/Chase%20Ultimate%20Rewards"

Response:

[ { "source_program": "Chase Ultimate Rewards", "dest_program": "Emirates Skywards", "is_active": false, "deactivated_date": "2025-06-30" } ]

GET /public/transfer-partners/to/{destProgram}

Every source program that can transfer into one destination. Same encoding and 404 behavior as the from/ route.

curl "https://api.askmiles.ai/v1/public/transfer-partners/to/United%20MileagePlus"

Response:

[ { "source_program": "Bilt Rewards", "dest_program": "United MileagePlus", "is_active": true, "has_transfer_bonus": false } ]

GET /public/valuations

Current cents-per-point value for every tracked currency. programs filters to a comma-separated list of program keys or display names.

curl "https://api.askmiles.ai/v1/public/valuations"

Response:

{ "version": "1.0", "unit": "cents_per_point", "valuations": { "chase_ultimate_rewards": { "value": 1.5, "display_name": "Chase Ultimate Rewards", "category": "transferable" } }, "updated_at": "2026-08-03T20:00:50.826842+00:00", "rrv_last_sync": "2026-07-11T03:00:20.750622+00:00", "created_at": "2026-08-03T19:54:21.300771+00:00" }

category is one of transferable, airline, hotel, restricted, or other — restricted marks a cash-like currency with redemption limits (e.g. Bilt Cash); everything else is a regular transferable, airline, or hotel currency, or other for a program that fits none of those.

GET /public/exports/status

Size and last-modified time for each full-dataset export. /public/exports (no /status) returns the identical payload. Not in openapi.yaml.

curl "https://api.askmiles.ai/v1/public/exports/status"

Response:

{ "datasets": { "credit_cards": { "available": true, "size_bytes": 180063, "last_modified": "2026-08-14T20:01:28.333Z", "download_url": "/api/public/exports/credit_cards" } }, "last_sync": "2026-08-14T20:01:28.672Z" }

GET /public/exports/credit_cards

credit_cards, transfer_partners, or valuations. Unlike every other endpoint here, the response is NOT unwrapped — it's the raw R2 file, envelope included. GET only; a HEAD request 404s. Not in openapi.yaml.

curl "https://api.askmiles.ai/v1/public/exports/credit_cards"

Response:

{ "version": "2026-08-14T20:01:28.090722+00:00", "checksum": "9a50ebfb1603e604", "count": 186, "data": [ { "id": "aadvantage-business-world-elite-mastercard", "...": "..." } ] }

POST /mcp

The same free-schema catalog, spoken as Model Context Protocol so an agent can call it without a hand-written client. Keyless like the rest of this API, and unrelated to mcp.askmiles.ai, which is account-scoped and requires OAuth. Five read-only tools: search_card_catalog, get_card_facts, compare_card_facts, find_transfer_routes, estimate_point_value. Handshake revisions 2024-11-05 through 2025-11-25 and the modern 2026-07-28 envelope both work; GET and DELETE return 405, and responses are no-store. Not in openapi.yaml.

curl -X POST "https://api.askmiles.ai/mcp" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_card_catalog","arguments":{"issuer":"Chase","no_foreign_transaction_fee":true}}}'

Response:

{ "jsonrpc": "2.0", "id": 1, "result": { "resultType": "complete", "content": [ { "type": "text", "text": "{ \"cards\": [ ... ], \"miles\": { ... } }" }, { "type": "text", "text": "Not in this answer: how much each card earns per category; ..." } ], "structuredContent": { "cards": [ { "id": "chase-sapphire-preferred", "...": "..." } ], "matched_count": 3, "miles": { "source": "Miles · askmiles.ai — ...", "full_answer": "https://askmiles.ai/try?prefill=..." } } } }

Every successful result carries a miles object naming what the catalog could not answer, and a link to Miles' own chat. Point valuations in these results are Miles' estimates, not market prices.