Developers
The Persequor API
- https://www.persequor.ai/api/v1
- 60 req/min per key
- Bearer auth
Quickstart
- Generate an API key in Settings → API. Copy the
pk_…token; it is shown once. - Save it as an environment variable so it stays out of your code.
export PERSEQUOR_API_KEY="pk_<paste_here>" - Make your first call.
curl https://www.persequor.ai/api/v1/leads?limit=5 \ -H "Authorization: Bearer $PERSEQUOR_API_KEY"
Empty response? You don't have leads yet. Hit /api/v1/leads?limit=1 (logged in) to confirm the route is reachable.
Authentication
Every request must include a bearer token in the Authorization header.
curl https://www.persequor.ai/api/v1/leads \
-H "Authorization: Bearer pk_<your_api_key>"Generate keys in Settings → API.
Base URL
https://www.persequor.ai/api/v1GET /api/v1/leads
Returns leads for the workspace the key belongs to, most recent first.
- limit
- integer
- Default 25, max 100.
- since
- ISO 8601 timestamp
- Only leads created on or after this time.
- status
- string
- Filter by status:
new,contacted,qualified,closed_won,closed_lost. - before
- ISO 8601 timestamp
- Cursor: pass the previous page's
nextBefore. - before_id
- uuid
- Cursor tiebreaker: pass the previous page's
nextBeforeIdalongsidebefore. Required to page correctly when records share a timestamp (e.g. a CSV import).
# curl
curl "https://www.persequor.ai/api/v1/leads?limit=50&status=closed_won" \
-H "Authorization: Bearer $PERSEQUOR_API_KEY"// Node.js / browser fetch
const res = await fetch(
'https://www.persequor.ai/api/v1/leads?limit=50&status=closed_won',
{ headers: { Authorization: `Bearer ${process.env.PERSEQUOR_API_KEY}` } },
)
const { data, nextBefore, nextBeforeId } = await res.json()# Python (requests)
import os, requests
r = requests.get(
"https://www.persequor.ai/api/v1/leads",
params={"limit": 50, "status": "closed_won"},
headers={"Authorization": f"Bearer {os.environ['PERSEQUOR_API_KEY']}"},
)
r.raise_for_status()
data = r.json()["data"]Response
{
"data": [
{
"id": "…",
"full_name": "Jane Doe",
"email": "jane@example.com",
"phone": "+1555…",
"lead_source": "meta",
"utm_source": "facebook",
"utm_medium": "cpc",
"utm_campaign": "spring_sale",
"status": "closed_won",
"value": 2400,
"created_at": "2026-04-20T18:30:15.000Z"
}
],
"nextBefore": "2026-04-20T18:30:15.000Z",
"nextBeforeId": "9a4dfc0a-e927-4f52-b5fd-b24baca0273d"
}GET /api/v1/orders
Returns orders for the workspace, most recent first.
- limit
- integer
- Default 25, max 100.
- since
- ISO 8601 timestamp
- before
- ISO 8601 timestamp
- Cursor.
Example
curl "https://www.persequor.ai/api/v1/orders?since=2026-04-01T00:00:00Z" \
-H "Authorization: Bearer $PERSEQUOR_API_KEY"GET /api/v1/attribution/daily
Daily orders and revenue per attribution tuple. Use it instead of summing /api/v1/orders yourself: it is the one place the revenue arithmetic happens, and it reports traffic that has no ad-platform campaign (creator links, bio links, hand-built UTMs), which campaign-level reporting cannot see.
- since
- YYYY-MM-DD or ISO 8601
- Default: 30 days ago.
- until
- YYYY-MM-DD or ISO 8601
- Default: now.
- tz
- IANA timezone
- Default UTC; days are bucketed in this zone.
Every row carries the raw utm_source and a resolved channel. They differ when a link is tagged with an alias (ig rather than instagram); source_aliased flags those so mistagged links stay visible instead of being silently merged.
Money is in integer minor units (cents). A refund reduces the original order's day, so past days can change. Don't cache them as final.
Orders with no UTMs come back as an explicit row with attributed: false rather than being omitted, so attributed and unattributed reconcile to the total. Maximum window: 400 days.
Example
curl "https://www.persequor.ai/api/v1/attribution/daily?since=2026-07-01&until=2026-07-31&tz=America/Chicago" \
-H "Authorization: Bearer $PERSEQUOR_API_KEY"Response
{
"data": [
{
"date": "2026-07-14",
"utm_source": "ig", "utm_medium": "social",
"utm_campaign": "misskays", "utm_content": null,
"channel": "instagram", "source_aliased": true, "attributed": true,
"orders": 6, "gross_revenue_cents": 32194,
"refunds_cents": 0, "net_revenue_cents": 32194
}
],
"range": { "since": "...", "until": "...", "timezone": "America/Chicago" },
"attribution_model": "last",
"currency_minor_units": true,
"totals": { "orders": 1088, "net_revenue_cents": 5948528,
"attributed_orders": 171, "unattributed_orders": 917 }
}MCP server
Connect Claude or any MCP client to your workspace. Its tools cover leads, orders, campaigns, workspaces, UTM links, lead attribution, workspace metrics, campaign performance and the agency rollup; a write-enabled key can also create and update leads and create UTM links. Every tool call is audit-logged, writes need an owner or admin role, and demo workspaces are read-only.
URL https://www.persequor.ai/api/mcp
Header Authorization: Bearer pk_<your_api_key>Zapier
No code needed: the Zapier integration wraps the same API. Triggers fire on every new lead and every new attributed order; pipe them into Slack, Google Sheets, Airtable, ConvertKit, your CRM, or any of 6,000+ apps Zapier supports. Same auth: paste your pk_...API key into Zapier's connection dialog. In private beta on Zapier's side; the button adds it to your Zapier account.
Tracking pixel
The first-party tracking pixel captures sessions, UTM parameters and form submissions, so attribution chains hold even when ad-platform pixels get blocked. Drop it once on every page you want tracked.
<script
src="https://www.persequor.ai/pixel.js"
data-pixel="<your_pixel_key>"
async
></script>Find your pixel key under Settings → Pixel. The same script works for Shopify (paste it in the theme.liquid head), WordPress, plain HTML, or via Google Tag Manager.
Form fills auto-track when the form has a [type="email"] field. To track manually, call window.persequor('lead', { email, name }).
Webhooks
We POST events to a URL you control when something happens in your workspace. Set up and manage subscriptions in Settings → API.
Events
- lead.created
- Fires when a new lead lands (form fill, Calendly booking, manual create).
- order.attributed
- Fires the first time an order is attributed (Shopify webhook).
- integration.broken
- Fires when an ad-platform token expires or revokes.
Request format
POST <your-url>
Content-Type: application/json
User-Agent: Persequor-Webhook/1.0
X-Persequor-Event: lead.created
X-Persequor-Delivery: 7f8c9b...
X-Persequor-Signature: t=1734031200,v1=<hmac_sha256_hex>
{
"event": "lead.created",
"data": {
"id": "...",
"email": "jane@example.com",
"lead_source": "meta",
"utm_campaign": "spring_sale"
},
"delivered_at": "2026-04-26T18:30:15.000Z"
}Verify signatures
Compute HMAC-SHA256 over ${timestamp}.${raw_body} using your endpoint secret (whsec_...), then compare it to the v1= portion of the X-Persequor-Signatureheader. Reject the delivery if they don't match.
// Node.js
import { createHmac, timingSafeEqual } from 'crypto'
function verifyPersequorWebhook(rawBody, header, secret) {
const m = /t=(\d+),v1=([0-9a-f]+)/.exec(header)
if (!m) return false
const [, ts, sig] = m
// Optional replay guard: reject deliveries older than 5 minutes
if (Date.now() / 1000 - Number(ts) > 300) return false
const expected = createHmac('sha256', secret)
.update(`${ts}.${rawBody}`)
.digest('hex')
return timingSafeEqual(Buffer.from(expected), Buffer.from(sig))
}Delivery semantics
- Fire-and-forget: no retries, and an 8-second timeout per delivery.
- If you need at-least-once delivery, poll
/api/v1/leadswithsince=as a backstop. - The last 30 days of deliveries (success and error bodies) live in Settings → API for debugging.
Rate limits and errors
60 requests per minute per API key. Hitting the cap returns 429 Too Many Requests with a Retry-After header (seconds until the window resets). Successful responses include X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset so SDKs can back off intelligently.
Unauthenticated or revoked keys return 401 Unauthorized. Malformed or out-of-range query parameters return 400 Bad Request with a message naming the offending parameter. Server-side issues return 500 with a JSON body containing error.
Error responses
Errors return a JSON body with a single error string and standard HTTP status codes:
{ "error": "Rate limit exceeded. Try again shortly." }- 200
- OK
- Successful read
- 400
- Bad request
- Malformed query param, e.g. an unparseable
since - 401
- Unauthorized
- Missing, malformed or revoked API key
- 404
- Not found
- Wrong path; check the
/api/v1base URL - 429
- Rate limited
- Read the
Retry-Afterheader and back off - 500
- Server error
- On us. Sentry catches it; retry after a moment
Need a higher limit for a syncing integration? Email support@persequor.ai with your use case.
Versioning policy
The current version is v1. We don't make breaking changes to existing endpoints: new fields may be added (clients should ignore unknown fields), and entire new endpoints land under /api/v1/*. A future v2 would live at /api/v2/* and run alongside v1 for a deprecation window of at least 12 months.
Breaking-change announcements go in the changelog and to every API key owner by email at least 30 days in advance.