← Back to Help Center

What the API does

Trakr already stores every generated link with its full context: source, medium, campaign, content, term, custom parameters, and the predicted GA4 channel. The API makes that store queryable from outside the app. What you can ask it:

Reading never changes anything, and deleted links (in Recently deleted or purged) never appear in responses. Writing is included too: every key carries read and write permissions, and the fair-use limits below are the only boundary. The write endpoints are documented in their own section further down.

Credentials: API keys

Authentication uses account-level API keys managed in the app under Settings → API. Creating and revoking keys requires a Pro account and the owner or admin role.

Key scope: account-wide by default, or scoped to one workspace. A standard Trakr API key reads every link your account holds, across all client workspaces if you use them. If you run client workspaces, you can instead scope a key to a single workspace when you create it (Settings → API → Key scope): a scoped key reads and writes only that workspace, resolves that workspace's default configuration first, and stops working if the workspace is archived or paused for payment. Hand a client-facing system a scoped key so it can never see another client's data; keep account-wide keys for your own infrastructure.

  1. Open Settings → API, give the key a name that says where it will live (for example sGTM production), and click Create key.
  2. Copy the key immediately. It starts with trakr_live_ and is shown exactly once: Trakr stores only a hash, so nobody (including us) can display it again.
  3. Store it in your secrets manager, never in client-side code. The key grants read access to your whole account's link history.
The API settings page with the key creation form, the key scope selector, and the active keys list
Settings → API: create keys, pick their scope, and manage the active ones.

Send the key on every request in the Authorization header:

Authorization: Bearer trakr_live_your_key_here

Lost a key? Revoke it on the same page (it stops working immediately) and create a new one. Your account can hold up to 10 active keys, so rotation without downtime is easy: create the new key first, switch your systems over, then revoke the old one.

Endpoints

Base URL:

https://dabnppsmamvzwneuzmha.supabase.co/functions/v1/trakr-api
EndpointWhat it returns
GET /v1/links/{id}One link by its Trakr ID. The exact-lookup path: use this when your URL carries a per-link identifier.
GET /v1/linksLinks matching the query filters, newest first. Filters: campaign_id, utm_source, utm_medium, batch_id, since, until (ISO dates). Pagination: page (from 1) and per_page (default 50, max 100); the response includes has_more.
GET /v1/campaignsCampaign summaries: each distinct campaign ID with its campaign name, link count, sources, mediums, and first/last created dates. limit caps the list (default 100, max 500).
GET /v1/configThe default configuration for your key's scope: { "data": { "id", "name", "mode", "updated_at", "config" } }, where config is the full configuration document your team built in the app: field IDs, syntax structures and separators, channel definitions, custom parameters. A workspace-scoped key resolves its workspace's default first and falls back to the account default; account-wide keys resolve the account default. When the resolved default is workspace-owned, the response also carries its workspace_id. Requires a shared default to be starred (the star in the config dropdown), or the endpoint answers 404 with instructions.
GET /v1/workspacesYour workspaces: { "data": [ { "id", "name", "created_at", "archived", "blocked" } ] }. A workspace-scoped key sees only its own workspace. Use the ids here to scope configurations and file links.

Example: fetch all links for one campaign ID.

curl -H "Authorization: Bearer trakr_live_..." \
  "https://dabnppsmamvzwneuzmha.supabase.co/functions/v1/trakr-api/v1/links?campaign_id=nl-summer26-social&per_page=10"

The write API

Everything a marketer does in the builder, your systems can do through the same governance layer: the write endpoints run every request through the exact validation, syntax rules, Auto ID minting, and GA4 channel prediction the app itself uses. Every Trakr API key carries the write permission; there is nothing to request and nothing extra to pay.

EndpointWhat it does
POST /v1/linksCreate one link. Body: { "field_values": { ... }, "source": "...", "medium": "..." }, where field_values is keyed by the configuration's field IDs (discover them via GET /v1/config or GET /v1/configs/{id}). Two optional extras: config_id generates against that specific configuration instead of the scope default, and workspace_id files the link in that workspace. When you pick a workspace-owned configuration, the link is filed in its workspace automatically, so the client team sees it in their library. Empty Auto ID fields are minted server-side. Returns the created link in the same shape as GET /v1/links, status 201.
POST /v1/batchesBulk creation: { "name": "...", "rows": [ ... ] } with up to 500 rows, each shaped like a single-link body. config_id and workspace_id work exactly as on single links and apply to the whole batch. Validation is all-or-nothing: one invalid row rejects the whole batch with per-row errors and nothing is created. Returns the batch plus every created link.
PATCH /v1/links/{id}Edit a link: the changes merge onto its stored form state and the URL regenerates through the full pipeline, using the configuration that originally built the link when it is still available (pass config_id to regenerate under a different one). The link keeps its ID, its Auto ID, and its workspace. Returns the updated link.
DELETE /v1/links/{id}Soft delete: the link moves to Recently deleted with the same 7-day restore window the app has. There is no hard delete via the API.
DELETE /v1/batches/{id}Soft-deletes the batch's links. The batch record itself remains as an empty container.
GET /v1/batchesList your batches, newest first, paginated like GET /v1/links.
POST /v1/workspacesCreate a workspace: { "name": "..." }. Works within the number of workspaces your account has purchased; at the ceiling the request answers workspace_limit_reached with your current count and maximum (buying more stays in the app under Settings → Workspaces). Requires an account-wide key.

