Skip to content

The public HTTP routes and SDK

Look up Tracking public routes, SDK ingestion, authentication, limits, and refusal responses.

Use the public tracking host for scripts, browser collection, and authenticated backend events. The four routes below are separate from Tracking’s management REST API. The TypeScript client exposes only backend event ingestion.

Choose the surface and credential for the operation:

Surface
Where
Authenticated with
Documented
The public edge routes — the four below
https://<tracking host>/<product id>/tracking/…
Nothing, except the Events API, which takes a public access key
This page
The management REST API — the toolkit’s actions: domains, the event catalog, profiles, reports, the Google Ads surfaces
https://api.lepsto.com
A platform credential — a session token or an lsk_ API key as Authorization: Bearer; see API and keys
The pages’ own traffic
Behind the product login
A signed-in session
Not documented as a customer API, because it is not one

Management actions are callable through MCP and the management API even though they do not live on the public tracking host. Tracking workflows also explains which actions can be workflow steps.

Three ways to drive Tracking from code

These interfaces operate on the same product’s data, with different coverage:

Way
What it covers
When to use it
MCP
Every action Tracking offers — listing and verifying domains, managing event names, reading profiles, running the reports, driving the Google Ads integration
The widest surface: the configuration these pages describe screen by screen
REST
Two surfaces: the four public-edge routes on this page, of which the Events API is the one your backend calls, and the management API on https://api.lepsto.com covering the toolkit’s actions
Anything that can make an HTTP request; no client library needed
The TypeScript SDK
One operation — events.ingest, the Events API below
Typed backend code sending events

Tracking tool names follow tracking_<resource>_<action>, such as tracking_link-domains_list and tracking_reports_event_volume. The MCP catalog is the full reference; individual guides link the tools relevant to their tasks.

The TypeScript SDK

The @lepsto/sdk package requires Node.js 20 or newer. It has no runtime dependencies and provides ES module and CommonJS builds, both with types.

npm install @lepsto/sdk

Import Tracking from @lepsto/sdk/tracking to avoid loading the other clients. This Acme example uses placeholders: replace prod_…, the event id, and Ada’s address, and set LEPSTO_PUBLIC_KEY in your backend environment.

import { createTracking } from '@lepsto/sdk/tracking';

const tracking = createTracking({
  productId: 'prod_…',
  credential: { kind: 'bearer', value: process.env.LEPSTO_PUBLIC_KEY },
});

const { accepted, duplicates, rejected } = await tracking.events.ingest({
  events: [{ event_id: '…', name: 'signup', email: 'ada@acme.com' }],
});

Pass productId, a credential, and optionally baseUrl to createTracking. For the Events API, use a product public access key with kind: 'bearer', which sends Authorization: Bearer. See Backend access keys.

The Tracking client exposes only events.ingest. It sends the Events API body described below and returns accepted, duplicates, and rejected. Use MCP or the management HTTP API for reports, domains, profiles, and Google Ads actions.

Credentials

The SDK maps credential kinds to headers explicitly:

Credential
Header
publishableKey
x-publishable-key
apiKey (server only)
x-api-key
accessToken
authorization: Bearer and x-access-token
bearer
authorization: Bearer

publishableKey never uses x-api-key: that header identifies a backend caller and bypasses the per-product origin allowlist. Browser credentials must not bypass that check. An accessToken uses both listed headers because the public edge strips authorization on public routes while preserving x-access-token.

For Tracking’s Events API, use the product public access key with the bearer credential.

Retries and the base address

Retries default to off. Enable them per call or client only when appropriate for the operation. Errors expose Retry-After whether or not retries are enabled. Event ids protect ingestion from duplicates, but a 402 requires the plan or billing refusal to be resolved rather than repeated requests.

Set baseUrl to use a different edge, such as a staging environment or local development stack.

The base address

The public tracking routes use this base path:

https://<tracking host>/<product id>/tracking/<route>
  • <tracking host> is the shared Lepsto edge, public.lepsto.com, by default, or the host you claimed on the Tracking domain page. See register the domains you track.
  • <product id> is your product’s id. It is part of the path, and it is what Tracking reads to know whose data this is.

