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.

Base URL: https://tasskelaffiliate.com

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:

FieldTypeDescription
affiliates:ratescopePATCH custom_rate / custom_rate_type on an affiliate.
affiliates:statusscopePATCH status on an affiliate.
commissions:createscopePOST /commissions (manual commissions, capped).
commissions:approvescopePOST /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)."
  }
}
FieldTypeDescription
401 unauthorizedstatusMissing, invalid or revoked API key.
400 invalid_requeststatusBody failed validation (details lists the fields).
403 missing_scopestatusKey lacks the required scope.
403 program_scopestatusThe partner, customer or transaction belongs to another program (or cannot be matched).
403 key_scope_requiredstatusLegacy key without a program; create a new key.
404 not_foundstatusResource or endpoint doesn't exist (or is outside the key's program).
409 conflictstatusDuplicate resource, id already used, or invalid state.
409 not_approvablestatusTest or exchange-rate-missing commission.
422 amount_over_capstatusManual commission above the configured cap (default ±1,000).
422 lead_rejectedstatusLead not accepted (generic; no reason is disclosed).
429 rate_limitedstatusToo many requests for this key.
500 server_errorstatusUnexpected failure. Messages are generic; details are logged on our side.

Affiliates

GET/api/public/v1/affiliates
read scope

List affiliates

FieldTypeDescription
statusstringFilter by status (pending, active, rejected, suspended).
limitnumberPage size, max 100 (default 25).
offsetnumberPagination 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
  }
}
GET/api/public/v1/affiliates/:id
read scope

Get 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"
  }
}
POST/api/public/v1/affiliates
write scope

Create an affiliate

FieldTypeDescription
email*stringAffiliate email.
display_namestringPublic display name.
program_iduuidMust be the key's program if given (always enrolled in the key's program).
companystringCompany name.
websitestring (url)Affiliate website.
statuspending | activeInitial status (default pending).
tagsstring[]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"
  }
}
PATCH/api/public/v1/affiliates/:id
write scope

Update an affiliate

FieldTypeDescription
display_namestring
statuspending | active | rejected | suspendedNeeds affiliates:status. Setting to active stamps approved_at.
tagsstring[]
custom_ratenumber | nullNeeds affiliates:rate.
custom_rate_typepercentage | flat | nullNeeds affiliates:rate.
countrystring (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

GET/api/public/v1/referrals
read scope

List referrals

FieldTypeDescription
affiliate_iduuidFilter 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
  }
}
POST/api/public/v1/referrals
write scope

Create a referral

FieldTypeDescription
affiliate_id*uuid
emailstringRequired if customer_id not set.
namestring
customer_idstringRequired if email not set.
sub_idstringSub-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

GET/api/public/v1/commissions
read scope

List commissions

FieldTypeDescription
statusstringFilter by status.
affiliate_iduuidFilter 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
  }
}
POST/api/public/v1/commissions
commissions:create scope

Create a manual commission

Capped at ±1,000 in the program currency (configurable). Manual commissions are never approved automatically after the hold period.

FieldTypeDescription
affiliate_id*uuid
amount*numberPositive or negative, within the cap.
currencystring (3)Defaults to program currency.
reasonstring
referral_iduuid

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"
  }
}
POST/api/public/v1/commissions/:id/approve
commissions:approve scope

Approve 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

GET/api/public/v1/payouts
read scope

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

GET/api/public/v1/transactions
read scope

List 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
  }
}
POST/api/public/v1/transactions
write scope

Record a transaction

FieldTypeDescription
type*sale | recurring | refund | chargeback | void
transaction_id*stringYour unique transaction id (idempotency key).
original_transaction_idstringFor refunds/voids/chargebacks.
subscription_idstring
customer_idstring
emailstringUsed to match/attribute the referral.
amount*number
currencystring (3)
planstring
affiliate_tokenstringOptional. 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_idstring
couponstring

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.

POST/api/public/v1/track/lead
write scope

Record a lead against a referral token

FieldTypeDescription
token*stringReferral token.
click_id*uuidThe tid from the partner link; must belong to that token and be inside the cookie window.
sub_idstring
emailstringRequired if customer_id not set.
customer_idstringRequired if email not set.
namestring

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
  }
}
POST/api/public/v1/track/sale
write scope

Record a sale against a referral token

201 on first record; 200 with duplicate: true and the original commissions on a replay.

FieldTypeDescription
transaction_id*string
amount*number
affiliate_tokenstring
recurringbooleanMarks 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
  }
}
POST/api/public/v1/track/refund
write scope

Record a refund, chargeback or void

FieldTypeDescription
transaction_id*string
original_transaction_id*string
amount*number
typerefund | chargeback | voidDefault 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:

FieldTypeDescription
X-Tasskel-EventheaderThe event name, e.g. commission.approved.
X-Tasskel-SignatureheaderHMAC signature, see verification below.
X-Tasskel-DeliveryheaderUnique 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.paid

Verifying 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>
FieldTypeDescription
window.tasskelPartners.readyPromiseResolves once click attribution has been captured.
window.tasskelPartners.getReferral()functionReturns { token, sub_id, tid, clicked_at } or null.
window.tasskelPartners.signup(data)functionSends the lead: signup({ email, customer_id, name }).
window.tasskelPartners.nmiFields()functionReturns { 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.

FieldTypeDescription
/p/:tokenGETCurrent share link: a page with the program's preview card; the browser logs the click and redirects.
/r/:tokenGETOlder link by token: logs the click, returns the preview page with an instant redirect.
/go/:slugGETOlder 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.

  1. In the Tasskel Pay merchant portal, a webhook points to the endpoint below for these events.
  2. Each delivery is signed with the webhook's signing key, which Tasskel stores securely.
  3. On your checkout form, pass the referral token captured by the tracking script as merchant defined fields:
FieldTypeDescription
merchant_defined_field_1stringAffiliate/referral token (from window.tasskelPartners.getReferral().token).
merchant_defined_field_2stringsub_id, for sub-affiliate tracking.
merchant_defined_field_3stringPromo/coupon code (falls back to parsing the order description).
POST/api/public/webhooks/nmi

Tasskel 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.delete

Need an API key or a webhook endpoint? Head to sign in and open Admin → Integrations.