Skip to main content
Back to settings

Developer docs

Read-only HTTP API for querying your Persequor data.

Quickstart

From zero to first API call in three steps:

  1. Generate an API key at Settings → API. Copy the pk_… token (shown once).
  2. Save it as an environment variable so it stays out of your code:
export PERSEQUOR_API_KEY="pk_<paste_here>"

3. 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.

Zapier integration

Don't want to write code? Persequor ships a Zapier integration that wraps the same API in a no-code workflow builder. 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 model — paste your pk_...API key into Zapier's connection dialog. Currently in private beta on Zapier's side; click the link above to add it to your Zapier account.

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/v1

GET /api/v1/leads

Returns leads for the workspace the key belongs to, most recent first.

limitintegerdefault 25, max 100
sinceISO 8601 timestamponly leads created on or after this time
statusstringfilter by status (new, contacted, qualified, closed_won, closed_lost)
beforeISO 8601 timestampcursor — pass the previous page's nextBefore

Examples

# 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 } = 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"
}

GET /api/v1/orders

Returns orders for the workspace, most recent first.

limitintegerdefault 25, max 100
sinceISO 8601 timestamp
beforeISO 8601 timestampcursor

Example

curl "https://www.persequor.ai/api/v1/orders?since=2026-04-01T00:00:00Z" \
  -H "Authorization: Bearer $PERSEQUOR_API_KEY"

Rate limits & errors

Rate limit: 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. Server-side issues return 500 with a JSON body containing error.

Need a higher limit (e.g. for a syncing integration)? Email support@persequor.ai with your use case.

Error responses

Errors return a JSON body with a single error string and standard HTTP status codes:

{ "error": "Rate limit exceeded. Try again shortly." }
StatusMeaningCommon cause
200OKSuccessful read
400Bad requestMalformed query param (e.g. unparseable since)
401UnauthorizedMissing, malformed, or revoked API key
404Not foundWrong path — check /api/v1 base URL
429Rate limitedRead Retry-After header, back off
500Server errorOn us — Sentry catches it; retry after a beat

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. Same script works for Shopify (paste in 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 to the v1= portion of the X-Persequor-Signatureheader. Reject 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. 8-second timeout per delivery.
  • · If you need at-least-once delivery, poll /api/v1/leads with since= as a backstop.
  • · The last 30 days of deliveries (success + error bodies) live in Settings → API for debugging.

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 via email at least 30 days in advance.