Skip to content
Dashboard

Platform API

The Platform API lets your own backend manage Detour resources and pull analytics without going through the Dashboard. It covers three areas:

  • short links, the parameters they carry, and their link metadata,
  • the app’s link settings,
  • analytics exports in JSON, CSV, or XLSX.

Every endpoint is authenticated with a secret API key. The base URL is https://api.godetour.link.

Each request carries two headers: a secret API key as a Bearer token, and the ID of the app it applies to. A request missing either one is rejected with 401.

Detour provides two types of API keys:

Key TypePurposeUse Case
PublishableIdentify your app, used in SDKsClient-side SDK implementations
SecretFull platform management & administrationServer-side API calls, automation

Secret keys are issued per app, from the API configuration tab of the app you want to manage:

  1. Open the app in the Dashboard and go to API configuration.
  2. In the Secret API Key card, click Generate secret key.
  3. Copy the key from the dialog and store it somewhere safe.

The card afterwards shows only whether a key is active and when it was generated. To replace a key, use Regenerate secret key: the previous key is revoked the moment the new one is issued, so any server-side integration still using the old key stops working immediately. Plan the rollout before you regenerate. Generating and regenerating keys requires the Owner or Admin role.

Include both headers in every request:

Authorization: Bearer <SECRET_API_KEY>
X-App-ID: <YOUR_APP_ID>
  • Authorization — the app’s secret key, as a Bearer token.
  • X-App-ID — the app’s ID (a UUID), from the same API configuration tab. A value that is not a UUID is rejected with 401.
Example request
Terminal window
curl -X GET https://api.godetour.link/api/manage/short-links \
-H "Authorization: Bearer 1234567890abc..." \
-H "X-App-ID: your-app-id"

Short links wrap the app’s main link together with a path and query parameters, which the SDK returns to your app after a deferred or direct open. Parameters added to the short link URL, for example fbclid added by Facebook, are added to the saved ones. See Parameters added by social apps.

A short link can have its own link metadata: the title, description, and images that social apps and messaging apps show when the link is shared. The metadata field holds only the values the short link sets. A field left empty uses the app link’s value from Link settings → Your app link.

A field the short link sets also replaces the app link fields that the page would otherwise show in its place:

  • A title replaces the app link’s Open Graph and Twitter/X titles.
  • A description replaces the app link’s Open Graph and Twitter/X descriptions.
  • An Open Graph image replaces the app link’s Twitter/X image, so X shows the Open Graph image.
title
Type string

Up to 70 characters.

description
Type string

Up to 160 characters.

openGraph
Type object

title (up to 95 characters), description (up to 200 characters), url, and images, an array of one { url, alt }. alt (up to 420 characters) requires url.

twitter
Type object

card (summary or summary_large_image), title (up to 70 characters), description (up to 200 characters), and images, an array of one { url }.

Every URL must be a full http or https URL of up to 2048 characters. Detour removes invisible characters from URLs, such as U+200B or U+FFFC, before it checks them. It trims text values, stores only the fields that are set, and drops unknown fields. A short link without its own values returns metadata as null.

{
"title": "Invite your friends",
"openGraph": {
"images": [
{ "url": "https://example.com/friends.png", "alt": "Two friends" }
]
}
}

GET /api/manage/short-links

Returns every short link belonging to the app, newest first. The endpoint takes no query parameters.

Status codes — 200, 401, 429, 500

Example request
Terminal window
curl -X GET https://api.godetour.link/api/manage/short-links \
-H "Authorization: Bearer a1b2c3d4e5f6..." \
-H "X-App-ID: my-app"
Example response
[
{
"id": "short_link_uuid",
"hash": "abc123",
"url": "https://acme-corp.godetour.link/abc123",
"parameters": "/summer?utm_campaign=summer_sale",
"name": "Summer Sale Campaign",
"metadata": null,
"link_id": "link_uuid",
"created_at": "2026-06-16T10:30:00.000Z"
}
]

POST /api/manage/short-links

Creates a short link on the app’s main link and returns it, including the public URL.

Request body

{
"parameters": "/summer?utm_campaign=summer_sale&utm_source=email",
"name": "Summer Sale Email Campaign",
"metadata": { "title": "Summer Sale" }
}

Required fields

parameters required
Type string

The path and query the short link adds to the app link, for example /summer?utm_campaign=summer_sale. Cannot be empty. Up to 16384 characters.

Optional fields

name
Type string

User-friendly name for the short link.

