Understanding OpenTripPlanner API error responses

When making requests to OpenTripPlanner (OTP), an open-source, multi-modal trip planner, things don't always work out and you may find yourself facing an error response.

This article provides a quick overview of such responses, on both versions of OTP.

Response format

OpenTripPlanner uses different data formats (namely XML and JSON) depending on its version, and slighlty different structures too.

OTP 1 (XML)

In it's first (= previous) version, OpenTripPlanner's response format uses XML, and it's quite consistent whether the request was successful or not. In both cases, it contains (among other things) an error property that is either empty when there's no error, or provides details on what went wrong when applicable.

The response looks like this:

<Response>
  <!-- List of all parameters given in the request. -->
  <requestParameters/>

  <!-- On success: the trip plan with all possible itineraries. -->
  <!-- On failure: empty. -->
  <!-- See http://dev.opentripplanner.org/apidoc/1.5.0/json_TripPlan.html -->
  <plan/> 

  <!-- On success: empty. -->
  <!-- On failure: description of the error. -->
  <!-- See http://dev.opentripplanner.org/apidoc/1.5.0/json_PlannerError.html -->
  <error> 
    <id>406</id>
    <msg>No transit times available. The date may be past or too far in the future or there may not be transit service for your trip at the time you chose.</msg>
    <message>NO_TRANSIT_TIMES</message>
    <noPath>true</noPath>
  </error>

  <!-- Information for debugging and profiling purposes. -->
  <!-- See http://dev.opentripplanner.org/apidoc/1.5.0/json_DebugOutput.html -->
  <debugOutput/>

  <!-- Metadata about elevation. -->
  <!-- See http://dev.opentripplanner.org/apidoc/1.5.0/json_ElevationMetadata.html -->
  <elevationMetadata/>
</Response>

OTP 2 (JSON)

In it's second (= latest) version, OTP's response uses JSON and is a little bit more different depending on whether the request succeeded or failed. Among other things, the error property only exists when an error has in fact occured.

The response looks like this:

{
  // List of all parameters given in the request.
  "requestParameters": {},

  "plan": {
    "date": 0,
    "from": {},
    "to": {},
    // On success: the list of all possible itineraries.
    // On failure: empty.
    "itineraries": []
  },

  // On success: non-existent.
  // On failure: description of the error.
  "error": {
    "id": 440,
    "msg": "Origin is unknown. Can you be a bit more descriptive?",
    "message": "GEOCODE_FROM_NOT_FOUND",
    "missing": ["FROM_PLACE"]
  },

  // On success: Contextual information about the response.
  // On failure: non-existent.
  "metadata": {},

  // On success: a value that can be used in a subsequent request's "pageCursor" parameter to fetch the previous/next page of itineraries.
  // On failure: non-existent.
  "previousPageCursor": "string",
  "nextPageCursor": "string",

  // Information for debugging and profiling purposes.
  // See https://docs.opentripplanner.org/api/dev-2.x/graphql-gtfs/types/debugOutput
  "debugOutput": {},
}

You may have noticed that the error object is comparable in both versions. Let's have a closer look at it.

The error object

OpenTripPlanner's error responses always provide an error object that contains the following elements:

id (required)

This 3-digit integer looks remarkably like an HTTP status code, but it's not.

Even though there's significant overlap between these ids and status code (e.g. the PATH_NOT_FOUND message uses the 404 id), you'll also find a bunch of ids that don't match a status code (e.g. 373, 470, etc.).

Furthermore, note that the HTTP code actually returned in the response's headers is always 200, even when an error occured and no itineraries were found, regardless of the error's pseudo-http-code-id value. This is a known discrepancy or confusion point (whatever you want to call it) as you can see in this GitHub issue.

So in short, the id property is just that: a unique, numerical identifier.

msg (required)

This is a human-readable explanation of the error encountered. When you're debugging a failed request, this is usually the clearest and most helpful piece of information. It's also fit for display to the end users of your planner.

The msg value is available in multiple languages in the Message_[languageCode].properties source files for OTP 1 and OTP 2.

message (required)

This is a short, machine-readable key to identify each error. It fulfils the same role as the error's id, but it's a lot more explicit. Therefore it's recommended to use this in scripts (e.g. if (code === "NO_TRANSIT_TIMES")) instead of the id (e.g. if (id === 406)).

It's a bit weird that OpenTripPlanner developers chose the msg property name for the longer, human-friendly message, and message for the shorter, machine-reable identifier. But there you have it.

noPath (required, OTP 1 only)

This is a boolean flag only available on OTP 1. It indicates whether OTP was unable to find any itinerary or "path".

I haven't tested it fully, but I'm fairly certain OTP 1 returns <noPath>true</noPath> in every error object. This is completely redundant, which I suppose is why this property no longer exists in OTP 2.

missing (conditionally required)

This last property only appears on errors of type VertexNotFoundException, i.e. when trying to use a location that doesn't exist in the planner's graph. This includes the message=GEOCODE_FROM_NOT_FOUND error for example.

Full list of error messages

Here are all the errors you can encounter in either version of the OpenTripPlanner API.

id message msg
340 GEOCODE_FROM_AMBIGUOUS The trip planner is unsure of the location you want to start from. Please select from the following options, or be more specific.
350 GEOCODE_TO_AMBIGUOUS The trip planner is unsure of the destination you want to go to. Please select from the following options, or be more specific.
360 GEOCODE_FROM_TO_AMBIGUOUS Both origin and destination are ambiguous. Please select from the following options, or be more specific.
370 UNDERSPECIFIED_TRIANGLE All of triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must be set if any are
371 TRIANGLE_NOT_AFFINE The values of triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must sum to 1
372 TRIANGLE_OPTIMIZE_TYPE_NOT_SET If triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor are provided, OptimizeType must be TRIANGLE
373 TRIANGLE_VALUES_NOT_SET If OptimizeType is TRIANGLE, triangleSafetyFactor, triangleSlopeFactor, and triangleTimeFactor must be set
400 OUTSIDE_BOUNDS Trip is not possible. You might be trying to plan a trip outside the map data boundary.
404 PATH_NOT_FOUND No trip found. There may be no transit service within the maximum specified distance or at the specified time, or your start or end point might not be safely accessible.
406 NO_TRANSIT_TIMES No transit times available. The date may be past or too far in the future or there may not be transit service for your trip at the time you chose.
408 REQUEST_TIMEOUT The trip planner is taking way too long to process your request. Please try again later.
409 TOO_CLOSE Origin is within a trivial distance of the destination.
413 BOGUS_PARAMETER "The request has errors that the server is not willing or able to process.”
440 GEOCODE_FROM_NOT_FOUND Origin is unknown. Can you be a bit more descriptive?
450 GEOCODE_TO_NOT_FOUND Destination is unknown. Can you be a bit more descriptive?
460 GEOCODE_FROM_TO_NOT_FOUND Both origin and destination are unknown. Can you be a bit more descriptive?
470 LOCATION_NOT_ACCESSIBLE The location was found, but no stops could be found within the search radius.
500 SYSTEM_ERROR We're sorry. The trip planner is temporarily unavailable. Please try again later.
503 GRAPH_UNAVAILABLE