Copy completed URLs from Install for the script and Public access for backend events. In examples, replace <tracking host> and <product id> with those product values.

The three unauthenticated routes accept any origin and use no cookies or credentials.

All four routes are rate limited at the edge to 10,000 requests per minute per IP address. Batch backend events rather than making one request per event; the browser script already batches its traffic.

GET /script/lt.v1.js

Returns the main browser script.

  • Authentication — none.
  • Request — no body. Bodies above 1 KB are refused.
  • Response — 200 with the script, as application/javascript; charset=utf-8, an ETag, and Cache-Control: public, max-age=600, must-revalidate. A request carrying a matching If-None-Match gets 304.

The filename fixes the script version used by your snippet. Updates within that version arrive through the ten-minute cache without a snippet change; a future version uses a new filename.

GET /script/lt-xdomain.v1.js

Returns the cross-domain code. The main script fetches it when a linking group requires it; do not add a separate tag. Authentication, body limit, and caching match the main script route.

POST /collect

Receives browser batches from the tracking script. In browser code, call lt(…) rather than constructing requests to this route; see Record events.

  • Authentication — none. The product comes from the path.
  • Content type — anything. The script sends text/plain; charset=UTF-8 because that avoids a preflight request; application/json works too. The body is JSON either way.
  • Body cap — 64 KiB.
{
  "sent_at": "2026-08-02T09:15:00.000Z",
  "events": [
    {
      "name": "page_view",
      "ts": "2026-08-02T09:14:58.000Z",
      "anonymous_id": "…",
      "session_id": "…",
      "url": "https://acme.com/pricing?utm_source=newsletter",
      "referrer": "https://news.example.com/",
      "title": "Pricing",
      "props": { "plan": "pro" },
      "context": { "screen": "1512x982", "lang": "en-GB", "tz": "Europe/Berlin" },
      "consent_statistics": true,
      "consent_marketing": false
    }
  ]
}

Only name is required per event. Identification events can also carry email, phone, and traits. Campaign tags and ad click ids are derived from url, rather than sent as separate fields. Replace example timestamps, ids, URLs, and properties when adapting this payload.

Status
When
204
Empty response; some or all submitted events may have been discarded without storage.
400
The body is not valid JSON.
413
The body is larger than 64 KiB.

An oversized body is stopped at the edge before Tracking receives it. Its 413 response contains JSON, unlike the collector’s empty responses:

{
  "error": {
    "code": "body_too_large",
    "message": "Payload Too Large"
  }
}

A custom caller must handle that JSON error body even if it normally expects an empty collector response.

The collector can discard an invalid event while retaining valid events from the batch. An unrecognized product also receives 204 with the batch discarded. A 204 does not confirm persistence. When a trial allowance is exhausted or billing prevents storage, /collect still returns 204; those events are lost and cannot be resent from the browser. Check Events not stored this month in Dashboard, Live, or Reports, then troubleshoot missing events.

POST /events

Accepts named events from your backend. Unlike the script and collector routes, it requires a public access key.

  • Authentication — a public access key, as a bearer token. The key is created by a product Owner or Admin under Product → Settings → Public Access; see send events from your backend. It is checked at the edge and removed from the request before Tracking sees it.
  • Body cap — 64 KiB, and at most 500 events in one request.
POST https://<tracking host>/<product id>/tracking/events
Authorization: Bearer YOUR_KEY_HERE
Content-Type: application/json

Existing integrations can also send the same key in X-Api-Key. Authorization: Bearer takes priority if both are present. Public access lists all supported key headers. Replace YOUR_KEY_HERE with a backend-held key; never expose it in browser code.

{
  "events": [
    {
      "event_id": "inv_20260802_8891",
      "name": "payment",
      "email": "ada@example.com",
      "amount_minor": 4900,
      "currency": "EUR",
      "ts": "2026-08-02T09:15:00Z",
      "props": { "subscription_ref": "sub_5512" }
    }
  ]
}

Names are free-form. signup, trial_started, subscription_started, payment, refund, and churn have lifecycle meaning; payment and refund require an amount. Other names require only event_id and name.