metadata
Type object

The short link’s link metadata. Leave it out to use the app link’s.

Status codes — 201, 400, 401, 402, 404, 429, 500

  • 400 — parameters is missing, or metadata is invalid.
  • 402 — the Free plan limit of 50 short links per app is reached. Paid plans have no limit.
  • 404 — the app has no link yet.
Example request
Terminal window
curl -X POST https://api.godetour.link/api/manage/short-links \
-H "Authorization: Bearer a1b2c3d4e5f6..." \
-H "X-App-ID: my-app" \
-H "Content-Type: application/json" \
-d '{
"parameters": "/summer?utm_campaign=summer_sale",
"name": "Summer Sale",
"metadata": { "title": "Summer Sale" }
}'
Example response
{
"id": "short_link_uuid",
"hash": "abc123",
"url": "https://acme-corp.godetour.link/abc123",
"parameters": "/summer?utm_campaign=summer_sale",
"name": "Summer Sale",
"metadata": { "title": "Summer Sale" },
"link_id": "link_uuid",
"created_at": "2026-06-16T10:35:00.000Z"
}

PATCH /api/manage/short-links

Changes the fields of an existing short link that the body contains, and returns the short link. The hash and URL stay the same, so links already distributed keep working.

Request body

{
"id": "short_link_uuid",
"metadata": { "title": "Fall Sale" }
}

Required fields

id required
Type string

UUID of the short link to update.

Optional fields

A field left out keeps its stored value. null clears name and metadata. parameters cannot be cleared. The body must contain at least one of them.

parameters
Type string

The new path and query, for example /fall?utm_campaign=fall_sale. Cannot be null or empty. Up to 16384 characters.

name
Type string | null

The new name. null or "" clears the name.

metadata
Type object | null

The new link metadata. It replaces the stored metadata as a whole. null removes it, so the short link uses the app link’s.

Status codes — 200, 400, 401, 404, 429, 500

  • 400 — the body has no field to change, id is not a UUID, or a field is invalid.
  • 404 — the app has no short link with this id.
Example request
Terminal window
curl -X PATCH https://api.godetour.link/api/manage/short-links \
-H "Authorization: Bearer a1b2c3d4e5f6..." \
-H "X-App-ID: my-app" \
-H "Content-Type: application/json" \
-d '{
"id": "short_link_uuid",
"metadata": { "title": "Fall Sale" }
}'
Example response
{
"id": "short_link_uuid",
"hash": "abc123",
"url": "https://acme-corp.godetour.link/abc123",
"parameters": "/summer?utm_campaign=summer_sale",
"name": "Summer Sale",
"metadata": { "title": "Fall Sale" },
"link_id": "link_uuid",
"created_at": "2026-06-16T10:35:00.000Z"
}

Every app has one main link. Its settings decide where a click is redirected and which parameters are forwarded.

GET /api/manage/links

Returns the app’s main link: its destination, its fallback, and how query parameters are passed through. The endpoint takes no query parameters.

Response fields

id
Type string

UUID of the link.

url
Type string

Primary redirect URL.

redirect_url
Type string

Fallback redirect URL.

params_pass_strategy
Type string

How parameters are passed: "all", "none", or "specific"

specific_params
Type array | null

If set, only these parameters are passed through.

created_at
Type string

Link creation timestamp (ISO 8601)

app_hash
Type string

Hash identifier used in short link URLs.

Status codes — 200, 401, 404, 429, 500

  • 404 — the app has no link.
Example request
Terminal window
curl -X GET https://api.godetour.link/api/manage/links \
-H "Authorization: Bearer a1b2c3d4e5f6..." \
-H "X-App-ID: my-app"
Example response
{
"id": "link_uuid",
"url": "https://example.com",
"redirect_url": "https://fallback.example.com",
"params_pass_strategy": "all",
"specific_params": null,
"created_at": "2026-06-16T10:00:00.000Z",
"app_hash": "x9y8z7"
}

GET /api/analytics/stats

Exports the same figures the Dashboard’s Analytics section shows, over a date range you choose. The response is always sent as a file download, in the format given by format.

Query parameters

type required
Type string

Data type: "overview", "links", or "events"

startDate required
Type string

Start of date range (ISO 8601, for example 2026-06-01T00:00:00Z)

endDate required
Type string

End of date range (ISO 8601, for example 2026-06-30T23:59:59Z)

format
Type string Default json

Export format: "json", "csv" or "xlsx"