Changed on 2026-07-14: the landing page URL follows your configuration. Whether baseUrl is required now comes from the configuration document, like every other field. Configurations that set the Landing Page URL to Optional or Off (a Syntax Builder global setting) accept requests without it and generate a bare parameter string, such as utm_id=abc123, ready for a Google Ads Final URL suffix; a configuration setting can add a leading question mark. For such links base_url is omitted from responses and full_url carries the parameter string. Nothing changes for existing configurations, where the URL stays required, and a provided baseUrl must still be a valid http(s) URL. Two companions shipped the same day: link responses now carry field_values, the per-field breakdown described in the response table below, and empty optional fields now always keep their position in generated values, as a doubled separator (a_b__d) or, with the Syntax Builder's Empty Field Placeholder setting, as a readable token (a_b_none_d). The value's split count never changes, so positional consumers can trust their indexes. And utm_content now appears in URLs only when it has a value: the GA4-required trio (utm_source, utm_medium, utm_campaign) stays in every classic-mode URL, while content follows your configuration like every other optional parameter.

Idempotency: required on every write

Every write must carry an Idempotency-Key header (any string up to 200 characters, unique per logical request). Retrying with the same key and the same payload returns the original response without creating anything twice; the same key with a different payload is refused with 409. Keys expire after roughly 24 hours.

Write limits

Writes are limited by generated URLs, not requests: 60 URLs per minute per key (a batch of 50 counts as 50), and generated URLs also count against the shared daily pool. Exceeding a limit returns 429 with a Retry-After header; retry with the same Idempotency-Key.

Configuration management

The write API also covers the governance layer itself, so an integration can stand up or maintain a team's tagging rules programmatically, at both levels: account-level configurations (shared with every workspace) and workspace-owned configurations (shared with one client workspace only). API-created configurations default to shared visibility so the whole team sees them. A private configuration belonging to another user stays invisible to your key and cannot be changed through the API. Every persisted change runs through the same structural validation the app enforces; a document with errors is refused with the full list, and warnings come back alongside the saved result.

