openapi: 3.1.0 info: title: RevRake API version: "2026-10-24" summary: The signed endpoints an operator's platform and an affiliate use, and the webhooks RevRake sends. description: | Four signed endpoints: two to write (events and daily files) and two to read (reports and the commission ledger). Keys are never shared. An operator key (`pb_…`, Integration page of the operator portal) sends events and reads the whole program. An affiliate key (`ak_…`, issued by each affiliate in their portal) reads that affiliate's own reports and commissions and nothing else; it cannot send events. Every request carries three headers. `X-RevRake-Signature` is the lower-case hex HMAC-SHA256, under the key's secret, of `.` for a POST and of `.?` (the request target exactly as sent; the path alone when there is no query) for a GET. The timestamp is Unix seconds and must be within 300 seconds of the real time. Test vector: secret `test_secret`, timestamp `1791072000`. Body `{"type":"registration","player_id":"PLR-1001","btag":"1001_1"}` signs to `115d8c73c6069d8953a36ca3a68c114da6232e4ba31889fd519d41ab1350a603`. Target `/v1/reports?by=link&days=7` signs to `ed1ed72dd7ddef22c762f63b8919011e7c36c7214b55b72693c5927fb4b911d0`. Human-readable reference: https://revrake.com/docs/api/ contact: email: hello@revrake.com servers: - url: https://app.revrake.com security: - keyId: [] timestamp: [] signature: [] tags: - name: Write description: Operator key only. Rate limited per key, 30,000 events a minute on /v1/postback (bursts of 1,000) and 60 files a minute on /v1/files (bursts of 10), with the body size limits. - name: Read description: Operator key or affiliate key. 120 reads a minute per key. paths: /v1/postback: post: tags: [Write] operationId: sendEvents summary: Report one event, or a batch of 1 to 1,000 events description: | The body is one event object or a JSON array of event objects, at most 4 MB. Each event is stored once under its natural key (`player_id` for a registration, `txn_id` for a deposit and a chargeback, `player_id` and `period` for ngr). An identical retry returns the stored answer and `"status":"duplicate"`; a deposit waiting for registration has its result updated when registration arrives. Reusing a deposit key with changed player, amount or currency conflicts. NGR restatements create revisions. A SALES daily summary counts covered postback amounts once while retaining their transaction IDs. A field that is not listed for the type is refused. requestBody: required: true content: application/json: schema: oneOf: - $ref: "#/components/schemas/Event" - type: array minItems: 1 maxItems: 1000 items: $ref: "#/components/schemas/Event" examples: registration: value: { type: registration, player_id: PLR-1001, click_id: 01K6QF3M8Z5T7W9XB2C4D6E8GH, btag: "1001_1" } deposit: value: { type: deposit, player_id: PLR-1001, txn_id: TX-88213, amount: "25.00", currency: KES, first: true } chargeback: value: { type: chargeback, txn_id: CB-5001, ref_txn_id: TX-88213 } ngr: value: { type: ngr, player_id: PLR-1001, period: "2026-10-03", amount: "118.40", currency: KES } responses: "200": description: | For one event: it was stored (`accepted`) or was already stored (`duplicate`). For a batch: it was read; each event has its own status, and some may be `rejected`. headers: X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" } X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" } content: application/json: schema: oneOf: - $ref: "#/components/schemas/EventResult" - $ref: "#/components/schemas/BatchResult" "400": description: "`bad_request`: the body starts as an array but is not a JSON array of events." content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } "401": { $ref: "#/components/responses/Unauthorized" } "405": { $ref: "#/components/responses/MethodNotAllowed" } "413": { $ref: "#/components/responses/TooLarge" } "422": { $ref: "#/components/responses/Invalid" } "429": { $ref: "#/components/responses/RateLimited" } "500": { $ref: "#/components/responses/Internal" } /v1/files: post: tags: [Write] operationId: pushFile summary: Push a daily REG or SALES file description: | The body is the CSV file itself, at most 32 MB and 200,000 data rows. The header row decides the kind: a `registration_date` column makes it a REG file (columns read: `registration_date`, `player_id`, `btag`, and `promo_code` and `player_country` when present), an `activitydate` column a SALES file. SALES requires `activitydate`, `player_id`, `currency`, `deposits`, `first_deposit_amount` and `chargeback` in the header. `deposits` is the day's gross deposits, including its first deposit; never a lifetime or chargeback-netted total. `first_deposit_amount` is nonzero only on the actual first-deposit day, is already included in deposits, and cannot exceed deposits. `chargeback` is the separate total charged back on that activity day, not a lifetime total or a reference to a particular deposit. All three amount columns accept nonnegative decimals with the row currency's precision; empty cells mean zero. An empty currency cell uses the workspace reporting currency. One immutable monetary summary is stored per brand, player and activity day, with its currency fixed on first acceptance. Identical values replay regardless of file name or row order. Changed totals, currency or timezone day boundaries conflict, including changes to zero totals; recorded money is preserved. The summary covers individual postbacks for that day and currency in the file timezone, so their amounts are not added twice. Actual transaction IDs remain usable. Conflicting known transaction totals or first-deposit evidence are refused. Columns named `net_revenue` or ending in `_net_revenue` are summed into a separate daily ngr event when any such cell is filled. Optional `gross_revenue`, `bonus`, `fees` and `taxes` columns, and columns ending in those names with an underscore, fill its ggr, bonuses, fees and taxes. Revenue corrections append adjustments. Money and revenue are processed independently; either can succeed while the other is rejected. Inspect rejected and problems even with a 200 response. Other columns are ignored. Send REG first. Monetary summaries received before registration are kept and priced when registration arrives. NGR for an unknown player is refused and must be resent after REG; resending SALES replays its saved money summary. The activity day is determined in the workspace file timezone (UTC by default), including for timestamps carrying an offset. Daily cashflows use their activity date's FX rate for CPA conversion; missing rates hold settlement. Missing chargeback FX blocks payment until reconciliation, even for claimed commissions. For first-deposit CPA, an ambiguous aggregate chargeback for a registered player rejects the monetary summary. If it arrived before REG, the summary is preserved and registration succeeds, but the resulting held CPA waits under chargeback_reference_missing for transaction-reference review. Use a signed chargeback with ref_txn_id for a specific original deposit. parameters: - name: filename in: query required: false description: The name shown in the portal. Not otherwise used. schema: { type: string, maxLength: 200 } example: sales-2026-10-03.csv requestBody: required: true content: text/csv: schema: { type: string } responses: "200": description: The file was read. Look at the counts; refused rows are in `problems` and in the inbound log. headers: X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" } X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" } content: application/json: schema: { $ref: "#/components/schemas/FileImport" } "401": { $ref: "#/components/responses/Unauthorized" } "405": { $ref: "#/components/responses/MethodNotAllowed" } "413": { $ref: "#/components/responses/TooLarge" } "422": { $ref: "#/components/responses/Invalid" } "429": { $ref: "#/components/responses/RateLimited" } "500": { $ref: "#/components/responses/Internal" } /v1/reports: get: tags: [Read] operationId: readReport summary: Read a report, as JSON or CSV description: | The report of the Reports page: clicks, flagged clicks, registrations, first deposits and earnings, grouped one way, for the days up to today in the workspace's timezone, or for any period chosen with `from` and `to`. A parameter with a value that is not allowed is ignored and its default is used. parameters: - name: by in: query description: How rows are grouped. `creative` counts only clicks through a creative's link (they carry `cr=`). `tag` gives a row per group of affiliates; an affiliate in two groups is in both rows, and the total is the program's, not the sum of the rows. With an affiliate key, or with `affiliate` set, `affiliate` and `tag` are answered as `day`. schema: { type: string, enum: [day, link, creative, sub1, country, affiliate, tag], default: day } - name: days in: query description: How many days, counting today. schema: { type: integer, minimum: 1, maximum: 365, default: 30 } - name: from in: query description: "The first day of any period, YYYY-MM-DD in the workspace's timezone. With from or to, days is ignored. At most 366 days; otherwise 422 invalid (field from)." schema: { type: string, format: date } - name: to in: query description: The last day of the period, included. Default today. schema: { type: string, format: date } - name: affiliate in: query description: An affiliate number. Operator key only; ignored with an affiliate key. schema: { type: integer } - name: tag in: query description: "A group's name, as on the Groups page; repeat for several (any of them). Operator key only; ignored with affiliate or an affiliate key. An unknown name is 422 unknown_tag." schema: { type: string } - name: format in: query description: "`csv` answers a CSV file instead of JSON." schema: { type: string, enum: [csv] } responses: "200": description: The report. headers: X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" } X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" } content: application/json: schema: { $ref: "#/components/schemas/Report" } text/csv: schema: type: string description: "Columns: the grouping (its label), clicks, flagged_clicks, registrations, first_deposits, earnings_. The last line is the total." "401": { $ref: "#/components/responses/Unauthorized" } "422": { $ref: "#/components/responses/Invalid" } "403": { $ref: "#/components/responses/AffiliateInactive" } "404": { $ref: "#/components/responses/UnknownAffiliate" } "405": { $ref: "#/components/responses/MethodNotAllowed" } "429": { $ref: "#/components/responses/RateLimited" } "500": { $ref: "#/components/responses/Internal" } /v1/commissions: get: tags: [Read] operationId: readCommissions summary: Read one page of the commission ledger description: | A row's amounts never change, but its `status` does (hold, then approved, then paid, or rejected). A sync must read a period again and update rows by `id`, not only fetch new rows. A parameter with a value that is not allowed is ignored and its default is used. parameters: - name: status in: query schema: { $ref: "#/components/schemas/CommissionStatus" } - name: from in: query description: Only rows that occurred at or after this time. RFC 3339, or a day YYYY-MM-DD in UTC. schema: { type: string } - name: to in: query description: Only rows that occurred before this time. Same forms. schema: { type: string } - name: affiliate in: query description: An affiliate number. Operator key only; ignored with an affiliate key. schema: { type: integer } - name: tag in: query description: "A group's name, as on the Groups page; repeat for several (any of them). Operator key only; ignored with affiliate or an affiliate key. An unknown name is 422 unknown_tag." schema: { type: string } - name: q in: query description: Rows whose player ID or affiliate name contains this text. schema: { type: string, maxLength: 100 } - name: sort in: query schema: { type: string, enum: [when, affiliate, player, status, deposit, commission], default: when } - name: dir in: query schema: { type: string, enum: [asc, desc], default: desc } - name: page in: query schema: { type: integer, minimum: 1, maximum: 100000, default: 1 } - name: per_page in: query schema: { type: integer, enum: [10, 25, 50, 100, 500, 1000], default: 25 } responses: "200": description: One page of the ledger. headers: X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" } X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" } content: application/json: schema: { $ref: "#/components/schemas/Ledger" } "401": { $ref: "#/components/responses/Unauthorized" } "403": { $ref: "#/components/responses/AffiliateInactive" } "404": { $ref: "#/components/responses/UnknownAffiliate" } "405": { $ref: "#/components/responses/MethodNotAllowed" } "429": { $ref: "#/components/responses/RateLimited" } "500": { $ref: "#/components/responses/Internal" } /v1/affiliates: get: tags: [Read] operationId: readAffiliates summary: List the brand's affiliates description: Operator key only. Oldest first. Writes (inviting an affiliate) are not offered yet. parameters: - name: status in: query description: Only affiliates in this status. Any other value is 422 invalid. schema: { $ref: "#/components/schemas/AffiliateStatus" } - name: page in: query schema: { type: integer, minimum: 1, maximum: 100000, default: 1 } - name: per_page in: query schema: { type: integer, minimum: 1, maximum: 500, default: 100 } responses: "200": description: One page of affiliates. headers: X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" } X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" } content: application/json: schema: type: object required: [affiliates, total, page, per_page] properties: affiliates: { type: array, items: { $ref: "#/components/schemas/ApiAffiliate" } } total: { type: integer } page: { type: integer } per_page: { type: integer } "401": { $ref: "#/components/responses/Unauthorized" } "403": { $ref: "#/components/responses/OperatorKeyRequired" } "405": { $ref: "#/components/responses/MethodNotAllowed" } "422": { $ref: "#/components/responses/Invalid" } "429": { $ref: "#/components/responses/RateLimited" } "500": { $ref: "#/components/responses/Internal" } /v1/links: get: tags: [Read] operationId: readLinks summary: List tracking links description: The brand's tracking links (promo codes are links too), by affiliate number then link number. With an affiliate key, that affiliate's own links. Writes (creating a link) are not offered yet. parameters: - name: affiliate in: query description: An affiliate number. Operator key only; ignored with an affiliate key. schema: { type: integer } - name: page in: query schema: { type: integer, minimum: 1, maximum: 100000, default: 1 } - name: per_page in: query schema: { type: integer, minimum: 1, maximum: 500, default: 100 } responses: "200": description: One page of links. headers: X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" } X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" } content: application/json: schema: type: object required: [links, total, page, per_page] properties: links: { type: array, items: { $ref: "#/components/schemas/ApiLink" } } total: { type: integer } page: { type: integer } per_page: { type: integer } "401": { $ref: "#/components/responses/Unauthorized" } "403": { $ref: "#/components/responses/AffiliateInactive" } "404": { $ref: "#/components/responses/UnknownAffiliate" } "405": { $ref: "#/components/responses/MethodNotAllowed" } "429": { $ref: "#/components/responses/RateLimited" } "500": { $ref: "#/components/responses/Internal" } /v1/deals: get: tags: [Read] operationId: readDeals summary: List the brand's deals with the terms in effect now description: Operator key only. A deal whose first version starts in the future is left out until it starts. responses: "200": description: The deals. headers: X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" } X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" } content: application/json: schema: type: object required: [deals] properties: deals: { type: array, items: { $ref: "#/components/schemas/ApiDeal" } } "401": { $ref: "#/components/responses/Unauthorized" } "403": { $ref: "#/components/responses/OperatorKeyRequired" } "405": { $ref: "#/components/responses/MethodNotAllowed" } "429": { $ref: "#/components/responses/RateLimited" } "500": { $ref: "#/components/responses/Internal" } webhooks: affiliate.applied: post: operationId: webhook_affiliate_applied summary: The affiliate.applied event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: affiliate.applied } data: type: object properties: affiliate_number: { type: integer } affiliate_name: { type: string } email: { type: string } promotes_on: { type: string } traffic: { type: array, items: { type: string } } responses: "200": { description: Any 2xx answer counts as delivered. } affiliate.approved: post: operationId: webhook_affiliate_approved summary: The affiliate.approved event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: affiliate.approved } data: type: object properties: affiliate_number: { type: integer } affiliate_name: { type: string } email: { type: string } reason: { type: string } decided_by: { type: string } invited: { type: boolean } responses: "200": { description: Any 2xx answer counts as delivered. } affiliate.rejected: post: operationId: webhook_affiliate_rejected summary: The affiliate.rejected event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: affiliate.rejected } data: type: object properties: affiliate_number: { type: integer } affiliate_name: { type: string } email: { type: string } reason: { type: string } decided_by: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } affiliate.suspended: post: operationId: webhook_affiliate_suspended summary: The affiliate.suspended event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: affiliate.suspended } data: type: object properties: affiliate_number: { type: integer } affiliate_name: { type: string } email: { type: string } reason: { type: string } decided_by: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } player.registered: post: operationId: webhook_player_registered summary: The player.registered event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: player.registered } data: type: object properties: player_id: { type: string } occurred_at: { type: string, format: date-time } attributed: { type: boolean } affiliate_number: { type: integer } affiliate_name: { type: string } attribution_reason: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } commission.created: post: operationId: webhook_commission_created summary: The commission.created event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: commission.created } data: type: object properties: player_id: { type: string } occurred_at: { type: string, format: date-time } attributed: { type: boolean } affiliate_number: { type: integer } affiliate_name: { type: string } commission_id: { type: string } commission_type: { type: string } amount: { type: string } amount_currency: { type: string } commission: { type: string } commission_currency: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } commission.approved: post: operationId: webhook_commission_approved summary: The commission.approved event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: commission.approved } data: type: object properties: player_id: { type: string } occurred_at: { type: string, format: date-time } attributed: { type: boolean } affiliate_number: { type: integer } affiliate_name: { type: string } commission_id: { type: string } commission_type: { type: string } amount: { type: string } amount_currency: { type: string } commission: { type: string } commission_currency: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } commission.rejected: post: operationId: webhook_commission_rejected summary: The commission.rejected event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: commission.rejected } data: type: object properties: player_id: { type: string } occurred_at: { type: string, format: date-time } attributed: { type: boolean } affiliate_number: { type: integer } affiliate_name: { type: string } commission_id: { type: string } commission_type: { type: string } amount: { type: string } amount_currency: { type: string } commission: { type: string } commission_currency: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } commission.paid: post: operationId: webhook_commission_paid summary: The commission.paid event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: commission.paid } data: type: object properties: player_id: { type: string } occurred_at: { type: string, format: date-time } attributed: { type: boolean } affiliate_number: { type: integer } affiliate_name: { type: string } commission_id: { type: string } commission_type: { type: string } amount: { type: string } amount_currency: { type: string } commission: { type: string } commission_currency: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } commission.reversed: post: operationId: webhook_commission_reversed summary: The commission.reversed event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: commission.reversed } data: type: object properties: player_id: { type: string } occurred_at: { type: string, format: date-time } attributed: { type: boolean } affiliate_number: { type: integer } affiliate_name: { type: string } commission_id: { type: string } commission_type: { type: string } amount: { type: string } amount_currency: { type: string } commission: { type: string } commission_currency: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } payout.batch_closed: post: operationId: webhook_payout_batch_closed summary: The payout.batch_closed event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: payout.batch_closed } data: type: object properties: batch_id: { type: string } period: { type: string } reference: { type: string } items: { type: integer } payable_by_currency: { type: object, additionalProperties: { type: string }, description: "Per currency, a decimal." } closed_by_hand: { type: boolean } responses: "200": { description: Any 2xx answer counts as delivered. } payout.batch_approved: post: operationId: webhook_payout_batch_approved summary: The payout.batch_approved event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: payout.batch_approved } data: type: object properties: batch_id: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } payout.paid: post: operationId: webhook_payout_paid summary: The payout.paid event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: payout.paid } data: type: object properties: batch_id: { type: string } item_id: { type: string } affiliate_number: { type: integer } affiliate_name: { type: string } currency: { type: string } amount: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } tracking.health_alert: post: operationId: webhook_tracking_health_alert summary: The tracking.health_alert event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: tracking.health_alert } data: type: object properties: brand: { type: string } kind: { type: string } since: { type: string, format: date-time } quiet_hours: { type: integer } clicks_at_least: { type: integer } responses: "200": { description: Any 2xx answer counts as delivered. } tracking.health_cleared: post: operationId: webhook_tracking_health_cleared summary: The tracking.health_cleared event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: tracking.health_cleared } data: type: object properties: brand: { type: string } raised_at: { type: string, format: date-time } cleared_at: { type: string, format: date-time } responses: "200": { description: Any 2xx answer counts as delivered. } flag.raised: post: operationId: webhook_flag_raised summary: The flag.raised event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. An endpoint in Slack or Teams format gets the event as a chat message instead. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: flag.raised } data: type: object properties: kind: { type: string } detail: { type: string } affiliate_number: { type: integer } affiliate_name: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } postback.destination_off: post: operationId: webhook_postback_destination_off summary: The postback.destination_off event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. An endpoint in Slack or Teams format gets the event as a chat message instead. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: postback.destination_off } data: type: object properties: destination: { type: string } failures: { type: integer } affiliate_number: { type: integer } affiliate_name: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } file.late: post: operationId: webhook_file_late summary: The file.late event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. An endpoint in Slack or Teams format gets the event as a chat message instead. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: file.late } data: type: object properties: source: { type: string } date: { type: string, format: date } responses: "200": { description: Any 2xx answer counts as delivered. } webhook.test: post: operationId: webhook_webhook_test summary: The webhook.test event description: "Sent to the endpoints that ticked this event, signed with the endpoint's secret (RevRake-Signature: hex HMAC-SHA256 of \".\"). Answer any 2xx within 5 seconds. What each field means: revrake.com/docs/api/#webhooks." requestBody: content: application/json: schema: allOf: - $ref: "#/components/schemas/WebhookBody" - type: object properties: type: { const: webhook.test } data: type: object properties: message: { type: string } responses: "200": { description: Any 2xx answer counts as delivered. } components: securitySchemes: keyId: type: apiKey in: header name: X-RevRake-Key description: The key ID. Starts with `pb_` (operator) or `ak_` (affiliate). timestamp: type: apiKey in: header name: X-RevRake-Timestamp description: The current Unix time in seconds, within 300 seconds of the real time. signature: type: apiKey in: header name: X-RevRake-Signature description: Lower-case hex HMAC-SHA256, under the secret, of `.` (POST) or `.?` (GET). headers: X-RateLimit-Limit: description: Reads allowed per minute per key. Always 120. schema: { type: integer, const: 120 } X-RateLimit-Remaining: description: Reads left right now. The allowance holds up to 120; one comes back every half second. schema: { type: integer, minimum: 0, maximum: 120 } Retry-After: description: Seconds to wait before sending the request again. schema: { type: integer } responses: Unauthorized: description: | `unknown_key`: the key is unknown or revoked, or it is an affiliate key on a write endpoint. `bad_timestamp`: the timestamp is missing, not whole seconds, or more than five minutes off. `bad_signature`: the signature does not match. content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } AffiliateInactive: description: "`affiliate_inactive`: an affiliate key whose affiliate is not approved in the program (pending, rejected or suspended)." content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } OperatorKeyRequired: description: "`operator_key_required`: the endpoint is about the whole program and needs the operator's key (pb_…), not an affiliate's." content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } UnknownAffiliate: description: "`unknown_affiliate`: no affiliate has the number in `affiliate`." content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } MethodNotAllowed: description: The endpoint does not accept that method. No JSON body. TooLarge: description: "`too_large`: the body is larger than the limit of the endpoint (4 MB for events, 32 MB for files)." content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } Invalid: description: "`invalid`: the event or the file cannot be used as it is. `field` names what to fix. The same request again gets the same answer." content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } RateLimited: description: "`rate_limited`: more requests than the key's allowance on this endpoint. Wait the seconds in Retry-After, then send the request again with a fresh timestamp." headers: Retry-After: { $ref: "#/components/headers/Retry-After" } X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" } X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" } content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } Internal: description: "`internal`: something went wrong on our side. Send the request again." content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } schemas: ApiAffiliate: type: object required: [number, name, email, status, deal, created_at, decided_at] properties: number: { type: integer, description: "The affiliate number, the first part of the btag." } name: { type: string } email: { type: string, format: email } status: { $ref: "#/components/schemas/AffiliateStatus" } deal: { type: string, description: "The name of the deal the affiliate is on; empty before approval." } created_at: { type: string, format: date-time } decided_at: { type: [string, "null"], format: date-time, description: "When the application was approved or rejected, or the account suspended." } ApiLink: type: object required: [affiliate_number, number, code, url, btag, campaign, landing_path, subs, promo_code, vanity_url, created_at] properties: affiliate_number: { type: integer } number: { type: integer, description: "The link number, the second part of the btag." } code: { type: string } url: { type: string, format: uri, description: "The tracking link: the brand's verified tracking domain, else the shared one." } btag: { type: string, description: "_." } campaign: { type: string } landing_path: { type: [string, "null"] } subs: { type: array, minItems: 5, maxItems: 5, items: { type: string }, description: "sub1 to sub5 fixed on the link; empty strings when unset." } promo_code: { type: [string, "null"], description: "Set on a link made for a promo code." } vanity_url: { type: [string, "null"], description: "The short address /go/, when the affiliate set one." } created_at: { type: string, format: date-time } ApiDeal: type: object required: [name, currency, listed, default, version, effective_at, terms] properties: name: { type: string } currency: { type: string, description: "The currency the deal earns and pays in (ISO 4217)." } listed: { type: boolean, description: "Shown on the public join page." } default: { type: boolean, description: "The brand's default deal for new affiliates." } version: { type: integer } effective_at: { type: string, format: date-time } terms: { $ref: "#/components/schemas/DealTerms" } DealTerms: type: object description: Amounts in minor units of the deal's currency; shares in basis points (2500 = 25%). properties: cpa_amount_minor: { type: [integer, "null"] } cpa_min_deposit_minor: { type: [integer, "null"] } cpa_baseline: { type: string, enum: [first_deposit, total_deposits], description: "What the CPA minimum is measured on: the player's first deposit, or their deposits not charged back, together." } revshare_bps: { type: [integer, "null"] } hold_days: { type: integer } negative_carry: { type: boolean, description: "Whether a month below zero carries into the next." } negative_carry_cap_minor: { type: [integer, "null"], description: "At most this much carries; the rest is written off." } attribution_window_days: { type: [integer, "null"], description: "The deal's own attribution window; null keeps the brand policy's." } ring_fence: { type: boolean, description: "Hybrid deals: a month whose revenue share ends below zero does not reduce the CPA; the loss is carried against later revenue share only." } tiers: type: array items: type: object properties: min_ftds: { type: integer } cpa_amount_minor: { type: integer } revshare_bps: { type: integer } WebhookBody: type: object required: [id, type, created_at, data] properties: id: { type: string, format: uuid, description: "The event's ID; the same on every retry. Handle each ID once." } type: { type: string } created_at: { type: string, format: date-time } data: { type: object } Error: type: object required: [error, message] properties: error: type: string enum: [bad_request, unknown_key, bad_timestamp, bad_signature, affiliate_inactive, unknown_affiliate, too_large, invalid, rate_limited, internal] message: { type: string, description: A sentence in English. } field: type: string description: "Only with `invalid`: what to fix. Absent for a batch of the wrong size." enum: [body, type, player_id, click_id, btag, promo_code, txn_id, ref_txn_id, amount, currency, ts, period, file, chargeback] Amount: description: A decimal as text or as a number, with no more decimals than the currency has. oneOf: - { type: string, pattern: '^-?\d{1,15}(\.\d{1,2})?$' } - { type: number } Event: oneOf: - $ref: "#/components/schemas/Registration" - $ref: "#/components/schemas/Deposit" - $ref: "#/components/schemas/Chargeback" - $ref: "#/components/schemas/Ngr" discriminator: propertyName: type mapping: registration: "#/components/schemas/Registration" deposit: "#/components/schemas/Deposit" chargeback: "#/components/schemas/Chargeback" ngr: "#/components/schemas/Ngr" Registration: type: object additionalProperties: false required: [type, player_id] properties: type: { const: registration } player_id: { type: string, minLength: 1, maxLength: 100, description: Your ID for the player. The key of the event. } click_id: { type: string, description: "The cid the player arrived with: 26 characters, upper or lower case." } btag: { type: string, pattern: '^\d{1,9}_\d{1,9}$', description: "_. A btag of another shape is recorded as unknown_btag." } promo_code: { type: string, description: "The promo code the player typed at sign-up. Not case-sensitive. By default it attributes the player only when click_id and btag do not (reason promo_code); when the brand's policy says the promo code wins, a code that matches is taken before both. A code that does not match gives unknown_promo_code or promo_code_retired when nothing else attributed." } email_hash: { type: string, description: "SHA-256 of the lower-cased email, in hex. Used only by the self-referral check." } email_domain: { type: string, description: "The domain of the player's email, the part after the @ (example.com). Used only by the throwaway-email fraud rule. Send the domain only: if a whole address arrives, only the domain is kept, but the inbound log still shows the request as it arrived." } ip: { type: string, description: The IP address at registration. Hashed; used by the self-referral check and to match a click for a btag. } country: { type: string, description: "The player's country, ISO 3166-1 alpha-2 (GB, KE). Lower case is accepted; a value that is not two letters leaves the player without a country. Without the field, the country of the click that brought the player is used. A player from a country the deal blocks earns nothing on it (country_blocked)." } ts: { type: string, format: date-time, description: When the player registered. Not more than a day in the future. } Deposit: type: object additionalProperties: false required: [type, player_id, txn_id, amount] properties: type: { const: deposit } player_id: { type: string, minLength: 1, maxLength: 100 } txn_id: { type: string, minLength: 1, maxLength: 200, description: Your transaction ID. The key of the event. } amount: { $ref: "#/components/schemas/Amount", description: Above zero. } currency: { type: string, description: "ISO 4217 code of the deposit. Any currency; lower case is accepted. Default: the workspace's reporting currency." } ts: { type: string, format: date-time } first: { type: boolean, description: "true for the player's first deposit. When left out, the deposit is first if no earlier one is known for the player." } Chargeback: type: object additionalProperties: false required: [type, txn_id, ref_txn_id] properties: type: { const: chargeback } txn_id: { type: string, minLength: 1, maxLength: 200, description: Your ID for the chargeback itself. The key of the event. } ref_txn_id: { type: string, description: The txn_id of the deposit that was charged back. The deposit must be known and not yet charged back. } player_id: { type: string, description: "Optional. If supplied, must match the player of the referenced deposit." } ts: { type: string, format: date-time } Ngr: type: object additionalProperties: false required: [type, player_id, period, amount] properties: type: { const: ngr } player_id: { type: string, minLength: 1, maxLength: 100, description: The player must be registered. } period: { type: string, pattern: '^\d{4}-\d{2}(-\d{2})?$', description: "A day (YYYY-MM-DD) or a month (YYYY-MM). A month and its first day are different periods; a month is priced by the deal version in effect on its last day. Not more than a day in the future." } amount: { $ref: "#/components/schemas/Amount", description: Negative when the player won. } currency: { type: string } ggr: { $ref: "#/components/schemas/Amount", description: "Optional: gross gaming revenue of the period. Shown to the affiliate; the commission is priced on amount." } bonuses: { $ref: "#/components/schemas/Amount", description: "Optional, shown only." } fees: { $ref: "#/components/schemas/Amount", description: "Optional, shown only." } taxes: { $ref: "#/components/schemas/Amount", description: "Optional, shown only." } ts: { type: string, format: date-time } EventStatus: type: string enum: [accepted, duplicate] EventResult: type: object required: [status, event_type, event_id] properties: status: { $ref: "#/components/schemas/EventStatus" } event_type: { type: string, enum: [registration, deposit, chargeback, ngr] } event_id: { type: string, format: uuid } attribution: { $ref: "#/components/schemas/Attribution" } commission: { $ref: "#/components/schemas/CommissionEffect" } waiting_for_player: { type: boolean, description: "true for a deposit whose player is not registered yet. Left out otherwise." } duplicate_of: { type: string, format: uuid, description: "For a legacy first-deposit match across live events and older daily files: the earlier event_id. Current SALES summaries use daily coverage and retain each actual transaction ID." } detail: { type: string, description: "With duplicate_of, a sentence naming the earlier deposit." } Attribution: type: object required: [attributed, reason, policy_version, window_days] properties: attributed: { type: boolean } reason: type: string description: click, first_click, btag_click, btag_only and promo_code mean attributed; the others mean not attributed. first_click means the policy is first click and an earlier click of the same visitor was credited instead of the reported one. enum: [click, first_click, btag_click, btag_only, promo_code, no_tag, unknown_click, unknown_btag, outside_window, affiliate_inactive, unknown_promo_code, promo_code_retired] affiliate_number: { type: integer } link_number: { type: integer } click_id: { type: string } policy_version: { type: integer } window_days: { type: integer, description: "The attribution window applied, in days: the deal's own window when the credited affiliate's deal sets one, else the brand policy's." } model: { type: string, enum: [last_click, first_click], description: The model of that policy version. } CommissionEffect: type: object required: [effect] properties: effect: { type: string, enum: [created, cancelled, reversed, none] } type: { $ref: "#/components/schemas/CommissionType" } amount_minor: { type: integer, description: Minor units of the deal's currency. Negative for a reversal and a downward adjustment. } status: { type: string, enum: [hold, approved, rejected] } reason: type: string description: Why nothing was created, when effect is none. enum: [unattributed, not_first, already_earned, no_deal_version, country_blocked, no_cpa_in_deal, below_threshold, no_commission, no_revshare_in_deal, fx_rate_missing, unchanged] BatchResult: type: object required: [results, accepted, duplicate, rejected] properties: results: type: array items: type: object required: [index, status] properties: index: { type: integer, minimum: 0 } status: { type: string, enum: [accepted, duplicate, rejected] } event_type: { type: string } event_id: { type: string, format: uuid } attribution: { $ref: "#/components/schemas/Attribution" } commission: { $ref: "#/components/schemas/CommissionEffect" } waiting_for_player: { type: boolean } field: { type: string, description: For a rejected event. } message: { type: string, description: For a rejected event. } accepted: { type: integer } duplicate: { type: integer } rejected: { type: integer } FileImport: type: object required: [id, filename, kind, uploaded_at, rows_total, accepted, duplicates, rejected, skipped] properties: id: { type: string, format: uuid } filename: { type: string } kind: { type: string, enum: [reg, sales] } uploaded_at: { type: string, format: date-time } rows_total: { type: integer } accepted: { type: integer, description: Events stored. } duplicates: { type: integer, description: Events that were already stored. } rejected: { type: integer, description: Rows or events refused. } skipped: { type: integer, description: Rows with nothing to record. } problems: { type: array, maxItems: 20, items: { type: string }, description: "The first refused lines, as 'line N: reason'." } Report: type: object required: [by, from, to, currency, rows, total] properties: by: { type: string, enum: [day, link, creative, sub1, country, affiliate, tag] } from: { type: string, format: date } to: { type: string, format: date } currency: { type: string, description: The workspace's reporting currency. } rows: { type: array, items: { $ref: "#/components/schemas/ReportRow" } } total: { $ref: "#/components/schemas/ReportRow" } ReportRow: type: object required: [key, label, clicks, flagged_clicks, registrations, ftds, earnings_minor] properties: key: { type: string } label: { type: string } clicks: { type: integer } flagged_clicks: { type: integer } registrations: { type: integer } ftds: { type: integer } earnings_minor: { type: integer, description: Minor units of the reporting currency. } CommissionType: type: string description: | tier is a top-up from a volume tier of the deal, written beside the commission it tops up by an hourly job; it is never the answer to an event. manual is an adjustment an operator admin made by hand (status_reason goodwill, correction, bonus, penalty, migration or other; the note in status_note); it has no player and no deal, and appears only once approved. enum: [cpa, revshare, adjustment, reversal, tier, manual] CommissionStatus: type: string description: hold, then approved, then paid; or rejected. enum: [hold, approved, paid, rejected] CommissionStatusReason: type: string description: | Empty for a row that only waits for its hold period, and normally for an approved or paid commission. chargeback_fx_missing can also appear on approved or paid rows: the CPA and its tier top-ups cannot enter a new payout or be marked paid in an existing one until the missing chargeback rate arrives and reconciliation completes. chargeback_reference_missing holds first-deposit CPA when a summary received before registration contains an ambiguous aggregate chargeback needing transaction-reference review. With hold: fx_rate_missing, self_referral, click_velocity, duplicate_conversion, paid_traffic, disposable_email, geo_mismatch, fast_registration, affiliate_pending, affiliate_rejected, affiliate_suspended. With rejected: chargeback, below_threshold, fraud, base_rejected (a tier row whose commission was rejected while both were on hold). When the operator takes a commission back, the reason is one of self_excluded, duplicate_account, bonus_abuse, fraud, chargeback, terms_breach, test_account or other (status_note says why): on the rejected row, or on an approved reversal row when the commission was already in a payout or paid. A reversal after a chargeback is approved with chargeback. enum: ["", fx_rate_missing, chargeback_fx_missing, chargeback_reference_missing, self_referral, click_velocity, duplicate_conversion, paid_traffic, disposable_email, geo_mismatch, fast_registration, affiliate_pending, affiliate_rejected, affiliate_suspended, chargeback, below_threshold, fraud, base_rejected, self_excluded, duplicate_account, bonus_abuse, terms_breach, test_account, other] Ledger: type: object required: [currency, totals, owed, period, commissions, total, page, per_page] properties: currency: { type: string, description: The workspace's reporting currency. } totals: type: object description: Sum per status, in minor units of the reporting currency, over the whole ledger or over the period when from or to is given. Not narrowed by the other filters, except affiliate. additionalProperties: { type: integer } owed: type: object description: "Sum per currency and status, in each commission's own currency." additionalProperties: type: object additionalProperties: { type: integer } period: { type: boolean, description: "true when from or to was given: totals and owed cover that period only." } commissions: { type: array, items: { $ref: "#/components/schemas/Commission" } } total: { type: integer, description: Rows that match the filters. } page: { type: integer } per_page: { type: integer, enum: [10, 25, 50, 100, 500, 1000] } Commission: type: object required: [id, occurred_at, type, currency, gross_currency, reporting_minor, affiliate_name, affiliate_number, player_ref, deal_name, deal_version, gross_minor, rate_snapshot, amount_minor, status, status_reason, status_note, hold_until] properties: id: { type: string, format: uuid } occurred_at: { type: string, format: date-time } type: { $ref: "#/components/schemas/CommissionType" } amount_minor: { type: integer, description: Minor units of currency. } currency: { type: string, description: The deal's currency. } reporting_minor: { type: [integer, "null"], description: The commission in the reporting currency at the rate of its day; null while the rate is missing. } gross_minor: { type: integer, description: "The deposit or net revenue behind the commission, in minor units of gross_currency." } gross_currency: { type: string } rate_snapshot: { type: integer, description: "The CPA amount in minor units, or the revenue share in hundredths of a percent." } status: { $ref: "#/components/schemas/CommissionStatus" } status_reason: { $ref: "#/components/schemas/CommissionStatusReason" } status_note: { type: string, maxLength: 500, description: "The operator's note on a commission taken back, as the affiliate reads it. Empty otherwise." } hold_until: { type: [string, "null"], format: date-time } affiliate_number: { type: integer } affiliate_name: { type: string } player_ref: { type: string } deal_name: { type: string } deal_version: { type: integer } # The enumerations below are not returned by the /v1 endpoints today. They are # listed so that every status shown in the portals has one published definition. PayoutBatchStatus: type: string description: A month closes into a batch. draft, then approved (locked; bank file), then paid. enum: [draft, approved, paid] PayoutItemStatus: type: string description: "One item per affiliate and currency in a batch. held: an open fraud flag, or an identity check not yet recorded. written_off: a losing month the deal does not carry forward." enum: [payable, carried, held, no_details, written_off, paid] AffiliateStatus: type: string enum: [pending, approved, rejected, suspended] InboundLogOutcome: type: string enum: [accepted, duplicate, rejected, bad_signature, test] PostbackEvent: type: string description: The events sent to an affiliate's tracker. enum: [registration, ftd, commission_approved, commission_rejected, commission_paid, chargeback] PostbackStatusWord: type: string description: "What the {status} macro carries: lead (registration), sale (ftd), approved, rejected (commission_rejected and chargeback), paid." enum: [lead, sale, approved, rejected, paid] PostbackDeliveryState: type: string description: "A failed delivery is retried after 1 min, 5 min, 25 min, 2 h, 10 h and 24 h, then is dead." enum: [queued, delivered, dead] PostbackErrorClass: type: string enum: [timeout, dns, refused, blocked_address, tls, too_many_redirects, http_error, other]