Skip to main content

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

MethodPathPurpose
GET/api/v1/meSanity-check your key — returns the tenant it's scoped to
GET/api/v1/campaignsList all campaigns
GET/api/v1/campaigns/{id}Get one campaign
POST/api/v1/campaignsCreate a campaign (starts in DRAFT)
PATCH/api/v1/campaigns/{id}Update a campaign
POST/api/v1/campaigns/{id}/pausePause — widget stops serving
POST/api/v1/campaigns/{id}/resumeResume from paused
GET/api/v1/responsesList feedback responses
GET/api/v1/responses/{id}Get one response with all answers + quality breakdown
POST/api/v1/responses/{id}/approveApprove the reward
POST/api/v1/responses/{id}/rejectReject the reward (optional { "reason": "…" })
GET/api/v1/webhooksList webhook subscriptions
POST/api/v1/webhooksCreate 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

StatusMeaningWhat to do
400Invalid body (missing fields, bad UUID)Check the error message for which field
401Missing / invalid / revoked keyRe-issue a key in the dashboard
404Resource not in your tenantVerify the ID — cross-tenant access returns 404, not 403, by design
409State conflict (e.g. reviewing an already-reviewed reward)Fetch current state first
429Rate limitHonour Retry-After
500Our bugRetry 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).