Documentation menu

Analytics API

The API exposes conversion analytics: totals, per-currency values, attribution by link, campaign and QR code, and goal reports. Click and page-view analytics are not part of the API.

What this covers

There is no /api/v1/analytics route. Clicks, page views, geography, devices and referrers are read in the analytics dashboard or taken from its CSV export on Growth plans and above. The analytics:read scope can be granted to a key but no endpoint requires it.

What the API does answer is the conversion side, through three read endpoints. All three require the conversions:read scope and spend from the shared limit of 60 requests per minute per key.

EndpointAnswers
GET /conversionsConversion totals for a window, broken down by event type and by managed object
GET /goalsThe goals saved in the workspace
GET /goals/{id}/reportOne goal measured over a window

Recording a conversion is a separate call, POST /api/v1/conversions, and needs the conversions:write scope. Attribution is server-side and works on managed objects: a conversion names the link, campaign or QR code it belongs to. Nothing here measures a browser, a visitor or a device.

Conversion summary

Totals for a window, optionally narrowed to one event type or one managed object. The workspace comes from the key; there is no parameter that can change it.

GET/api/v1/conversions

Query parameters

ParameterTypeRequiredDescription
fromstringNoISO 8601 start of the window. Default: 30 days before to.
tostringNoISO 8601 end of the window. Default: now.
eventTypestringNoOne of signup, lead, purchase, booking, download, custom
customNamestringNoNarrows custom events to one name. 40 characters or fewer.
linkIdstringNoRestrict to conversions naming this link
campaignIdstringNoRestrict to conversions naming this campaign
qrCodeIdstringNoRestrict to conversions naming this QR code

The window may not exceed 400 days. A longer one is refused with 400 rather than silently clamped, and the error body names the offending parameter in a field key.

Request: purchases in a date range
curl "https://mask.pk/api/v1/conversions?eventType=purchase&from=2026-03-01T00:00:00Z&to=2026-03-31T23:59:59Z" \
  -H "Authorization: Bearer mk_3f9a1c7e5b208d46a1f0c93e78b25d4610fa8c3729de154bb06e7a9f2c8d3510"
Response
{
  "data": {
    "windowFrom": "2026-03-01T00:00:00.000Z",
    "windowTo": "2026-03-31T23:59:59.000Z",
    "total": 128,
    "attributedConversions": 121,
    "unattributedConversions": 7,
    "byEventType": [
      { "eventType": "purchase", "label": "Purchase", "conversions": 128 }
    ],
    "byLink": [
      {
        "id": "clx1a2b3c4d5e6f7g8h9i0j1",
        "conversions": 94,
        "value": [{ "currency": "PKR", "valueMinor": 4820000, "conversions": 94 }],
        "rate": { "supported": true, "conversions": 94, "clicks": 1580, "rate": 0.0595 }
      }
    ],
    "byCampaign": [],
    "byQr": [],
    "value": [{ "currency": "PKR", "valueMinor": 6140000, "conversions": 121 }],
    "rate": { "supported": true, "conversions": 121, "clicks": 2040, "rate": 0.0593 },
    "unavailable": false
  }
}

Reading the response

Value is per currency and never summed across them. Each entry in value carries valueMinor in integer minor units, plus how many conversions actually carried a value in that currency. A conversion recorded without a value is not a conversion worth zero, so it is absent from the total and from that count.

Rate can be undefined, and says so. When it is, supported is false and reason is one of beyond_click_retention, no_clicks_in_window or clicks_unavailable. Do not substitute zero: the rate is two independent counts over one interval, not a cohort followed through time.

Unattributed conversions are counted separately. A conversion that named no link is in total and in unattributedConversions, and is excluded from every rate.

Metadata is never returned. Whatever a caller attached when recording a conversion is stored but not echoed back by this endpoint or any other.

Goals

A goal is a saved, named question about conversions that already exist. Reading goals is part of the API; creating and editing them is done in the dashboard, so there is no write endpoint and no goals:write scope.

GET/api/v1/goals

Takes one optional status parameter: ACTIVE, PAUSED or ARCHIVED. Anything else is ignored. The list carries definitions only; progress is a separate request per goal, because each report costs several aggregates.

