REST API Reference
Pay4Feedback exposes a tenant-scoped REST API for programmatic access to the same data and actions you can take from the dashboard. Use it to script campaigns, mirror responses into your warehouse, or build custom approval workflows on top of our AI scoring.
Base URL
https://app.pay4feedback.com
All public endpoints live under /api/v1/. The stable contract is:
- We'll add new fields to response bodies without notice — parse defensively.
- We'll never remove fields or change their type in v1. Breaking changes ship as
/api/v2/.
Interactive docs (Swagger)
The full, always-current reference with "Try it out" buttons:
https://app.pay4feedback.com/swagger-ui/index.html
Paste your API key into the Authorize button at the top, then every example request the UI generates includes it. The raw OpenAPI 3.0 spec is at /v3/api-docs if you want to generate clients.
Authentication
Every /api/v1/* request needs an API key.
Create one in the dashboard: Settings → API Keys → Create key. The plaintext is shown once on creation; after that only a prefix is visible. Store it in your secret manager the same way you'd store a database password.
Two accepted header forms (pick one):
Authorization: Bearer p4f_live_abc...xyz
X-API-Key: p4f_live_abc...xyz
Keys are:
- Tenant-scoped — a key can only see and act on the tenant it was created in.
- Hashed at rest — we store a SHA-256, so we can't recover a lost key. Issue a new one and revoke the old.
- Revocable instantly — revocation takes effect on the next request. No grace period.
Rate limits
60 requests per minute per tenant. On the 61st within a 60-second window:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
Content-Type: application/json
{"success":false,"error":"Rate limit exceeded. Retry in 12s."}
If you need higher throughput, email sales@pay4feedback.com — Enterprise tier raises the limit.
Response envelope
Every successful response has the same shape:
{
"success": true,
"data": { /* endpoint-specific payload */ },
"timestamp": "2026-04-19T14:22:51Z"
}
Errors:
{
"success": false,
"error": "Human-readable message",
"timestamp": "2026-04-19T14:22:51Z"
}
HTTP status codes follow standard REST semantics: 200 ok, 400 validation error, 401 missing/invalid key, 404 not found, 409 state conflict, 429 rate limited, 500 server error.
Endpoints at a glance
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/me | Sanity-check your key — returns the tenant it's scoped to |
| GET | /api/v1/campaigns | List all campaigns |
| GET | /api/v1/campaigns/{id} | Get one campaign |
| POST | /api/v1/campaigns | Create a campaign (starts in DRAFT) |
| PATCH | /api/v1/campaigns/{id} | Update a campaign |
| POST | /api/v1/campaigns/{id}/pause | Pause — widget stops serving |
| POST | /api/v1/campaigns/{id}/resume | Resume from paused |
| GET | /api/v1/responses | List feedback responses |
| GET | /api/v1/responses/{id} | Get one response with all answers + quality breakdown |
| POST | /api/v1/responses/{id}/approve | Approve the reward |
| POST | /api/v1/responses/{id}/reject | Reject the reward (optional { "reason": "…" }) |
| GET | /api/v1/webhooks | List webhook subscriptions |
| POST | /api/v1/webhooks | Create a subscription |
| DELETE | /api/v1/webhooks/{id} | Delete a subscription |
Outbound webhook events are documented on their own page: Webhooks.
Quick start — curl
export P4F=p4f_live_your_key_here
# 1. Smoke test the key
curl -H "Authorization: Bearer $P4F" \
https://app.pay4feedback.com/api/v1/me
# 2. List campaigns
curl -H "Authorization: Bearer $P4F" \
https://app.pay4feedback.com/api/v1/campaigns
# 3. Pull the 50 most recent responses
curl -H "Authorization: Bearer $P4F" \
https://app.pay4feedback.com/api/v1/responses
# 4. Approve a specific response
curl -X POST \
-H "Authorization: Bearer $P4F" \
https://app.pay4feedback.com/api/v1/responses/RESPONSE_UUID/approve
# 5. Subscribe to webhooks
curl -X POST \
-H "Authorization: Bearer $P4F" \
-H "Content-Type: application/json" \
-d '{
"url":"https://api.yourapp.com/p4f-webhook",
"events":["reward_approved","payout_completed"],
"secret":"whsec_generate_a_random_one"
}' \
https://app.pay4feedback.com/api/v1/webhooks
Quick start — Node
const P4F = process.env.P4F_API_KEY;
const BASE = 'https://app.pay4feedback.com/api/v1';
async function p4f(path, init = {}) {
const res = await fetch(BASE + path, {
...init,
headers: {
Authorization: `Bearer ${P4F}`,
'Content-Type': 'application/json',
...(init.headers ?? {}),
},
});
if (res.status === 429) {
const wait = Number(res.headers.get('Retry-After') ?? 30);
await new Promise(r => setTimeout(r, wait * 1000));
return p4f(path, init);
}
const body = await res.json();
if (!body.success) throw new Error(body.error);
return body.data;
}
// Usage:
const campaigns = await p4f('/campaigns');
const response = await p4f(`/responses/${id}`);
await p4f(`/responses/${id}/approve`, { method: 'POST' });
Quick start — Python
import os, time, requests
P4F = os.environ["P4F_API_KEY"]
BASE = "https://app.pay4feedback.com/api/v1"
def p4f(path, method="GET", json=None):
r = requests.request(method, BASE + path,
headers={"Authorization": f"Bearer {P4F}"},
json=json, timeout=10)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", 30)))
return p4f(path, method, json)
body = r.json()
if not body.get("success"):
raise RuntimeError(body.get("error"))
return body["data"]
campaigns = p4f("/campaigns")
p4f(f"/responses/{id}/approve", method="POST")
Pagination
All list endpoints currently return up to 500 rows, newest first. Cursor pagination lands in v1.1 — the response envelope will gain a next field when it does, and passing ?cursor=<token> will retrieve the next page. Until then, keep data in sync by subscribing to the matching webhook events.
Idempotency
POST endpoints aren't idempotent today. If a request times out, query GET on the affected resource before retrying. Idempotency keys (Idempotency-Key header) land in v1.1.
Errors you'll actually see
| Status | Meaning | What to do |
|---|---|---|
| 400 | Invalid body (missing fields, bad UUID) | Check the error message for which field |
| 401 | Missing / invalid / revoked key | Re-issue a key in the dashboard |
| 404 | Resource not in your tenant | Verify the ID — cross-tenant access returns 404, not 403, by design |
| 409 | State conflict (e.g. reviewing an already-reviewed reward) | Fetch current state first |
| 429 | Rate limit | Honour Retry-After |
| 500 | Our bug | Retry with exponential backoff; if it persists, email support@pay4feedback.com with the timestamp from the response |
Changelog
v1.0 — 2026-04
Initial release.
- Campaign CRUD + pause/resume
- Response list + detail + approve/reject
- Webhook subscriptions
- Self-identity endpoint (
/me)
Planned for v1.1: cursor pagination, idempotency keys, query filters on /responses (by campaign, status, date).
Related
- Webhooks — the inbound side: subscribe to events, receive HMAC-signed POSTs
- API & Integrations — roadmap + planned pre-built integrations (Zapier, Segment)
- Contact & Support