API documentation
The Tasskel Affiliates public REST API lets you manage affiliates, referrals, commissions and payouts, record transactions from your own billing system, receive outgoing webhooks, and embed the tracking script on your site. All requests and responses use JSON.
On this page
Overview
Every endpoint below is prefixed with /api/public/v1 and requires an API key. Create and revoke keys from Admin → Integrations → API keys. Responses are wrapped as { data, pagination? } on success or { error: { code, message, details? } } on failure.
Authentication
Send your API key as a bearer token on every request:
Authorization: Bearer <key>Each key belongs to one program and only ever sees or changes that program's partners, referrals, commissions, transactions and payouts. Scopes: read (GET) and write (POST/PATCH). Four money scopes are never granted by default and only an owner can add them when creating a key:
| Field | Type | Description |
|---|---|---|
| affiliates:rate | scope | PATCH custom_rate / custom_rate_type on an affiliate. |
| affiliates:status | scope | PATCH status on an affiliate. |
| commissions:create | scope | POST /commissions (manual commissions, capped). |
| commissions:approve | scope | POST /commissions/:id/approve. |
A request needing a scope the key doesn't have returns 403 missing_scope. Every write is recorded in the audit log with the key's name as the actor.
Errors & rate limits
Every key is limited to 120 requests per minute. Exceeding it returns 429 with code rate_limited. List endpoints accept limit (max 100, default 25) and offset query params, and return a pagination: { limit, offset, total } object.
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded (120 requests/minute)."
}
}| Field | Type | Description |
|---|---|---|
| 401 unauthorized | status | Missing, invalid or revoked API key. |
| 400 invalid_request | status | Body failed validation (details lists the fields). |
| 403 missing_scope | status | Key lacks the required scope. |
| 403 program_scope | status | The partner, customer or transaction belongs to another program (or cannot be matched). |
| 403 key_scope_required | status | Legacy key without a program; create a new key. |
| 404 not_found | status | Resource or endpoint doesn't exist (or is outside the key's program). |
| 409 conflict | status | Duplicate resource, id already used, or invalid state. |
| 409 not_approvable | status | Test or exchange-rate-missing commission. |
| 422 amount_over_cap | status | Manual commission above the configured cap (default ±1,000). |
| 422 lead_rejected | status | Lead not accepted (generic; no reason is disclosed). |
| 429 rate_limited | status | Too many requests for this key. |
| 500 server_error | status | Unexpected failure. Messages are generic; details are logged on our side. |
Affiliates
/api/public/v1/affiliatesList affiliates
| Field | Type | Description |
|---|---|---|
| status | string | Filter by status (pending, active, rejected, suspended). |
| limit | number | Page size, max 100 (default 25). |
| offset | number | Pagination offset (default 0). |
Request
curl -s "https://tasskelaffiliate.com/api/public/v1/affiliates?status=active&limit=25" \
-H "Authorization: Bearer <key>"Response
{
"data": [
{
"id": "…",
"email": "jane@acme.com",
"status": "active",
"level": 1
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}/api/public/v1/affiliates/:idGet an affiliate
Request
curl -s "https://tasskelaffiliate.com/api/public/v1/affiliates/<id>" \
-H "Authorization: Bearer <key>"Response
{
"data": {
"id": "…",
"email": "jane@acme.com",
"status": "active"
}
}/api/public/v1/affiliatesCreate an affiliate
| Field | Type | Description |
|---|---|---|
| email* | string | Affiliate email. |
| display_name | string | Public display name. |
| program_id | uuid | Must be the key's program if given (always enrolled in the key's program). |
| company | string | Company name. |
| website | string (url) | Affiliate website. |
| status | pending | active | Initial status (default pending). |
| tags | string[] | Up to 20 tags. |
Request
curl -s -X POST "https://tasskelaffiliate.com/api/public/v1/affiliates" \
-H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"email":"jane@acme.com","display_name":"Jane"}'Response
{
"data": {
"id": "…",
"email": "jane@acme.com",
"status": "pending",
"referral_token": "a1b2c3d4e5"
}
}/api/public/v1/affiliates/:idUpdate an affiliate
| Field | Type | Description |
|---|---|---|
| display_name | string | |
| status | pending | active | rejected | suspended | Needs affiliates:status. Setting to active stamps approved_at. |
| tags | string[] | |
| custom_rate | number | null | Needs affiliates:rate. |
| custom_rate_type | percentage | flat | null | Needs affiliates:rate. |
| country | string (ISO-2) |
Request
curl -s -X PATCH "https://tasskelaffiliate.com/api/public/v1/affiliates/<id>" \
-H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"status":"active"}'Response
{
"data": {
"id": "…",
"status": "active"
}
}Referrals
/api/public/v1/referralsList referrals
| Field | Type | Description |
|---|---|---|
| affiliate_id | uuid | Filter by affiliate. |
Request
curl -s "https://tasskelaffiliate.com/api/public/v1/referrals?affiliate_id=<id>" \
-H "Authorization: Bearer <key>"Response
{
"data": [
{
"id": "…",
"affiliate_id": "…",
"status": "lead"
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}/api/public/v1/referralsCreate a referral
| Field | Type | Description |
|---|---|---|
| affiliate_id* | uuid | |
| string | Required if customer_id not set. | |
| name | string | |
| customer_id | string | Required if email not set. |
| sub_id | string | Sub-tracking identifier. |
Request
curl -s -X POST "https://tasskelaffiliate.com/api/public/v1/referrals" \
-H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"affiliate_id":"<id>","email":"lead@customer.com"}'Response
{
"data": {
"id": "…",
"status": "lead"
}
}A customer can have only one active referral: 409 conflict if one exists.
Commissions
/api/public/v1/commissionsList commissions
| Field | Type | Description |
|---|---|---|
| status | string | Filter by status. |
| affiliate_id | uuid | Filter by affiliate. |
Request
curl -s "https://tasskelaffiliate.com/api/public/v1/commissions?status=pending" \
-H "Authorization: Bearer <key>"Response
{
"data": [
{
"id": "…",
"amount": 25.5,
"currency": "USD",
"status": "pending",
"test": false
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}/api/public/v1/commissionsCreate a manual commission
Capped at ±1,000 in the program currency (configurable). Manual commissions are never approved automatically after the hold period.
| Field | Type | Description |
|---|---|---|
| affiliate_id* | uuid | |
| amount* | number | Positive or negative, within the cap. |
| currency | string (3) | Defaults to program currency. |
| reason | string | |
| referral_id | uuid |
Request
curl -s -X POST "https://tasskelaffiliate.com/api/public/v1/commissions" \
-H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"affiliate_id":"<id>","amount":25.5,"reason":"Manual bonus"}'Response
{
"data": {
"id": "…",
"amount": 25.5,
"status": "pending"
}
}/api/public/v1/commissions/:id/approveApprove a pending commission
Request
curl -s -X POST "https://tasskelaffiliate.com/api/public/v1/commissions/<id>/approve" \
-H "Authorization: Bearer <key>"Response
{
"data": {
"id": "…",
"status": "approved"
}
}Payouts
/api/public/v1/payoutsList payout items
Request
curl -s "https://tasskelaffiliate.com/api/public/v1/payouts" \
-H "Authorization: Bearer <key>"Response
{
"data": [
{
"id": "…",
"affiliate_id": "…",
"amount": 100,
"currency": "USD",
"status": "paid",
"method": "bank",
"simulated": false
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}Transactions
Report a transaction from your own billing system so commissions are calculated automatically. This is the same pipeline used for Tasskel Pay webhooks. Sending the same transaction_id again returns the original result with duplicate: true (200) — never a second commission.
/api/public/v1/transactionsList transactions
Request
curl -s "https://tasskelaffiliate.com/api/public/v1/transactions" \
-H "Authorization: Bearer <key>"Response
{
"data": [
{
"id": "…",
"external_transaction_id": "inv_1001",
"type": "sale",
"amount": 99,
"currency": "USD",
"test": false
}
],
"pagination": {
"limit": 25,
"offset": 0,
"total": 1
}
}/api/public/v1/transactionsRecord a transaction
| Field | Type | Description |
|---|---|---|
| type* | sale | recurring | refund | chargeback | void | |
| transaction_id* | string | Your unique transaction id (idempotency key). |
| original_transaction_id | string | For refunds/voids/chargebacks. |
| subscription_id | string | |
| customer_id | string | |
| string | Used to match/attribute the referral. | |
| amount* | number | |
| currency | string (3) | |
| plan | string | |
| affiliate_token | string | Optional. Referral token from ?fpr= / the tracking cookie; counts only with a click on that link inside the program's cookie window. Without it, a charge is matched to an existing referral by subscription_id, customer_id or email (refused with 403 program_scope if it can't be matched). |
| sub_id | string | |
| coupon | string |
Request
curl -s -X POST "https://tasskelaffiliate.com/api/public/v1/transactions" \
-H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"type":"sale","transaction_id":"inv_1001","email":"jane@acme.com","amount":99,"currency":"USD","affiliate_token":"a1b2c3d4e5"}'Response
{
"ok": true,
"duplicate": false,
"commissions": 1,
"reversed": 0,
"adjustments": 0
}Track (lead / sale / refund)
Lower-level endpoints under /api/public/v1/track/* used by the tracking script and for direct server-to-server integrations.
/api/public/v1/track/leadRecord a lead against a referral token
| Field | Type | Description |
|---|---|---|
| token* | string | Referral token. |
| click_id* | uuid | The tid from the partner link; must belong to that token and be inside the cookie window. |
| sub_id | string | |
| string | Required if customer_id not set. | |
| customer_id | string | Required if email not set. |
| name | string |
Request
curl -s -X POST "https://tasskelaffiliate.com/api/public/v1/track/lead" \
-H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"token":"a1b2c3d4e5","click_id":"<tid>","email":"lead@customer.com"}'Response
{
"data": {
"referral_id": "…",
"duplicate": false
}
}/api/public/v1/track/saleRecord a sale against a referral token
201 on first record; 200 with duplicate: true and the original commissions on a replay.
| Field | Type | Description |
|---|---|---|
| transaction_id* | string | |
| amount* | number | |
| affiliate_token | string | |
| recurring | boolean | Marks as a recurring charge. |
Request
curl -s -X POST "https://tasskelaffiliate.com/api/public/v1/track/sale" \
-H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"transaction_id":"inv_1001","amount":99,"affiliate_token":"a1b2c3d4e5"}'Response
{
"data": {
"transaction_id": "<internal uuid>",
"referral_id": "…",
"duplicate": false,
"commissions": [
{
"id": "…",
"level": 1,
"amount": 29.7,
"status": "pending"
}
],
"note": null
}
}/api/public/v1/track/refundRecord a refund, chargeback or void
| Field | Type | Description |
|---|---|---|
| transaction_id* | string | |
| original_transaction_id* | string | |
| amount* | number | |
| type | refund | chargeback | void | Default refund. |
Request
curl -s -X POST "https://tasskelaffiliate.com/api/public/v1/track/refund" \
-H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
-d '{"transaction_id":"rfnd_1","original_transaction_id":"inv_1001","amount":99}'Response
{
"data": {
"transaction_id": "<internal uuid>",
"reversed": [
"<commission id>"
],
"adjustments": [],
"duplicate": false
}
}There are also unauthenticated, CORS-enabled endpoints used directly by the browser tracking script: POST /api/public/track/click and POST /api/public/track/lead (no API key). Leads need the click id (tid) from the partner link inside the cookie window, are rate-limited per IP and per link, run through the blocklist and fraud checks, and get a generic reply either way. A lead that never buys expires after the cookie window and never beats a valid partner token or coupon at checkout.
Outgoing webhooks
Configure a webhook endpoint (URL + secret + subscribed events) in Admin → Integrations. Every subscribed event is delivered as an HTTP POST with a JSON body:
{
"id": "…",
"event": "commission.approved",
"created_at": "2024-01-01T00:00:00.000Z",
"data": {
"commission_id": "…",
"affiliate_id": "…",
"amount": 25.5,
"currency": "USD",
"status": "approved"
}
}Each delivery includes these headers:
| Field | Type | Description |
|---|---|---|
| X-Tasskel-Event | header | The event name, e.g. commission.approved. |
| X-Tasskel-Signature | header | HMAC signature, see verification below. |
| X-Tasskel-Delivery | header | Unique id of this delivery attempt (for dedup). |
Failed deliveries (non-2xx response or network error) are retried with exponential backoff, up to 6 attempts total. Your endpoint should respond quickly with a 2xx status and process the payload asynchronously.
Supported event names:
affiliate.createdaffiliate.approvedaffiliate.tier_changedaffiliate.first_clickaffiliate.first_commissionreferral.createdcommission.createdcommission.approvedcommission.reversedpayout.paidVerifying webhook signatures
X-Tasskel-Signature has the format t=<unix timestamp>,v1=<hex hmac-sha256>. The signature is computed over the string {timestamp}.{raw request body} using your webhook's secret. Verify it with a timing-safe comparison and reject requests with an old timestamp to prevent replay attacks.
const crypto = require("node:crypto");
function verifyTasskelSignature(header, rawBody, secret, toleranceSeconds = 300) {
if (!header) return false;
const parts = Object.fromEntries(
header.split(",").map((p) => {
const i = p.indexOf("=");
return [p.slice(0, i), p.slice(i + 1)];
}),
);
const ts = parts["t"];
const sig = parts["v1"];
if (!ts || !sig) return false;
// Reject stale/future timestamps to prevent replay attacks.
const age = Math.abs(Date.now() / 1000 - Number(ts));
if (!Number.isFinite(age) || age > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const a = Buffer.from(sig, "utf8");
const b = Buffer.from(expected, "utf8");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express example (use raw body, not the parsed JSON). webhookSecret = the secret shown for your endpoint.
app.post("/webhooks/tasskel", express.raw({ type: "application/json" }), (req, res) => {
const ok = verifyTasskelSignature(req.header("X-Tasskel-Signature"), req.body.toString("utf8"), webhookSecret);
if (!ok) return res.status(401).send("invalid signature");
const event = JSON.parse(req.body.toString("utf8"));
// handle event.event / event.data …
res.status(200).send("ok");
});Tracking script
Add this script tag to every page of the Tasskel site to capture referral clicks and attribute signups. It reads ?fpr=, ?ref= and ?sub=/?sub_id= query params (and the tid click id), stores them in a first-party cookie + localStorage on that site for the program's cookie window, and exposes a small API on window.tasskelPartners.
<script src="https://tasskelaffiliate.com/track.js" async></script>| Field | Type | Description |
|---|---|---|
| window.tasskelPartners.ready | Promise | Resolves once click attribution has been captured. |
| window.tasskelPartners.getReferral() | function | Returns { token, sub_id, tid, clicked_at } or null. |
| window.tasskelPartners.signup(data) | function | Sends the lead: signup({ email, customer_id, name }). |
| window.tasskelPartners.nmiFields() | function | Returns { merchant_defined_field_1, merchant_defined_field_2 } to attach to a Tasskel Pay checkout form. |
// Call after your signup form submits successfully:
window.tasskelPartners.signup({ email: "new.customer@example.com" }).then((r) => {
if (!r.ok) console.warn("No referral to attribute:", r.error);
});
// Or read the current referral to store alongside a lead in your own DB:
const referral = window.tasskelPartners.getReferral();Redirect links
Partner links log the click and send the visitor on to the program's destination page. They do not answer with an HTTP 302 and set no cookie: the response is a small page with a share preview that redirects the browser immediately. Attribution travels in the destination URL as ?fpr=<token>&sub=<sub id>&tid=<click id>, and the tracking script on the destination stores it.
| Field | Type | Description |
|---|---|---|
| /p/:token | GET | Current share link: a page with the program's preview card; the browser logs the click and redirects. |
| /r/:token | GET | Older link by token: logs the click, returns the preview page with an instant redirect. |
| /go/:slug | GET | Older link by custom slug: same as /r. |
https://tasskelaffiliate.com/p/a1b2c3d4e5
https://tasskelaffiliate.com/r/a1b2c3d4e5
https://tasskelaffiliate.com/go/jane-newsletter
→ https://tasskel.com/pricing?fpr=a1b2c3d4e5&tid=<click id>Tasskel Pay payment gateway setup
If you charge customers through Tasskel Pay, Tasskel can consume Tasskel Payments's webhooks directly to attribute sales, refunds, chargebacks and subscription changes without any code on your checkout backend.
How money moves
Tasskel Pay reports customer transactions so commissions can be calculated. Approved commissions are paid to partners by direct deposit to a U.S. bank account through Tasskel Pay, once a partner's balance reaches the program's minimum payout.
- In the Tasskel Pay merchant portal, a webhook points to the endpoint below for these events.
- Each delivery is signed with the webhook's signing key, which Tasskel stores securely.
- On your checkout form, pass the referral token captured by the tracking script as merchant defined fields:
| Field | Type | Description |
|---|---|---|
| merchant_defined_field_1 | string | Affiliate/referral token (from window.tasskelPartners.getReferral().token). |
| merchant_defined_field_2 | string | sub_id, for sub-affiliate tracking. |
| merchant_defined_field_3 | string | Promo/coupon code (falls back to parsing the order description). |
/api/public/webhooks/nmiTasskel Pay webhook receiver
No API key required — authenticity is verified via the Webhook-Signature header below. Configure this URL as the webhook destination in Tasskel Pay.
Request
curl -s -X POST "https://tasskelaffiliate.com/api/public/webhooks/nmi" \
-H "Content-Type: application/json" \
-H "Webhook-Signature: t=1700000000,s=<hmac>" \
-d '{"event_type":"transaction.sale.success","event_body":{ ... }}'Response
{
"ok": true,
"result": {
"duplicate": false,
"commissions": 1
}
}Tasskel Pay signs each webhook with a Webhook-Signature header formatted as t=<nonce>,s=<hex hmac-sha256>, where the signature covers the string {nonce}.{raw body} using your signing key. Tasskel verifies it with a timing-safe comparison before processing.
Supported Tasskel Pay event types:
transaction.sale.successtransaction.capture.successtransaction.refund.successtransaction.void.successchargeback.*recurring.subscription.addrecurring.subscription.updaterecurring.subscription.deleteNeed an API key or a webhook endpoint? Head to sign in and open Admin → Integrations.