platform
Type string Default all

Filter by platform: "all", "ios", or "android"

linkId
Type string

UUID of a specific link to scope the query. Defaults to the main app link. Relevant for "links" and "overview" types, ignored for "events"

limit
Type number | "all" Default 1000

Rows per page, 1–1000, or "all" for the complete set in one response. type=links and type=events only.

offset
Type number Default 0

Row offset for paging. Not accepted with limit=all. type=links and type=events only.

envelope
Type boolean Default false

format=json only. Wraps the array as { meta, rows } instead of returning a bare array.

paramKeys
Type string

Comma-separated parameter names to keep in the breakdown. type=links only.

excludeParams
Type string

Comma-separated parameter names to hide from the breakdown. Their clicks still count. type=links only.

Click-level filters are listed separately under Filtering link analytics.

Plan requirements

typeRequired Plan
overviewFree
linksPaid
eventsPaid

Response — one row per day, with the columns for the requested type:

typeColumns
overviewdate, total_clicks, already_installed_opens, fallback_redirects, organic_installs, non_organic_installs, top_event, retention_rate
linksdate, short_link, path, param_key, param_value, clicks, matched_installs, fallbacks, already_installed_opens
eventsdate, dimensions (event_name, platform, is_retention), metrics (event_count, unique_devices)

CSV and XLSX exports flatten dimensions and metrics into plain columns.

A links export mixes two kinds of row:

RowHow to recognize itWhat it holds
Path rowparam_key and param_value are ""Totals for one date, short link, and path
Parameter rowparam_key is setOne parameter value inside that path

Path rows and parameter rows both carry already_installed_opens, the taps that opened an installed app directly through Universal Links or App Links. These opens are included in clicks:

  • clicks — all opens
  • clicks - already_installed_opens — web opens only

An open is split into parameter rows the same way a web click is, and filters apply to it. Its short_link is empty when the tap was on a plain deep link, or when the open was recorded before Detour began attributing these opens.

Summed this way, type=links matches type=overview and the Dashboard for the same app, period, and platform.

200, 400, 401, 402, 403, 404, 429, 500, 503

  • 400 — type is missing or invalid, the date range is longer than 120 days, or a filter or pagination parameter is out of range or does not apply to type.
  • 402 — links and events require a paid plan.
  • 403 — linkId does not belong to this app.
  • 404 — the app was not found. Checked for links and events only.
  • 503 — the analytics query timed out. Narrow the date range, or drop high-cardinality parameters with excludeParams.

Error bodies on this endpoint carry a machine-readable code alongside error, and sometimes a hint:

{
"error": "Analytics query timed out",
"code": "query_timeout",
"hint": "Narrow the date range, or use paramKeys/excludeParams to drop high-cardinality parameters such as fbclid."
}

type=links and type=events are paged. The default page is 1,000 rows, and the response always reports whether more rows exist, so a truncated result is always signalled.

Every format carries the same metadata in response headers, because CSV and XLSX cannot carry a response envelope:

HeaderMeaning
X-Total-RowsRows in the full result set. Absent when the page is empty.
X-Returned-RowsRows in this response
X-Row-OffsetOffset this page started at
X-Row-LimitPage size used. Absent for limit=all
X-Truncatedtrue when more rows follow
Link<next page url>; rel="next", when more rows follow

All of them are listed in Access-Control-Expose-Headers, so a browser-based consumer reading the API cross-origin can see them.

With envelope=true and format=json the same values also come back in the body:

{
"meta": {
"total_rows": 4821,
"returned_rows": 1000,
"limit": 1000,
"offset": 0,
"truncated": true,
"next_offset": 1000
},
"rows": [...]
}

Follow next_offset, or the Link header, until truncated is false.

limit=all returns the complete set in a single response instead. It is opt-in because a large result takes longer to answer than a page, and it rejects offset.

Two independent filter layers apply to type=links, and they compose. Both are rejected with 400 on type=overview and type=events rather than silently ignored. A dropped filter would read as “no clicks matched”, which is indistinguishable from a real answer.

Row filters: paramKeys and excludeParams. These decide which parameter rows come back. paramKeys keeps only the listed parameters, and excludeParams hides the listed ones. Neither removes a click. Path rows always come back and still count every click, so a path row is not the sum of the parameter rows you kept. Use row filters to keep a high-cardinality parameter such as fbclid from filling the whole page:

Terminal window
curl -X GET "https://api.godetour.link/api/analytics/stats?type=links&startDate=2026-06-01T00:00:00Z&endDate=2026-06-30T23:59:59Z&excludeParams=fbclid,gclid" \
-H "Authorization: Bearer a1b2c3d4e5f6..." \
-H "X-App-ID: my-app"

Click filters. These decide which clicks are counted, so they change every number in the response, path rows included. They are the same filters as the filter bar in Dashboard → Analytics → Links. There are four dimensions, path, param_name, param_kv, and param_value, each usable with the operators is, is_not, contains, and starts_with. The filter bar labels them is, is not, contains, and starts with. Shorthand parameters avoid URL-encoding JSON:

ParameterOperator parameterDimensionDashboard filter
filterPathfilterPathOppathPath
filterParamNamefilterParamNameOpparam_nameParam name
filterParamValuefilterParamValueOpparam_valueParam value
filterParam.<key>filterParamOpparam_kvParam key = value

Comma-separated values inside one filter are combined with OR. Separate filter parameters are combined with AND. The operator defaults to is. filterParamOp applies to every filterParam.<key> in the request. To use different operators on two filterParam.<key> filters, use the JSON form below.

For the same dimension and values, is_not keeps exactly the clicks that is drops, so the two counts add up to the unfiltered total. With several values, it keeps the clicks that match none of them. A click that does not carry the parameter is kept. For example, filterParam.utm_source=facebook with filterParamOp=is_not also keeps the clicks that have no utm_source. The exception is an imported install without a link path, which every active click filter drops, is_not included.

Terminal window
curl -X GET "https://api.godetour.link/api/analytics/stats?type=links&startDate=2026-06-01T00:00:00Z&endDate=2026-06-30T23:59:59Z&filterPath=/checkout&filterPathOp=starts_with&filterParam.utm_source=facebook" \
-H "Authorization: Bearer a1b2c3d4e5f6..." \
-H "X-App-ID: my-app"

The same filters can be sent as JSON in a single filters parameter, as an array of { "dimension": { "key": … }, "operator": …, "values": [ … ], "paramKey": … } objects. operator takes the same four values. Use that form when a value contains a comma, which the shorthand cannot express.

At most 20 filters and 200 filter values may be combined in one request.

The table below compares row filters and click filters on four clicks on /shop:

/shop?color=red&id=111
/shop?color=red&id=222
/shop?color=blue&id=333
/shop
RowNo filterparamKeys=colorexcludeParams=idfilterParamName=id
/shop (path row)4443
color=red2222
color=blue1111
id=1111——1
id=2221——1
id=3331——1

paramKeys and excludeParams hide parameter rows only, so the path row still counts all four clicks. filterParamName=id removes the fourth click before counting, so the path row counts 3.

More example requests

Get overview analytics:

Terminal window
curl -X GET "https://api.godetour.link/api/analytics/stats?type=overview&startDate=2026-06-01T00:00:00Z&endDate=2026-06-30T23:59:59Z" \
-H "Authorization: Bearer a1b2c3d4e5f6..." \
-H "X-App-ID: my-app"

Export link analytics as CSV (paid):

Terminal window
curl -X GET "https://api.godetour.link/api/analytics/stats?type=links&format=csv&startDate=2026-06-01T00:00:00Z&endDate=2026-06-30T23:59:59Z" \
-H "Authorization: Bearer a1b2c3d4e5f6..." \
-H "X-App-ID: my-app" \
-o analytics.csv

Filter by platform:

Terminal window
curl -X GET "https://api.godetour.link/api/analytics/stats?type=events&platform=ios&startDate=2026-06-01T00:00:00Z&endDate=2026-06-30T23:59:59Z" \
-H "Authorization: Bearer a1b2c3d4e5f6..." \
-H "X-App-ID: my-app"

Detour limits requests per app and per minute, with a separate limit for each endpoint.

A 429 response carries Retry-After (seconds), X-RateLimit-Remaining and X-RateLimit-Reset. Wait for Retry-After before retrying.


All errors return a JSON response with an error field. Each endpoint above lists the codes it returns and explains the ones specific to it. A 400 for an invalid request body can also carry issues, a list of path and message pairs, one for each problem. path names the field, and is empty for a problem with the body as a whole.

StatusMeaning
400Invalid request body
401Missing or invalid API key / App ID
402Plan limit reached, upgrade required
403Resource belongs to another app
404Resource not found
429Rate limit exceeded, back off and retry
500Server error
503Analytics query timed out