Developers
Build on the affiliate platform, not around it.
One Partner API, a first-party tracking SDK, server-to-server postbacks, and an MCP server your AI can call, all over standard HTTPS and JSON. Real endpoints, copy-paste code, and docs you don't have to email sales to read.
Authenticate with an API key you scope and IP-restrict yourself. Every call, including denials, lands in your request log.
Four ways in
Everything you can build on
Read your program from your own systems, track without third-party cookies, receive conversions server to server, or point an AI assistant straight at your data. Pick where to start.
Partner API v1
Read reports, offers, affiliates and campaigns, and create affiliates. HTTPS + JSON, one key.
Jump to the API →Tracking SDK
One line of sdk.js on your own domain sets first-party cookies that survive Safari and iOS.
Postbacks & S2S
Fire conversions server to server, idempotent on the transaction ID, and read back the conversion ID.
Jump to postbacks →MCP server
Give Claude and any MCP client typed, tenant-scoped tools over the same authenticated API.
Open the MCP page →Partner API v1
A REST API any language can call
Every response is a { "success", "data", "meta" } envelope over plain HTTPS
and JSON, so any language or tool that makes web requests can use it. Authenticate with an
API key in the X-Api-Key header, page list endpoints with page and
per_page, and branch on the stable error.code, never on message text.
The endpoints
The whole /v1 surface, with the scope each one needs. Try any of them live,
with a real key, in the interactive reference, no request body required for the reads.
| Endpoint | What it returns | Scope |
|---|---|---|
| GET/v1/ping | Your account name and the key's scopes | none |
| GET/v1/reports/clicks | Row-level clicks, newest first | read:reports |
| GET/v1/reports/conversions | Row-level conversions, newest first | read:reports |
| GET/v1/offers | Your offers (and /v1/offers/{id}) | read:entities |
| GET/v1/affiliates | Your affiliates (and /v1/affiliates/{id}) | read:entities |
| POST/v1/affiliates | Create an affiliate (idempotent on email) | write:affiliates |
| GET/v1/campaigns | Your campaigns (and /v1/campaigns/{id}) | read:entities |
Quickstart
# The base URL is shown in Settings → Security → API access curl -G https://api.limelijourney.com/v1/reports/conversions \ -H "X-Api-Key: $LLJ_API_KEY" \ --data-urlencode "from=2026-08-01" \ --data-urlencode "to=2026-08-31" \ --data-urlencode "per_page=200"
import os, requests BASE = "https://api.limelijourney.com/v1" HEADERS = {"X-Api-Key": os.environ["LLJ_API_KEY"]} def conversions(frm, to): page = 1 while True: r = requests.get(f"{BASE}/reports/conversions", headers=HEADERS, params={"from": frm, "to": to, "page": page, "per_page": 200}) body = r.json() assert body["success"], body.get("error") yield from body["data"] meta = body["meta"] if page >= meta["total_pages"]: return page += 1 for row in conversions("2026-08-01", "2026-08-31"): print(row["conversion_id"], row["revenue"])
# Idempotent on contact_email: a repeat returns 409 # with the existing affiliate_id in error.details curl https://api.limelijourney.com/v1/affiliates \ -H "X-Api-Key: $LLJ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Sunrise Media", "contact_email":"partners@sunrise.example"}'
The base URL above is illustrative; use the exact execute-api invoke URL shown in your dashboard's API access view. Only name and contact_email are required to create an affiliate.
Tracking SDK
One line, first-party, cookie-loss proof
Serve sdk.js from your own tracking domain and it sets first-party cookies
server-side, so a Safari or iOS visitor still attributes weeks later, long after ITP has
capped third-party JavaScript cookies at seven days. Drop it sitewide or on a single
landing page; it only records a click when the URL actually carries tracking parameters.
What the tag gives you
The tracker keeps its own copy of the click ID, so conversions still attribute even if a pixel forgets to pass it. For ad platforms that dislike redirect hops, the SDK beacons the click directly, with an image-pixel fallback, and carries all five sub-ID slots through.
- First-party cookies (
ll_cid,ll_vid) set on your domain, outliving the ITP seven-day cap - Sitewide include or single placement, both one line, both auto-updating
- Direct tracking with no redirect hop, plus a redirect link (
/c) that needs nothing installed - Sub-IDs s1 through s5 captured on every click, reportable and controllable per campaign
Embed
<!-- Load the first-party tracker from your own domain --> <script src="https://track.yourbrand.com/sdk.js" async></script> // on click, first-party cookies are set on your domain: // ll_cid = a4f1c9... (the click id) // ll_vid = 7b02e1... (the visitor id)
# Redirect link on your tracking domain https://track.yourbrand.com/c?o=12&aff=34&cmp=212 &s1=newsletter&s2=aug&s3=variantB
Postbacks & S2S
Conversions in, exactly once
Fire a conversion from your server the moment it happens. Post the fields, read back the minted conversion ID for your CRM, and never worry about double-counting: ingestion is idempotent on the transaction ID, so a retry or an SQS redelivery returns the same ID instead of a second row.
Build and validate the URL
Not sure which parameters your network sends? The postback URL builder maps the four canonical inputs to your parameter names and validates the result before you paste it into an advertiser or a network. Every attempt, even the malformed ones, is captured in the pixel log, so debugging takes minutes.
- Server-to-server postback returns JSON with the conversion ID to store in your CRM
- Image pixel on
/convreturns a 1×1 GIF and can't break a thank-you page - Idempotent on the transaction ID: a re-fire returns the same conversion, never a second row
- Every attempt logged, including denials, so you can trace exactly what a network sent
Fire a conversion
# Fire from your server on a confirmed sale curl -G https://track.yourbrand.com/conv \ --data-urlencode "o=12" \ --data-urlencode "txn=ORDER-8842" \ --data-urlencode "amt=249.00" # => {"conversion_id": 12} (same txn, same id, always)
<img src="https://track.yourbrand.com/conv ?o=12&txn=ORDER-8842&amt=249.00" width="1" height="1" alt="" />
Set track.yourbrand.com to your own tracking domain. Fires twice? The same txn returns the same conversion_id both times. One conversion, always.
Keys & security
You hold the keys, and the guardrails
Create keys in Settings → Security → API access. Each one carries only the scopes you grant, can be pinned to specific IPs, and shows up in a request log the moment it's used, so you can grant the least a job needs and see exactly what it did.
Scoped API keys
Grant only what a key needs: read:entities, read:reports, or write:affiliates. A call without the scope returns 403 insufficient_scope.
IP allowlists
Restrict a key to the IPs your servers call from. A request from anywhere else is refused with 403 ip_not_allowed, before it ever touches your data.
Request log
Every call, including denials, is recorded in Settings: which key, which endpoint, the status. Rotate or revoke a key the instant something looks off.
Tenant isolation
The key identifies your account; you only ever see your own data. tenant_id is never a request parameter, so it can't be spoofed from the client.
MCP server
The same API, as tools your AI can call
Point Claude Desktop, Claude Code, or any MCP client at the LimeliJourney MCP server and your assistant gets typed, tenant-scoped tools over the same authenticated API your dashboard uses. The tool surface is generated from the OpenAPI spec, so it tracks the API automatically; reads and safe writes are on, DELETE is off until you enable it. No competing tracker ships one.
Connected in one config entry
Install the server from mcp-server/, drop one entry into your client's MCP
config with your API URL and login token, and restart. The limelijourney tools
appear and your assistant can read your live program and propose safe changes, each call
approved in your client before it runs.
Add it to your client
{
"mcpServers": {
"limelijourney": {
"command": "python",
"args": ["/abs/path/to/mcp-server/server.py"],
"env": {
"LIMELI_API_BASE": "https://<your-api>/prod",
"LIMELI_BEARER_TOKEN": "<your-cognito-id-token>"
}
}
}
}
Claude Code reads the same shape. To expose DELETE and bulk backfill, add LIMELI_ALLOW_DESTRUCTIVE=1 to the env; it's off until you do.
Developer questions, answered
How do I authenticate?
Create an API key in the dashboard under Settings → Security → API access, then send it on every request in the X-Api-Key header (or as Authorization: Bearer llj_live_...). The key identifies your account, so you only ever see your own data. Start with GET /v1/ping to confirm the key works and see its scopes.
What does a response look like?
Every response is a JSON envelope: {"success": true, "data": ..., "meta": ...} on success, or {"success": false, "error": {"code", "message"}} on failure. Error codes are stable strings, so branch on error.code, never on the message text. List endpoints return paging in meta: page, per_page, total and total_pages.
Can I write data through the API?
Version 1 is read-first, with one write endpoint: POST /v1/affiliates creates an affiliate and needs the write:affiliates scope. It's idempotent on contact_email, so a repeat returns 409 conflict with the existing affiliate_id. Only name and contact_email are required; send "status": "pending" to queue a self-registered partner for review.
How is rate limiting and pagination handled?
Requests are throttled per account; on HTTP 429 retry with exponential backoff. List endpoints take page (1-based) and per_page (max 200, default 50); use meta.total_pages to drive your paging loop, as the Python example above does.
Do the postback and the API share a key?
No. The tracking endpoints (/c, /conv, sdk.js) run on your own tracking domain and identify traffic by the offer, campaign and click parameters, not an API key. The Partner API keys (llj_live_...) are for reading and writing your account data. See the postback URL builder for the tracking side and the API reference for the account side.
Is there an OpenAPI spec I can generate a client from?
Yes. The interactive API reference is driven by a published OpenAPI 3 document you can point a code generator at, and the same spec is what generates the MCP server's tools, so the API, the reference, and the AI tools never drift apart.
What you're building on
The API and the SDK are only as good as the platform under them. Here is the tracking they read, how the journey is credited, the reporting they reason over, and the AI that acts inside the product.
First-party tracking
Redirect links, a drop-in pixel and a first-party SDK that survives Safari and iOS cookie loss.
Explore tracking AttributionEvery click, credited
Idempotent ingestion and end-to-end sub-IDs so re-fires never double-count the journey.
Explore attribution ReportingReporting that reads back
Live reports across clicks, conversions, affiliates, offers and revenue, on your real data.
Explore reporting MCPTools your AI can call
A tenant-scoped MCP server so Claude and any MCP client read your program over the same API.
Explore MCPStart with a key and a curl.
Open the interactive reference, create a scoped key, and make your first call in a minute. Building something bigger? Book a demo and we'll walk the API, the SDK and the MCP server against your kind of program.
Not ready to build?
Get launch notes and API updates. No spam, unsubscribe anytime.