Revup API reference
A JSON HTTP API for reading promotions, participants, contacts, and winners and for syncing audience data into your own systems. Account‑scoped, key‑authenticated, and predictable.
Base URL
All requests use HTTPS. JSON request and response bodies are expected throughout; application/json is the only supported content type.
Quickstart
Generate a key from Account › API, then pass it on every request via the x-api-key header.
# List promotions for your account curl https://revup.com/api/v1/promotions \ -H "x-api-key: rvup_v1_your_key_here" \ -H "accept: application/json"
<?php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/promotions', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', 'accept: application/json', ], ]); $response = curl_exec($ch); curl_close($ch); $data = json_decode($response, true);
Authentication
Every request must carry an API key in the x-api-key request header. Keys are account‑scoped: a key only ever returns data belonging to the account that created it.
Raw API keys are shown exactly once at creation. Revup stores only the hashed key plus a non‑secret prefix. Treat keys like passwords and revoke any that may have been exposed.
x-api-key: rvup_v1_... accept: application/json
Scopes & access
Each key includes scopes that define allowed access. Requests without the required scope fail with insufficient_scope.
| Scope | Access |
|---|---|
promotions:read | Read promotion metadata available to the account. |
participants:read | Read participant records and submission state. |
participants:write | Create or update participant records. |
contacts:read | Read contact and entry‑facing contact fields. |
contacts:write | Reserved for the coming contact write endpoint. |
winners:read | Read winner records and prize assignment details. |
Promotion restrictions
Keys may be unrestricted, in which case they reach every promotion in the account, or limited to a specific subset. When a request targets a promotion the key cannot access, the API responds with promotion_restricted. Unknown promotion IDs, account mismatches, and key promotion restrictions intentionally share that code so inaccessible promotion existence is not leaked.
{
"success": false,
"request_id": "2f4a8f2c0d4a6e9b7c1d2e3f",
"error": {
"code": "insufficient_scope",
"message": "This API key does not include the required scope."
}
}
Errors
Errors are JSON with a stable machine‑readable code and a human‑readable message. The HTTP status reflects the category.
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_api_keyinvalid_api_keyrevoked_api_key | Missing, malformed, or revoked key. |
| 403 | insufficient_scopepromotion_restricted | Key lacks the required scope, the promotion is outside the key's account, or the key is restricted away from that promotion. |
| 429 | rate_limited | Key exceeded its per-minute request limit. |
| 404 | not_found | The requested API endpoint does not exist. |
| 405 | method_not_allowed | The endpoint exists but does not support the HTTP method. |
| 422 | validation_failed | Path or query parameters failed validation. |
{
"success": false,
"request_id": "2f4a8f2c0d4a6e9b7c1d2e3f",
"error": {
"code": "validation_failed",
"message": "The 'limit' parameter must be between 1 and 250."
}
}
Rate limits
Rate limits are enforced per API key. Default limits are 120 read requests per minute, 200 write requests per minute, and 60 heavy read requests per minute. Contact support if a production integration needs a higher key-specific limit.
| Class | Default limit | Applies to |
|---|---|---|
read | 120 / minute | Default for GET, HEAD, and OPTIONS requests. |
write | 200 / minute | Default for write requests such as participant imports and bonus entries. |
heavy_read | 60 / minute | Reserved for expensive read endpoints. |
Authenticated requests include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset after API key validation. Missing or invalid keys and pre-auth route/method errors do not include these headers. When a request is limited, the API returns HTTP 429 with Retry-After.
X-RateLimit-Limit: 120 X-RateLimit-Remaining: 117 X-RateLimit-Reset: 1747856460
Pagination
Collection endpoints return a cursor. Pass the returned next_cursor as the cursor query parameter to fetch the next page, and stop when has_more is false.
| Parameter | Type | Notes |
|---|---|---|
limit | integer | Page size. Endpoint-specific maximum. |
cursor | string | Opaque cursor returned from a prior response. |
{
"success": true,
"request_id": "2f4a8f2c0d4a6e9b7c1d2e3f",
"data": [ ... ],
"pagination": {
"next_cursor": "eyJpZCI6MTIzfQ",
"has_more": true
}
}
Retrieve account
Fetch the account available to the authenticated API key.
Returns
| Field | Type | Notes |
|---|---|---|
id | integer | Account ID that owns the API key. |
name | string | Account name. |
website | string/null | Account website when present. |
avatar_image_url | string/null | Account avatar or logo image URL when present. |
created_at_epoch | integer/null | UTC Unix timestamp from account creation metadata. |
curl https://revup.com/api/v1/account \
-H "x-api-key: rvup_v1_your_key_here"
<?php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/account', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', ], ]); $response = curl_exec($ch); curl_close($ch); $account = json_decode($response, true);
{
"success": true,
"request_id": "2f4a8f2c0d4a6e9b7c1d2e3f",
"data": {
"id": 2048,
"name": "Acme Brewing Co.",
"website": "https://acmebrewing.com",
"avatar_image_url": "https://cdn.revup.com/.../acme.png",
"created_at_epoch": 1716508800
}
}
List promotions
List non-variant promotions visible to the authenticated API key account. Results are ordered by promotion ID and support cursor pagination.
Query parameters
| Parameter | Type | Notes |
|---|---|---|
limit | integer | Page size. Default 50. |
cursor | string | Opaque cursor from a prior response. |
Returns
| Field | Type | Notes |
|---|---|---|
id | integer | Promotion ID. |
account_id | integer | Account ID that owns the promotion. |
name | string | Promotion name. |
type | string | Internal promotion type. |
status | string | pending, live, or completed. |
start_date | string/null | Promotion-local YYYY-MM-DD HH:MM:SS. |
end_date | string/null | Promotion-local YYYY-MM-DD HH:MM:SS. |
timezone | string | IANA timezone for promotion-local timestamps. |
default_language | string | Promotion default language code. |
hosted_website_url | string | Customer-configured hosted or installed URL when present. |
form_fields | array | API/import-writable participant field IDs and human-readable titles for mapping. Option-backed fields include options. Custom fields use IDs such as ff_123; unsupported CAPTCHA, payment, signature, and file-upload fields are omitted. |
curl https://revup.com/api/v1/promotions?limit=50 \
-H "x-api-key: rvup_v1_your_key_here"
<?php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/promotions?limit=50', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', ], ]); $response = curl_exec($ch); curl_close($ch); $promotions = json_decode($response, true);
{
"success": true,
"request_id": "a1b2c3d4e5f6",
"data": [
{
"id": 123,
"account_id": 2048,
"name": "Summer Launch Giveaway",
"type": "giveaway",
"status": "live",
"start_date": "2026-05-01 09:00:00",
"end_date": "2026-06-15 23:59:59",
"timezone": "America/Los_Angeles",
"default_language": "en-us",
"hosted_website_url": "https://acmebrewing.com/giveaway",
"form_fields": [
{ "id": "email", "title": "Email" },
{ "id": "first_name", "title": "First name" },
{ "id": "ff_456", "title": "VIP tier",
"options": ["Gold", "Silver", "Bronze"] }
]
}
],
"pagination": {
"next_cursor": null,
"has_more": false
}
}
Retrieve a promotion
Fetch a single promotion by ID. Returns the same shape as a list item.
Path parameters
| Parameter | Type | Notes |
|---|---|---|
promotion_id | integer | Promotion ID. Must be accessible to the API key. |
curl https://revup.com/api/v1/promotions/123 \
-H "x-api-key: rvup_v1_your_key_here"
<?php $promotion_id = 123; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/promotions/' . $promotion_id, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', ], ]); $response = curl_exec($ch); curl_close($ch); $promotion = json_decode($response, true);
{
"success": true,
"request_id": "a1b2c3d4e5f6",
"data": {
"id": 123,
"account_id": 2048,
"name": "Summer Launch Giveaway",
"type": "giveaway",
"status": "live",
"start_date": "2026-05-01 09:00:00",
"end_date": "2026-06-15 23:59:59",
"timezone": "America/Los_Angeles",
"default_language": "en-us",
"hosted_website_url": "https://acmebrewing.com/giveaway",
"form_fields": [ ... ]
}
}
List participants
List participants for a single promotion. Default page size is 250. Promotion restrictions on the key are enforced. Add include_entry_data=true to include the raw bonus/entry ledger on each participant.
Query parameters
| Parameter | Type | Notes |
|---|---|---|
limit | integer | Page size. Default 250. |
cursor | string | Opaque cursor from a prior response. |
include_entry_data | boolean | When true, includes the decoded entry ledger on each row. |
Returns
| Field | Type | Notes |
|---|---|---|
id | integer | Participant/entrant ID. |
promotion_id | integer | Promotion ID this participant belongs to. |
contact_id | integer/null | Linked account contact ID. |
email, unique_id, first_name, middle_name, last_name, phone | string/null | Core identity fields. |
address, address2, city, state, postal_code, country | string/null | Address fields when collected. |
gender, birthday | string/null | Profile fields when collected. |
created_at, processed_at | string/null | Promotion-local YYYY-MM-DD HH:MM:SS timestamps. |
created_at_epoch | integer/null | UTC Unix timestamp for created_at. |
status | string | Internal processing status, commonly pending or processed. |
invalid, reporting_excluded, test | boolean | Participant validity/reporting/test flags. |
payment_status, integration_status | string | Payment or integration state when applicable. |
language_id | integer/null | Selected promotion language ID. |
location | string/null | Stored location display value. |
total_entries, total_referrals | integer | Current entry and referral totals. |
referring_participant_id, campaign_id | integer/null | Referral and tracking IDs when present. |
entry_source_type, referring_source_url, entry_source_url | string/null | Captured source attribution fields. |
device_type | string | desktop, mobile, or tablet. |
avatar_url | string/null | Participant avatar URL when available. |
ff_{promotion_form.id} | string/array/null | Custom promotion field value at the top level using IDs from GET /promotions. |
entry_data | object | Only present with include_entry_data=true. Raw decoded entry ledger keyed by block ID. |
curl https://revup.com/api/v1/promotions/123/participants?limit=250&include_entry_data=true \
-H "x-api-key: rvup_v1_your_key_here"
<?php $query = http_build_query([ 'limit' => 250, 'include_entry_data' => 'true', ]); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/promotions/123/participants?' . $query, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', ], ]); $response = curl_exec($ch); curl_close($ch); $page = json_decode($response, true);
{
"success": true,
"request_id": "a1b2c3d4e5f6",
"data": [
{
"id": 98801,
"promotion_id": 123,
"contact_id": 55421,
"email": "[email protected]",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": null,
"created_at": "2026-05-10 14:22:11",
"created_at_epoch": 1747145731,
"status": "processed",
"total_entries": 12,
"total_referrals": 3,
"device_type": "desktop",
"ff_456": "Gold"
}
],
"pagination": {
"next_cursor": "eyJpZCI6OTg4MDF9",
"has_more": true
}
}
Retrieve a participant
Fetch a single participant by ID within a promotion. Returns the same shape as a list item.
Path parameters
| Parameter | Type | Notes |
|---|---|---|
promotion_id | integer | Promotion ID. |
participant_id | integer | Participant/entrant ID belonging to the promotion. |
curl https://revup.com/api/v1/promotions/123/participants/98801 \
-H "x-api-key: rvup_v1_your_key_here"
<?php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/promotions/123/participants/98801', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', ], ]); $response = curl_exec($ch); curl_close($ch); $participant = json_decode($response, true);
Create participants
Create or import up to 50 participants per request. Existing participants are matched by normalized email, then phone, then unique_id and are skipped unless update_existing=true.
Custom values use top-level field IDs such as ff_456; multi-value custom fields may send list arrays. New participants default to one initial entry when initial_entries is omitted. If an update also supplies initial_entries, include an Idempotency-Key header so retries cannot duplicate bonus entries.
Body parameters
| Parameter | Type | Notes |
|---|---|---|
participants | array | Up to 50 participant objects per request. |
update_existing | boolean | When true, matching rows are updated instead of skipped. |
trigger_workflows | boolean | Opt-in workflow trigger for newly created participants. |
workflow_contact_permission_confirmed | boolean | Required alongside trigger_workflows to confirm consent. |
Workflow triggering is opt-in. When enabled, Revup queues the promotion's active attached form_submitted workflow for newly created participants only. Updates, skipped duplicates, and failed rows do not trigger workflows. If no active attached workflow exists, the request still succeeds and the response reports the trigger as skipped.
Per-row workflow result
When workflows are requested, each successful row includes a workflow object with the immediate trigger result. trigger_status=queued means the trigger event was persisted; event_status is the current runtime state, usually pending immediately after the API request. trigger_status=skipped means no active attached workflow exists, not_triggered means the row was not newly created, and unknown means the participant was created but the trigger could not be immediately verified.
curl https://revup.com/api/v1/promotions/123/participants \ -H "x-api-key: rvup_v1_your_key_here" \ -H "Idempotency-Key: crm-sync-2026-05-13-batch-0042" \ -H "content-type: application/json" \ -d '{ "trigger_workflows": true, "workflow_contact_permission_confirmed": true, "participants": [ { "email": "[email protected]", "first_name": "Ada", "ff_456": "VIP" } ] }'
<?php $payload = [ 'trigger_workflows' => true, 'workflow_contact_permission_confirmed' => true, 'participants' => [ [ 'email' => '[email protected]', 'first_name' => 'Ada', 'ff_456' => 'VIP', ], ], ]; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/promotions/123/participants', CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', 'Idempotency-Key: crm-sync-2026-05-13-batch-0042', 'content-type: application/json', ], CURLOPT_POSTFIELDS => json_encode($payload), ]); $response = curl_exec($ch); curl_close($ch); $result = json_decode($response, true);
{
"success": true,
"request_id": "a1b2c3d4e5f6",
"data": {
"created": 1,
"updated": 0,
"skipped": 0,
"failed": 0,
"idempotent_replays": 0,
"results": [
{
"index": 0,
"success": true,
"action": "created",
"participant_id": 98802,
"identity_key": "email:[email protected]",
"participant": {
"id": 98802,
"promotion_id": 123,
"email": "[email protected]",
"first_name": "Ada",
"total_entries": 1
},
"workflow": {
"trigger_requested": true,
"trigger_status": "queued",
"trigger_event_id": 789,
"event_status": "pending",
"message": "Workflow trigger event queued."
}
}
]
}
}
Add bonus entries
Add bonus entries to an existing participant by email or phone. The request must include an Idempotency-Key header so retries cannot add duplicate entries. If both identifiers are sent, email is used.
The endpoint appends a manual entry ledger row, recalculates the participant's total_entries, and returns the updated participant.
Body parameters
| Parameter | Type | Notes |
|---|---|---|
email | string | Lookup identifier. Preferred over phone. |
phone | string | Fallback identifier when email is omitted. |
entries | integer | Number of bonus entries to add (positive integer). |
title | string | Optional label stored on the ledger row. |
An Idempotency-Key header is required. Replays with the same key return the original result without adding more entries.
curl https://revup.com/api/v1/promotions/123/participants/entries \ -H "x-api-key: rvup_v1_your_key_here" \ -H "Idempotency-Key: crm-bonus-ada-2026-05-21" \ -H "content-type: application/json" \ -d '{ "email": "[email protected]", "entries": 25, "title": "VIP launch bonus" }'
<?php $payload = [ 'email' => '[email protected]', 'entries' => 25, 'title' => 'VIP launch bonus', ]; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/promotions/123/participants/entries', CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', 'Idempotency-Key: crm-bonus-ada-2026-05-21', 'content-type: application/json', ], CURLOPT_POSTFIELDS => json_encode($payload), ]); $response = curl_exec($ch); curl_close($ch); $result = json_decode($response, true);
{
"success": true,
"request_id": "a1b2c3d4e5f6",
"data": {
"participant_id": 98801,
"promotion_id": 123,
"manual_key": "manual_points_api_7b1d2f9c6e4a8b0c1d3e5f7a9b2c4d6e",
"entries_added": 25,
"total_entries": 37,
"participant": {
"id": 98801,
"promotion_id": 123,
"email": "[email protected]",
"total_entries": 37
}
}
}
List contacts
List contacts for the authenticated API key account. Default page size is 250. Promotion-restricted keys only see contacts linked to visible participants in allowed promotions.
Returns
| Field | Type | Notes |
|---|---|---|
id | integer | Account-level contact ID. |
email, unique_id, first_name, middle_name, last_name, phone | string/null | Core identity fields. |
address, address2, city, state, postal_code, country | string/null | Address fields when collected. |
gender, birthday | string/null | Profile fields when collected. |
created_at, updated_at | string/null | YYYY-MM-DD HH:MM:SS timestamps. |
created_at_epoch | integer/null | UTC Unix timestamp for created_at. |
ip_address | string/null | Last stored IP address associated with the contact. |
location | string/null | Stored location display value. |
curl https://revup.com/api/v1/contacts?limit=250 \
-H "x-api-key: rvup_v1_your_key_here"
<?php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/contacts?limit=250', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', ], ]); $response = curl_exec($ch); curl_close($ch); $contacts = json_decode($response, true);
{
"success": true,
"request_id": "a1b2c3d4e5f6",
"data": [
{
"id": 55421,
"email": "[email protected]",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": null,
"created_at": "2026-05-10 14:22:11",
"created_at_epoch": 1747145731,
"ip_address": "203.0.113.42",
"location": "Portland, OR, US"
}
],
"pagination": {
"next_cursor": "eyJpZCI6NTU0MjF9",
"has_more": true
}
}
Create contact
Reserved for the coming contact write endpoint.
POST /contacts is coming soon. The contacts:write scope is available for future contact create/update support, but contact writes are not part of the live API v1 surface yet.
// Endpoint not yet available. // The contacts:write scope can be reserved on API // keys now so future calls do not need re-issuance.
List winners
List non-deleted winners and prize assignment details for a promotion. Default page size is 250.
Returns
| Field | Type | Notes |
|---|---|---|
id | integer | Winner record ID. |
promotion_id | integer | Promotion ID this winner belongs to. |
participant_id | integer | Participant/entrant ID selected as the winner. |
contact_id | integer/null | Account-level contact ID linked to the participant when available. |
email, first_name, last_name, phone | string/null | Selected participant identity fields. |
prize_id | integer/null | Prize row assigned to this winner when present. |
prize_name | string/null | Prize name from the promotion prize table. |
prize_value | string/null | Prize value as a decimal string. |
prize_currency | string/null | Prize currency code/text. |
draw_id | integer | Winner draw ID. |
draw_group_number, draw_group_rank | integer | Group and rank for grouped draws or alternates. |
is_current_winner | boolean | Whether this row is the current active winner for its draw/group slot. |
status | string | won, notified, claimed, disqualified, selected, or expired. |
recovery_status | string/null | Recovery/replacement workflow status when used. |
date_drawn | string/null | Promotion-local draw timestamp in YYYY-MM-DD HH:MM:SS format. |
fraud_flag | boolean | Whether the winner row is marked with a fraud flag. |
selection_index, selection_target, selection_total_weight | integer/null | Stored weighted-selection audit values when available. |
selection_hash | string/null | Stored selection audit hash when available. |
disqualified_at, promoted_at, announced_at | string/null | Promotion-local winner lifecycle timestamps. |
promoted_from_winner_id | integer/null | Prior winner row this winner replaced when promoted. |
winning_slot_start, winning_slot_end | integer/null | Instant-win slot start/end timestamp or offset when present. |
random_source | string/null | Draw random source such as local or drand. |
drand_round | integer/null | Drand round used for the draw when applicable. |
drand_requested_at, drand_round_published_at | string/null | Drand request and publication timestamps. |
drand_beacon_id, drand_chain_hash | string/null | Drand beacon metadata when applicable. |
randomness_value | string/null | Stored randomness value used for the draw when retained. |
fallback_reason | string/null | Reason local fallback randomness was used when present. |
curl https://revup.com/api/v1/promotions/123/winners \
-H "x-api-key: rvup_v1_your_key_here"
<?php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/promotions/123/winners', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', ], ]); $response = curl_exec($ch); curl_close($ch); $winners = json_decode($response, true);
{
"success": true,
"request_id": "a1b2c3d4e5f6",
"data": [
{
"id": 7711,
"promotion_id": 123,
"participant_id": 98801,
"email": "[email protected]",
"prize_id": 412,
"prize_name": "Grand Prize",
"prize_value": "500.00",
"prize_currency": "USD",
"draw_id": 88,
"is_current_winner": true,
"status": "notified",
"date_drawn": "2026-06-16 12:00:00",
"random_source": "drand"
}
],
"pagination": {
"next_cursor": null,
"has_more": false
}
}
Retrieve a winner
Fetch a single winner record by ID. Returns the same shape as a list item.
Path parameters
| Parameter | Type | Notes |
|---|---|---|
promotion_id | integer | Promotion ID. |
winner_id | integer | Winner record ID belonging to the promotion. |
curl https://revup.com/api/v1/promotions/123/winners/7711 \
-H "x-api-key: rvup_v1_your_key_here"
<?php $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'https://revup.com/api/v1/promotions/123/winners/7711', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'x-api-key: rvup_v1_your_key_here', ], ]); $response = curl_exec($ch); curl_close($ch); $winner = json_decode($response, true);