Walk15 Connect V1
API reference
Every partner-facing Walk15 Connect endpoint with parameters, examples and errors.
All paths are rooted at the issuer origin Walk15 gives you — https://api.walk15.lt in production. Examples use $WALK15_ISSUER for it. Curl's -u puts the client secret in your shell history, so use it only for local experiments.
Conventions
| Transport | HTTPS only |
| Browser endpoint | GET /oauth/authorize is opened in the user's browser. Never call it from your backend. |
| Backend endpoints | Everything else is server-to-server. |
| Client authentication | HTTP Basic with client ID and secret — token, revoke and disconnect endpoints |
| User-delegated authentication | Authorization: Bearer <access_token> — activity endpoints |
| Request bodies | application/x-www-form-urlencoded for OAuth endpoints; activity and disconnect requests have no body |
| Dates | Calendar dates YYYY-MM-DD; instants are UTC ISO 8601 with milliseconds and Z |
| Caching | Every backend response is Cache-Control: no-store, Pragma: no-cache, Referrer-Policy: no-referrer |
| Correlation | Responses carry a server-generated X-Request-ID; include it when contacting Walk15 support |
Two error shapes exist. OAuth endpoints (token, revoke, disconnect) return:
{ "error": "invalid_grant", "error_description": "The authorization grant is invalid." }Activity endpoints return:
{
"error": { "code": "invalid_date_range", "message": "The date range is invalid." },
"request_id": "44444444-4444-4444-8444-444444444444"
}All codes are listed in Errors and limits.
Discovery
/ .well-known/ oauth-authorization-serverBackend onlyReturns RFC 8414 authorization server metadata. No authentication.
curl "$WALK15_ISSUER/.well-known/oauth-authorization-server"Response 200 — see the full example in Discovery. Fields: issuer, authorization_endpoint, token_endpoint, revocation_endpoint, token_endpoint_auth_methods_supported, revocation_endpoint_auth_methods_supported, code_challenge_methods_supported, scopes_supported, response_types_supported, grant_types_supported.
Start authorization
/ oauth/ authorizeBrowser navigationStarts the Walk15 sign-in and consent flow. Redirect the user's browser here (HTTP 303 from your backend); do not fetch it server-side. Authentication: none — your client is identified by client_id and validated against your registration.
| Query parameter | Required | Description |
|---|---|---|
client_id | Yes | Your client UUID |
redirect_uri | Yes | Exact registered HTTPS callback, max 2048 bytes |
response_type | Yes | code |
scope | Yes | Space-separated unique scopes from your allowance, max 512 characters |
state | Yes | 22–2048 visible ASCII characters; returned unchanged |
code_challenge | Yes | 43-character unpadded base64url SHA-256 of your PKCE verifier |
code_challenge_method | Yes | S256 |
Every parameter must appear exactly once; unknown parameters are rejected. The whole URL may not exceed 16 KB.
GET /oauth/authorize?client_id=11111111-1111-4111-8111-111111111111
&redirect_uri=https%3A%2F%2Fpartner.example%2Foauth%2Fwalk15%2Fcallback
&response_type=code
&scope=daily_steps%3Aread+cycling_daily%3Aread
&state=EjzQZI751F_PEmxjmkfXDfqM8cL--2DKrXcjVV0jO3w
&code_challenge=v8yANszyUApYb9P0q-VnGZQ5F5PTNrC06lSrGHkfJGw
&code_challenge_method=S256
Host: api.walk15.ltResult. Walk15 responds 303 to its own sign-in pages. After the user decides, the browser is redirected to your callback:
HTTP/1.1 303 See Other
Location: https://partner.example/oauth/walk15/callback?state=EjzQZI751F_PEmxjmkfXDfqM8cL--2DKrXcjVV0jO3w&code=w15c_<authorization-code>or, when the user cancels:
HTTP/1.1 303 See Other
Location: https://partner.example/oauth/walk15/callback?state=EjzQZI751F_PEmxjmkfXDfqM8cL--2DKrXcjVV0jO3w&error=access_deniedErrors. An invalid request (invalid_request, unauthorized_client, unsupported_response_type, invalid_scope) or an expired request renders a generic Walk15 error page with status 400. It is never redirected to your callback.
Caveats. The flow must finish within 10 minutes. For a user who is already connected, see How do we reconnect a user?
Exchange code
/ oauth/ tokenBackend onlyRedeems a single-use authorization code (valid for at most 120 seconds) for tokens. Authentication: HTTP Basic.
| Header | Value |
|---|---|
Authorization | Basic base64(urlencode(client_id) ":" urlencode(client_secret)) — exactly one |
Content-Type | application/x-www-form-urlencoded (optionally ; charset=utf-8) |
| Form field | Required | Description |
|---|---|---|
grant_type | Yes | authorization_code |
code | Yes | The code from your callback |
redirect_uri | Yes | The same registered callback |
code_verifier | Yes | Your PKCE verifier, 43–128 characters of [A-Za-z0-9._~-] |
No other fields are allowed. The body may not exceed 8 KB, and the request may not have a query string.
curl -X POST "$WALK15_ISSUER/oauth/token" \
-u "$WALK15_CLIENT_ID:$WALK15_CLIENT_SECRET" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=$AUTHORIZATION_CODE" \
--data-urlencode "redirect_uri=https://partner.example/oauth/walk15/callback" \
--data-urlencode "code_verifier=$CODE_VERIFIER"Response 200
{
"grant_id": "22222222-2222-4222-8222-222222222222",
"history_from": "2026-10-10",
"connection_version": "1",
"access_token": "w15a_<access-token>",
"token_type": "Bearer",
"expires_in": 300,
"refresh_token": "w15r2_<refresh-token>",
"scope": "daily_steps:read cycling_daily:read"
}| Field | Description |
|---|---|
grant_id | Pairwise UUID for this user and your company. Use it in activity paths. Stable across refreshes and reconnects. |
history_from | Earliest date you may request: the user's authorization calendar day |
connection_version | Decimal string; increases with every new authorization and every disconnect (revoking tokens alone does not change it) |
access_token | Opaque Bearer token |
token_type | Bearer |
expires_in | Seconds until the access token expires (at most 300) |
refresh_token | Opaque, single-use refresh token |
scope | Space-separated scopes approved by the user |
Errors: 400 invalid_request (malformed body, unknown or duplicate fields), 400 unsupported_grant_type, 400 invalid_grant (unknown, expired or used code; wrong redirect_uri or verifier; connection changed), 401 invalid_client, 500 server_error.
Refresh tokens
/ oauth/ tokenBackend onlyRotates a refresh token. Authentication: HTTP Basic. Same headers and body rules as the code exchange.
| Form field | Required | Description |
|---|---|---|
grant_type | Yes | refresh_token |
refresh_token | Yes | The current refresh token for the connection |
curl -X POST "$WALK15_ISSUER/oauth/token" \
-u "$WALK15_CLIENT_ID:$WALK15_CLIENT_SECRET" \
--data-urlencode "grant_type=refresh_token" \
--data-urlencode "refresh_token=$WALK15_REFRESH_TOKEN"Response 200 — the same fields as the code exchange, with a new access_token and a new refresh_token. grant_id, scope, history_from and connection_version are unchanged.
Errors: 400 invalid_request, 400 unsupported_grant_type, 400 invalid_grant (expired by inactivity, revoked, already used, user deleted, scope no longer allowed), 401 invalid_client, 500 server_error.
Revoke token
/ oauth/ revokeBackend onlyRFC 7009 token revocation. Authentication: HTTP Basic.
| Form field | Required | Description |
|---|---|---|
token | Yes | An access token or refresh token issued to you |
token_type_hint | No | access_token or refresh_token. Walk15 determines the real type itself. |
curl -X POST "$WALK15_ISSUER/oauth/revoke" \
-u "$WALK15_CLIENT_ID:$WALK15_CLIENT_SECRET" \
--data-urlencode "token=$WALK15_REFRESH_TOKEN" \
--data-urlencode "token_type_hint=refresh_token"Response 200 with an empty body — also for unknown, foreign or already-revoked tokens, so the response never reveals whether a token existed.
- An access token revokes only that token.
- A refresh token revokes every token of that connection version and sends a
connection.revokedwebhook. The consent itself remains: re-authorizing with the same scopes keepshistory_from.
To end a user's connection completely, use Disconnect grant instead.
Errors: 400 invalid_request, 400 unsupported_token_type (unknown hint), 401 invalid_client, 500 server_error.
Daily steps
/ connect-walk15/ v1/ grants/ {grant_id}/ daily-activityBackend onlyReturns adjusted Walk15 daily step totals for an inclusive date range. Authentication: Bearer access token. Scope: daily_steps:read.
| Parameter | In | Required | Description |
|---|---|---|---|
grant_id | path | Yes | The grant_id returned with the access token |
from | query | Yes | First date, YYYY-MM-DD, not before history_from (or the user's Walk15 registration day) |
to | query | Yes | Last date, inclusive. At most 30 days after from (31 dates) and not later than tomorrow in UTC |
curl "$WALK15_ISSUER/connect-walk15/v1/grants/22222222-2222-4222-8222-222222222222/daily-activity?from=2026-10-10&to=2026-10-12" \
-H "Authorization: Bearer $WALK15_ACCESS_TOKEN"Response 200
{
"data": [
{ "date": "2026-10-10", "steps": 8412, "updated_at": null },
{ "date": "2026-10-12", "steps": 0, "updated_at": null }
],
"meta": {
"from": "2026-10-10",
"to": "2026-10-12",
"date_basis": "walk15_recorded_date",
"source": "walk15",
"snapshot_at": "2026-10-12T18:04:11.920Z"
}
}| Field | Description |
|---|---|
data[].date | Walk15 recorded calendar date; ascending, at most one row per date |
data[].steps | Non-negative integer: the recorded total plus any Walk15 support correction, never below zero |
data[].updated_at | Always null in V1 — no reliable source-update time is exposed |
meta.from, meta.to | The requested range |
meta.date_basis | walk15_recorded_date |
meta.source | walk15 |
meta.snapshot_at | When Walk15 produced this response (UTC) |
In the example, 11 October is absent: Walk15 has no step record for that date. Absence is not zero.
Errors: 400 invalid_request, 400 invalid_date_range, 401 invalid_access_token, 403 insufficient_scope, 404 not_found, 429 rate_limited, 503 data_unavailable.
Daily cycling
/ connect-walk15/ v1/ grants/ {grant_id}/ cycling-dailyBackend onlyReturns daily cycling distance per recorded start date. Authentication: Bearer access token. Scope: cycling_daily:read.
| Parameter | In | Required | Description |
|---|---|---|---|
grant_id | path | Yes | The grant_id returned with the access token |
from | query | Yes | First date, not before history_from |
to | query | Yes | Last date, inclusive; at most 31 dates; not later than tomorrow in UTC |
curl "$WALK15_ISSUER/connect-walk15/v1/grants/22222222-2222-4222-8222-222222222222/cycling-daily?from=2026-10-10&to=2026-10-16" \
-H "Authorization: Bearer $WALK15_ACCESS_TOKEN"Response 200
{
"data": [
{ "date": "2026-10-11", "distance_meters": 18240, "source": "walk15", "updated_at": null },
{ "date": "2026-10-14", "distance_meters": 6105, "source": "walk15", "updated_at": null }
],
"meta": {
"from": "2026-10-10",
"to": "2026-10-16",
"date_basis": "walk15_recorded_date",
"snapshot_at": "2026-10-16T08:30:00.000Z"
}
}| Field | Description |
|---|---|
data[].date | Start date of the cycling activities that make up the total |
data[].distance_meters | Non-negative integer metres (not kilometres), including Walk15 support adjustments for that date. An adjustment alone never creates a date. |
data[].source | walk15 |
data[].updated_at | Always null in V1 |
meta | from, to, date_basis (walk15_recorded_date), snapshot_at |
Errors: 400 invalid_request, 400 invalid_date_range, 401 invalid_access_token, 403 insufficient_scope, 404 not_found, 429 rate_limited, 503 data_unavailable.
Cycling activities
/ connect-walk15/ v1/ grants/ {grant_id}/ cycling-activitiesBackend onlyReturns individual cycling activities whose recorded start date is in the range, oldest first, with cursor pagination. Authentication: Bearer access token. Scope: cycling_activities:read.
| Parameter | In | Required | Description |
|---|---|---|---|
grant_id | path | Yes | The grant_id returned with the access token |
from | query | Yes | First date, not before history_from |
to | query | Yes | Last date, inclusive; at most 31 dates; not later than tomorrow in UTC |
limit | query | No | Page size 1–500, plain decimal without leading zeros. Default 100 |
cursor | query | No | meta.next_cursor from the previous page, unchanged |
curl "$WALK15_ISSUER/connect-walk15/v1/grants/22222222-2222-4222-8222-222222222222/cycling-activities?from=2026-10-10&to=2026-10-16&limit=100" \
-H "Authorization: Bearer $WALK15_ACCESS_TOKEN"Response 200
{
"data": [
{
"id": "ca_Q2xKw9mN3pR7sT1vX5yZ8aB4cD6eF0gH2iJ4kL6mN8o",
"date": "2026-10-11",
"distance_meters": 18240,
"recorded_start_time": "2026-10-11T07:42:10.000000",
"recorded_end_time": "2026-10-11T08:31:55.000000",
"timezone": null,
"manual": null,
"granularity": "unknown",
"source": "walk15",
"updated_at": null
}
],
"meta": {
"from": "2026-10-10",
"to": "2026-10-16",
"date_basis": "walk15_recorded_date",
"time_basis": "walk15_recorded_clock",
"snapshot_at": "2026-10-16T08:30:00.000Z",
"next_cursor": "cc1_<opaque-cursor>"
}
}| Field | Description |
|---|---|
id | Opaque activity ID (ca_…), stable for this activity, user and your company |
date | Recorded start date |
distance_meters | Non-negative integer metres for this activity (support adjustments are not allocated to activities) |
recorded_start_time | Unzoned recorded clock YYYY-MM-DDTHH:mm:ss.ffffff — not UTC; never append Z |
recorded_end_time | Same format, or null when unknown or invalid |
timezone, manual, updated_at | Always null in V1 |
granularity | Always unknown |
source | walk15 |
meta.time_basis | walk15_recorded_clock |
meta.next_cursor | Cursor for the next page, or null on the last page |
Pagination. Request the next page with the same from, to and limit plus cursor. Omitting limit and sending limit=100 are equivalent. A traversal expires 15 minutes after its first page, and a cursor stops working when the connection version changes; restart from the first page on 400 invalid_cursor. Results reflect live data, so rows changed during a traversal can be missed or repeated — upsert by id. See Following cursors.
Errors: 400 invalid_request (including a bad limit), 400 invalid_date_range, 400 invalid_cursor, 401 invalid_access_token, 403 insufficient_scope, 404 not_found, 429 rate_limited, 503 data_unavailable.
Disconnect grant
/ connect-walk15/ v1/ customer/ grants/ {grant_id}Backend onlyEnds a user's connection to your company: all codes and tokens of the connection stop working for new requests, a connection.revoked webhook is sent, and any future connection needs fresh consent and starts a new access window. Authentication: HTTP Basic. No query string and no body.
curl -X DELETE "$WALK15_ISSUER/connect-walk15/v1/customer/grants/22222222-2222-4222-8222-222222222222" \
-u "$WALK15_CLIENT_ID:$WALK15_CLIENT_SECRET"Response 204 with no body — for owned, already-revoked, unknown and other customers' grants alike, so it is safe to retry and reveals nothing about other companies.
Errors: 400 invalid_request (not a UUID, query string or body present), 401 invalid_client, 500 server_error.
Remove the grant from your system only after a 204. If the outcome is uncertain, stop using the connection's tokens and retry the request.
OpenAPI document
A machine-readable OpenAPI 3.1 document describes the backend HTTP endpoints above (discovery, token, revoke, activity reads and disconnect). The browser authorization flow is described on this page and in OAuth and PKCE rather than as an API operation you call.
Not part of the partner API
The Walk15-hosted sign-in, session and consent pages that the browser passes through after /oauth/authorize, and the endpoints the Walk15 app uses to list and disconnect a user's connected companies, are Walk15-owned surfaces. Do not call or automate them from your backend; follow the browser redirects instead.