
Read your organization's commission and dispute data programmatically. Useful for building reports, piping data into a data warehouse, or wiring Payarity into your own tooling.
All requests require an API key passed as a Bearer token. Generate keys in Settings → Developer API. Keys are org-scoped — they only return data for your organization.
curl https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/deals \ -H "Authorization: Bearer pa_live_your_key_here"
Keep keys secret — treat them like passwords. Revoke and rotate them from Settings if they're ever exposed.
Each key is created with one or more scopes that limit what it can access.
| Scope | Access |
|---|---|
| deals:read | List and retrieve deals and commission data |
| deals:write | Create/update deals — for a homegrown tool or custom data source |
| disputes:read | List and retrieve open and resolved disputes |
Each key is limited to 60 requests per minute, tracked in fixed 1-minute windows. Every response includes X-RateLimit-Limit and X-RateLimit-Remaining headers so you can back off before hitting the limit. If you exceed it, you'll get a 429 with a Retry-After header (seconds until the window resets).
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
{"error": "Rate limit exceeded: max 60 requests per minute per API key."}All errors return JSON with an error field and an appropriate HTTP status code.
| Status | Meaning |
|---|---|
| 400 | Malformed request body (bad JSON, empty/oversized deals array) |
| 401 | Missing, invalid, or revoked API key |
| 403 | Key doesn't have the required scope for this endpoint |
| 404 | Resource not found (or not in your org) |
| 405 | Method not allowed — GET for reads, POST only on /deals |
| 422 | Every deal in the batch was rejected — see the errors array |
| 429 | Rate limit exceeded — see Retry-After header |
| 500 | Internal error — contact support if it persists |
https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/dealsdeals:readReturns a paginated list of deals for your organization, ordered by last_calculated descending.
| Name | Type | Description |
|---|---|---|
| page | integer | 0-indexed page number (default: 0) |
| limit | integer | Results per page, max 100 (default: 50) |
| status | string | Filter by status: matched | exception | pending |
curl "https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/deals?status=exception&limit=10" \ -H "Authorization: Bearer pa_live_your_key"
{
"data": [
{
"id": "uuid",
"deal_name": "Acme Corp — Enterprise",
"account": "Acme Corp",
"owner": "Jane Smith",
"period": "2025-Q1",
"amount": 120000,
"expected_commission": 6000,
"actual_commission": 5660,
"variance": -340,
"status": "exception",
"discrepancy_reason": "rate_mismatch",
"close_date": "2025-03-15",
"last_calculated": "2025-04-01T10:00:00Z"
}
],
"total": 47,
"page": 0,
"limit": 10
}https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/deals/:iddeals:readReturns a single deal by its UUID.
curl "https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/deals/uuid-here" \ -H "Authorization: Bearer pa_live_your_key"
{
"id": "uuid",
"deal_name": "Acme Corp — Enterprise",
"account": "Acme Corp",
"owner": "Jane Smith",
"period": "2025-Q1",
"amount": 120000,
"expected_commission": 6000,
"actual_commission": 5660,
"variance": -340,
"status": "exception",
"discrepancy_reason": "rate_mismatch",
"close_date": "2025-03-15",
"last_calculated": "2025-04-01T10:00:00Z"
}https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/dealsdeals:writeBulk create/update deals — for a homegrown commission tool, internal system, or anything else without a native connector. Send up to 500 deals per request as { deals: [...] } (a bare array also works). Include external_id — your own record ID for that deal — and re-posting it later updates the same row instead of creating a duplicate; omit it and the deal is always inserted fresh. A "Custom API" entry appears automatically on the Data Sources page after your first successful sync.
deals array)| Name | Type | Description |
|---|---|---|
| external_id | string, optional | Your own record ID — enables upsert-by-ID |
| deal_name | string, required | |
| account | string, required | |
| owner | string, required | |
| amount | number, required | Deal amount |
| expected_commission | number, required | |
| actual_commission | number, optional | Defaults to expected_commission if omitted |
| status | "matched" | "exception", optional | Auto-computed from variance if omitted |
| discrepancy_reason | string, optional | Auto-computed ("rate_mismatch"/"none") if omitted |
| territory, product, stage, tier, period, rate, close_date, crm_record_url | optional |
curl -X POST "https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/deals" \
-H "Authorization: Bearer pa_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"deals": [
{
"external_id": "comp-engine-8842",
"deal_name": "Acme Corp — Enterprise",
"account": "Acme Corp",
"owner": "Jane Smith",
"amount": 120000,
"expected_commission": 6000,
"actual_commission": 5660
}
]
}'{
"inserted": 0,
"upserted": 1,
"errors": []
}https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/disputesdisputes:readReturns a paginated list of disputes for your organization, ordered by created_at descending.
| Name | Type | Description |
|---|---|---|
| page | integer | 0-indexed page number (default: 0) |
| limit | integer | Results per page, max 100 (default: 50) |
| status | string | Filter by status: open | in_review | resolved |
curl "https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/disputes?status=open" \ -H "Authorization: Bearer pa_live_your_key"
{
"data": [
{
"id": "uuid",
"deal_id": "uuid",
"status": "open",
"created_at": "2025-04-02T08:30:00Z",
"updated_at": "2025-04-02T08:30:00Z",
"deals": {
"deal_name": "Acme Corp — Enterprise",
"account": "Acme Corp",
"owner": "Jane Smith"
}
}
],
"total": 3,
"page": 0,
"limit": 50
}https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/disputes/:iddisputes:readReturns a single dispute by its UUID.
curl "https://dpkpxtbvxclycugpausg.supabase.co/functions/v1/api-v1/disputes/uuid-here" \ -H "Authorization: Bearer pa_live_your_key"
{
"id": "uuid",
"deal_id": "uuid",
"status": "open",
"created_at": "2025-04-02T08:30:00Z",
"updated_at": "2025-04-02T08:30:00Z",
"deals": {
"deal_name": "Acme Corp — Enterprise",
"account": "Acme Corp",
"owner": "Jane Smith"
}
}Configure an endpoint in Settings → Webhooks and subscribe to the events you care about. Each event fires a single POST, best-effort — there's no retry queue yet, so a failed delivery won't be retried automatically. Recent deliveries (success/failure, response code) are visible per-endpoint in Settings.
| Event | Fires when |
|---|---|
| dispute.raised | A new dispute is raised on a deal |
| dispute.status_changed | A dispute moves to in review or resolved |
| deals.imported | A CSV import finishes |
| statement.signoff_requested | Reps are asked to sign off on statements for a period |
| statement.signed | A rep signs off on their commission statement |
| statement.manager_approved | A manager approves a report's statement |
| statement.paid | A commission statement is marked paid |
Every delivery is a POST with a JSON body shaped like this, plus X-Payarity-Event and X-Payarity-Signature headers.
{
"event": "dispute.status_changed",
"timestamp": "2025-04-02T08:30:00Z",
"data": {
"dispute_id": "uuid",
"deal_id": "uuid",
"deal_name": "Acme Corp — Enterprise",
"previous_status": "open",
"status": "in_review"
}
}X-Payarity-Signature is sha256=<hex>, an HMAC-SHA256 of the raw request body using your endpoint's secret (shown in Settings). Recompute it and compare — don't parse the body first, sign the exact bytes you received.
// Node.js
const crypto = require("crypto");
function isValid(rawBody, signatureHeader, secret) {
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}