Request
curl "https://mask.pk/api/v1/goals?status=ACTIVE" \
  -H "Authorization: Bearer mk_3f9a1c7e5b208d46a1f0c93e78b25d4610fa8c3729de154bb06e7a9f2c8d3510"
Response
{
  "data": [
    {
      "id": "clg7h6j5k4l3m2n1p0q9r8s7",
      "workspaceId": "clw1x2y3z4a5b6c7d8e9f0g1",
      "name": "Spring purchases",
      "type": "purchase",
      "customName": null,
      "linkId": "clx1a2b3c4d5e6f7g8h9i0j1",
      "qrCodeId": null,
      "campaignId": null,
      "targetCount": 500,
      "targetValueMinor": null,
      "targetCurrency": null,
      "windowStart": "2026-03-01T00:00:00.000Z",
      "windowEnd": "2026-05-31T23:59:59.000Z",
      "status": "ACTIVE",
      "archivedAt": null,
      "definitionVersion": 2,
      "definitionUpdatedAt": "2026-03-04T09:12:00.000Z",
      "createdAt": "2026-02-27T11:00:00.000Z",
      "updatedAt": "2026-03-04T09:12:00.000Z"
    }
  ]
}

Goal report

Measures one goal over a window. The window defaults to the last 30 days and may not exceed 400 days, the same bounds the conversion summary enforces. The goal's own window is then intersected with the one you asked for.

GET/api/v1/goals/{id}/report

A goal id belonging to another workspace returns 404, identical to an id that never existed.

Request
curl "https://mask.pk/api/v1/goals/clg7h6j5k4l3m2n1p0q9r8s7/report?from=2026-03-01T00:00:00Z" \
  -H "Authorization: Bearer mk_3f9a1c7e5b208d46a1f0c93e78b25d4610fa8c3729de154bb06e7a9f2c8d3510"
Response
{
  "goal": { "id": "clg7h6j5k4l3m2n1p0q9r8s7", "name": "Spring purchases", "type": "purchase" },
  "data": {
    "goalId": "clg7h6j5k4l3m2n1p0q9r8s7",
    "window": {
      "from": "2026-03-01T00:00:00.000Z",
      "to": "2026-03-31T00:00:00.000Z",
      "narrowedByGoal": false,
      "disjoint": false
    },
    "conversions": 128,
    "value": [{ "currency": "PKR", "valueMinor": 6140000, "conversions": 121 }],
    "unattributedConversions": 0,
    "unattributedIncluded": false,
    "countProgress": { "target": 500, "current": 128, "ratio": 0.256, "reached": false },
    "valueProgress": null,
    "rate": { "supported": true, "conversions": 128, "clicks": 2040, "rate": 0.0627 },
    "definition": { "version": 2, "updatedAt": "2026-03-04T09:12:00.000Z" },
    "completeness": {
      "windowTruncatedByRetention": false,
      "scopeObjectMissing": false
    }
  }
}

The goal key holds the same full goal object the list returns, abbreviated above. It travels with its own report because a goal's definition can change and the match is retroactive. Two reports are only comparable when their definition.version agrees. conversions counts conversions matching this goal and is not a workspace total: two goals can match the same conversion, so summing across goals is arithmetic that is wrong by construction.

Check completeness before quoting a number. windowTruncatedByRetention means the window reaches past conversion retention and the count is a floor rather than a total. scopeObjectMissing means the link, QR code or campaign the goal measures no longer exists.

Retention

Conversion events are kept for 400 days by default, which is why the window ceiling is 400 days. Raw click events, which supply the denominator of every rate, are kept for 90 days by default. A window reaching further back than the clicks is why a rate can come back as beyond_click_retention while the conversion count itself is still complete.

Availability

On a deployment where conversion tracking or goals are not yet installed, these endpoints answer 503 with a stable code of goals_unavailable or conversions_unavailable. This is not a transient failure and retrying will not clear it.

Rate limiting

These endpoints share the same 60 requests per minute per key as every other API endpoint. A goal report is several aggregates per call, so read the goal list once and request the reports you need rather than polling all of them.