PawaBoost Partner Earn API
Version 1 · Base path /api/v1/earn · JSON over HTTPS
This guide is written for a developer who is integrating the Partner Earn API for the first time. It covers the concepts, a working quick start, every endpoint, every error, and the money model.
Table of contents
- Overview
- Quick start
- Core concepts
- Authentication and security (includes getting your API key)
- Endpoint reference
- Error reference
- Rate limits and retries
- Best practices and integration checklist
- Wallet, reversals and settlement
- Data handling and privacy
- FAQ and troubleshooting
- Versioning, changelog and support
1. Overview
What it is
The Partner Earn API lets a partner (your app, site or community) offer PawaBoost micro-tasks to your own users. A task is a small social action, such as following an account, liking a post or watching a video. Your user does the task, hands in a screenshot or other proof, and PawaBoost reviews it. For every approved task, PawaBoost credits your partner wallet.
It is for businesses that already have an audience and want to monetise it without building their own task marketplace.
The money model
Read this section before you write any code.
- Workers are anonymous. A "worker" is just an ID you choose (
externalUserId). PawaBoost never learns who the person is and never pays them. - PawaBoost credits the partner, not the worker. Each approved submission adds the task reward to your partner wallet.
- You pay your own workers. How much of the reward you pass on, and how, is entirely between you and your users.
- PawaBoost settles with you separately. Periodically, PawaBoost pays out your wallet balance to you. Each payout appears in your ledger as a
SETTLEMENTentry with a payment reference. - Mistakes are reversible. If an approved submission is later found to be invalid, it is reversed and the amount is taken back from your wallet. See section 9.
Your worker ──does task──▶ PawaBoost reviews ──approves──▶ PawaBoost credits YOUR wallet
│
you pay your worker (your own rules) │
▼
PawaBoost settles your wallet to you2. Quick start
Six steps, from "does my key work" to "has my worker been paid".
Set two variables first. BASE is the PawaBoost address, and KEY is your API key (create one first: see Getting your API key).
Linux / macOS
export BASE="https://www.pawaboost.com"
export KEY="pbp_live_xxxxxxxxxxxxxxxx"Windows CMD
set BASE=https://www.pawaboost.com
set KEY=pbp_live_xxxxxxxxxxxxxxxxStep 1. Check your key (ping)
curl -s "$BASE/api/v1/earn/ping" -H "Authorization: Bearer $KEY"curl -s "%BASE%/api/v1/earn/ping" -H "Authorization: Bearer %KEY%"{
"success": true,
"partnerId": "clpartner0001",
"name": "Acme Rewards",
"status": "ACTIVE"
}Step 2. List available tasks
curl -s "$BASE/api/v1/earn/tasks?limit=2" -H "Authorization: Bearer $KEY"curl -s "%BASE%/api/v1/earn/tasks?limit=2" -H "Authorization: Bearer %KEY%"{
"success": true,
"tasks": [
{
"id": "cltask000001",
"title": "Follow @example on Instagram",
"platform": "INSTAGRAM",
"category": "FOLLOWERS",
"type": "Instagram Follow",
"rewardKobo": 3900,
"remainingSlots": 12,
"durationSeconds": null,
"instructions": "Open the task link and follow the account using your real account. You must open the link we give you after claiming before you can submit proof."
}
],
"nextCursor": null
}Step 3. Claim a slot for one worker
curl -s -X POST "$BASE/api/v1/earn/tasks/cltask000001/claim" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"externalUserId":"w_9f3a1c2e"}'curl -s -X POST "%BASE%/api/v1/earn/tasks/cltask000001/claim" -H "Authorization: Bearer %KEY%" -H "Content-Type: application/json" -d "{\"externalUserId\":\"w_9f3a1c2e\"}"{
"success": true,
"claimId": "clclaim000001",
"expiresAt": "2026-10-07T14:30:00.000Z",
"redirectUrl": "https://www.pawaboost.com/r/AbCdEfGh1234"
}Step 4. The worker opens the link
Show redirectUrl to the worker and have their own browser open it. They are sent on to the task (for example the Instagram profile) and PawaBoost records that the link was opened. They then do the action. You cannot do this step with curl on their behalf: the link must be opened by the worker. See the tracked link.
Step 5. Submit proof
curl -s -X POST "$BASE/api/v1/earn/claims/clclaim000001/submit" \
-H "Authorization: Bearer $KEY" \
-F "screenshot=@proof.png" \
-F "proofUsername=worker_handle"curl -s -X POST "%BASE%/api/v1/earn/claims/clclaim000001/submit" -H "Authorization: Bearer %KEY%" -F "screenshot=@C:\proofs\proof.png" -F "proofUsername=worker_handle"{
"success": true,
"submissionId": "clsub0000001",
"status": "PENDING"
}Step 6. Check the result
curl -s "$BASE/api/v1/earn/submissions/clsub0000001" -H "Authorization: Bearer $KEY"curl -s "%BASE%/api/v1/earn/submissions/clsub0000001" -H "Authorization: Bearer %KEY%"{
"success": true,
"submissionId": "clsub0000001",
"claimId": "clclaim000001",
"taskId": "cltask000001",
"externalUserId": "w_9f3a1c2e",
"status": "APPROVED",
"rewardKobo": "3900",
"submittedAt": "2026-10-07T14:05:11.000Z",
"reviewedAt": "2026-10-07T15:20:42.000Z",
"reversed": false
}When status is APPROVED, the reward has been credited to your wallet. Check it with GET /api/v1/earn/balance (see section 5.7).
3. Core concepts
Tasks and shared slots
A task is one action PawaBoost needs done a set number of times. Each unit of work is a slot. Slots are shared between all partners and PawaBoost's own users, so remainingSlots goes down as anyone claims. The task list can lag by about 30 seconds; the claim call is the authority.
To keep things fair, one partner cannot reserve every slot of a task. If you hit a limit, claiming returns 409 Claim limit reached for this task. Try another task, or wait for your open claims to finish or expire.
Claims and the time window
A claim reserves one slot for one worker. It is valid for 30 minutes (expiresAt). For tasks that need watching time, the window is the watch time plus 5 minutes, if that is longer than 30 minutes. If the worker does not submit in time, the claim becomes EXPIRED and the slot is released for others.
A worker can hold one active claim per task. After an expiry or a rejection the worker may claim the task again. After an approval they cannot.
The tracked link
Every claim comes with a one-time redirectUrl. When the worker opens it, PawaBoost records the time and redirects them to the task.
- The link must be opened by the worker's own browser or app. Proof submitted before the link was opened is refused with
409 Open the task link before submitting proof. - For tasks that need watching time, the waiting time starts when the link is first opened.
- The link is shown once, in the claim response. It is not returned by any other call. Hand it to the worker straight away.
- The link stops working when the claim expires or is submitted.
Worker IDs
externalUserId identifies your worker. We treat it as an opaque string.
- 1 to 128 characters, no leading or trailing spaces, no control characters.
- Keep it stable: always use the same ID for the same person. Review history and worker standing follow the ID.
- Do not send real names, emails or phone numbers. We recommend sending a one-way hash of your own user id, so the value means nothing to us:
// Node.js: derive a stable, anonymous worker id from your own user id.
import { createHmac } from "node:crypto";
export function workerId(userId, secret) {
// `secret` is a long random string only your server knows.
return "w_" + createHmac("sha256", secret).update(String(userId)).digest("hex").slice(0, 32);
}Rewards, kobo and naira
Amounts are integer kobo. 100 kobo = 1 naira (₦1).
rewardKobo already includes any bonus agreed for your partner account. It is fixed when the worker submits, so later changes never alter an existing submission.
Worked example: a task shows rewardKobo: 3900.
- 3900 kobo ÷ 100 = ₦39.00 per approved submission.
- 25 approved submissions = 25 × 3900 = 97,500 kobo = ₦975.00 credited to your wallet.
Task lists show rewardKobo as a JSON number. Submissions and balances show money as a decimal string ("3900"), because totals can exceed the range a JSON number holds exactly. Parse them as big integers, never as floating point.
The submission lifecycle
A claim and its submission move through these states:
claim submit review
(none) ───────────────▶ OPEN ───────────────▶ SUBMITTED ─────────┬──▶ APPROVED
│ │
│ not submitted in time └──▶ REJECTED
├────────────────────▶ EXPIRED
│ closed by PawaBoost
└────────────────────▶ CANCELLED| Claim status | Meaning |
|---|---|
OPEN | The worker may still submit before expiresAt. |
SUBMITTED | Proof received, waiting for review. |
APPROVED | Approved. The reward was credited to your wallet. |
REJECTED | Rejected. The worker may claim the task again if slots remain. |
EXPIRED | Not submitted in time. The slot was released. |
CANCELLED | Closed by PawaBoost. The slot was released. |
The submission itself has three statuses: PENDING, APPROVED and REJECTED. A submission that is not reviewed within a few days is closed as REJECTED with a reason saying it was not reviewed in time. The worker may claim the task again.
Reversals
A reversal takes back an earlier approval that turned out to be invalid. The submission stays APPROVED with reversed: true, a REVERSAL entry appears in your ledger, and the amount is subtracted from your balance. The worker cannot claim that task again. Details are in section 9.
4. Authentication and security
Bearer key
Send your API key in the Authorization header on every request:
Authorization: Bearer pbp_live_<random characters>- Keys start with
pbp_live_followed by a long random string. - Your partner identity comes from the key alone. There is no partner id parameter. Anything you send that looks like one is ignored.
- A missing, malformed, unknown or revoked key, and a suspended partner account, all return the same
401 Unauthorized. The response never says which.
Getting your API key
You create and manage your own keys in the PawaBoost app. There is no waiting for a key to be issued.
- Sign in to PawaBoost at https://www.pawaboost.com. If you do not have an account yet, sign up and verify your email address first.
- Ask support to link your account to your partner. Message PawaBoost support on Telegram or email (see Support) with your partner name and the email address of your PawaBoost account. A PawaBoost administrator links your account to your partner. Until then you will not see the Partner API page. One account belongs to one partner.
- Open Partner API. Go to your Profile and choose Partner API.
- Create a key. Choose New API Key, give it a name you will recognise (for example "Production server"), and confirm.
- Copy it now. The full key is shown once. PawaBoost keeps only a one-way fingerprint and cannot show it again. Copy it straight into your server's secret store.
- Test it with
GET /api/v1/earn/ping(see the Quick start).
Limits and behaviour:
- You can have at most 3 active keys at a time. Revoke one to create another.
- You can create at most 5 keys per hour. Wait and try again if you hit the limit.
- The page shows each key's name, the start of the key, when it was last used and its status. It never shows a full key again.
- If your partner account is suspended (or not yet active), the page is read-only: you can see your keys but cannot create or revoke any, and your keys do not work until the account is active again. Contact support.
- Anyone you want to manage keys needs their own PawaBoost account linked to your partner by support.
Keeping the key safe
- Server-side only. Call the API from your backend. Never put the key in a web page, a mobile app, a browser script or a public repository. Anyone holding the key can claim tasks and read your balance as you.
- Shown once. A key is displayed in full only once, when you create it. We store only a one-way fingerprint, so we cannot show it again. Save it in your secrets manager straight away. If you lose it, revoke it and create a new one.
- Rotation. Rotate keys on a schedule and whenever someone with access leaves. Create the new key first on the Partner API page, deploy it, confirm it works with
GET /ping, and only then revoke the old one. Up to 3 keys can be active at the same time, so rotation needs no downtime. - Revocation. If a key may have leaked, revoke it on the Partner API page straight away; it stops working immediately and returns
401from then on. Then create a replacement. If you cannot sign in, contact support and we will revoke it for you. - HTTPS only. Use
https://for every call. Never send the key over plain HTTP. - Logging. Do not log the
Authorizationheader or the key.
Rate limits and IP addresses
Each key is limited to a number of requests per minute (see section 7). Repeated failed authentication attempts from one IP address are temporarily blocked with 429, so do not retry a 401 in a loop. If you call from several servers, each server can use the same key; limits are counted per key, not per IP address.
5. Endpoint reference
All endpoints require Authorization: Bearer <key>. All responses are JSON and include Cache-Control: no-store and the rate-limit headers from section 7. Every success response contains "success": true; every error is { "success": false, "error": "<message>" }. Timestamps are ISO 8601 in UTC. Do not cache responses.
| Method and path | Purpose |
|---|---|
GET /api/v1/earn/ping | Check your key |
GET /api/v1/earn/tasks | List tasks with slots left |
POST /api/v1/earn/tasks/{id}/claim | Claim a slot for a worker |
GET /api/v1/earn/claims/{id} | Claim status |
POST /api/v1/earn/claims/{id}/submit | Submit proof |
GET /api/v1/earn/submissions/{id} | One submission |
GET /api/v1/earn/submissions | List your submissions |
GET /api/v1/earn/balance | Wallet totals and recent ledger |
5.1 Ping
GET /api/v1/earn/pingNo parameters. Confirms the key works and which partner it belongs to.
{
"success": true,
"partnerId": "clpartner0001",
"name": "Acme Rewards",
"status": "ACTIVE"
}Errors: 401, 429.
5.2 List tasks
GET /api/v1/earn/tasks| Query parameter | Type | Rules |
|---|---|---|
limit | integer | Optional. 1 to 50. Default 20. |
cursor | string | Optional. The nextCursor from the previous page. |
curl -s "$BASE/api/v1/earn/tasks?limit=20" -H "Authorization: Bearer $KEY"{
"success": true,
"tasks": [
{
"id": "cltask000001",
"title": "Watch our video for 30 seconds",
"platform": "YOUTUBE",
"category": "WATCHTIME",
"type": "YouTube Watch Time",
"rewardKobo": 2500,
"remainingSlots": 40,
"durationSeconds": 30,
"instructions": "Open the task link and watch the video for the full required time. You must open the link we give you after claiming before you can submit proof."
}
],
"nextCursor": "cltask000001"
}| Field | Notes |
|---|---|
id | Use this in the claim call. |
rewardKobo | Integer kobo you earn per approved submission. |
remainingSlots | Slots left, up to about 30 seconds out of date. |
durationSeconds | Watch time the worker must wait, or null. |
instructions | Generic instructions you can show the worker. |
Only tasks with slots left are listed, in a stable order. When nextCursor is not null, pass it back as cursor for the next page. It is null on the last page. The destination of the task is never listed; the worker reaches it through the tracked link.
Errors: 400 Invalid request (bad limit or cursor), 401, 429.
5.3 Claim a task
POST /api/v1/earn/tasks/{id}/claim
Content-Type: application/json{id} is the task id.
| Body field | Type | Rules |
|---|---|---|
externalUserId | string | Required. 1 to 128 characters, no leading or trailing spaces, no control characters. No other fields are allowed. |
{ "externalUserId": "w_9f3a1c2e" }Success, 201 Created:
{
"success": true,
"claimId": "clclaim000001",
"expiresAt": "2026-10-07T14:30:00.000Z",
"redirectUrl": "https://www.pawaboost.com/r/AbCdEfGh1234"
}Errors:
| Status | error | Notes |
|---|---|---|
| 400 | Invalid request | Body is not JSON, has extra fields, or externalUserId is invalid. |
| 401 | Unauthorized | |
| 403 | This worker is not allowed | The worker is blocked. |
| 404 | Task not available | The task is finished, paused, or the id is wrong. |
| 409 | No slots remaining for this task | |
| 409 | This worker has already claimed this task | |
| 409 | Claim limit reached for this task | |
| 429 | Too many requests | |
| 503 | Please retry | With Retry-After. |
Notes: redirectUrl is returned only here. If you lose it, the claim cannot be used; wait for it to expire and claim again.
5.4 Claim status
GET /api/v1/earn/claims/{id}{id} is the claimId. You can only read your own claims.
{
"success": true,
"claimId": "clclaim000001",
"taskId": "cltask000001",
"status": "OPEN",
"expiresAt": "2026-10-07T14:30:00.000Z",
"linkOpened": true
}| Field | Notes |
|---|---|
status | OPEN, SUBMITTED, APPROVED, REJECTED, EXPIRED or CANCELLED. A claim past expiresAt is reported as EXPIRED. |
linkOpened | true once the worker has opened the tracked link. |
Errors: 401, 404 Not found (unknown id, or not your claim, both identical), 429.
5.5 Submit proof
POST /api/v1/earn/claims/{id}/submit
Content-Type: multipart/form-data{id} is the claimId. Send the proof as a multipart form. Each field may appear once and no other fields are accepted.
| Field | Type | Rules |
|---|---|---|
screenshot | file | Optional. JPEG, PNG or WebP, at most 2 MB. The file contents are checked, not the extension. |
proofUsername | text | Optional. Up to 200 characters. |
proofUrl | text | Optional. Must start with http:// or https://, up to 2048 characters. Stored for review, never visited. |
selectedComment | text | Optional. Up to 1000 characters. |
curl -s -X POST "$BASE/api/v1/earn/claims/clclaim000001/submit" \
-H "Authorization: Bearer $KEY" \
-F "screenshot=@proof.png" \
-F "proofUsername=worker_handle"Success, 201 Created:
{
"success": true,
"submissionId": "clsub0000001",
"status": "PENDING"
}Nothing is paid at this point. The submission waits for review.
Which proof a task needs. It depends on the task type, for example a screenshot, a username, a link, or a combination. If something required is missing you get 400 Missing required proof: <field> where the field is screenshot, proofUsername, proofUrl, screenshot or proofUrl, or proof (at least one proof field is always needed). You can fix it and submit again on the same claim.
Watch-time tasks. When durationSeconds is set, proof is refused with 409 The required watch time has not elapsed yet until that many seconds have passed since the worker opened the link. Wait and retry.
Duplicates. Every proof image is compared with images already submitted. The same screenshot cannot be used twice, including a copy that was merely saved again or re-compressed. A repeat returns 409 This proof has already been submitted. Each worker must use their own, fresh screenshot.
What to tell workers about good screenshots.
- Take it after doing the action, so the result is visible (for example the "Following" button).
- Show the whole screen or the relevant part, not a tiny crop.
- Make the account name and the action clearly readable.
- Do not edit, crop out information, reuse someone else's image, or submit an old screenshot.
- Keep it under 2 MB. Screenshots from a phone are usually fine; if not, save as JPEG.
Errors:
| Status | error | Notes |
|---|---|---|
| 400 | Invalid request | Not multipart, unknown or repeated field, bad text field. |
| 400 | Invalid image | Not a valid JPEG, PNG or WebP. |
| 400 | Missing required proof: <field> | See above. |
| 401 | Unauthorized | |
| 403 | This worker is not allowed | |
| 404 | Not found | Unknown claim, or not yours. |
| 409 | This claim can no longer accept a submission | Expired, cancelled, or already submitted. |
| 409 | Open the task link before submitting proof | |
| 409 | The required watch time has not elapsed yet | |
| 409 | Task not available | The task closed. |
| 409 | This task cannot accept submissions | |
| 409 | This proof has already been submitted | |
| 413 | Image is too large (maximum 2 MB) | |
| 429 | Too many requests | |
| 429 | Too many new workers today | Daily limit on first-time workers. Wait the Retry-After time. |
| 503 | Please retry | With Retry-After. |
5.6 Submission status
GET /api/v1/earn/submissions/{id}{id} is the submissionId. You can only read your own submissions.
{
"success": true,
"submissionId": "clsub0000001",
"claimId": "clclaim000001",
"taskId": "cltask000001",
"externalUserId": "w_9f3a1c2e",
"status": "PENDING",
"rewardKobo": "3900",
"submittedAt": "2026-10-07T14:05:11.000Z",
"reviewedAt": null,
"reversed": false
}| Field | Notes |
|---|---|
status | PENDING, APPROVED or REJECTED. |
rewardKobo | Decimal string. The value of this submission in kobo. |
submittedAt | When you submitted. |
reviewedAt | null while PENDING. |
reversed | true if an approved submission was later reversed. status stays APPROVED. |
rejectionReason | Present only when status is REJECTED. |
A rejected submission:
{
"success": true,
"submissionId": "clsub0000002",
"claimId": "clclaim000002",
"taskId": "cltask000001",
"externalUserId": "w_41be77aa",
"status": "REJECTED",
"rewardKobo": "3900",
"submittedAt": "2026-10-07T14:06:30.000Z",
"reviewedAt": "2026-10-07T15:22:10.000Z",
"reversed": false,
"rejectionReason": "Screenshot does not show the follow"
}Errors: 401, 404 Not found (unknown id, or not yours, both identical), 429.
5.7 Balance
GET /api/v1/earn/balanceNo parameters.
{
"success": true,
"balanceKobo": "125000",
"totalEarnedKobo": "200000",
"totalReversedKobo": "5000",
"totalSettledKobo": "70000",
"ledger": [
{ "type": "SETTLEMENT", "amountKobo": "70000", "createdAt": "2026-10-07T16:00:00.000Z", "reference": "BANK-TRF-0091" },
{ "type": "REVERSAL", "amountKobo": "5000", "createdAt": "2026-10-06T10:12:00.000Z", "reference": null },
{ "type": "CREDIT", "amountKobo": "3900", "createdAt": "2026-10-05T09:30:00.000Z", "reference": null }
]
}| Field | Notes |
|---|---|
balanceKobo | What PawaBoost currently owes you. Equals earned minus reversed minus settled. Can be negative. |
totalEarnedKobo | Sum of all approvals credited. |
totalReversedKobo | Sum of all reversals. |
totalSettledKobo | Sum of all payouts to you. |
ledger | Your 20 most recent entries, newest first. |
All amounts are strings. A partner with no activity yet gets zeros and an empty ledger. See section 9 for what each entry type means.
Errors: 401, 429.
5.8 List submissions
GET /api/v1/earn/submissions| Query parameter | Type | Rules |
|---|---|---|
externalUserId | string | Optional. Only this worker's submissions. Same rules as when claiming. |
status | string | Optional. Exactly PENDING, APPROVED or REJECTED (upper case). |
limit | integer | Optional. 1 to 50. Default 20. |
cursor | string | Optional. The nextCursor of the previous page. Must be one of your own submissions. |
curl -s "$BASE/api/v1/earn/submissions?status=PENDING&limit=20" -H "Authorization: Bearer $KEY"{
"success": true,
"submissions": [
{
"submissionId": "clsub0000001",
"claimId": "clclaim000001",
"taskId": "cltask000001",
"externalUserId": "w_9f3a1c2e",
"status": "PENDING",
"rewardKobo": "3900",
"submittedAt": "2026-10-07T14:05:11.000Z",
"reviewedAt": null,
"reversed": false
}
],
"nextCursor": "clsub0000001"
}Newest first. Each item has the same fields as 5.6, including rejectionReason on rejected items only. When nextCursor is not null, pass it back as cursor to get the next page; it is null on the last page. Remember to URL-encode externalUserId.
Errors: 400 Invalid request (any invalid query value, or a cursor that is not one of your submissions), 401, 429.
6. Error reference
Every error has the form { "success": false, "error": "<message>" }. "Retry?" says whether sending the same request again can succeed.
| Status | Message | Meaning | Retry? |
|---|---|---|---|
| 400 | Invalid request | Bad body, query parameter, field or cursor. | No. Fix the request. |
| 400 | Invalid image | Screenshot is not a valid JPEG, PNG or WebP. | No. Send a valid image. |
| 400 | Missing required proof: <field> | The task requires that proof field. | After adding it. |
| 401 | Unauthorized | Key missing, wrong, revoked, or partner suspended. | No. Check the key; contact support if it used to work. |
| 403 | This worker is not allowed | This worker cannot take part. No reason is given. | No. Stop offering tasks to this worker. |
| 404 | Not found | The claim or submission does not exist, or is not yours. | No. |
| 404 | Task not available | The task is not open for claims. | No. Choose another task. |
| 409 | No slots remaining for this task | All slots are taken. | Later, or another task. |
| 409 | This worker has already claimed this task | The worker has an active or approved claim on it. | No. Read the existing claim. |
| 409 | Claim limit reached for this task | A claim limit was reached for your account on this task. | Later, when claims finish or expire. |
| 409 | This claim can no longer accept a submission | Claim expired, was cancelled, or already has a submission. | No. Read the claim state. |
| 409 | Open the task link before submitting proof | The worker has not opened the tracked link. | After the worker opens it. |
| 409 | The required watch time has not elapsed yet | Submitted too early. | Yes, after the wait. |
| 409 | Task not available | The task closed before the proof arrived. | No. |
| 409 | This task cannot accept submissions | The task has no payable reward. | No. |
| 409 | This proof has already been submitted | Duplicate screenshot. | No. A new screenshot is needed. |
| 413 | Image is too large (maximum 2 MB) | File over 2 MB. | After shrinking it. |
| 429 | Too many requests | Rate limited. Retry-After gives the seconds to wait. | Yes, after Retry-After. |
| 429 | Too many new workers today | Daily limit on first-time workers. Retry-After is sent. | Yes, after Retry-After. |
| 500 | Something went wrong | Unexpected error on our side. | Yes, with backoff (see the safety rules in section 7). |
| 503 | Please retry | Temporary contention. Retry-After is sent. | Yes, after Retry-After. |
| 503 | Service unavailable | Temporary problem on our side. | Yes, with backoff. |
7. Rate limits and retries
Limits and headers
Each key may make 30 requests per minute. Every authenticated response includes:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per window. |
X-RateLimit-Remaining | Requests left in this window. |
X-RateLimit-Reset | When the window ends, in Unix seconds. |
Over the limit you receive 429 Too many requests with Retry-After (seconds). Treat the limit as burst protection. Stay well below it, spread bulk work out, and slow down when X-RateLimit-Remaining gets low.
Backoff
- Honour
Retry-Afterwhen present. - Otherwise use exponential backoff with jitter: wait about 1 s, 2 s, 4 s, 8 s, up to a cap of about 30 s, with a random +/- 50% each time.
- Stop after a handful of attempts and surface an error. Never retry in a tight loop.
- Do not retry
400,401,403,404or409unchanged. They will fail the same way.
What is safe to retry
| Endpoint | Safe to retry? | Notes |
|---|---|---|
GET endpoints (ping, tasks, claim status, submission status, list, balance) | Yes, always | Read-only. |
POST .../claim | Yes, with care | A 503 means nothing was recorded, so retry. After a timeout or 500 the outcome is unknown: the retry may return 409 This worker has already claimed this task, which means the first call worked, but its redirectUrl is lost. That claim will expire; claim again afterwards. |
POST .../submit | Yes, with care | A 503 means nothing was recorded. After a timeout or 500, call GET /claims/{id} first: if it is SUBMITTED the proof arrived, so do not send it again (a repeat gets a 409). |
A 409 that says "already" is not a failure to retry. It means an earlier request took effect, so read the current state instead.
Sample retry code: JavaScript
// Node 18+ (global fetch). Retries only 429, 500 and 503, and only when it is safe.
async function callApi(url, options = {}, maxAttempts = 5) {
for (let attempt = 1; ; attempt++) {
let res;
try {
res = await fetch(url, {
...options,
headers: { ...options.headers, Authorization: `Bearer ${process.env.PAWABOOST_KEY}` },
});
} catch (err) {
if (attempt >= maxAttempts) throw err; // network error
await sleep(backoff(attempt));
continue;
}
if (![429, 500, 503].includes(res.status) || attempt >= maxAttempts) return res;
const retryAfter = Number(res.headers.get("retry-after"));
await sleep(retryAfter > 0 ? retryAfter * 1000 : backoff(attempt));
}
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// 1s, 2s, 4s, 8s ... capped at 30s, with +/- 50% jitter.
const backoff = (attempt) => Math.min(30000, 1000 * 2 ** (attempt - 1)) * (0.5 + Math.random());Sample retry code: PHP
<?php
// PHP 7.4+ with the cURL extension. Retries only 429, 500 and 503.
function callApi(string $url, array $curlOptions = [], int $maxAttempts = 5): array
{
for ($attempt = 1; ; $attempt++) {
$retryAfter = 0;
$ch = curl_init($url);
curl_setopt_array($ch, $curlOptions + [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('PAWABOOST_KEY')],
CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$retryAfter) {
if (stripos($line, 'retry-after:') === 0) {
$retryAfter = (int) trim(substr($line, 12));
}
return strlen($line);
},
]);
$body = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$retryable = $body === false || in_array($status, [429, 500, 503], true);
if (!$retryable || $attempt >= $maxAttempts) {
return ['status' => $status, 'body' => $body === false ? null : json_decode($body, true)];
}
$delay = $retryAfter > 0
? $retryAfter
: min(30, 2 ** ($attempt - 1)) * (0.5 + mt_rand() / mt_getrandmax());
usleep((int) ($delay * 1000000));
}
}8. Best practices and integration checklist
Polling for status
- Submissions are reviewed by people, so results are not instant. Do not poll faster than every 30 seconds; every few minutes is plenty.
- Prefer one list call over many single calls:
GET /submissions?status=PENDING&limit=50gives all waiting items at once. - Stop polling a submission once it is
APPROVEDorREJECTED; those do not change, except that an approval may later showreversed: true. - Check the whole list for changes now and then rather than only the items you remember.
Showing workers their status
| API state | Suggested wording to the worker |
|---|---|
Claim OPEN, link not opened | "Open the task link to start." |
Claim OPEN, link opened | "Do the task, then upload your screenshot before expiresAt." |
Submission PENDING | "Under review." |
Submission APPROVED | "Approved. Your reward is on its way." (credit the worker per your own rules.) |
Submission REJECTED | "Not approved: <rejectionReason>." You may offer another try. |
Claim EXPIRED | "Time ran out. You can start the task again." |
Handling rejection reasons
rejectionReasonis written to be shown to your worker. Show it as it is, and do not parse it; the wording can change.- Do not credit a worker for a rejected submission.
- A rejected worker may claim the task again if slots remain. Limit repeated attempts on your side to avoid wasted effort.
Handling expired claims
- Expiry is normal. Treat
EXPIREDas "try again", not as an error. - Show a countdown from
expiresAtso the worker knows the deadline. - Do not hold workers on claims they will not finish; an unfinished claim blocks a slot that others could use.
Paying your workers
- Credit your worker only after the submission is
APPROVED, and consider holding the payment until you are sure it will not bereversed(see section 9). - Remember that PawaBoost pays you, in settlements, not per submission. Keep enough cash flow to pay workers before the next settlement.
Going-live checklist
- ☐ The key is stored on the server and is not in any client code or repository.
- ☐
GET /pingreturnsACTIVEfrom the production server. - ☐ Worker ids are stable, anonymous hashes. No names, emails or phone numbers.
- ☐ The worker, not your server, opens
redirectUrl. - ☐
redirectUrlis passed to the worker immediately; it cannot be fetched again. - ☐ Screenshots are checked for type (JPEG, PNG or WebP) and size (2 MB) before upload.
- ☐ Retries use backoff, honour
Retry-After, and never repeat a4xx. - ☐ Money is handled as integer kobo or big integers, never floats.
- ☐ Polling is no faster than every 30 seconds.
- ☐ You handle
403 This worker is not allowedby stopping tasks for that worker. - ☐ You handle
401by alerting your team, not by retrying in a loop. - ☐ You reconcile
GET /balanceagainst your own records regularly.
9. Wallet, reversals and settlement
Reading the balance
GET /balance returns four totals and your 20 most recent ledger entries.
balanceKobo = totalEarnedKobo - totalReversedKobo - totalSettledKoboThe ledger is an append-only record. Entries are never edited or deleted. A correction is always a new entry.
Ledger entry types
type | Meaning | Effect on balance |
|---|---|---|
CREDIT | A submission was approved and its reward credited to you. | Increases |
REVERSAL | An earlier approval was taken back. | Decreases |
SETTLEMENT | PawaBoost paid out part or all of your balance to you. | Decreases |
amountKobo is always positive. The type tells you the direction.
Reversals and negative balances
If an approved submission is later found to be invalid, it is reversed:
- the submission keeps
status: "APPROVED"and showsreversed: true; - a
REVERSALentry is added to your ledger andtotalReversedKobogrows; balanceKobogoes down by the reward.
If your balance was already paid out, the balance can become negative. A negative balance is not a separate bill. It is simply netted: new approvals first bring the balance back up, and the next settlement is calculated on what is left. Because a reversal can arrive after an approval, hold your own worker payments for a sensible period before treating a reward as final.
Settlement
PawaBoost pays your balance out to you separately, outside the API. When it does, a SETTLEMENT entry is added, totalSettledKobo grows, and balanceKobo goes down by the amount paid. The entry's reference is the payment reference recorded for that payout, for example a bank transfer reference. Use it to match the payout to your bank statement. The reference is null for CREDIT and REVERSAL entries.
The ledger shows only your 20 latest entries, so keep your own record of references as you see them.
10. Data handling and privacy
- Proofs are deleted after review. A screenshot is kept only long enough to review it. Once the submission is approved or rejected, the image is deleted. We keep the decision, not the picture. We cannot return a deleted image, so keep your own copy if you need one.
- No worker personal data is requested. The API only needs an opaque worker id. Do not send real names, emails, phone numbers or social handles as
externalUserId. - What you may see about a rejection is limited to a short, worker-friendly reason. Internal review details are never shown.
- Proof text (
proofUsername,proofUrl,selectedComment) is used for review only. Do not put private information in it. - Screenshots may show personal content. Tell workers to crop or avoid showing private messages, notifications and other people's details, as long as the proof needed for the task stays visible.
- You are responsible for the lawful handling of your own users' data on your side, including your agreement with them.
11. FAQ and troubleshooting
1. My submit returns 409. What does it mean?
Read the message. Open the task link before submitting proof: the worker has not opened redirectUrl. The required watch time has not elapsed yet: wait for durationSeconds after the link was opened. This claim can no longer accept a submission: the claim expired or already has a submission. This proof has already been submitted: the screenshot was used before; a new one is needed. Call GET /claims/{id} to see the claim's state.
2. I get 403 "This worker is not allowed".
That worker cannot take part, and no reason is given. Stop offering tasks to that worker. Other workers are not affected.
3. My worker's claim expired. What now?
The slot was released. The worker can claim the task again with a new claim, if slots remain. Show the countdown so it does not happen again.
4. My balance is negative.
A reversal was applied after earlier earnings had already been settled. It is netted automatically: new approvals bring the balance back up and the next settlement is based on what remains. See section 9.
5. I lost the redirectUrl.
It is shown only once and cannot be fetched again. Let that claim expire (the claim call for the same worker returns 409 This worker has already claimed this task until then) and claim again.
6. All my requests return 401.
Check the header is exactly Authorization: Bearer <key>, that the key is complete, and that you use the production key on production. A revoked key or a suspended partner account also returns 401, with no further detail. Contact support if a key that worked has stopped. You can check your keys, and whether the key is revoked, on the Partner API page.
6b. I cannot see the Partner API page, or "New API Key" is greyed out.
No Partner API page: your PawaBoost account has not been linked to your partner yet. Ask support to link it (send your partner name and account email). "New API Key" greyed out: you already have 3 active keys (revoke one), or your partner account is suspended, in which case the page is read-only.
6c. How do I get or rotate an API key?
Create it yourself on the Partner API page (see Getting your API key). To rotate, create the new key first, switch your server over, confirm it works, then revoke the old one.
7. I get 429. How long do I wait?
Use the Retry-After header (seconds). Reduce your request rate and spread bulk calls. Too many new workers today is a daily limit on first-time workers; it clears with time.
8. My screenshot is rejected with 400 "Invalid image".
The file is not a real JPEG, PNG or WebP. The contents are checked, so renaming a different file type does not work. Convert it or take a new screenshot.
9. I get 413 "Image is too large".
The limit is 2 MB. Ask workers to use a normal phone screenshot, or compress it, before uploading.
10. The task list is empty.
No task currently has free slots, or all of them were claimed. Try again later.
11. A submission was rejected. Can the worker try again?
Yes, if slots remain. They need a new claim and a new, genuine screenshot.
12. An approved submission now says reversed: true.
It was found to be invalid after approval and was taken back. A REVERSAL entry is in your ledger. The worker cannot claim that task again.
13. A submit call timed out. Was it received?
Call GET /claims/{id}. If the status is SUBMITTED, the proof arrived. Do not send it again.
14. A submission has been PENDING for a long time.
Reviews are done by people and can take a while. Keep polling gently. A submission that is never reviewed is closed as rejected after a few days, and the worker may try again.
15. GET /submissions returns 400 with a cursor.
The cursor must be exactly a nextCursor value you received, from your own list. If it no longer works, start again from the first page.
12. Versioning, changelog and support
Versioning
This is version 1, served under /api/v1/. Within v1 we only make backwards-compatible changes: new endpoints, new optional fields and new response fields. Write your client to ignore fields it does not know. A breaking change would be released as a new version under a new path, with notice.
Changelog
| Date | Change |
|---|---|
| 2026-10-10 | Partners now create and revoke their own API keys on the Partner API page (see "Getting your API key"). |
| 2026-10-07 | Added GET /submissions, GET /submissions/{id} and GET /balance. Rewrote this guide and added a Postman collection. |
| Earlier | GET /ping, GET /tasks, POST /tasks/{id}/claim, GET /claims/{id}, POST /claims/{id}/submit. |
Postman
A ready-made collection is in docs/partner-api-postman.json. Import it into Postman and set the baseUrl and apiKey collection variables. Keep the key out of shared workspaces.
Support
Contact PawaBoost support through either channel:
- Telegram: https://t.me/pawaboostsupport
- Email: support@pawaboost.com
Contact support for: linking your PawaBoost account to your partner, problems creating or revoking keys, a worker that is blocked, balance or settlement questions, and anything you suspect is wrong (unexpected 401 or 403 responses, missing credits, a reversal you do not understand).
When you write, include:
- your partner name
- the
claimIdorsubmissionId - the time of the request in UTC
- the HTTP status and error message you received
Never send your API key. Support will never ask for it.