EndpointWhat it does
GET /v1/configsList the configurations your key can see (shared ones plus the key creator's own), without the full documents. Workspace-owned rows carry their workspace_id; account-level rows omit it.
GET /v1/configs/{id}One configuration including its full document, in the same shape GET /v1/config uses.
POST /v1/configsCreate a configuration: { "name": "...", "config": { ... }, "visibility": "shared", "default": false }. Add "workspace_id" to create it workspace-owned (the workspace must exist and be live), and "cloned_from" to record which configuration it was copied from. Setting "default": true makes it the default of its own scope: an account-level row becomes the account default, a workspace-owned row becomes that workspace's default, replacing only that scope's previous default in the same request.
PATCH /v1/configs/{id}Update the name, visibility, document, or default flag. A configuration's scope is fixed at creation (to move one into a workspace, read its document and POST a copy with workspace_id and cloned_from). Making a configuration private clears its default flag, and a private configuration cannot become a default.
POST /v1/configs/validateDry run: validates a configuration document without saving anything and returns { "valid", "errors", "warnings" }. This is the one write endpoint that needs no Idempotency-Key, because it changes nothing.

What to expect: responses and schema

Every response is JSON. Single lookups return { "data": { ...link } }, lists return { "data": [ ...links ], "page": 1, "per_page": 50, "has_more": false }. A link object can carry these fields. Fields without a value are omitted entirely, so check for key presence rather than comparing against null:

FieldMeaning
idTrakr's unique ID for this link. Stable, never reused.
created_atISO timestamp of generation.
base_url, full_urlThe landing page and the complete tagged URL.
utm_source, utm_medium, utm_campaign, utm_content, utm_termThe five classic UTM values as generated.
campaign_idThe generated utm_id value. This is the lookup key for the sGTM pattern below.
source_platform, creative_format, marketing_tacticThe extended GA4 parameters, when your configuration uses them.
country, campaign_descriptionContext captured in the builder.
ga4_channelThe GA4 channel Trakr predicts for this source/medium pair.
custom_paramsObject with your custom parameters and their values.
batch_idSet when the link came from a bulk generation; all rows of one batch share it.
config_idThe configuration that generated this link. Edits regenerate under the same configuration while it exists.
field_valuesThe link's building blocks keyed by field name, exactly as entered: { "productName": "aj+", "country": "dach", "productPhase": "", ... }. This is the machine-readable form of the link. An empty string means the field was part of the link and left unset (the one place empty values are kept on purpose); a missing key means the field never entered this link. Read values from here by name instead of splitting utm_campaign by position.

Example response for a single link:

{
  "data": {
    "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "created_at": "2026-07-09T10:15:00.000Z",
    "base_url": "https://example.com/summer",
    "full_url": "https://example.com/summer?utm_source=facebook&utm_medium=cpc&utm_campaign=nl.summer26&utm_id=nl-summer26-social",
    "utm_source": "facebook",
    "utm_medium": "cpc",
    "utm_campaign": "nl.summer26",
    "utm_content": "carousel.a",
    "campaign_id": "nl-summer26-social",
    "country": "nl",
    "campaign_description": "Summer 2026 launch",
    "ga4_channel": "Paid Social",
    "custom_params": {}
  }
}

Changed on 2026-07-12: unconfigured fields are now omitted. Responses previously carried every field, with null or "" for values your configuration never set ("utm_term": null, "source_platform": null, and so on). They are now left out entirely. If your integration reads fields unconditionally, switch to key-presence checks. Cached responses served the old shape for up to 5 minutes around the cutover.

Errors

Errors follow one shape: { "error": "code", "message": "human readable" }. The message always says what to do next. Every code you can meet:

CodeHTTPMeaning and what to do
unauthorized401Missing, unknown, revoked, or expired key. Check the Authorization header and the key's state on Settings → API.
forbidden403The account's subscription is inactive, the key's workspace is paused or archived, or a workspace-scoped key called a configuration write (those need an account-wide key). The message names the exact reason.
unknown_endpoint404The path is not a Trakr API endpoint. The message lists every valid endpoint for the method you used.
not_found404No such link, batch, or configuration within your key's scope. Inaccessible rows answer exactly like unknown ids.
method_not_allowed405The endpoint exists but not for this HTTP method (for example GET on /v1/configs/validate, which is POST only).
invalid_json400The request body is not valid JSON.
idempotency_key_required400Writes need an Idempotency-Key header (1 to 200 characters, unique per logical request).
idempotency_conflict409The same Idempotency-Key was already used with a different payload. Use a fresh key for a different request.
request_in_flight409A request with this Idempotency-Key is still being processed. Wait for the Retry-After and retry with the same key.
outcome_unknown409 / 500An earlier attempt with this key started executing but its result could not be recorded. Verify via GET before doing anything else; retrying with the same key returns this status, and a NEW key is only safe after you confirmed the write did not apply.
validation_failed422The request failed the same validation the app enforces. details (or per-row rows on batches) lists every problem.
batch_too_large422A batch can create at most 500 links per request. Split the rows and send multiple batches.
no_default_config422No default configuration is set for the key's scope. Star a shared configuration as the default in the app (or via PATCH /v1/configs/:id), pass an explicit config_id, then retry.
config_invalid422The resolved configuration fails validation and cannot generate links. POST /v1/configs/validate shows the errors.
config_not_found422The config_id you passed does not name a configuration this key can use. GET /v1/configs lists the usable ones.
workspace_limit_reached422Your account already holds its purchased number of workspaces. Purchase another under Settings → Workspaces in the app, then retry.
link_not_editable422The link predates stored form state and cannot be edited via the API. Recreate it instead.
rate_limited429A fair-use window is full. Wait for the Retry-After header and retry; on writes, retry with the SAME Idempotency-Key.
temporarily_unavailable503The idempotency store had a hiccup. Retry with the same Idempotency-Key after the Retry-After.
internal_error500Something failed on our side before anything was written. Retry with the same Idempotency-Key; if it persists, write to support@trakr.studio.

Rate limits and caching

Fair-use limits protect the service for everyone:

Responses are built to be cached. Successful lookups carry Cache-Control: private, max-age=300 and an ETag; send If-None-Match and you get a body-less 304 when nothing changed. Misses (404) are cacheable for 60 seconds. Campaign data barely changes after launch, so a 5-minute cache on your side turns thousands of hits into a handful of lookups. If your genuine volume outgrows the limits, please contact support@trakr.studio for higher rate limits. We want you to get the most out of the API, and we will find limits that fit your volume.

The pattern: campaign IDs + server-side GTM

This is the use case the API was built around. The problem it solves: long UTM strings in every ad URL are fragile, leak your campaign taxonomy into the public URL, and drift when someone edits by hand. The alternative:

  1. Marketing builds every campaign link in Trakr, as usual. The configuration includes the utm_id parameter (an Auto ID field, or your own composed structure), so every link carries an ID.
  2. Tracking URLs carry only the ID. Instead of the full parameter set, the ad URL ships with just utm_id=nl-summer26-social. Short, stable, and nothing to mistype.
  3. Server-side GTM enriches the hit. When a tagged hit arrives, your sGTM container reads the ID, calls GET /v1/links?campaign_id=nl-summer26-social&per_page=1, takes data[0], and writes utm_source, utm_medium, and whatever else you need onto the GA4 event before it goes out.

The one design rule: the ID in the URL must identify a single link, not a whole campaign. A campaign in Trakr usually has one link per channel, all sharing the campaign name but each with its own source and medium. If the URL only says "summer campaign", the API cannot tell whether that click came from Paid Social or Email. Two ways to get there: add an Auto ID field to your Campaign ID structure in the Syntax Builder (Trakr then mints a unique 8-character ID for every link, no naming discipline required), or compose the structure from a campaign field plus a channel field (for example nl-summer26-social, nl-summer26-email). Either way the lookup is exact. The full setup, including the Lean URL profile, is on the Campaign IDs & Lean URLs page.

Lean URLs: emitting only the ID

The step-by-step marketing-side guide lives on the Campaign IDs & Lean URLs page; the short version:

Once your sGTM lookup is live, you can go all the way: in the Syntax Builder's Global Settings, switch URL Output from Full to Lean. Lean URLs carry only the Campaign ID (utm_id) plus the custom parameters you keep for client-side consumers (a CRM reading its parameter from the landing URL, for example). Source, medium, campaign, and content stay stored in Trakr and reach GA4 through the API lookup. A lean URL looks like this:

https://example.com/summer?utm_id=k7f3a2cq&mycrmparameter=dach_launch_awareness

One warning that matters: Lean only works when your tracking runs through server-side GTM with the lookup in place. Without it, GA4 receives hits with no source or medium at all. Trakr shows this warning when you switch, and flags a Lean config whose Campaign ID structure is empty.

Using stape's Supabase Lookup template

If your sGTM container runs on stape, their Supabase Lookup variable template gets you most of the way. Two notes from a production integration:

Two habits for anything in the tracking path

Common questions

Is the API included in Pro, or does it cost extra?

The full API, reading and writing, is included in the Pro plan at no extra charge. There is no API add-on and no per-call pricing: you pay for team seats and client workspaces, everything else is included. Fair use applies: 120 requests per minute per key, 60 generated URLs per minute per key on writes, and 100,000 requests per day per account. If your genuine volume outgrows them, please contact support@trakr.studio for higher rate limits.

Can I create links through the API?

Yes. POST /v1/links and POST /v1/batches create links through the exact validation and generation pipeline the app uses, and PATCH/DELETE cover the rest of the lifecycle. Every API key carries the write permission; it is part of Pro like everything else.

Can I manage configurations through the API?

Yes, at both levels. You can list, read, create, and update account-level and workspace-owned configurations, validate a document without saving it, and star the default each scope uses for API link generation. You can also create workspaces themselves, within the number your account has purchased. Every change runs through the same validation the app enforces, and another user's private configuration stays out of reach. Configuration and workspace writes need an account-wide key; workspace-scoped keys keep configuration access read-only.

Why are some fields missing from a response?

Fields your configuration never set are omitted from responses instead of being sent as null or an empty string (changed 2026-07-12). A missing key means the value is not configured for this link. Check for key presence in your integration rather than comparing values against null.

Why does a campaign ID lookup return several links?

A campaign in Trakr usually has one link per channel: the same campaign ID with different source and medium values for Paid Social, Email, Display, and so on. If you need the lookup to identify exactly one link, make your utm_id unique per link, for example by including a channel field in its structure.

What happens when I exceed the rate limit?

The API answers with HTTP 429 and a Retry-After header. Nothing breaks and nothing is billed; wait for the window to pass and retry. Responses are cacheable for 5 minutes, so a cache on your side keeps real traffic far below the limits.

I lost my API key. Can I see it again?

No. The full key is shown exactly once when you create it, and Trakr stores only a hash. If a key is lost, revoke it on the Settings API page and create a new one. Revocation is immediate.

Is the API fast enough to sit in my tracking request path?

Lookups are indexed and responses carry cache headers, but any network dependency in a tracking path needs two safety habits: cache lookups on your side for about 5 minutes, and fail open. If the API is ever unreachable, send the hit with the data you have instead of dropping it.

Open the App, free, no signup →