Skip to main content
Glama

Check TfL journeys for overcharges

check_tfl_journeys
Read-onlyIdempotent

Checks Transport for London contactless or Oyster journeys for overcharges: incomplete journeys (no touch out, so the maximum fare was charged) and missed Hopper fares. Takes journey rows from a journey history, or the text of the CSV download from a TfL account. PDFs and images are not accepted. TfL refunds an incomplete journey as the maximum fare minus the fare for the journey actually made. Without the destination, the refund shown is an upper limit equal to the whole charge; with intended_destination on the row, it is an estimate from the usual fare to that station. Returns the refundable journeys and total, older journeys outside TfL’s 8 week window, how to claim in the TfL account, a claim pack and the details needed to save the journeys to an Untap account. It does not check service delays on the Tube, DLR, Elizabeth line or Overground, or Santander Cycles; the result lists these under not_checked. No sign-in needed. Nothing is saved to an account. A record of the check, with no name, account or IP address, is kept for a year so answers can be verified.

Input Schema

NameRequiredDescriptionDefault
csv_textNoThe text of the CSV journey history downloaded from a TfL account. Takes priority over journeys.
journeysNoJourney rows from a TfL contactless or Oyster journey history. Used when csv_text is not given.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / journeys / anyOf
      Previous value: -[
      -  {
      -    "items": {
      -      "additionalProperties": false,
      -      "properties": {
      -        "bus_route": {
      -          "description": "Bus route number for bus rows, otherwise null.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "date": {
      -          "description": "Journey date as YYYY-MM-DD.",
      -          "type": "string"
      -        },
      -        "end_time": {
      -          "description": "Tap-out time as HH:MM, or null.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "fare_pence": {
      -          "description": "Charge for this row in pence. £2.20 = 220.",
      -          "maximum": 100000,
      -          "minimum": 0,
      -          "type": "integer"
      -        },
      -        "from_location": {
      -          "description": "Tap-in station / stop name, or null if not shown.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "intended_destination": {
      -          "anyOf": [
      -            {
      -              "maxLength": 80,
      -              "type": "string"
      -            },
      -            {
      -              "type": "null"
      -            }
      -          ],
      -          "description": "Optional, for an incomplete journey with no touch out: the station the person was travelling to. With it, the refund is estimated as the fare charged minus the usual fare to that station. Without it, the figure is an upper limit equal to the whole charge."
      -        },
      -        "is_auto_corrected": {
      -          "description": "Optional. True when the statement says TfL has already corrected this journey (for example \"Auto-completed\" or \"Adjusted\").",
      -          "type": [
      -            "boolean",
      -            "null"
      -          ]
      -        },
      -        "is_daily_cap_hit": {
      -          "description": "Optional. True when the statement marks this row as the one where the daily cap was reached.",
      -          "type": [
      -            "boolean",
      -            "null"
      -          ]
      -        },
      -        "is_hopper_free": {
      -          "description": "Optional. True when the statement shows this fare as a free Hopper fare (a second bus or tram within 60 minutes).",
      -          "type": [
      -            "boolean",
      -            "null"
      -          ]
      -        },
      -        "is_incomplete": {
      -          "description": "True if this journey has no tap-out recorded (e.g. label \"Incomplete\" or \"No exit\").",
      -          "type": "boolean"
      -        },
      -        "is_max_fare": {
      -          "description": "True if the statement labels this as a max-fare charge for a failed revenue inspection.",
      -          "type": "boolean"
      -        },
      -        "is_weekly_cap_hit": {
      -          "description": "Optional. True when the statement marks this row as the one where the weekly cap was reached.",
      -          "type": [
      -            "boolean",
      -            "null"
      -          ]
      -        },
      -        "mode": {
      -          "description": "Transport mode for this row, or \"unknown\" when the statement does not say.",
      -          "enum": [
      -            "tube",
      -            "bus",
      -            "overground",
      -            "elizabeth",
      -            "dlr",
      -            "tram",
      -            "cable_car",
      -            "thameslink",
      -            "unknown"
      -          ],
      -          "type": "string"
      -        },
      -        "start_time": {
      -          "description": "Tap-in time as HH:MM, or null.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "to_location": {
      -          "description": "Tap-out station / stop name, or null for incomplete journeys.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        }
      -      },
      -      "required": [
      -        "date",
      -        "start_time",
      -        "end_time",
      -        "mode",
      -        "from_location",
      -        "to_location",
      -        "bus_route",
      -        "fare_pence",
      -        "is_max_fare",
      -        "is_incomplete"
      -      ],
      -      "type": "object"
      -    },
      -    "maxItems": 500,
      -    "type": "array"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "items": {
      +      "additionalProperties": false,
      +      "properties": {
      +        "bus_route": {
      +          "description": "Bus route number for bus rows, otherwise null.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        },
      +        "date": {
      +          "description": "Journey date as YYYY-MM-DD.",
      +          "type": "string"
      +        },
      +        "end_time": {
      +          "description": "Tap-out time as HH:MM, or null.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        },
      +        "fare_pence": {
      +          "description": "Charge for this row in pence. £2.20 = 220.",
      +          "maximum": 100000,
      +          "minimum": 0,
      +          "type": "integer"
      +        },
      +        "from_location": {
      +          "description": "Tap-in station / stop name, or null if not shown.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        },
      +        "intended_destination": {
      +          "anyOf": [
      +            {
      +              "maxLength": 80,
      +              "type": "string"
      +            },
      +            {
      +              "type": "null"
      +            }
      +          ],
      +          "description": "Optional, for an incomplete journey with no touch out: the station the person was travelling to. With it, the refund is estimated as the fare charged minus the usual fare to that station. Without it, the figure is an upper limit equal to the whole charge."
      +        },
      +        "is_auto_corrected": {
      +          "description": "Optional. True when the statement says TfL has already corrected this journey (for example \"Auto-completed\" or \"Adjusted\").",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "is_daily_cap_hit": {
      +          "description": "Optional. True when the statement marks this row as the one where the daily cap was reached.",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "is_hopper_free": {
      +          "description": "Optional. True when the statement shows this fare as a free Hopper fare (a second bus or tram within 60 minutes).",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "is_incomplete": {
      +          "description": "True if this journey has no tap-out recorded (e.g. label \"Incomplete\" or \"No exit\"), including when it was charged the maximum fare for that reason.",
      +          "type": "boolean"
      +        },
      +        "is_inspection_penalty": {
      +          "description": "Optional. True only when the statement shows a penalty for failing a ticket inspection, which TfL does not refund. A maximum fare for a missing touch out counts as is_incomplete, not this.",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "is_max_fare": {
      +          "description": "Deprecated. Replaced by is_incomplete and is_inspection_penalty.",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "is_weekly_cap_hit": {
      +          "description": "Optional. True when the statement marks this row as the one where the weekly cap was reached.",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "mode": {
      +          "description": "Transport mode for this row, or \"unknown\" when the statement does not say.",
      +          "enum": [
      +            "tube",
      +            "bus",
      +            "overground",
      +            "elizabeth",
      +            "dlr",
      +            "tram",
      +            "cable_car",
      +            "thameslink",
      +            "unknown"
      +          ],
      +          "type": "string"
      +        },
      +        "start_time": {
      +          "description": "Tap-in time as HH:MM, or null.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        },
      +        "to_location": {
      +          "description": "Tap-out station / stop name, or null for incomplete journeys.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        }
      +      },
      +      "required": [
      +        "date",
      +        "start_time",
      +        "end_time",
      +        "mode",
      +        "from_location",
      +        "to_location",
      +        "bus_route",
      +        "fare_pence",
      +        "is_incomplete"
      +      ],
      +      "type": "object"
      +    },
      +    "maxItems": 500,
      +    "type": "array"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
  2. Changed3 schema fields changed
    • changedInput schema / properties / csv_text / description
      Previous value: -"The text of the CSV file from the TfL account’s journey history download."New value: +"The text of the CSV journey history downloaded from a TfL account. Takes priority over journeys."
    • changedInput schema / properties / journeys / anyOf
      Previous value: -[
      -  {
      -    "items": {
      -      "additionalProperties": false,
      -      "properties": {
      -        "bus_route": {
      -          "description": "Bus route number for bus rows, otherwise null.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "date": {
      -          "description": "Journey date as YYYY-MM-DD.",
      -          "type": "string"
      -        },
      -        "end_time": {
      -          "description": "Tap-out time as HH:MM, or null.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "fare_pence": {
      -          "description": "Charge for this row in pence. £2.20 = 220.",
      -          "maximum": 100000,
      -          "minimum": 0,
      -          "type": "integer"
      -        },
      -        "from_location": {
      -          "description": "Tap-in station / stop name, or null if not shown.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "is_auto_corrected": {
      -          "description": "OPTIONAL. True if the statement says TfL already auto-corrected this journey (e.g. \"Auto-completed\", \"Adjusted\").",
      -          "type": [
      -            "boolean",
      -            "null"
      -          ]
      -        },
      -        "is_daily_cap_hit": {
      -          "description": "OPTIONAL. True if the statement explicitly marks this row as the daily-cap-reached entry.",
      -          "type": [
      -            "boolean",
      -            "null"
      -          ]
      -        },
      -        "is_hopper_free": {
      -          "description": "OPTIONAL. True if the statement marks this fare as a free Hopper fare (second bus/tram tap within 60 min).",
      -          "type": [
      -            "boolean",
      -            "null"
      -          ]
      -        },
      -        "is_incomplete": {
      -          "description": "True if this journey has no tap-out recorded (e.g. label \"Incomplete\" or \"No exit\").",
      -          "type": "boolean"
      -        },
      -        "is_max_fare": {
      -          "description": "True if the statement labels this as a max-fare charge for a failed revenue inspection.",
      -          "type": "boolean"
      -        },
      -        "is_weekly_cap_hit": {
      -          "description": "OPTIONAL. True if the statement marks this row as a weekly-cap-reached entry.",
      -          "type": [
      -            "boolean",
      -            "null"
      -          ]
      -        },
      -        "mode": {
      -          "description": "Transport mode for this row. Use \"unknown\" only if the statement does not say.",
      -          "enum": [
      -            "tube",
      -            "bus",
      -            "overground",
      -            "elizabeth",
      -            "dlr",
      -            "tram",
      -            "cable_car",
      -            "thameslink",
      -            "unknown"
      -          ],
      -          "type": "string"
      -        },
      -        "start_time": {
      -          "description": "Tap-in time as HH:MM, or null.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        },
      -        "to_location": {
      -          "description": "Tap-out station / stop name, or null for incomplete journeys.",
      -          "type": [
      -            "string",
      -            "null"
      -          ]
      -        }
      -      },
      -      "required": [
      -        "date",
      -        "start_time",
      -        "end_time",
      -        "mode",
      -        "from_location",
      -        "to_location",
      -        "bus_route",
      -        "fare_pence",
      -        "is_max_fare",
      -        "is_incomplete"
      -      ],
      -      "type": "object"
      -    },
      -    "maxItems": 500,
      -    "type": "array"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]New value: +[
      +  {
      +    "items": {
      +      "additionalProperties": false,
      +      "properties": {
      +        "bus_route": {
      +          "description": "Bus route number for bus rows, otherwise null.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        },
      +        "date": {
      +          "description": "Journey date as YYYY-MM-DD.",
      +          "type": "string"
      +        },
      +        "end_time": {
      +          "description": "Tap-out time as HH:MM, or null.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        },
      +        "fare_pence": {
      +          "description": "Charge for this row in pence. £2.20 = 220.",
      +          "maximum": 100000,
      +          "minimum": 0,
      +          "type": "integer"
      +        },
      +        "from_location": {
      +          "description": "Tap-in station / stop name, or null if not shown.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        },
      +        "intended_destination": {
      +          "anyOf": [
      +            {
      +              "maxLength": 80,
      +              "type": "string"
      +            },
      +            {
      +              "type": "null"
      +            }
      +          ],
      +          "description": "Optional, for an incomplete journey with no touch out: the station the person was travelling to. With it, the refund is estimated as the fare charged minus the usual fare to that station. Without it, the figure is an upper limit equal to the whole charge."
      +        },
      +        "is_auto_corrected": {
      +          "description": "Optional. True when the statement says TfL has already corrected this journey (for example \"Auto-completed\" or \"Adjusted\").",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "is_daily_cap_hit": {
      +          "description": "Optional. True when the statement marks this row as the one where the daily cap was reached.",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "is_hopper_free": {
      +          "description": "Optional. True when the statement shows this fare as a free Hopper fare (a second bus or tram within 60 minutes).",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "is_incomplete": {
      +          "description": "True if this journey has no tap-out recorded (e.g. label \"Incomplete\" or \"No exit\").",
      +          "type": "boolean"
      +        },
      +        "is_max_fare": {
      +          "description": "True if the statement labels this as a max-fare charge for a failed revenue inspection.",
      +          "type": "boolean"
      +        },
      +        "is_weekly_cap_hit": {
      +          "description": "Optional. True when the statement marks this row as the one where the weekly cap was reached.",
      +          "type": [
      +            "boolean",
      +            "null"
      +          ]
      +        },
      +        "mode": {
      +          "description": "Transport mode for this row, or \"unknown\" when the statement does not say.",
      +          "enum": [
      +            "tube",
      +            "bus",
      +            "overground",
      +            "elizabeth",
      +            "dlr",
      +            "tram",
      +            "cable_car",
      +            "thameslink",
      +            "unknown"
      +          ],
      +          "type": "string"
      +        },
      +        "start_time": {
      +          "description": "Tap-in time as HH:MM, or null.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        },
      +        "to_location": {
      +          "description": "Tap-out station / stop name, or null for incomplete journeys.",
      +          "type": [
      +            "string",
      +            "null"
      +          ]
      +        }
      +      },
      +      "required": [
      +        "date",
      +        "start_time",
      +        "end_time",
      +        "mode",
      +        "from_location",
      +        "to_location",
      +        "bus_route",
      +        "fare_pence",
      +        "is_max_fare",
      +        "is_incomplete"
      +      ],
      +      "type": "object"
      +    },
      +    "maxItems": 500,
      +    "type": "array"
      +  },
      +  {
      +    "type": "null"
      +  }
      +]
    • changedInput schema / properties / journeys / description
      Previous value: -"Journey rows read from the person’s TfL journey history. Send these or csv_text, not both."New value: +"Journey rows from a TfL contactless or Oyster journey history. Used when csv_text is not given."
  3. First observed

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the readOnly/idempotent/non-destructive annotations: states no sign-in is needed, nothing is saved to an account, and that an anonymised record is retained for a year. It also explains the refund arithmetic (max fare minus fare actually made) and the upper-limit vs estimate distinction, which is exactly the behavioural context an agent needs to set expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose and defect classes before caveats, and nearly every sentence carries information an agent would otherwise have to infer. It is dense and long, but the density is justified by a tool with two input shapes, a refund model, and multiple exclusions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, and the description compensates fully by enumerating what is returned: refundable journeys and total, out-of-window journeys, claim instructions, a claim pack, and Untap save details. Combined with the input-format rules and scope exclusions, an agent has everything needed to call it and describe the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description lifts it by explaining the consequence of intended_destination on the computed refund (upper limit vs estimated fare) rather than just restating the field name. The csv_text-over-journeys precedence is left to the schema, which is acceptable at this coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource+scope ("Checks Transport for London contactless or Oyster journeys for overcharges") and immediately names the two defect classes it detects: incomplete journeys and missed Hopper fares. This clearly separates it from sibling write/aggregation tools like add_tfl_journeys and find_money_owed.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states accepted inputs (journey rows or CSV download text), rules out PDFs and images, and enumerates what is out of scope (Tube/DLR/Elizabeth/Overground delays, Santander Cycles) with a pointer to the not_checked field. It does not name a sibling alternative for the excluded modes, which keeps it just below a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.