REST API reference
Server-to-server API for managing referrals: list and inspect them, create them from your own systems, and report conversions. All endpoints are JSON over HTTPS.
Authentication
Every request must carry your API key in the X-API-Key header. Keys look like oft_ followed by 32 hex characters and are issued in your partner dashboard.
curl https://offertown.net/api/v1/partners/referrals \
-H "X-API-Key: oft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"- The key identifies your partner account — there is no
partnerIdparameter anywhere in the API, and supplying one is rejected. - Keep the key server-side only. If it leaks, rotate it from the dashboard.
- Missing or invalid keys return
401; keys for partners that aren’t approved yet return403.
Rate limits
- Write requests are limited to 30 per minute per IP.
- Conversion reporting is additionally limited to 60 per minute per partner.
- Exceeding a limit returns
429with aRetry-Afterheader andretryAfterMsin the body. Back off and retry — all write endpoints are idempotent, so retries are safe.
Errors
All errors share one shape:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable description",
"fields": [{ "field": "friendEmail", "message": "Invalid email" }],
"retryAfterMs": 12000
}
}| HTTP | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Malformed query parameter or body field. |
| 401 | MISSING_API_KEY / INVALID_API_KEY | No key, or the key doesn’t resolve. |
| 403 | PARTNER_NOT_APPROVED | Valid key, but the partner account isn’t approved. |
| 404 | NOT_FOUND | Referral doesn’t exist or isn’t yours. |
| 409 | DUPLICATE_CONVERSION | The referral was already converted under a different transaction ID. |
| 429 | RATE_LIMITED | Slow down; retry after the indicated delay. |
| 500 | INTERNAL_ERROR | Something went wrong on our side. |
The referral object
{
"referralId": "jx7…", // stable ID, use it in URLs
"referralCode": "a1b2c3d4", // the referrer's 8-char share code
"status": "pending", // clicked | pending | converted | expired
"friendEmail": "ana@example.com",
"friendName": "Ana",
"createdAt": 1765467600000, // epoch ms
"convertedAt": null, // epoch ms when converted
"conversionValue": null, // decimal string of cents, e.g. "12999"
"currency": null, // "EUR" once converted
"referrerIdentifier": "cust_42", // your identifier for the referrer
"externalSourceId": null, // your identifier for the referral
"metadata": {} // free-form, up to 4 KiB
}List referrals
| Query parameter | Description |
|---|---|
status | clicked | pending | converted | expired |
referrerEmail | Exact-match filter on the referrer’s email. |
dateFrom / dateTo | Creation window, epoch milliseconds. |
search | Free-text search (max 256 chars). |
sortBy / sortOrder | createdAt, convertedAt, or conversionValue; asc or desc. |
page / pageSize | Pagination; default 1 / 25, max page size 100. |
{
"referrals": [ { …referral } ],
"pagination": { "page": 1, "pageSize": 25, "total": 137 }
}Create a referral
Registers a referral you captured yourself (your own form, support flow, or import).
{
"referrerIdentifier": "cust_42", // required — how YOU identify the referrer
"friendEmail": "ana@example.com", // required
"friendName": "Ana", // required
"externalSourceId": "lead_991", // optional — your reference
"metadata": { "campaign": "spring" } // optional, ≤ 4 KiB
}{
"referralId": "jx7…",
"referralCode": "a1b2c3d4",
"status": "pending",
"idempotent": false
}Idempotency: the combination of referrerIdentifier + friendEmail is the idempotency key. Re-posting the same pair returns the existing referral with idempotent: true.
Get a referral
Returns the full referral object.
Update a referral
Only metadata and externalSourceIdare writable. Everything else — status, conversion data, the friend’s identity — is read-only and rejected if present in the body.
{
"externalSourceId": "lead_991",
"metadata": { "crmStage": "won" }
}Report a conversion
Marks the referral converted and starts the reward flow (your approval, maturation, then funding).
{
"conversionValue": 12999, // optional — order value in cents
"currency": "EUR", // optional — EUR only
"externalTransactionId": "order_77", // strongly recommended — your order ID
"metadata": { "sku": "PRO-1" } // optional
}{
"referralId": "jx7…",
"status": "converted",
"convertedAt": 1765467600000,
"conversionValue": "12999",
"idempotent": false
}- Idempotency: replays with the same
externalTransactionIdreturn the original result. Reporting a conversion for an already-converted referral with a different transaction ID — or reusing a transaction ID on another referral — returns409 DUPLICATE_CONVERSION. - Send
conversionValuein cents (integer); responses echo it as a decimal string.
A typical integration
- Capture referred friends with the widget (or create referrals via
POST /referralsfrom your backend). - When an order completes in your system, look up the referral (by your
externalSourceIdor the friend’s email via the list endpoint) and call…/convertwith the order ID asexternalTransactionId. - Approve conversions in your partner dashboard; rewards mature and are funded automatically.