Map matching and trace attributes
POST https://api.trailsplits.com/valhalla/trace_attributes
Checked by a live probe Key optional · 500 credits a day without one
Matches a line to the mapped network and returns each way it follows with surface (and whether the surface is mapped or a class default), use and SAC grade, plus the matched line per segment, unmatched stretches with reasons, and three lengths. Valhalla-compatible input; up to 2,000 points. Edge lengths are kilometres. For a full analysis with climb, time and unknowns use route analysis.
Example
# Without a key, leave out the Authorization line (500 credits a day per address).
curl --fail-with-body 'https://api.trailsplits.com/valhalla/trace_attributes' \
-H "Authorization: Bearer $TRAILSPLITS_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"shape":[{"lat":46.5369,"lon":12.1352},{"lat":46.536,"lon":12.133}],"costing":"pedestrian","shape_match":"map_snap"}' Request body
| Field | Type | Description |
|---|---|---|
shaperequired | array | |
costingrequired | string: pedestrian | bicycle | |
shape_match | string: map_snap |
Response
| Field | Type | Description |
|---|---|---|
edges | array | |
shape | string | Polyline6 of the matched line, only when the match is one segment. |
units | string: kilometers | |
trailsplits | object |
Full response schema (JSON Schema)
{
"type": "object",
"properties": {
"edges": {
"type": "array",
"items": {
"type": "object",
"properties": {
"length": {
"type": "number",
"description": "Kilometres."
},
"begin_shape_index": {
"type": "integer"
},
"end_shape_index": {
"type": "integer"
},
"surface": {
"type": "string",
"nullable": true
},
"trailsplits_surface_tagged": {
"type": "boolean"
},
"use": {
"type": "string",
"nullable": true
},
"sac_scale": {
"type": "integer",
"nullable": true
}
},
"required": [
"length"
]
}
},
"shape": {
"type": "string",
"description": "Polyline6 of the matched line, only when the match is one segment."
},
"units": {
"type": "string",
"enum": [
"kilometers"
]
},
"trailsplits": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"matched",
"partial",
"unmatched"
]
},
"coverage": {
"type": "number"
},
"trace_length_m": {
"type": "number"
},
"matched_length_m": {
"type": "number"
},
"segments": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
},
"unmatched": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
}
}
},
"required": [
"status",
"coverage",
"segments"
]
}
},
"required": [
"edges",
"trailsplits"
]
}Errors
400, 422, plus the gateway's 401, 429 and 503 (error conventions). A refused request costs no credits.
Contract
What the answer now carries
| Field | Meaning |
|---|---|
shape | Valhalla's top-level matched line, polyline6 ([lat, lon] × 1e6, Valhalla's encoding). Only when the match is one segment. |
trailsplits.segments[i].shape | The line of matched segment i, polyline6. Always present for every segment. |
edges[k].begin_shape_index, edges[k].end_shape_index | First and last vertex of edge k in the line of its segment, inclusive (Valhalla's names). Consecutive edges of a segment share a vertex: end of one is begin of the next. |
edges[k].trailsplits_segment | The segment whose line the indices refer to. |
Unchanged:
-
edges[k].lengthis in kilometres (units: "kilometers"), and equals the length of the edge's stretch of the line within rounding. -
The facts per edge, including
trailsplits_surface_tagged:falsemeans the surface is the class default, not a mapped surface.
Lengths: three different facts
| Field | What it measures |
|---|---|
trailsplits.trace_length_m | The authored trace, end to end |
trailsplits.matched_length_m | The authored trace inside matched segments, measured along the trace |
trailsplits.segments[i].distance_m = Σ edges.length of the segment × 1000 | The network path the trace was matched to |
coverage = matched_length_m / trace_length_m. A coverage of 1 says the whole trace was matched. It does not say that the network path is as long as the trace.
Gaps and refusals
-
Partial match (
trailsplits.status: "partial"):- several segments, each with its own line;
-
no top-level
shape, and no line is ever drawn across an unmatched stretch; -
the stretches are listed in
trailsplits.unmatched(fromIndex,toIndex,length_m,reason:no_edge_nearbyorno_connection).
-
No match (
status: "unmatched"): HTTP 422 with Valhalla's error 443 andtrailsplits.code: "no_match". - Never changed: the authored trace is returned as sent; endpoints are never rerouted.
- Bicycle traces are answered by the same matcher.
Controls (2 Oct 2026, router fbf1ee8f2)
| Case | Result |
|---|---|
A01 (Dolomites, 2 points, docs/b2b/commercial/benchmarks/observations-2026-10-02.json) | Matched, coverage 1, 1 segment, 49 vertices. Edges contiguous from 0 to 48. The line measures 393.6 m, the segment 395 m and the edges 394.6 m; the trace is 196 m. |
| A02 (Zermatt, 14 points) | Matched, coverage 1, 1 segment. Edges contiguous from 0 to 28. |
Consumers
- DX: examples and the public reference.