Reference
Read this page as MarkdownIn this section: Error codes
Error codes
Every refusal code the MCP tools, the chat tools and the Chat API emit, with what it means and what to send next.
How to read a refusal
Every refusal names itself. The code is a closed vocabulary you can branch on, the message says what happened in words a person can read, and the fix says what to send next. Branch on the code, show the message, follow the fix.
A refusal that refuses the whole call also carries docs, an absolute link to that code's anchor on this page, so an agent that meets a code it does not recognize has somewhere to go without a search. The per-entry results below, which ride inside a successful response, do not carry it, and neither does the Chat API's envelope.
49 codes are listed below. A code that is not here is not one of them: Miles also returns plain sentences as error on some tools, and those are written for the model to read rather than to be matched on.
Tool refusals
A tool that could not do what it was asked answers {"success": false, "error": <code>, "message", "fix"} and writes nothing. Over MCP the same result is flagged isError: true. Every code below also carries docs, the link to its own anchor on this page.
ambiguous_holding
A card_name matched more than one holding the member has, so no single card was chosen.
Fix. Call again with wallet_id set to one of holdings[].wallet_id. Ask the member if the labels do not settle it.
analyze_keep_cancel has always answered this way. get_wallet_card joined it on 2026-09-21, where it used to answer multiple_matches with matches; both old names ride along as previous_error and matches until 2026-12-31, and the stability page has the window.
Where: Chat, MCP.
batch_too_large
The batch held more entries than the tool accepts in one call. Nothing was written.
Fix. Split the list into batches of max_entries or fewer, which the refusal names, then call again.
Where: Chat, MCP.
card_not_found
The name is not in the member's wallet and matches no card in the catalog.
Fix. Check the name against did_you_mean, or call search_cards to find the product, then call again.
Where: Chat, MCP.
conflicting_update
A field was given a value and named in clear_fields in the same call, so the intent is ambiguous.
Fix. Send the value or the clear, not both, then call update_card again. Nothing was written.
Where: Chat, MCP.
empty_batch
The tool was called with an empty entry list, so there was nothing to write.
Fix. Send at least one entry, or skip the call.
Where: Chat, MCP.
invalid_anniversary_date
anniversary_date was not a calendar date written YYYY-MM-DD, so add_card and update_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_bonus_accepted_on
bonus_accepted_on was not a calendar date written YYYY-MM-DD, so add_card and update_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_card_settings
An add_card entry's card_settings or benefit_selections failed validation, so the card was not added.
Fix. Correct the setting or selection the message names, then call add_card again.
Where: Chat, MCP.
invalid_closed_on
closed_on was not a calendar date written YYYY-MM-DD, so add_card and update_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_date_opened
date_opened was not a calendar date written YYYY-MM-DD, so add_card and update_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_entries
The entries argument was not a list, so the batch was never read and nothing was written.
Fix. Send the entries as a JSON list of objects, then call again.
Where: Chat, MCP.
invalid_expiration
expiration was not a calendar date written YYYY-MM-DD, so add_gift_card and update_gift_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_fee_posts_on
fee_posts_on was not a calendar date written YYYY-MM-DD, so update_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_new_anniversary_date
new_anniversary_date was not a calendar date written YYYY-MM-DD, so update_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_review_by
review_by was not a calendar date written YYYY-MM-DD, so update_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_signup_bonus_received_on
signup_bonus_received_on was not a calendar date written YYYY-MM-DD, so add_card and update_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_sub_deadline
sub_deadline was not a calendar date written YYYY-MM-DD, so add_card and update_card refused the call at the door.
Fix. Send the date as YYYY-MM-DD, or omit the field. Ask the member for the exact date rather than guessing one. Nothing was written.
One of ten invalid_<field> codes, one per date field the write tools guard. A date is refused rather than stored because the member's own form is a date input, which shows an empty box for a value it cannot read, so a guessed date makes a row nobody can repair.
Where: MCP.
invalid_update
An update_card argument failed validation: card_settings, benefit_selections, or a cardholder who is not in the household.
Fix. Correct the argument the message names, then call update_card again. Nothing was written.
Where: Chat, MCP.
issuer_mismatch
The name you sent and the only wallet card it could mean come from different issuers, so nothing was analyzed.
Fix. Ask the member which card they mean, then call again with that name or a wallet_id.
Where: Chat, MCP.
merchant_lookup_quota_exceeded
get_best_card was given a merchant name it had never resolved before, and this member's own daily cap on new merchant lookups (25) is already spent. No category was found, so no card was recommended.
Fix. Call again with category set directly (dining, groceries, travel, ...) instead of relying on merchant to resolve one. The cap resets at midnight Pacific.
Where: Chat, MCP.
missing_identifier
The call named neither an id nor a lookup name, so the tool cannot tell which record you mean.
Fix. Send the record's id, or the lookup name the tool accepts. Wallet ids come from get_wallet_cards.
Where: Chat, MCP.
multiple_matches
A name matched more than one watchlist entry, gift card, or card to write to, so nothing was chosen.
Fix. Call again with an id from matches, or narrow the name. Wallet ids come from get_wallet_cards.
Still current for those tools, where the rows are not holdings and the retry takes a different argument. Wallet lookups moved to ambiguous_holding on 2026-09-21 and carry this code as previous_error until 2026-12-31; see the stability page.
Where: Chat, MCP.
not_a_wallet_id
The wallet_id you sent is not in the cc_N shape every wallet id uses, so it was not looked up.
Fix. Send a cc_N id from get_wallet_cards, or pass the name as card_name instead.
Where: Chat, MCP.
not_clearable
clear_fields was not a list, named a field update_card cannot clear, or rode along with product_change.
Fix. Send clear_fields as a list of clearable field names, in a call of its own. Nothing was written.
Where: Chat, MCP.
not_found
No wallet holding carries that id, or no watchlist entry matches that name.
Fix. Read the current ids with get_wallet_cards or get_watchlist, then call again with one of them.
Where: Chat, MCP.
unknown_cardholder
The cardholder you sent matches no member of this household, so no holding was picked.
Fix. Call again with a household member's name, or leave cardholder out for the account holder's own card.
Where: Chat, MCP.
wallet_unreadable
The wallet could not be read to validate this write, so nothing was written.
Fix. Retry the call. If it repeats, the wallet read is failing, not the entries you sent.
Where: Chat, MCP.
write_failed
The write was staged but never committed, so the ledger holds nothing from this call.
Fix. Retry the call. Read the message, which names what the storage layer reported.
Where: Chat, MCP.
Per-entry results
These are not refusals of the call. They ride inside a 200 response, one per entry, saying what happened to that entry while the rest of the batch went through. period_not_writable and period_required are rejections; already_recorded and member_recorded are skips, and a skip means the ledger already held the better answer.
already_recorded
The period already holds an answer, and only_if_absent leaves an answered period alone. The entry was skipped.
Fix. Leave it, or send the entry again without only_if_absent to replace the recorded answer.
Where: Chat, MCP.
member_recorded
The member recorded this period themselves, and an agent's write does not replace the member's own answer.
Fix. Leave it. On a write, pass overwrite: true only when the member told you in this conversation to replace it.
Where: Chat, MCP.
period_not_writable
The period named is outside the window this ledger can still be corrected for, so the entry was rejected.
Fix. Send a label from writable_periods. A period later than the current one cannot be recorded at all.
Where: Chat, MCP.
period_required
A clear_credit_usage entry left period out, and a removal never defaults to the current period.
Fix. Send the period label of the entry to remove, then call clear_credit_usage again.
Where: Chat, MCP.
MCP connection and quota gates
These come from the MCP server itself, before the tool runs. They arrive as a tool result with isError: true, with the code under structuredContent.result's error object alongside the message a person can read. /docs/mcp/quotas has the four ceilings and what the advisory looks like before one is spent.
account_not_found
The token is valid, but the Miles account behind it is gone. This is not a spent ceiling.
Fix. The member reconnects the app from Miles settings. Waiting does not clear this.
Where: MCP.
no_identity
The call carried no signed-in Miles identity, so no wallet could be read.
Fix. Connect the app again so every call carries a Miles token.
Where: MCP.
not_allowed
The tool named is not available over the remote MCP connection.
Fix. Call a tool the connection lists. The listed set is what this connection can reach.
Where: MCP.
paused
The member turned MCP access off in Miles settings, so the connection cannot call tools.
Fix. The member re-enables MCP access in Miles settings. The token stays valid, so nothing needs re-minting.
Where: MCP.
quota_exceeded
One of the four daily ceilings is spent: this tool, all calls, writes, or changed rows.
Fix. Wait for midnight Pacific, or send a smaller batch. Reads keep working when the write ceiling is spent.
Where: MCP.
refused
A connection gate refused this call, and the refusal carried no more specific code.
Fix. Read the message. Check the connection's identity, pause state, and scope before retrying.
Where: MCP.
service_unavailable
knowledge_search did not run: the tool is off on this deployment, or the backend is unreachable.
Fix. Read the message to tell the two apart. Do not read the result as evidence the corpus lacks an answer.
Where: MCP.
supporter_required
The tool is part of Membership, and this account is not a Member.
Fix. The member joins Membership. No scope grants this tool, and the code keeps its historical spelling.
Where: MCP.
write_scope
The connection holds read-only access, and the tool needs the write or the reconciliation scope.
Fix. The member disconnects the app in Miles settings and reconnects with the matching toggle checked.
Where: MCP.
Chat API
The OpenAI-compatible surface answers in OpenAI's envelope, so these are type and code values rather than Miles refusal codes, and no docs field is added to them. /docs/chat-api/errors has the full status table, the mid-stream shape and the headers.
connections_paused
The account paused its AI app connections, so a valid key is refused with 403.
Fix. The member turns them back on at Settings, Connected AI Apps. Nothing needs re-minting.
Where: The code field on the Chat API.
insufficient_quota
The account's monthly budget is spent, so the turn was refused with 429.
Fix. Wait for Retry-After, which is the seconds until the reset date and can be days. Members get a larger budget.
Where: The type field on the Chat API.
invalid_api_key
The personal API key was revoked, never existed, or belongs to a deleted account.
Fix. Mint a new key in Miles settings, or refresh the OAuth token if you sent one.
Where: The code field on the Chat API.
invalid_request_error
The request was refused before the turn ran, for its body, its credential, or paused connections.
Fix. Read the code and message to see which, then correct the body or the credential. Retrying changes nothing.
Where: The type field on the Chat API.
pipeline_error
The chat pipeline failed to answer. It rides on server_error, before the stream opens or mid-stream.
Fix. Retry once. A mid-stream failure arrives as a final data: event, so read that event.
Where: The code field on the Chat API.
rate_limit_exceeded
A per-minute window, the concurrency cap, or the daily spend allowance refused this turn.
Fix. Wait for Retry-After, then retry. Reduce parallelism when the message names concurrency. Retrying is safe.
Where: The type field on the Chat API.
server_error
Miles could not produce an answer. The turn failed before, during, or after the provider call.
Fix. Retry once. If it repeats, the fault is on Miles' side, not in your request.
Where: The type field on the Chat API.
timeout_error
A non-streaming turn passed the 90 second deadline and was refused with 504.
Fix. Retry with stream: true, which has no turn timeout. Retrying is safe, since nothing was half-written.
Where: The type field on the Chat API.
Codes you will not see here
The first-party app routes that the Miles website and the iOS app call have refusal codes of their own, and they are not on this page because no public surface emits them. past_deadline, invalid_until, invalid_surface, overlapping_interval, invalid_since, invalid_intervals, too_many_intervals, credit_not_found and invalid_request are all of that kind. If you are reading one of those, you are reading a response from an endpoint that is not part of the developer surface and is free to change.