Any event can include amount_minor, with currency required alongside it. Amounts outside the six lifecycle names are retained but do not contribute to revenue. See Revenue fields for units, currency rules, and timestamp limits.

200 returns batch accounting directly, without an envelope:

{
  "accepted": 1,
  "duplicates": 0,
  "rejected": []
}
Field
Meaning
accepted
Events stored by this request.
duplicates
Events whose event_id had already been stored for your product. They are not stored twice, which is what makes a retry after a timeout safe.
rejected
One entry per event that failed validation, with its index in your batch, its event_id if you sent one, and a reason.

Inspect rejected even after 200. A partially valid batch stores valid entries and reports the invalid ones individually:

{
  "accepted": 1,
  "duplicates": 0,
  "rejected": [
    {
      "index": 1,
      "event_id": "inv_20260802_8892",
      "reason": "amount_minor is required for payment"
    }
  ]
}

Errors

Use the status and response shape to distinguish key, body, and entitlement failures.

Key checks run before Tracking receives the request. A failed check returns the edge envelope:

{
  "error": {
    "code": "public_key_invalid",
    "message": "Unauthorized",
    "step": "public_key_check"
  }
}
Status
code
Meaning
401
public_key_required
No key reached the edge on any accepted carrier. The examples here use Authorization: Bearer, and the legacy X-Api-Key also carries it; public access lists every accepted header.
401
public_key_invalid
Not one of this product’s keys. A revoked key, and a key belonging to another product, both read this way.
403
public_key_scope
A real key whose scope does not cover this route.
413
body_too_large
The body is over 64 KiB. Send fewer events per request.

The edge also refuses bodies above 64 KiB. That error includes code and message, but no step.

Tracking’s request-validation errors use this flat message, error, and statusCode format. Entitlement refusals use the separate 402 format below.

{
  "message": "request body must be { \"events\": [ ... ] }",
  "error": "Bad Request",
  "statusCode": 400
}
Status
Meaning
What to do
400
The body is not { "events": [ … ] }, or the batch is over 500 events
Fix the request body; split the batch.
402
Storage is refused for the current plan or billing state; the entire batch is refused
Stop retrying and resolve the reason below.

A 402 from the public-key Events API has this body:

{
  "error": "entitlement_exhausted",
  "reason": "trial_cap_reached"
}

error is entitlement_exhausted. Only the following reasons are documented here; treat other fields as undocumented. This public-key response does not disclose organization plan or usage figures.

reason
Recovery
trial_cap_reached
Open the organization’s Billing page in the Lepsto Workspace. Upgrade from the trial, or wait for the next calendar month.
billing_paused
Resolve the billing pause on the organization’s Billing page, for example a failed payment.
budget_paused
Raise or remove the reached budget and resume usage on Billing.

A batch that would exceed the remaining allowance is refused whole: nothing is stored, and there is no partial-acceptance rejected list. A smaller batch that fits the remaining allowance can be accepted. Do not repeatedly send the refused request while its condition remains. Keep backend events if you intend to resend after resolving it, reuse their event_id values, and respect the writable timestamp window. Browser events discarded by /collect cannot be recovered this way.

The SDK reports this refusal as err.code === 'entitlement_exhausted':

try {
  await tracking.events.ingest({ events });
} catch (err) {
  if ((err as { code?: string }).code === 'entitlement_exhausted') {
    // Stop sending; retain events until the refusal is resolved.
  } else {
    throw err;
  }
}

A 5xx means the batch was not stored. Retry the whole batch with unchanged event_id values so a repeated request cannot store duplicates.

What is not here

  • The management API is not on this page — it is a different host and a different credential, and the generated Tracking API reference documents its routes. It is supported; it is simply not part of the public edge.
  • The pages’ own traffic is not an API. The toolkit’s screens talk to routes that require a signed-in session on your product. Those are the user interface’s own calls, not a customer surface, and they are not documented as one.
  • The tracking script needs no key and must never be given one. See send events from your backend.

Next steps

Was this page helpful?
Esc

Start typing to search the docs.

↑↓navigate↵select