{"info":{"_postman_id":"a234ba01-2c09-4711-a83d-7f57c03a079a","name":"iGsuite Manager Documentation","description":"<html><head></head><body><h2 id=\"ℹ️-introduction\"><strong>ℹ️ Introduction</strong></h2>\n<blockquote>\n<p>This document will guide you through the integration of both the <strong>BI</strong> &amp; <strong>Affiliate Platform</strong> of <strong>iGsuite</strong>. These are the parties we have identified to take part in this process: </p>\n</blockquote>\n<ul>\n<li><p>The <strong>Operator</strong>: This is the Client who will use the software to manage their Brands</p>\n</li>\n<li><p>The <strong>iGaming Platform</strong>: This is the Platform under which the Operator Brand is built</p>\n</li>\n<li><p><strong>iGsuite</strong>: Us, who manage the Software of the <strong>Affiliate Platform</strong> and <strong>BI</strong></p>\n</li>\n</ul>\n<h2 id=\"🧬-1-what-is-igsuite\"><strong>🧬 1. What is iGsuite?</strong></h2>\n<p><strong>iGsuite</strong> combines two products in one <strong>- Affiliate Platform</strong> and <strong>BI:</strong></p>\n<ul>\n<li><p><strong>Affiliate Platform -</strong> built for the iGaming industry. Its purpose is to enable Affiliate Marketing for the operators allowing them to build, manage and scale their partners to ensure sustainable growth. The software itself provides the following key functionality:</p>\n<ul>\n<li><p>Registration and management of affiliates</p>\n</li>\n<li><p>Allow Affiliates to access all sorts of creatives</p>\n</li>\n<li><p>Tracking affiliate customers</p>\n</li>\n<li><p>Collecting and visualising data about customers</p>\n</li>\n<li><p>Calculates affiliates' profit based on customer activities</p>\n</li>\n<li><p>Monitor and manage affiliate portfolio and payments</p>\n</li>\n</ul>\n</li>\n<li><p><strong>Newton</strong> - the <strong>BI</strong>, tailored for the iGaming industry is the only BI built to support end to end the customer journey, cost and P&amp;L of the company. Its main purpose is to:</p>\n<ul>\n<li><p>Automate all reports</p>\n</li>\n<li><p>Provide ready Analysis and insights of the business</p>\n</li>\n<li><p>Update all data in real-time</p>\n</li>\n</ul>\n</li>\n</ul>\n<h2 id=\"📋-2-integration-flow\"><strong>📋 2. Integration Flow</strong></h2>\n<p>Below you will find described the exact order of actions that should take place in order to get iGsuite products live for you as fast as possible.</p>\n<ol>\n<li><p><strong>iGsuite</strong> should initiate the integration with the relevant parties</p>\n</li>\n<li><p>The <strong>Operator</strong> has to provide iGsuite with their specifications towards the Affiliate Platform setup.</p>\n</li>\n<li><p>The <strong>iGaming Platform</strong> should start to collect and return iGsuite tokens.</p>\n</li>\n<li><p>The <strong>iGaming Platform</strong> should complete the File Integration:</p>\n<ol>\n<li><p>Registration File</p>\n</li>\n<li><p>Activity File</p>\n</li>\n</ol>\n</li>\n<li><p><strong>iGsuite</strong> will handle full test on the Affiliate Platform end to end.</p>\n</li>\n<li><p>The <strong>Operator</strong> should state the launch date.</p>\n</li>\n</ol>\n<h2 id=\"🚀-3-get-started---operator-requirements\"><strong>🚀 3. Get Started - Operator Requirements</strong></h2>\n<p>In order to configure iGsuite, the <strong>Operator</strong> should provide some key preferences for the platform, a questionnaire will be provided to the client later on.</p>\n<p>In this section, we also explain the necessary server configuration to have the platform under the Operator's domain.</p>\n<p><strong>Note! Everything under point 3, is a blocker for the next steps of the integration process!</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Main Participants:</th>\n<th><strong>The Operator</strong></th>\n</tr>\n</thead>\n<tbody>\n</tbody>\n</table>\n</div><h3 id=\"31-platform-preferences\"><strong>3.1 Platform Preferences</strong></h3>\n<p>The <strong>Operator</strong> should provide the following in the Integration Ticket:</p>\n<ol>\n<li><p><strong>Branding</strong>:</p>\n<ol>\n<li><p>Affiliate Platform Official name</p>\n</li>\n<li><p>Domain name under which you will operate the Affiliate Platform</p>\n</li>\n<li><p>Logo</p>\n</li>\n</ol>\n</li>\n<li><p><strong>Platform Preferences &amp; Access</strong>:</p>\n<ol>\n<li><p>Timezone</p>\n</li>\n<li><p>Currency</p>\n</li>\n<li><p>Type of integration: (API/SFTP)</p>\n</li>\n<li><p>Data transfer option: (Json, CSV, etc)</p>\n</li>\n<li><p>Frequency: (1h, 6h, 24h, etc)</p>\n</li>\n<li><p>Email under which to sign the initial Admin Account</p>\n</li>\n<li><p>Email under which to sign the data Integration Account</p>\n</li>\n<li><p>Email used for our system mailers (Password reset, Confirm Account Registration, Affiliate Account approval, etc.)</p>\n</li>\n</ol>\n</li>\n</ol>\n<h3 id=\"32-operator-domain-server-setup\"><strong>3.2 Operator Domain Server Setup</strong></h3>\n<p>The Operator should provide the domain under which they would like the platform to be made available. Our integration team will provide the Operator with the necessary details so they can configure the DNS record on their domain provider.</p>\n<h2 id=\"🔐-4-authentication\"><strong>🔐 4. Authentication</strong></h2>\n<p>Every request to a <code>/workspaces/api/...</code> endpoint must include the bearer token in the <code>Authorization</code> header:</p>\n<p><code>Authorization: Bearer YOUR_TOKEN</code></p>\n<p>There are two ways to obtain a token.</p>\n<h3 id=\"41-obtaining-a-token-via-the-report-book-ui-recommended\"><strong>4.1 Obtaining a token via the report-book UI (Recommended)</strong></h3>\n<p>This produces a <strong>long-lived api token</strong> that does not expire and does not interfere with anyone's active app session. It is the right choice for production integrations.</p>\n<p>Step by step:</p>\n<ol>\n<li><p>Open the report-book web application in your browser and <strong>log in</strong> as the integration account (the email you provided as the <em>Data Integration Account</em> in section 3.1).</p>\n</li>\n<li><p>If 2-Factor Authentication is enabled on this account, complete the 2FA challenge.</p>\n</li>\n<li><p>From the main navigation, open <strong>Tools</strong>.</p>\n</li>\n<li><p>Open the <strong>Api Tokens</strong> tab.</p>\n</li>\n<li><p>Click the <strong>Generate</strong> button. A modal will appear with the plaintext token.</p>\n</li>\n<li><p><strong>Copy the token immediately and store it somewhere safe</strong> (e.g. your secrets manager). The token is shown <strong>only once</strong> — there is no way to retrieve it again later.</p>\n</li>\n<li><p>Close the modal. The Api Tokens tab will now show an <strong>Active</strong> state with the creation date.</p>\n</li>\n</ol>\n<p>Send the token as the value of the <code>Authorization</code> header (formatted as <code>Bearer YOUR_TOKEN</code>) on any<br><code>/workspaces/api/...</code> request. That's it — you're authenticated.</p>\n<h4 id=\"token-characteristics\">Token characteristics</h4>\n<ul>\n<li><p><strong>No expiration.</strong> Valid until you explicitly revoke or re-generate it.</p>\n</li>\n<li><p><strong>Survives deploys.</strong> Restarts and deploys of the platform do not invalidate it.</p>\n</li>\n<li><p><strong>Independent of UI sessions.</strong> Logging in or out of the app does not affect the token, and creating the token does not log anyone out.</p>\n</li>\n<li><p><strong>Acts as the user.</strong> Requests authenticated with this token carry the integration account's full permissions — treat the token like a password.</p>\n</li>\n<li><p><strong>One active api token per user at a time.</strong> Re-generating creates a new one and invalidates the old one.</p>\n</li>\n<li><p><strong>Revocable anytime</strong> via the same Tools → Api Tokens screen, or programmatically via the <code>Revoke Api Token</code> request in the AUTH folder.</p>\n</li>\n</ul>\n<p>We strongly recommend <strong>rotating</strong> the token periodically to limit blast radius in the event of a leak.</p>\n<h3 id=\"42-obtaining-a-token-via-the-report-book-api\"><strong>4.2 Obtaining a token via the report-book API</strong></h3>\n<blockquote>\n<p><strong>Not recommended for production integrations.</strong> Use 4.1 unless you have a specific reason not to. </p>\n</blockquote>\n<p>Partners can also authenticate by calling <code>POST /workspaces/api/login</code> with email and password and using the returned bearer token directly. See the <code>Login</code> request in the AUTH folder for full details.</p>\n<h4 id=\"why-this-is-not-recommended\">Why this is not recommended</h4>\n<ul>\n<li><p><strong>Destroys active app sessions.</strong> Calling <code>POST /login</code> invalidates the user's existing session token — if the same user is currently signed in to the report-book UI, they will be <strong>signed out</strong>.</p>\n</li>\n<li><p><strong>Token expires after 7 days.</strong> You will need to re-login periodically. Each re-login produces a new token and (again) signs the user out of the app.</p>\n</li>\n<li><p><strong>Only one active session token per user at a time.</strong> Parallel integrations under the same user account will stomp on each other.</p>\n</li>\n</ul>\n<p>The api token from section 4.1 has none of these downsides.</p>\n<h2 id=\"🌐-5-customer-tracking\"><strong>🌐 5. Customer Tracking</strong></h2>\n<p>After the Affiliate Platform is set, iGsuite will be able to generate tracking links that will be used to track the customers of the affiliates. At this point, the <strong>iGaming Platform</strong> should proceed with the integration process.</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Main Participants:</th>\n<th><strong>The iGaming Platform</strong></th>\n</tr>\n</thead>\n<tbody>\n</tbody>\n</table>\n</div><h3 id=\"51-how-does-igsuite-track-the-customers\"><strong>5.1 How does iGsuite track the customers?</strong></h3>\n<p>Every Affiliate who has signed in the Affiliate Platform will have a unique tracking link. The Affiliate uses this link to refer the customers towards the Operator Brand. The customer journey is as follows:</p>\n<ol>\n<li><p>Customer clicks on the affiliate link</p>\n</li>\n<li><p>iGsuite tracking link redirects the customer to the given Landing Page by the Operator</p>\n</li>\n<li><p>A unique token is generated upon the redirect.</p>\n<ol>\n<li><p>This token is unique for every click</p>\n</li>\n<li><p>Through this token, we track the Customers</p>\n</li>\n</ol>\n</li>\n</ol>\n<h3 id=\"52-about-the-customer-token\"><strong>5.2 About The Customer Token</strong></h3>\n<p>The customer token is generated by iGsuite and is unique for every single customer! The customer token is generated when the customer clicks on any of the Affiliate Platform tracking links.</p>\n<ul>\n<li><p><em>Example of a Tracking Link:</em> <a href=\"http://www.affiliate-platform-domain.com/api/tracking-links/record?trackingLinkId=1&amp;affiliateId=1\"><b>www.Affiliate-Platform-Domain.com/api/tracking-links/record?trackingLinkId=1&amp;affiliateId=1<br></b></a></p>\n</li>\n<li><p><em>Example of where the Tracking Link will Redirect:</em> <a href=\"http://www.operator-brand.com/signup?15bedea716ee7a35279520b61aa3d2a5\"><b>www.Operator-Brand.com/signup?15bedea716ee7a35279520b61aa3d2a5</b></a></p>\n<ul>\n<li>The Token starts after the “?”</li>\n</ul>\n</li>\n<li><p><em>Example of the Customer Token</em><strong>:</strong><code>15bedea716ee7a35279520b61aa3d2a5</code></p>\n</li>\n</ul>\n<h3 id=\"53-token-requirements-from-the-igaming-operator\"><strong>5.3 Token Requirements from the iGaming Operator</strong></h3>\n<ol>\n<li><p>The iGaming Platform should ensure the following:</p>\n</li>\n<li><p>Requirements from the iGaming Operator:</p>\n<ol>\n<li><p>To store the customer token of the customer upon successful Registration</p>\n</li>\n<li><p>To return the token of the customer within the Registration File</p>\n</li>\n</ol>\n</li>\n<li><p>Customer Token Specifications:</p>\n<ol>\n<li>The token itself contains valuable information about the customer &amp; the Affiliate, such as:</li>\n</ol>\n</li>\n</ol>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th><strong>Parameter</strong></th>\n<th><strong>Description</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>affiliateId</strong></td>\n<td><strong>iGsuite ID of the affiliate this customer belongs to</strong></td>\n</tr>\n<tr>\n<td><strong>username</strong></td>\n<td><strong>The username of the affiliate</strong></td>\n</tr>\n<tr>\n<td><strong>ipAddress</strong></td>\n<td><strong>The IP Address that the customer accessed the landing page from</strong></td>\n</tr>\n<tr>\n<td><strong>referralLink</strong></td>\n<td><strong>The page the customer landed on after visiting the tracking link (nullable)</strong></td>\n</tr>\n<tr>\n<td><strong>externalAffiliateClickId</strong></td>\n<td><strong>The external click identifier the affiliate inserts through iGsuite when forming the tracking link</strong></td>\n</tr>\n<tr>\n<td><strong>trafficSource</strong></td>\n<td><strong>The type of traffic that the customer came from based on the type selected for the affiliate (Example: \"SEO\")</strong></td>\n</tr>\n<tr>\n<td><strong>trackingLinkId</strong></td>\n<td><strong>iGsuite ID of the tracking link this customer came from</strong></td>\n</tr>\n<tr>\n<td><strong>brandId</strong></td>\n<td><strong>iGsuite ID of the brand the tracking link points to</strong></td>\n</tr>\n<tr>\n<td><strong>brandName</strong></td>\n<td><strong>Display name of the brand the tracking link points to</strong></td>\n</tr>\n<tr>\n<td><strong>campaignId</strong></td>\n<td><strong>iGsuite ID of the campaign associated with the click (optional — present only when the affiliate set a campaign on the tracking link)</strong></td>\n</tr>\n<tr>\n<td><strong>campaignName</strong></td>\n<td><strong>Display name of the campaign (optional — present only when campaignId is present)</strong></td>\n</tr>\n</tbody>\n</table>\n</div><ul>\n<li><p>The iGaming Platform may decode the token to collect and store the provided information in the previous point.</p>\n</li>\n<li><p>The token is generated upon a click on the tracking links and it’s unique for each and every click.</p>\n</li>\n</ul>\n<h3 id=\"54-how-the-igaming-platform-can-decode-the-customer-token\"><strong>5.4 How The iGaming Platform Can Decode the Customer Token</strong></h3>\n<p>The iGaming Platform can decode the Customer Token in order to collect the embedded information for the customer via an API request.</p>\n<ul>\n<li><strong>You can find more information within the</strong> <a href=\"#5ddce0c7-c486-4ebc-8fb5-8129ad6326d3\">Tracking</a> <strong>section of the documentation.</strong></li>\n</ul>\n<h2 id=\"🤝-6-data-integration\"><strong>🤝 6. Data integration</strong></h2>\n<p>The integration is achieved through HTTPS or SFTP where we expect to receive consistently the given set of files in the agreed format.</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Main Participants:</th>\n<th><strong>The iGaming Platform</strong></th>\n</tr>\n</thead>\n<tbody>\n</tbody>\n</table>\n</div><h3 id=\"61-minimum-required-data--format\"><strong>6.1 Minimum Required Data &amp; Format</strong></h3>\n<p>In order to ensure stable and consistent data flow without interruption, iGsuite requires that The iGaming Platform respects the following basic <strong>requirements</strong>:</p>\n<ol>\n<li><p>The IGaming Platform will always provide two files with a different set of data:</p>\n<ol>\n<li><p>Registration File (see <a href=\"https://igsuite.postman.co/workspace/018a3f86-2f6c-447d-bf30-e224373f95f9/request/28572506-515b2251-88b6-4007-bd56-68044b9ccc54?action=share&amp;source=copy-link&amp;creator=17749899&amp;ctx=documentation\">Import Customers</a>)</p>\n</li>\n<li><p>Activity File (see <a href=\"https://igsuite.postman.co/workspace/018a3f86-2f6c-447d-bf30-e224373f95f9/request/28572506-799ceec7-e632-4e3b-a8e2-e733fbe2b9c1?action=share&amp;source=copy-link&amp;creator=17749899&amp;ctx=documentation\">Import Activity</a>)</p>\n</li>\n</ol>\n</li>\n<li><p>The iGaming Platform should always respect the data Format requirements for every column</p>\n</li>\n<li><p>Each column should have a single header only. To make the integration easier, the exact header names can be discussed and changed prior to beginning the process.</p>\n</li>\n</ol>\n<p><strong>Note! All the fields in the CSV, JSON files and Body contents are subject to agreement between the two parties before beginning the daily integration process.</strong></p>\n<h3 id=\"62-sending-data-to-igsuite\"><strong>6.2 Sending data to iGsuite</strong></h3>\n<h4 id=\"sftp\"><strong>SFTP</strong></h4>\n<p>If you choose to proceed with the SFTP integration approach, our team will grant you server access and dedicated credentials for your iGsuite platform. To complete the process, simply upload two files - registrations.csv and activities.csv - into the files directory at the agreed times. Once uploaded, our team will handle the rest, ensuring that your data is seamlessly available in your iGsuite platform.</p>\n<blockquote>\n<p>Note: SFTP file uploads are not exercised in this collection. If you choose SFTP, the integration is arranged with our team outside Postman. </p>\n</blockquote>\n<h4 id=\"https\"><strong>HTTPS</strong></h4>\n<p>Data integration is achieved via API integration with your iGsuite instance. This method offers a more streamlined and automated approach and reduces the risk of file upload failures.</p>\n<p>The API integration with iGsuite involves two separate requests for the customer registration and activity files, respectively. These requests can be made using the example requests provided below. The baseURL corresponds to the URL of the provided iGsuite instance, which will be communicated to you separately.</p>\n<h4 id=\"sending-registration-file\"><strong>Sending Registration File</strong></h4>\n<p>To integrate data via API, a POST request is used to send the CSV file. The registration CSV file should adhere to the format described in <a href=\"https://igsuite.postman.co/workspace/018a3f86-2f6c-447d-bf30-e224373f95f9/request/28572506-515b2251-88b6-4007-bd56-68044b9ccc54?action=share&amp;source=copy-link&amp;creator=17749899&amp;ctx=documentation\">Import Customers</a>.</p>\n<h4 id=\"sending-activity-file\"><strong>Sending Activity File</strong></h4>\n<p>To integrate data via API, a POST request is used to send the CSV file. The activities CSV file should adhere to the format described in <a href=\"https://igsuite.postman.co/workspace/018a3f86-2f6c-447d-bf30-e224373f95f9/request/28572506-799ceec7-e632-4e3b-a8e2-e733fbe2b9c1?action=share&amp;source=copy-link&amp;creator=17749899&amp;ctx=documentation\">Import Activity</a>.</p>\n<p>⚠️ Note: Only customers who are affiliated with our system should be sent to us. Any customer record without an assigned affiliate or token must not be included in the import files. Submitting unregistered or unaffiliated customers will result in the files being rejected.</p>\n<h3 id=\"63-import-frequency\">6.3 Import Frequency</h3>\n<p>The frequency of data imports is fully customizable, depending on your integration method:</p>\n<ul>\n<li><p><strong>API Integration:</strong> If you're sending data via direct API, you may import data <strong>at any preferred interval</strong>—daily, hourly, or even in real-time. However, it's important to ensure a 5-min delay between the customer registration import and the corresponding activity import. This allows the system to process and calculate the relevant metrics accurately.</p>\n</li>\n<li><p><strong>SFTP Integration:</strong> If you choose to integrate via SFTP, please inform our team of your preferred import schedule during the initial integration setup. This enables us to configure the system accordingly and ensure timely and accurate data processing.</p>\n</li>\n</ul>\n<blockquote>\n<p>If the frequency of the import is Hourly or less, we insist on using overwrite method over merge.</p>\n</blockquote>\n</body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[],"owner":"28572506","collectionId":"a234ba01-2c09-4711-a83d-7f57c03a079a","publishedId":"2sA3s7k9k4","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"2B77FF"},"publishDate":"2025-01-09T11:38:33.000Z"},"item":[{"name":"AUTH","item":[{"name":"Get Api Token","id":"f3834732-cdb9-389a-9c01-c840a901754a","request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"GET","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/api-token","description":"<p>Returns metadata about the user's currently active long-lived api token, if one exists.</p>\n<blockquote>\n<p>⚠️ <strong>Auth requirements:</strong> this endpoint requires a <strong>2FA-verified UI session token</strong> obtained via <code>Login</code> followed by <code>Verify Token (2FA)</code> if 2FA is enabled on the account. Long-lived api tokens <strong>cannot</strong> be used to call this endpoint by design — manage api tokens from a session that is already verified through the regular login flow, or use the report-book UI (Tools → Api Tokens).</p>\n</blockquote>\n<h3 id=\"request\">Request</h3>\n<p>No request body or query parameters.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 Ok</h4>\n<p>The user has an active api token. Returns its metadata. The plaintext token value is <strong>not</strong> included — it is only returned at mint time via <code>Generate Api Token</code>.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"id\": 47,\n  \"createdAt\": \"2026-03-04T08:21:13.000Z\",\n  \"lastUsedAt\": \"2026-05-10T14:09:55.000Z\"\n}\n\n</code></pre>\n<h4 id=\"404-not-found\">404 Not Found</h4>\n<p>The user does not currently have an active api token.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"No api token found\"\n}\n\n</code></pre>\n","urlObject":{"path":["workspaces","api","api-token"],"host":[""],"query":[],"variable":[]}},"response":[],"_postman_id":"f3834732-cdb9-389a-9c01-c840a901754a"},{"name":"Generate Api Token","id":"425abad6-a42f-c6cc-70f0-1136a059f171","request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"POST","header":[],"url":"/workspaces/api/api-token","description":"<p>Generates a new long-lived api token for the authenticated user. If the user already has an active api token, it is <strong>immediately invalidated</strong> and a new one is created.</p>\n<p>The plaintext token is returned <strong>once</strong> in this response — store it immediately, you will not be able to retrieve it again.</p>\n<blockquote>\n<p>⚠️ <strong>Auth requirements:</strong> this endpoint requires a <strong>2FA-verified UI session token</strong> obtained via <code>Login</code> followed by <code>Verify Token (2FA)</code> if 2FA is enabled on the account. Long-lived api tokens <strong>cannot</strong> mint a new api token by design. The same operation is available without programmatic auth via the report-book UI (Tools → Api Tokens), which is the recommended path.</p>\n</blockquote>\n<h3 id=\"request\">Request</h3>\n<p>No request body or query parameters.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"201-created\">201 Created</h4>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"id\": 47,\n  \"createdAt\": \"2026-05-11T08:21:13.000Z\",\n  \"token\": \"opaque-plaintext-token-shown-once\"\n}\n\n</code></pre>\n<h3 id=\"token-characteristics\">Token characteristics</h3>\n<ul>\n<li><strong>No expiration.</strong> Once minted, the token is valid until explicitly revoked or replaced by a re-generation.</li>\n<li><strong>Survives deploys.</strong> Restarts and deploys of the platform do not invalidate it.</li>\n<li><strong>Acts as the user.</strong> Requests authenticated with this token carry the user's full permissions — treat it like a password.</li>\n<li><strong>Single active token per user.</strong> Generating a new token automatically revokes the previous one.</li>\n</ul>\n","urlObject":{"path":["workspaces","api","api-token"],"host":[""],"query":[],"variable":[]}},"response":[],"_postman_id":"425abad6-a42f-c6cc-70f0-1136a059f171"},{"name":"Revoke Api Token","id":"b5201070-e4ba-a75b-81b2-4b86315a830b","request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"DELETE","header":[],"url":"/workspaces/api/api-token","description":"<p>Revokes the user's currently active long-lived api token. Subsequent requests using the revoked token return <code>401 Unauthorized</code>.</p>\n<blockquote>\n<p>⚠️ <strong>Auth requirements:</strong> this endpoint requires a <strong>2FA-verified UI session token</strong> obtained via <code>Login</code> followed by <code>Verify Token (2FA)</code> if 2FA is enabled on the account. Long-lived api tokens <strong>cannot</strong> revoke themselves by design. The same operation is available via the report-book UI (Tools → Api Tokens → Disable).</p>\n</blockquote>\n<h3 id=\"request\">Request</h3>\n<p>No request body or query parameters.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"204-no-content\">204 No Content</h4>\n<p>The api token was successfully revoked. The user has no active api token after this call.</p>\n","urlObject":{"path":["workspaces","api","api-token"],"host":[""],"query":[],"variable":[]}},"response":[],"_postman_id":"b5201070-e4ba-a75b-81b2-4b86315a830b"},{"name":"Login","event":[{"listen":"test","script":{"id":"634f8ec3-3ed7-48ab-a0d2-db21b4592cd0","exec":["var token = pm.response.json().token","pm.environment.set(\"token\", token)","postman.setEnvironmentVariable(\"token\", token);",""],"type":"text/javascript","packages":{},"requests":{}}}],"id":"442fc0b0-5762-40f4-868a-5646bd47fa72","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"email\": \"john.smith@email.com\",\n  \"password\": \"password123\"\n}"},"url":"/workspaces/api/login","description":"<p>This API endpoint authenticates a user and returns a short-lived <strong>session bearer token</strong> for use against <code>/api/*</code> endpoints.</p>\n<blockquote>\n<p>⚠️ <strong>For programmatic API integrations, this is the not-recommended path.</strong> The <strong>recommended</strong> way to authenticate a service is to obtain a long-lived api token via the report-book UI — see <strong>section 4.1</strong> of the collection overview. The flow described below has significant downsides — see <strong>section 4.2</strong>.</p>\n</blockquote>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><p><code>email</code> (string, required): The email address of the user.</p>\n</li>\n<li><p><code>password</code> (string, required): The password of the user.</p>\n</li>\n</ul>\n<h3 id=\"response\">Response</h3>\n<p>Upon successful login, the server returns:</p>\n<ul>\n<li><p><code>token</code> — the bearer token to send as <code>Authorization: Bearer &lt;token&gt;</code> on subsequent requests.</p>\n</li>\n<li><p><code>requiresTwoFactorAuthentication</code> — if <code>true</code>, the token is unverified and must be verified by calling <code>Verify Token (2FA)</code> with the code emailed to the user before it can be used on most endpoints.</p>\n</li>\n</ul>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"token\": \"opaque-bearer-token-value\",\n  \"type\": \"client\",\n  \"requiresTwoFactorAuthentication\": true\n}\n\n</code></pre>\n<h3 id=\"token-characteristics\">Token characteristics</h3>\n<ul>\n<li><p><strong>Short-lived:</strong> the session token expires after 7 days. After expiration, requests return <code>401 Unauthorized</code>. Generate a new one by logging in again.</p>\n</li>\n<li><p><strong>Session-bound:</strong> logging in via <code>POST /login</code> invalidates the user's previous active session token, including the one their active app session may be using. <strong>Performing this login while a person is signed in to the report-book UI under the same account will sign them out.</strong></p>\n</li>\n<li><p><strong>One active session token per user at a time.</strong> You cannot run parallel integrations under the same user account this way without them stomping on each other.</p>\n</li>\n</ul>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Returned when the supplied credentials are invalid, or when the account is not allowed to authenticate (e.g. it has been disabled).</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Invalid credentials!\"\n}\n\n</code></pre>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>Returned when the request body fails validation (e.g. <code>email</code> or <code>password</code> missing).</p>\n","urlObject":{"path":["workspaces","api","login"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"9a0ba204-ceaf-4f0c-af96-a8149f85e841","name":"Login","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"email\": \"john.smith@email.com\",\n  \"password\": \"password123\"\n}"},"url":"/api/login"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"131"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 06:31:00 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"{\n    \"type\": \"bearer\",\n    \"token\": \"someRandomCharactersComprisingAUniqueToken\",\n    \"requiresTwoFactorAuthentication\": false\n}"}],"_postman_id":"442fc0b0-5762-40f4-868a-5646bd47fa72"},{"name":"Verify Token (2FA)","event":[{"listen":"test","script":{"type":"text/javascript","exec":["// 2FA verification returns an empty body on success.","// The token obtained from Login becomes verified server-side; no extraction needed here."]}}],"id":"6d831525-0605-4e2e-bac2-efda4ae14bfc","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"code\": \"12345678\"\n}","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/verify-token","description":"<h3 id=\"request\">Request</h3>\n<p>The endpoint is used for token verification, given that you've got 2-Factor Authentication enabled. Upon logging in, a code will be sent to the email address associated with the account in question. This exact code should be included in the request body.</p>\n<h4 id=\"request-body\">Request Body</h4>\n<ul>\n<li><code>code</code> (string, required): The code to be verified.</li>\n</ul>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>The session token was successfully verified. Subsequent requests using it pass the <code>requireTwoFactor</code> middleware. Returns an empty body.</p>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>The supplied code does not match the one issued for the current session, or the code has expired.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Invalid verification code\"\n}\n\n</code></pre>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>Returned when <code>code</code> is missing from the request body.</p>\n","urlObject":{"path":["workspaces","api","verify-token"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"30daa000-ad0c-46d6-ba6c-fdbb4e646c64","name":"Verify Token (2FA)","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"code\": \"12345678\"\n}","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/verify-token"},"status":"OK","code":200,"_postman_previewlanguage":"plain","header":[{"key":"Date","value":"Wed, 21 Aug 2024 06:39:36 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"},{"key":"Transfer-Encoding","value":"chunked"}],"cookie":[],"responseTime":null,"body":null}],"_postman_id":"6d831525-0605-4e2e-bac2-efda4ae14bfc"}],"id":"59a78ccd-bc5b-484d-927b-3bd86b1b2494","_postman_id":"59a78ccd-bc5b-484d-927b-3bd86b1b2494","description":""},{"name":"AFFILIATE","item":[{"name":"Affiliates","event":[{"listen":"test","script":{"id":"b2374704-6068-44c4-abb4-dbfda194aebc","exec":[""],"type":"text/javascript","packages":{},"requests":{}}}],"id":"5f230c83-44b3-4930-ac7c-7ee827bc0c70","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"GET","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":""},"url":"/workspaces/api/affiliates","description":"<p>This endpoint returns a paginated list of affiliates. By default the response includes all affiliates the requester has access to, ordered newest first. The list can be narrowed via optional query parameters.</p>\n<h4 id=\"request\">Request</h4>\n<p>All query parameters are optional:</p>\n<ul>\n<li><p><code>type</code> (string) — Filter by affiliate status. One of: <code>approved</code>, <code>rejected</code>, <code>waiting</code>, <code>suspended</code>. Returns 422 Unprocessable Entity if any other value is supplied.</p>\n</li>\n<li><p><code>search</code> (string) — Substring match against affiliate name, username, email, website, or numeric id.</p>\n</li>\n<li><p><code>page</code> (integer) — Page number (&gt;= 1). Default: 1.</p>\n</li>\n<li><p><code>perPage</code> (integer) — Page size. Default: 15.</p>\n</li>\n</ul>\n<p>Filters compose: <code>?type=approved&amp;search=nik</code> returns approved affiliates whose name/username/email/website contains \"nik\".</p>\n<h4 id=\"response\">Response</h4>\n<h5 id=\"200-ok\">200 OK</h5>\n<p>Returns a JSON object with two top-level keys:</p>\n<ul>\n<li><p><code>meta</code> — pagination information (total records, perPage, currentPage, lastPage, firstPage, etc.).</p>\n</li>\n<li><p><code>data</code> — array of affiliate objects.</p>\n</li>\n</ul>\n<h5 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h5>\n<p>Returned when <code>type</code> is not one of the allowed values listed above.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"errors\": [\n    { \"field\": \"type\", \"message\": \"The selected type is invalid\", \"rule\": \"enum\" }\n  ]\n}\n\n</code></pre>\n","urlObject":{"path":["workspaces","api","affiliates"],"host":[""],"query":[{"disabled":true,"description":{"content":"<p>Filter by affiliate status. One of: approved, rejected, waiting, suspended.</p>\n","type":"text/plain"},"key":"type","value":"approved"},{"disabled":true,"description":{"content":"<p>Substring match against affiliate name, username, email, website, or numeric id.</p>\n","type":"text/plain"},"key":"search","value":"nik"},{"disabled":true,"description":{"content":"<p>Page number (&gt;= 1). Default: 1.</p>\n","type":"text/plain"},"key":"page","value":"1"},{"disabled":true,"description":{"content":"<p>Page size. Default: 15.</p>\n","type":"text/plain"},"key":"perPage","value":"15"}],"variable":[]}},"response":[{"id":"cf658dc1-9346-4ff3-ae46-aaf0bdd2e037","name":"Affiliates","originalRequest":{"method":"GET","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":""},"url":{"raw":"/workspaces/api/affiliates?type=approved","path":["workspaces","api","affiliates"],"query":[{"key":"type","value":"approved"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Date","value":"Tue, 24 Sep 2024 11:30:16 GMT"},{"key":"Content-Type","value":"application/json; charset=utf-8"},{"key":"Transfer-Encoding","value":"chunked"},{"key":"Connection","value":"keep-alive"},{"key":"x-ratelimit-limit","value":"600"},{"key":"x-ratelimit-remaining","value":"599"},{"key":"x-do-app-origin","value":"c3e1d531-2936-4810-8479-54fa5cf2030c"},{"key":"Cache-Control","value":"private"},{"key":"x-do-orig-status","value":"200"},{"key":"Last-Modified","value":"Tue, 24 Sep 2024 11:30:16 GMT"},{"key":"CF-Cache-Status","value":"MISS"},{"key":"Set-Cookie","value":"__cf_bm=hKUEi3TwQDuBtpX7FmCB7egkljvDXNkRTl5hek.4gqs-1727177416-1.0.1.1-hM9aZV4NqAzb2c2h0y.A.Kh.8RsHSVo97pk.J4vIAbyy9xyY8OUyz0JG7Ym8RWaM5HJD0Y7EatNOe9o_xR4UOg; path=/; expires=Tue, 24-Sep-24 12:00:16 GMT; domain=.develop.report-book.com; HttpOnly; Secure; SameSite=None"},{"key":"Vary","value":"Accept-Encoding"},{"key":"Server","value":"cloudflare"},{"key":"CF-RAY","value":"8c8271060a34d0ea-SOF"},{"key":"Content-Encoding","value":"br"}],"cookie":[],"responseTime":null,"body":"{\n    \"meta\": {\n        \"total\": 15784,\n        \"perPage\": 15,\n        \"currentPage\": 1,\n        \"lastPage\": 1053,\n        \"firstPage\": 1,\n        \"firstPageUrl\": \"/?page=1\",\n        \"lastPageUrl\": \"/?page=1053\",\n        \"nextPageUrl\": \"/?page=2\",\n        \"previousPageUrl\": null\n    },\n    \"data\": [\n        {\n            \"id\": 1,\n            \"email\": \"niki@email.com\",\n            \"username\": \"niki\",\n            \"name\": \"Nicholas Smith\",\n            \"statusReason\": \"\",\n            \"masterAffiliateId\": null,\n            \"approved\": \"2023-05-19T08:07:38.840+00:00\",\n            \"rejected\": null,\n            \"suspended\": null,\n            \"createdAt\": \"2023-04-12T06:32:36.455+00:00\",\n            \"updatedAt\": \"2023-11-28T02:20:00.041+00:00\",\n            \"twoFactorAuthMethod\": \"email\",\n            \"labelId\": 3,\n            \"label\": {\n                \"id\": 3,\n                \"name\": \"dormant\"\n            },\n            \"affiliateProfile\": {\n                \"id\": 5,\n                \"website\": \"https://front-butter.com\",\n                \"paymentType\": \"Other\",\n                \"expectedVolumes\": \"1-10 FTD's\",\n                \"businessModel\": \"Hybrid\",\n                \"agreedToTerms\": null,\n                \"receivePromotionalMaterials\": \"2023-04-12T06:32:36.608+00:00\",\n                \"receiveUpdates\": \"2023-04-12T06:32:36.608+00:00\",\n                \"affiliateId\": 11,\n                \"firstTimeActive\": null,\n                \"registeredAt\": \"2021-10-14T23:00:07.706+00:00\",\n                \"createdAt\": \"2023-04-12T06:32:36.609+00:00\",\n                \"updatedAt\": \"2023-04-12T06:32:36.609+00:00\",\n                \"address\": null,\n                \"city\": null,\n                \"zipCode\": null,\n                \"taxNumber\": null,\n                \"countryId\": null,\n                \"companyName\": null,\n                \"mainProduct\": null,\n                \"trafficSourceId\": 7\n            }\n        },\n        ...\n    ]\n}"}],"_postman_id":"5f230c83-44b3-4930-ac7c-7ee827bc0c70"},{"name":"Affiliate","event":[{"listen":"test","script":{"id":"72ad6de9-167f-479e-9250-a537b6682b28","exec":[""],"type":"text/javascript","packages":{},"requests":{}}}],"id":"98e42015-2d39-466b-9af4-5590eed37ac3","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"GET","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":""},"url":"/workspaces/api/affiliates/1","description":"<p>This endpoint retrieves the details of a specific affiliate based on the provided <code>id</code> in the URL path. A request to <code>/workspaces/api/affiliates/1</code> would return information about the affiliate with id <code>1</code>.</p>\n<h4 id=\"request\">Request</h4>\n<p>No request body is required for this endpoint.</p>\n<h4 id=\"response\">Response</h4>\n<p>The response will be a JSON object containing information about the affiliate in question, their role, permissions and manager.</p>\n<p>If the provided <code>id</code> does not correspond to an affiliate user, the response will be the following:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">  {\n    \"error\": \"User is not an affiliate\"\n  }\n\n</code></pre>\n","urlObject":{"path":["workspaces","api","affiliates","1"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"50090870-0f7d-4e60-a7cc-d96ee528f66a","name":"Affiliate","originalRequest":{"method":"GET","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":""},"url":"/workspaces/api/affiliates/1"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Date","value":"Tue, 24 Sep 2024 10:20:17 GMT"},{"key":"Content-Type","value":"application/json; charset=utf-8"},{"key":"Transfer-Encoding","value":"chunked"},{"key":"Connection","value":"keep-alive"},{"key":"x-ratelimit-limit","value":"600"},{"key":"x-ratelimit-remaining","value":"597"},{"key":"x-do-app-origin","value":"c3e1d531-2936-4810-8479-54fa5cf2030c"},{"key":"Cache-Control","value":"private"},{"key":"x-do-orig-status","value":"200"},{"key":"CF-Cache-Status","value":"MISS"},{"key":"Last-Modified","value":"Tue, 24 Sep 2024 10:20:17 GMT"},{"key":"Vary","value":"Accept-Encoding"},{"key":"Server","value":"cloudflare"},{"key":"CF-RAY","value":"8c820a7dea5a20ca-IAD"},{"key":"Content-Encoding","value":"br"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": 1,\n    \"email\": \"niki.smith@email.com\",\n    \"username\": \"niki\",\n    \"name\": \"Nicholas Smith\",\n    \"statusReason\": \"\",\n    \"masterAffiliateId\": null,\n    \"approved\": \"2023-05-19T08:07:38.840+00:00\",\n    \"rejected\": null,\n    \"suspended\": null,\n    \"createdAt\": \"2023-04-12T06:32:36.455+00:00\",\n    \"updatedAt\": \"2023-11-28T02:20:00.041+00:00\",\n    \"twoFactorAuthMethod\": \"email\",\n    \"labelId\": 3,\n    \"roles\": [\n        {\n            \"id\": 3,\n            \"slug\": \"affiliate\",\n            \"name\": \"Affiliate\",\n            \"description\": \"Affiliate Role\",\n            \"createdAt\": \"2023-04-12T06:32:27.011+00:00\",\n            \"updatedAt\": \"2023-04-12T06:32:27.011+00:00\"\n        }\n    ],\n    \"permissions\": [\n        {\n            \"id\": 11,\n            \"slug\": \"view-own-reports\",\n            \"name\": \"View Own Reports\",\n            \"description\": \"View own reports permission\",\n            \"parentPermissionId\": null,\n            \"createdAt\": \"2023-04-12T06:30:54.754+00:00\",\n            \"updatedAt\": \"2023-04-12T06:30:54.754+00:00\"\n        },\n        {\n            \"id\": 12,\n            \"slug\": \"view-own-tracking-links\",\n            \"name\": \"Affiliate View Tracking Links\",\n            \"description\": \"Affiliate View Tracking Links\",\n            \"parentPermissionId\": null,\n            \"createdAt\": \"2023-04-12T06:30:55.264+00:00\",\n            \"updatedAt\": \"2023-04-12T06:30:55.265+00:00\"\n        },\n        {\n            \"id\": 13,\n            \"slug\": \"view-own-dashboard\",\n            \"name\": \"View Own Dashboard\",\n            \"description\": \"View Own Dashboard relates to Affiliate View\",\n            \"parentPermissionId\": null,\n            \"createdAt\": \"2023-04-12T06:30:55.786+00:00\",\n            \"updatedAt\": \"2023-04-12T06:30:55.786+00:00\"\n        }\n    ],\n    \"affiliateResponsibleUsers\": [\n        {\n            \"id\": 1,\n            \"email\": \"virk@adonisjs.com\",\n            \"username\": \"virk@adonis\",\n            \"name\": \"Gwendolyn Padberg\",\n            \"statusReason\": \"\",\n            \"masterAffiliateId\": null,\n            \"approved\": \"2023-04-12T06:31:53.007+00:00\",\n            \"rejected\": null,\n            \"suspended\": null,\n            \"createdAt\": \"2023-04-12T06:31:53.188+00:00\",\n            \"updatedAt\": \"2023-04-12T06:31:53.188+00:00\",\n            \"twoFactorAuthMethod\": null,\n            \"labelId\": null\n        }\n    ],\n    \"affiliateProfile\": {\n        \"id\": 5,\n        \"website\": \"https://front-butter.com\",\n        \"paymentType\": \"Other\",\n        \"expectedVolumes\": \"1-10 FTD's\",\n        \"businessModel\": \"Hybrid\",\n        \"agreedToTerms\": null,\n        \"receivePromotionalMaterials\": \"2023-04-12T06:32:36.608+00:00\",\n        \"receiveUpdates\": \"2023-04-12T06:32:36.608+00:00\",\n        \"affiliateId\": 11,\n        \"firstTimeActive\": null,\n        \"registeredAt\": \"2021-10-14T23:00:07.706+00:00\",\n        \"createdAt\": \"2023-04-12T06:32:36.609+00:00\",\n        \"updatedAt\": \"2023-04-12T06:32:36.609+00:00\",\n        \"address\": null,\n        \"city\": null,\n        \"zipCode\": null,\n        \"taxNumber\": null,\n        \"countryId\": null,\n        \"companyName\": null,\n        \"mainProduct\": null,\n        \"trafficSourceId\": 7,\n        \"country\": null\n    },\n    \"userProfile\": null,\n    \"firstManager\": {\n        \"id\": 1,\n        \"email\": \"virk@adonisjs.com\",\n        \"username\": \"virk@adonis\",\n        \"name\": \"Gwendolyn Padberg\",\n        \"statusReason\": \"\",\n        \"masterAffiliateId\": null,\n        \"approved\": \"2023-04-12T06:31:53.007+00:00\",\n        \"rejected\": null,\n        \"suspended\": null,\n        \"createdAt\": \"2023-04-12T06:31:53.188+00:00\",\n        \"updatedAt\": \"2023-04-12T06:31:53.188+00:00\",\n        \"twoFactorAuthMethod\": null,\n        \"labelId\": null\n    }\n}"}],"_postman_id":"98e42015-2d39-466b-9af4-5590eed37ac3"}],"id":"bf6018e1-9352-4236-9990-05a4b22c0414","_postman_id":"bf6018e1-9352-4236-9990-05a4b22c0414","description":""},{"name":"CUSTOMER","item":[{"name":"Customer","id":"535b2c7d-902f-4d04-a465-962fcff27ec4","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"GET","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/customers/1","description":"<p>This endpoint retrieves the details of a specific customer based on the provided <code>id</code> in the URL path. A request to <code>/workspaces/api/customers/1</code> would return information about the customer with id <code>1</code>.</p>\n<h4 id=\"request\">Request</h4>\n<p>No request body is required for this endpoint.</p>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>The response will be a JSON object containing information about the customer and their activities.</p>\n","urlObject":{"path":["workspaces","api","customers","1"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"5ffa736d-8498-4cc4-9b26-99749d236234","name":"Customer","originalRequest":{"method":"GET","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/customers/1"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"3115"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 06:49:36 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"{\n    \"id\": 1,\n    \"externalCustomerId\": \"4e93dc11-da3f-4f14-8591-f200cca48a0e\",\n    \"token\": null,\n    \"externalCustomerStatus\": null,\n    \"qualifiedCpaAt\": null,\n    \"countryId\": 69,\n    \"affiliateId\": 11,\n    \"brandId\": 4,\n    \"dealId\": 50,\n    \"registrationTrackingLinkClickId\": null,\n    \"firstTimeActive\": \"2024-08-19T00:00:00.000+03:00\",\n    \"registeredAt\": \"2024-08-20T00:00:00.000+03:00\",\n    \"createdAt\": \"2024-08-20T09:15:00.376+03:00\",\n    \"updatedAt\": \"2024-08-20T09:15:04.565+03:00\",\n    \"activities\": [\n        {\n            \"id\": 1,\n            \"ftd\": true,\n            \"ftdAmount\": 500,\n            \"depositsAmount\": 500,\n            \"withdrawalsAmount\": 10,\n            \"betsAmount\": 500,\n            \"winsAmount\": 100,\n            \"bonusCost\": 100,\n            \"ggr\": 400,\n            \"adminFee\": 100,\n            \"ngr\": 296,\n            \"totalFees\": 4,\n            \"depositsCount\": 1,\n            \"createdAt\": \"2024-08-20T09:15:00.455+03:00\",\n            \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n            \"dealActionId\": 6,\n            \"meta\": {\n                \"through_customer_id\": 1\n            }\n        }\n    ],\n    \"meta\": {}\n}"}],"_postman_id":"535b2c7d-902f-4d04-a465-962fcff27ec4"},{"name":"Customers","id":"e770710d-8ce0-472f-a93d-839479b1e616","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"GET","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/customers","description":"<p>The endpoint retrieves a list of customers with pagination support. It accepts optional parameters for page number and items per page (refer to the <strong>Query Params</strong> section). If omitted, default pagination parameters will be used.</p>\n<h3 id=\"request\">Request</h3>\n<p>No request body is required for this endpoint.</p>\n<h3 id=\"200-ok\">200 OK</h3>\n<p>A successful response returns a JSON object with two main keys:</p>\n<ul>\n<li><p><code>meta</code>: This key contains pagination information, such as the total number of records, the number of records per page, the current page, etc.</p>\n</li>\n<li><p><code>data</code>: This key holds an array of objects containing data about customers, along with their respective activities.</p>\n</li>\n</ul>\n","urlObject":{"path":["workspaces","api","customers"],"host":[""],"query":[{"disabled":true,"description":{"content":"<p>Page number (&gt;= 1). Default: 1</p>\n","type":"text/plain"},"key":"page","value":"1"},{"disabled":true,"description":{"content":"<p>Results per page. Default: 15</p>\n","type":"text/plain"},"key":"perPage","value":"15"}],"variable":[]}},"response":[{"id":"74831515-9d2c-4680-993b-1cafd5d45b1b","name":"Customers","originalRequest":{"method":"GET","header":[{"key":"page","value":"2","type":"text","disabled":true},{"key":"perPage","value":"15","type":"text","disabled":true}],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":{"raw":"/workspaces/api/customers?page=1&perPage=2","path":["workspaces","api","customers"],"query":[{"key":"page","value":"1"},{"key":"perPage","value":"2"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"4201"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 06:51:23 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"{\n    \"meta\": {\n        \"total\": 25,\n        \"perPage\": 2,\n        \"currentPage\": 1,\n        \"lastPage\": 13,\n        \"firstPage\": 1,\n        \"firstPageUrl\": \"/?page=1\",\n        \"lastPageUrl\": \"/?page=13\",\n        \"nextPageUrl\": \"/?page=2\",\n        \"previousPageUrl\": null\n    },\n    \"data\": [\n        {\n            \"id\": 17,\n            \"externalCustomerId\": \"56789\",\n            \"token\": \"token12345\",\n            \"externalCustomerStatus\": \"Duplicate Account\",\n            \"qualifiedCpaAt\": null,\n            \"countryId\": 136,\n            \"affiliateId\": 16,\n            \"brandId\": 2,\n            \"dealId\": 50,\n            \"registrationTrackingLinkClickId\": null,\n            \"firstTimeActive\": \"2024-08-20T00:00:00.000+03:00\",\n            \"registeredAt\": \"2024-08-20T00:00:00.000+03:00\",\n            \"createdAt\": \"2024-08-20T09:15:02.447+03:00\",\n            \"updatedAt\": \"2024-08-20T14:43:41.544+03:00\",\n            \"activities\": [\n                {\n                    \"id\": 71,\n                    \"ftd\": true,\n                    \"ftdAmount\": 50,\n                    \"depositsAmount\": 30290,\n                    \"withdrawalsAmount\": 10520,\n                    \"betsAmount\": 70016,\n                    \"winsAmount\": 12004,\n                    \"bonusCost\": 37,\n                    \"ggr\": 58012,\n                    \"adminFee\": 14503,\n                    \"ngr\": 57394.88,\n                    \"totalFees\": 580.12,\n                    \"depositsCount\": 3,\n                    \"createdAt\": \"2024-08-20T09:15:02.501+03:00\",\n                    \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n                    \"dealActionId\": 86,\n                    \"meta\": {\n                        \"through_customer_id\": 17\n                    }\n                },\n                {\n                    \"id\": 74,\n                    \"ftd\": false,\n                    \"ftdAmount\": 0,\n                    \"depositsAmount\": 50,\n                    \"withdrawalsAmount\": 3500,\n                    \"betsAmount\": 70000,\n                    \"winsAmount\": 12000,\n                    \"bonusCost\": 150,\n                    \"ggr\": 58000,\n                    \"adminFee\": 14500,\n                    \"ngr\": 57270,\n                    \"totalFees\": 580,\n                    \"depositsCount\": 1,\n                    \"createdAt\": \"2024-08-20T09:15:02.560+03:00\",\n                    \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n                    \"dealActionId\": 91,\n                    \"meta\": {\n                        \"through_customer_id\": 17\n                    }\n                },\n                {\n                    \"id\": 75,\n                    \"ftd\": false,\n                    \"ftdAmount\": 0,\n                    \"depositsAmount\": 15120,\n                    \"withdrawalsAmount\": 3510,\n                    \"betsAmount\": 800,\n                    \"winsAmount\": 20,\n                    \"bonusCost\": 50,\n                    \"ggr\": 780,\n                    \"adminFee\": 195,\n                    \"ngr\": 722.2,\n                    \"totalFees\": 7.8,\n                    \"depositsCount\": 1,\n                    \"createdAt\": \"2024-08-20T09:15:02.580+03:00\",\n                    \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n                    \"dealActionId\": 92,\n                    \"meta\": {\n                        \"through_customer_id\": 17\n                    }\n                },\n                {\n                    \"id\": 76,\n                    \"ftd\": false,\n                    \"ftdAmount\": 0,\n                    \"depositsAmount\": 15920,\n                    \"withdrawalsAmount\": 3520,\n                    \"betsAmount\": 200,\n                    \"winsAmount\": 0,\n                    \"bonusCost\": 150,\n                    \"ggr\": 200,\n                    \"adminFee\": 50,\n                    \"ngr\": 48,\n                    \"totalFees\": 2,\n                    \"depositsCount\": 1,\n                    \"createdAt\": \"2024-08-20T09:15:02.599+03:00\",\n                    \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n                    \"dealActionId\": 93,\n                    \"meta\": {\n                        \"through_customer_id\": 17\n                    }\n                }\n            ],\n            \"meta\": {}\n        },\n        {\n            \"id\": 16,\n            \"externalCustomerId\": \"5678910\",\n            \"token\": \"token123456\",\n            \"externalCustomerStatus\": \"Confirmed Fraud\",\n            \"qualifiedCpaAt\": null,\n            \"countryId\": 23,\n            \"affiliateId\": 16,\n            \"brandId\": 2,\n            \"dealId\": 50,\n            \"registrationTrackingLinkClickId\": null,\n            \"firstTimeActive\": \"2024-08-20T00:00:00.000+03:00\",\n            \"registeredAt\": \"2024-08-20T00:00:00.000+03:00\",\n            \"createdAt\": \"2024-08-20T09:15:02.447+03:00\",\n            \"updatedAt\": \"2024-08-20T14:43:41.544+03:00\",\n            \"activities\": [\n                {\n                    \"id\": 87,\n                    \"ftd\": false,\n                    \"ftdAmount\": 0,\n                    \"depositsAmount\": 705,\n                    \"withdrawalsAmount\": 0,\n                    \"betsAmount\": 8916.27,\n                    \"winsAmount\": 8078.1,\n                    \"bonusCost\": 134.52,\n                    \"ggr\": 838.17,\n                    \"adminFee\": 209.54,\n                    \"ngr\": 703.65,\n                    \"totalFees\": 0,\n                    \"depositsCount\": 2,\n                    \"createdAt\": \"2024-08-20T13:22:24.734+03:00\",\n                    \"updatedAt\": \"2024-08-20T13:55:24.817+03:00\",\n                    \"dealActionId\": 123,\n                    \"meta\": {\n                        \"through_customer_id\": 16\n                    }\n                },\n                {\n                    \"id\": 77,\n                    \"ftd\": true,\n                    \"ftdAmount\": 50,\n                    \"depositsAmount\": 30290,\n                    \"withdrawalsAmount\": 10520,\n                    \"betsAmount\": 70016,\n                    \"winsAmount\": 12004,\n                    \"bonusCost\": 37,\n                    \"ggr\": 58012,\n                    \"adminFee\": 14503,\n                    \"ngr\": 55615.86,\n                    \"totalFees\": 2359.14,\n                    \"depositsCount\": 3,\n                    \"createdAt\": \"2024-08-20T09:15:02.619+03:00\",\n                    \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n                    \"dealActionId\": 87,\n                    \"meta\": {\n                        \"through_customer_id\": 16\n                    }\n                },\n                {\n                    \"id\": 80,\n                    \"ftd\": false,\n                    \"ftdAmount\": 0,\n                    \"depositsAmount\": 50,\n                    \"withdrawalsAmount\": 3500,\n                    \"betsAmount\": 70000,\n                    \"winsAmount\": 12000,\n                    \"bonusCost\": 150,\n                    \"ggr\": 58000,\n                    \"adminFee\": 14500,\n                    \"ngr\": 56792.5,\n                    \"totalFees\": 1057.5,\n                    \"depositsCount\": 1,\n                    \"createdAt\": \"2024-08-20T09:15:02.672+03:00\",\n                    \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n                    \"dealActionId\": 97,\n                    \"meta\": {\n                        \"through_customer_id\": 16\n                    }\n                },\n                {\n                    \"id\": 81,\n                    \"ftd\": false,\n                    \"ftdAmount\": 0,\n                    \"depositsAmount\": 15120,\n                    \"withdrawalsAmount\": 3510,\n                    \"betsAmount\": 800,\n                    \"winsAmount\": 20,\n                    \"bonusCost\": 50,\n                    \"ggr\": 780,\n                    \"adminFee\": 195,\n                    \"ngr\": 63.7,\n                    \"totalFees\": 666.3,\n                    \"depositsCount\": 1,\n                    \"createdAt\": \"2024-08-20T09:15:02.689+03:00\",\n                    \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n                    \"dealActionId\": 98,\n                    \"meta\": {\n                        \"through_customer_id\": 16\n                    }\n                },\n                {\n                    \"id\": 82,\n                    \"ftd\": false,\n                    \"ftdAmount\": 0,\n                    \"depositsAmount\": 15920,\n                    \"withdrawalsAmount\": 3520,\n                    \"betsAmount\": 200,\n                    \"winsAmount\": 0,\n                    \"bonusCost\": 150,\n                    \"ggr\": 200,\n                    \"adminFee\": 50,\n                    \"ngr\": -644.4,\n                    \"totalFees\": 694.4,\n                    \"depositsCount\": 1,\n                    \"createdAt\": \"2024-08-20T09:15:02.705+03:00\",\n                    \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n                    \"dealActionId\": 99,\n                    \"meta\": {\n                        \"through_customer_id\": 16\n                    }\n                }\n            ],\n            \"meta\": {}\n        }\n    ]\n}"}],"_postman_id":"e770710d-8ce0-472f-a93d-839479b1e616"},{"name":"Import Customers","event":[{"listen":"test","script":{"id":"e733c894-bff4-47b6-8803-448d10d7e73a","exec":[""],"type":"text/javascript","packages":{},"requests":{}}}],"id":"515b2251-88b6-4007-bd56-68044b9ccc54","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"value":"","key":"file","type":"file"},{"value":"<case_sensitive_platform_name>","uuid":"deef6df0-b7ba-4f50-9174-af860a24788e","description":"<p>(Optional) A specific Platform configuration to use for the imported data.</p>\n","key":"platform","type":"text","disabled":true}]},"url":"/workspaces/api/customers/import","description":"<p>This endpoint allows you to import customer data via <code>file</code> or <code>json body</code>.</p>\n<ul>\n<li><p><code>file</code> import</p>\n<ul>\n<li><p>The request body should be of type <code>form-data</code>, with a key \"file\" of type <code>File</code>.</p>\n</li>\n<li><p>The format of the file could be either <code>csv</code>, <code>xlsx</code> or <code>json</code></p>\n</li>\n<li><p>It is recommended that the <code>Content-Type</code> header is set to <code>multipart/form-data</code></p>\n</li>\n</ul>\n</li>\n<li><p><code>json body</code> import</p>\n<ul>\n<li><p>The request body should be a json object in the file information format described below</p>\n</li>\n<li><p>It is recommended that the <code>Content-Type</code> header is set to <code>application/json</code></p>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"file-information\">File information</h3>\n<p>Regardless of the format, the headers included in the file should be the following:</p>\n<ul>\n<li><p><code>Customer</code> - this field should contain the <code>id</code> of the customer.</p>\n</li>\n<li><p><code>Token (optional)</code> - this field should contain the <code>token</code> of the customer, if they have such.</p>\n</li>\n<li><p><code>Customer Join Date</code> - date on which the customer joined the platform in format - <code>DD/MM/YYYY</code>.</p>\n</li>\n<li><p><code>Customer Country</code> - this field should contain the country from which the customer registered. It can either be the full country name or the country code based on <strong>ISO 3166-1 alpha-2/3</strong></p>\n</li>\n<li><p><code>Brand</code> - this field should contain the brand under which the customer is.</p>\n</li>\n<li><p><code>Affiliate</code> - the <code>username</code> of the Affiliate under which the customer has been registered.</p>\n</li>\n<li><p><code>CPA Excluded</code> - whether the Customer should be permanently excluded from CPA generation.</p>\n</li>\n<li><p><code>Nickname (optional)</code> - a free-form display name for the customer, such as their name, email, or username. We store it exactly as you send it, with no formatting rules applied. Send the customer again with a different nickname to update it. See <strong>Re-importing a customer</strong> below.</p>\n</li>\n</ul>\n<blockquote>\n<p>These are the default headers. As per our Client Integration documentation, their names can be completely and freely customized by communicating this need with our administration team. </p>\n</blockquote>\n<h3 id=\"re-importing-a-customer\">Re-importing a customer</h3>\n<p>Send a customer we already hold and we update their <code>Nickname</code>. Everything else recorded at their first import stays as it was: affiliate, country, join date and CPA exclusion. A re-import corrects a display name, it never re-attributes a customer.</p>\n<ul>\n<li><p>Send a different, non-empty <code>Nickname</code> to replace the stored one.</p>\n</li>\n<li><p>Leave <code>Nickname</code> empty, or omit the column, to keep the stored one. An empty value never clears a nickname.</p>\n</li>\n<li><p>A file that only refreshed nicknames is a successful import (<code>201</code>), not an empty one (<code>200</code>).</p>\n</li>\n<li><p>Send a customer we already hold with nothing to change and the row is skipped, exactly as before.</p>\n</li>\n<li><p>Name the same customer twice in one file and we take the first occurrence.</p>\n</li>\n</ul>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><code>file</code> (file): The file containing the activities to be imported. It can either be <code>csv</code>, <code>xlsx</code> or <code>json</code></li>\n</ul>\n<p>OR</p>\n<ul>\n<li>JSON body with the imported data, describing the file information as explained below. This is an alternative to directly import JSON, without first writing it to a JSON file.</li>\n</ul>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">// An array of objects with imported data in described format\n[\n  {\n   \"Brand\": \"BrandName\",\n   \"Affiliate\": \"AffiliateUsername\",\n   \"Nickname\": \"playerOne\",\n    // ...rest of the keys mentioned in File Information (or different, if configured otherwise)\n  }\n]\n\n</code></pre>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>Returned when the request succeeded but there was nothing to import. The file was empty, contained data we could not recognize at all, or named only customers we already hold with nothing about them to change.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"There is no new data to import from this file.\"\n}\n\n</code></pre>\n<h4 id=\"201-created\">201 Created</h4>\n<p>Returned when every row was valid and imported, including rows that only refreshed the nickname of a customer we already hold. Further processing happens in the background, so a customer's registration and display in the platform can lag behind this response.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Customer data imported successfully\"\n}\n\n</code></pre>\n<h4 id=\"206-partial-content\">206 Partial Content</h4>\n<p>This response will be returned when some rows have been found to contain invalid data values, but not all. In this case we'll parse and import all valid rows and collect error information for the invalid ones with details as to why the data is considered to be invalid and we are not able to import these rows.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Customer data partially imported\",\n  \"details\": {\n    \"imported\": 18,\n    \"skipped\": 2\n  },\n  \"errors\": [\n    {\n      \"column\": \"Customer\",\n      \"message\": \"Values are invalid or do not exist\",\n      \"rows\": [4, 9],\n      \"values\": [\"1005\", \"1006\"]\n    }\n  ]\n}\n\n</code></pre>\n<h4 id=\"400-bad-request\">400 Bad Request</h4>\n<p>This response will be returned upon receiving an invalid file or file that is missing information, such as a required column with data for the type of import.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n    \"error\": \"Invalid Customer values: 1005\",\n    \"column\": \"Customer\",\n    \"reason\": \"Invalid 'Customer' values 1005.\"\n}\n\n</code></pre>\n<h4 id=\"413-payload-too-large\">413 Payload Too Large</h4>\n<p>Returned when the upload exceeds the maximum size. Import files may be up to 20 MB, measured across the whole request body (including the file). Larger uploads are rejected before any rows are processed.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">  {\n      \"message\": \"request entity too large\"\n  }\n\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>This response will be returned when too many import requests have been sent in a short period of time among all import endpoints, including both customers and activities.</p>\n<p>Current rate allows for 10 requests per 1 minute.</p>\n","urlObject":{"path":["workspaces","api","customers","import"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"1177fa2f-31c4-4a84-82f3-2b67f76202b8","name":"CSV Customers Import","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"multipart/form-data","type":"text"}],"body":{"mode":"formdata","formdata":[{"key":"platform","value":"<case_sensitive_platform_name>","description":"(Optional) A specific Platform configuration to use for the imported data.","type":"text","uuid":"da8e76b3-10cf-41cb-b56d-67190580657b","disabled":true},{"key":"file","type":"file","uuid":"39636a98-828d-43f3-a74c-1f70cc389de7","value":null}]},"url":"/workspaces/api/customers/import"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"51"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 07:06:21 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"{\n    \"message\": \"Customers data imported successfully!\"\n}"},{"id":"0cfcfe47-dd26-41f6-ae5d-5660e01ce75a","name":"JSON Customers Import","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/x-www-form-urlencoded","type":"text"}],"body":{"mode":"raw","raw":" {\n      // \"platform\": \"<optional_case_sensitive_platform_name>\",\n\n      // This is the data that will be imported, and it is always REQUIRED.\n      \"data\": [\n          {\n              \"Customer\": \"12345\",\n              \"Token\": \"62caf7fd9d6a1ffa681e3a6c7dac165f\",\n              \"Customer Join Date\": \"DD/MM/YYYY\",\n              \"Customer Country\": \"Belgium\",\n              \"Brand\": \"brandName\",\n              \"Affiliate\": \"affiliateUsername\"\n          },\n          {\n              \"Customer\": \"123456\",\n              \"Token\": \"z62caf7fd9d6a1ffa681e3a6c7dac165\",\n              \"Customer Join Date\": \"DD/MM/YYYY\",\n              \"Customer Country\": \"ca\",\n              \"Brand\": \"brandName\",\n              \"Affiliate\": \"affiliateUsername\"\n          }\n      ]\n  }","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/customers/import"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"51"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 07:06:21 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"{\n    \"message\": \"Customers data imported successfully!\"\n}"},{"id":"bd125cb7-e4f7-b461-80be-a5ec085ca4d7","name":"413 Payload Too Large (file over 20 MB)","originalRequest":{"auth":{"type":"bearer","bearer":{"token":"{{token}}"}},"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"value":"","key":"file","type":"file"},{"value":"<case_sensitive_platform_name>","uuid":"deef6df0-b7ba-4f50-9174-af860a24788e","description":"(Optional) A specific Platform configuration to use for the imported data.","key":"platform","type":"text","disabled":true}]},"url":"/workspaces/api/customers/import","description":"This endpoint allows you to import customer data via `file` or `json body`.\n\n- `file` import\n    \n    - The request body should be of type `form-data`, with a key \"file\" of type `File`.\n        \n    - The format of the file could be either `csv`, `xlsx` or `json`\n        \n    - It is recommended that the `Content-Type` header is set to `multipart/form-data`\n        \n- `json body` import\n    \n    - The request body should be a json object in the file information format described below\n        \n    - It is recommended that the `Content-Type` header is set to `application/json`\n        \n\n### File information\n\nRegardless of the format, the headers included in the file should be the following:\n\n- `Customer` - this field should contain the `id` of the customer.\n    \n- `Token (optional)` - this field should contain the `token` of the customer, if they have such.\n    \n- `Customer Join Date` - date on which the customer joined the platform in format - `DD/MM/YYYY`.\n    \n- `Customer Country` - this field should contain the country from which the customer registered. It can either be the full country name or the country code based on **ISO 3166-1 alpha-2/3**\n    \n- `Brand` - this field should contain the brand under which the customer is.\n    \n- `Affiliate` - the `username` of the Affiliate under which the customer has been registered.\n    \n- `CPA Excluded` - whether the Customer should be permanently excluded from CPA generation.\n    \n- `Nickname (optional)` - a free-form display name for the customer, such as their name, email, or username. We store it exactly as you send it, with no formatting rules applied. Send the customer again with a different nickname to update it. See **Re-importing a customer** below.\n    \n\n> These are the default headers. As per our Client Integration documentation, their names can be completely and freely customized by communicating this need with our administration team. \n  \n\n### Re-importing a customer\n\nSend a customer we already hold and we update their `Nickname`. Everything else recorded at their first import stays as it was: affiliate, country, join date and CPA exclusion. A re-import corrects a display name, it never re-attributes a customer.\n\n- Send a different, non-empty `Nickname` to replace the stored one.\n    \n- Leave `Nickname` empty, or omit the column, to keep the stored one. An empty value never clears a nickname.\n    \n- A file that only refreshed nicknames is a successful import (`201`), not an empty one (`200`).\n    \n- Send a customer we already hold with nothing to change and the row is skipped, exactly as before.\n    \n- Name the same customer twice in one file and we take the first occurrence.\n    \n\n### Request Body\n\n- `file` (file): The file containing the activities to be imported. It can either be `csv`, `xlsx` or `json`\n    \n\nOR\n\n- JSON body with the imported data, describing the file information as explained below. This is an alternative to directly import JSON, without first writing it to a JSON file.\n    \n\n``` json\n// An array of objects with imported data in described format\n[\n  {\n   \"Brand\": \"BrandName\",\n   \"Affiliate\": \"AffiliateUsername\",\n   \"Nickname\": \"playerOne\",\n    // ...rest of the keys mentioned in File Information (or different, if configured otherwise)\n  }\n]\n\n ```\n\n### Response\n\n#### 200 OK\n\nReturned when the request succeeded but there was nothing to import. The file was empty, contained data we could not recognize at all, or named only customers we already hold with nothing about them to change.\n\n``` json\n{\n  \"message\": \"There is no new data to import from this file.\"\n}\n\n ```\n\n#### 201 Created\n\nReturned when every row was valid and imported, including rows that only refreshed the nickname of a customer we already hold. Further processing happens in the background, so a customer's registration and display in the platform can lag behind this response.\n\n``` json\n{\n  \"message\": \"Customer data imported successfully\"\n}\n\n ```\n\n#### 206 Partial Content\n\nThis response will be returned when some rows have been found to contain invalid data values, but not all. In this case we'll parse and import all valid rows and collect error information for the invalid ones with details as to why the data is considered to be invalid and we are not able to import these rows.\n\n``` json\n{\n  \"message\": \"Customer data partially imported\",\n  \"details\": {\n    \"imported\": 18,\n    \"skipped\": 2\n  },\n  \"errors\": [\n    {\n      \"column\": \"Customer\",\n      \"message\": \"Values are invalid or do not exist\",\n      \"rows\": [4, 9],\n      \"values\": [\"1005\", \"1006\"]\n    }\n  ]\n}\n\n ```\n\n#### 400 Bad Request\n\nThis response will be returned upon receiving an invalid file or file that is missing information, such as a required column with data for the type of import.\n\n``` json\n{\n    \"error\": \"Invalid Customer values: 1005\",\n    \"column\": \"Customer\",\n    \"reason\": \"Invalid 'Customer' values 1005.\"\n}\n\n ```\n\n#### 413 Payload Too Large\n\nReturned when the upload exceeds the maximum size. Import files may be up to 20 MB, measured across the whole request body (including the file). Larger uploads are rejected before any rows are processed.\n\n``` json\n  {\n      \"message\": \"request entity too large\"\n  }\n\n ```\n\n#### 429 Too Many Requests\n\nThis response will be returned when too many import requests have been sent in a short period of time among all import endpoints, including both customers and activities.\n\nCurrent rate allows for 10 requests per 1 minute."},"status":"Payload Too Large","code":413,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"message\": \"request entity too large\"\n}"}],"_postman_id":"515b2251-88b6-4007-bd56-68044b9ccc54"},{"name":"Import Customer Status Data","event":[{"listen":"test","script":{"id":"e733c894-bff4-47b6-8803-448d10d7e73a","exec":[""],"type":"text/javascript","packages":{},"requests":{}}}],"id":"1bfbf670-106f-4b49-9ef7-bd339e20c2d5","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"value":"","key":"file","type":"file"},{"value":"<case_sensitive_platform_name>","uuid":"81cacfec-b851-4478-bb7c-d5f0010be408","description":"<p>(Optional) A specific Platform configuration to use for the imported data.</p>\n","key":"platform","type":"text","disabled":true}]},"url":"/workspaces/api/customers/status-import","description":"<p>This endpoint allows you to import customer status data via <code>file</code> or <code>json body</code>.</p>\n<ul>\n<li><p><code>file</code> import</p>\n<ul>\n<li><p>The request body should be of type <code>form-data</code>, with a key \"file\" of type <code>File</code>.</p>\n</li>\n<li><p>The format of the file could be either <code>csv</code>, <code>xlsx</code> or <code>json</code></p>\n</li>\n<li><p>It is recommended that the <code>Content-Type</code> header is set to <code>multipart/form-data</code></p>\n</li>\n</ul>\n</li>\n<li><p><code>json body</code> import</p>\n<ul>\n<li><p>The request body should be a json object in the file information format described below</p>\n<ul>\n<li>It is recommended that the <code>Content-Type</code> header is set to <code>application/json</code></li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"file-information\">File information</h3>\n<p>Regardless of the format, the headers included in the file should be the following:</p>\n<ul>\n<li><p><code>Customer</code> - this field should contain the <code>id</code> of the customer</p>\n</li>\n<li><p><code>Status</code> - this field should contain the <code>status</code> of the customer.</p>\n</li>\n</ul>\n<blockquote>\n<p>These are the default headers. As per our Client Integration documentation, their names can be completely and freely customized by communicating this need with our administration team. </p>\n</blockquote>\n<blockquote>\n<p>We also support multiple Configurations per single Instance. This means that you can perform different imports with varying headers and other options. Configuration for the ongoing import request is selected by providing a valid <code>platform</code> parameter, associated with an additional configuration, along with the data to import. </p>\n</blockquote>\n<h3 id=\"request-body\">Request Body</h3>\n<ul>\n<li><code>file</code> (file): The file containing the data to be imported. It can either be <code>csv</code>, <code>xlsx</code> or <code>json</code>.<ul>\n<li><code>platform</code> (string, optional): A specific Platform configuration to use for the imported data. Only required if you have more than one brand and want to target a specific Platform configuration.</li>\n</ul>\n</li>\n</ul>\n<p>OR</p>\n<ul>\n<li>JSON body with the imported data, describing the file information as explained below. This is an alternative to directly import JSON, without first writing it to a JSON file.</li>\n</ul>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"platform\": \"BrandName\", // Can be omitted if you only have one brand\n  \"data\": [\n    // Array of objects with imported data in described format\n  ]\n}\n\n</code></pre>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 Ok</h4>\n<p>This response will be returned when the request was successfully, but we found no data to import. Either the file was empty, contained data that we could not recognize at all, or all of its contents are already present on our side.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"There is no new data to import from this file.\"\n}\n\n</code></pre>\n<h4 id=\"201-created\">201 Created</h4>\n<p>This response will be returned when all of the rows were valid and successfully imported. Further processing will happen in the background, and their ultimate registration and display in the platform might lag behind this.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Customer status data imported successfully!\"\n}\n\n</code></pre>\n<h4 id=\"400-bad-request\">400 Bad Request</h4>\n<p>This response will be returned upon receiving an invalid file or file that is missing information, such as a required column with data for the type of import. The example shows an attempt to import data for missing customer with id 1005.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n    \"error\": \"Invalid Customer values: 1005\",\n    \"column\": \"Customer\",\n    \"reason\": \"Invalid 'Customer' values 1005.\"\n}\n\n</code></pre>\n<h4 id=\"413-payload-too-large\">413 Payload Too Large</h4>\n<p>Returned when the upload exceeds the maximum size. Import files may be up to 20 MB, measured across the whole request body (including the file). Larger uploads are rejected before any rows are processed.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">  {\n      \"message\": \"request entity too large\"\n  }\n\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>This response will be returned when too many import requests have been sent in a short period of time among all import endpoints, including both customers and activities.</p>\n<p>Current rate allows for 10 requests per 1 minute.</p>\n","urlObject":{"path":["workspaces","api","customers","status-import"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"d9a3c95b-9254-4b7b-ad49-0f22541bee42","name":"CSV Customer Status Import","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"multipart/form-data","type":"text"}],"body":{"mode":"formdata","formdata":[{"key":"file","type":"file","value":null},{"key":"platform","value":"<optional_case_sensitive_platform_name>","description":"(Optional) A specific Platform configuration to use for the imported data.","type":"text","uuid":"fe12dad4-1555-4af6-aaa6-1cab98ba8344","disabled":true}]},"url":"/workspaces/api/customers/status-import"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"57"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 06:59:17 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"{\n    \"message\": \"Customer status data imported successfully!\"\n}"},{"id":"1803166e-6ce7-48df-9c56-20aba5135147","name":"JSON Customer Status Import","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n    // \"platform\": \"<optional_case_sensitive_platform_name>\",\n\n    // This is the data that will be imported, and it is always REQUIRED.\n    \"data\": [\n        {\n            \"Customer\": \"12345\",\n            \"Status\": \"Duplicate Account\"\n        },\n        {\n            \"Customer\": \"123456\",\n            \"Status\": \"Confirmed Fraud\"\n        }\n    ]\n}","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/customers/status-import"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"57"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 06:59:17 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"{\n    \"message\": \"Customer status data imported successfully!\"\n}"},{"id":"eb1fe5f2-e071-d6c6-14ab-91c2e744f0a4","name":"413 Payload Too Large (file over 20 MB)","originalRequest":{"auth":{"type":"bearer","bearer":{"token":"{{token}}"}},"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"value":"","key":"file","type":"file"},{"value":"<case_sensitive_platform_name>","uuid":"81cacfec-b851-4478-bb7c-d5f0010be408","description":"(Optional) A specific Platform configuration to use for the imported data.","key":"platform","type":"text","disabled":true}]},"url":"/workspaces/api/customers/status-import","description":"This endpoint allows you to import customer status data via `file` or `json body`.\n\n- `file` import\n    \n    - The request body should be of type `form-data`, with a key \"file\" of type `File`.\n        \n    - The format of the file could be either `csv`, `xlsx` or `json`\n        \n    - It is recommended that the `Content-Type` header is set to `multipart/form-data`\n        \n- `json body` import\n    \n    - The request body should be a json object in the file information format described below\n        \n        - It is recommended that the `Content-Type` header is set to `application/json`\n            \n\n### File information\n\nRegardless of the format, the headers included in the file should be the following:\n\n- `Customer` - this field should contain the `id` of the customer\n    \n- `Status` - this field should contain the `status` of the customer.\n    \n\n> These are the default headers. As per our Client Integration documentation, their names can be completely and freely customized by communicating this need with our administration team. \n  \n> We also support multiple Configurations per single Instance. This means that you can perform different imports with varying headers and other options. Configuration for the ongoing import request is selected by providing a valid `platform` parameter, associated with an additional configuration, along with the data to import. \n  \n\n### Request Body\n\n- `file` (file): The file containing the data to be imported. It can either be `csv`, `xlsx` or `json`.\n    - `platform` (string, optional): A specific Platform configuration to use for the imported data. Only required if you have more than one brand and want to target a specific Platform configuration.\n        \n\nOR\n\n- JSON body with the imported data, describing the file information as explained below. This is an alternative to directly import JSON, without first writing it to a JSON file.\n    \n\n``` json\n{\n  \"platform\": \"BrandName\", // Can be omitted if you only have one brand\n  \"data\": [\n    // Array of objects with imported data in described format\n  ]\n}\n\n ```\n\n### Response\n\n#### 200 Ok\n\nThis response will be returned when the request was successfully, but we found no data to import. Either the file was empty, contained data that we could not recognize at all, or all of its contents are already present on our side.\n\n``` json\n{\n  \"message\": \"There is no new data to import from this file.\"\n}\n\n ```\n\n#### 201 Created\n\nThis response will be returned when all of the rows were valid and successfully imported. Further processing will happen in the background, and their ultimate registration and display in the platform might lag behind this.\n\n``` json\n{\n  \"message\": \"Customer status data imported successfully!\"\n}\n\n ```\n\n#### 400 Bad Request\n\nThis response will be returned upon receiving an invalid file or file that is missing information, such as a required column with data for the type of import. The example shows an attempt to import data for missing customer with id 1005.\n\n``` json\n{\n    \"error\": \"Invalid Customer values: 1005\",\n    \"column\": \"Customer\",\n    \"reason\": \"Invalid 'Customer' values 1005.\"\n}\n\n ```\n\n#### 413 Payload Too Large\n\nReturned when the upload exceeds the maximum size. Import files may be up to 20 MB, measured across the whole request body (including the file). Larger uploads are rejected before any rows are processed.\n\n``` json\n  {\n      \"message\": \"request entity too large\"\n  }\n\n ```\n\n#### 429 Too Many Requests\n\nThis response will be returned when too many import requests have been sent in a short period of time among all import endpoints, including both customers and activities.\n\nCurrent rate allows for 10 requests per 1 minute.\n"},"status":"Payload Too Large","code":413,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"message\": \"request entity too large\"\n}"}],"_postman_id":"1bfbf670-106f-4b49-9ef7-bd339e20c2d5"}],"id":"529f3827-33c4-425a-8c37-fc842821f106","_postman_id":"529f3827-33c4-425a-8c37-fc842821f106","description":""},{"name":"ACTIVITY","item":[{"name":"Get Activity by Customer ID","id":"078ca278-afd6-48a4-83c6-137ce1bed93e","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"GET","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/activities/1","description":"<p>This endpoint retrieves activity details for a specific customer based on the <code>id</code> provided in the URL path. A request to <code>/workspaces/api/activities/1</code> would return information about the activities of the customer with id <code>1</code>.</p>\n<h4 id=\"request-body\">Request Body</h4>\n<p>This endpoint does not require a request body.</p>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>The response body returns an array of activity objects. An example response body can be seen below.</p>\n","urlObject":{"path":["workspaces","api","activities","1"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"ee764380-b69b-4d82-8d36-69b6ffafa2d8","name":"Get Activity by Customer ID","originalRequest":{"method":"GET","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/activities/1"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"315"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 06:56:46 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"[\n    {\n        \"id\": 70,\n        \"ftd\": true,\n        \"ftdAmount\": 500,\n        \"depositsAmount\": 500,\n        \"withdrawalsAmount\": 10,\n        \"betsAmount\": 500,\n        \"winsAmount\": 100,\n        \"bonusCost\": 100,\n        \"ggr\": 400,\n        \"adminFee\": 100,\n        \"ngr\": 296,\n        \"totalFees\": 4,\n        \"depositsCount\": 1,\n        \"createdAt\": \"2024-08-20T09:15:01.755+03:00\",\n        \"updatedAt\": \"2024-08-20T09:15:04.549+03:00\",\n        \"dealActionId\": 85,\n        \"meta\": {}\n    }\n]"}],"_postman_id":"078ca278-afd6-48a4-83c6-137ce1bed93e"},{"name":"Import Activity Data","event":[{"listen":"test","script":{"id":"5472aadf-7b50-4bb3-a498-ee1eb2f20e3a","exec":[""],"type":"text/javascript","packages":{},"requests":{}}}],"id":"799ceec7-e632-4e3b-a8e2-e733fbe2b9c1","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"type":"file","key":"file","value":""},{"key":"platform","value":"<case_sensitive_platform_name>","uuid":"ec0112ee-22eb-45a1-aa30-48059202c39a","type":"text","description":"<p>(Optional) A specific Platform configuration to use for the imported data.</p>\n","disabled":true}]},"url":"/workspaces/api/activities/import","description":"<h3 id=\"import-activities\">Import Activities</h3>\n<p>This endpoint allows the user to import activities via <code>file</code> or <code>json body</code>.</p>\n<ul>\n<li><p><code>file</code> import</p>\n<ul>\n<li><p>The request body should be of type <code>form-data</code>, with a key \"file\" of type <code>File</code>.</p>\n</li>\n<li><p>The format of the file could be either <code>csv</code>, <code>xlsx</code> or <code>json</code></p>\n</li>\n<li><p>It is recommended that the <code>Content-Type</code> header is set to <code>multipart/form-data</code></p>\n</li>\n</ul>\n</li>\n<li><p><code>json body</code> import</p>\n<ul>\n<li><p>The request body should be a json object in the file information format described below</p>\n</li>\n<li><p>It is recommended that the <code>Content-Type</code> header is set to <code>application/json</code></p>\n</li>\n</ul>\n</li>\n</ul>\n<h3 id=\"hybrid-integration\"><strong>Hybrid integration</strong></h3>\n<p>You may want to integrate some of the activity data as soon as possible. For example, we might want to reflect the FTD status and amount of the customer as soon as it is available. This endpoint can receive data multiple times. It means that data can be sent partially or incrementally in pieces.</p>\n<p>For example, it can only receive FTD (or any other column) information, as soon as it is available, and then receive all other values later on. In order to do this, simply provide the values that are known at the time, and leave all other required values as 0 (optional values can be skipped, as always). As other values become available they can be sent via this endpoint in a similar manner.</p>\n<blockquote>\n<p>Depending on the import mode configured on integration, subsequent imports will either merge (incremental import) or overwrite (partial import) existing data. </p>\n</blockquote>\n<h4 id=\"request-body\">Request Body</h4>\n<ul>\n<li><code>file</code> (file): The file containing the activities to be imported. It can either be <code>csv</code>, <code>xlsx</code> or <code>json</code></li>\n</ul>\n<p>OR</p>\n<ul>\n<li>JSON body with the imported data, describing the file information as explained below. This is an alternative to directly import JSON, without first writing it to a JSON file.</li>\n</ul>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">// An array of objects with imported data in described format\n[\n  {\n   \"Customer\": \"CustomerId\",\n   \"Affiliate\": \"AffiliateUsername\",\n    // ...rest of the keys configured based on the File Information section below\n  }\n]\n\n</code></pre>\n<h3 id=\"file-information\">File information</h3>\n<p>Regardless of the format (<code>csv</code> or <code>xlsx</code>), the headers included in the file should be the following:</p>\n<ul>\n<li><p><code>Customer</code> - this field should contain the <code>id</code> of the customer. <strong>Required when country and affiliate are missing, optional otherwise.</strong></p>\n</li>\n<li><p><code>Affiliate</code> - the <code>username</code> of the Affiliate under which the customer has been registered. <strong>Required with Country when Customer value is missing, optional otherwise.</strong></p>\n</li>\n<li><p><code>Country</code> - the country of origin for the activity. When customer is present, it must match the country of the customer. <strong>Required with Affiliate when Customer value is missing, optional otherwise.</strong></p>\n</li>\n<li><p><code>Date</code> - date on which the activity occurred. Format - <code>DD/MM/YYYY</code>.</p>\n</li>\n<li><p><code>FTD</code> (number) - Whether or not this is a First Time Deposit. <code>1</code> should be passed for <code>true</code>, and <code>0</code> for <code>false</code>.</p>\n</li>\n<li><p><code>Ftd Amount</code> - Amount of the first time deposit. If the activity is not FTD related, the imported value will be 0.</p>\n</li>\n<li><p><code>Total Deposits</code> - Total amount of deposits during the given date.</p>\n</li>\n<li><p><code>Deposit Count (optional)</code> - The number of deposits made within the current activity batch. Default: <code>1</code>.</p>\n</li>\n<li><p><code>Withdrawals</code> - The withdrawal amount made by the customer during the given date.</p>\n</li>\n<li><p><code>Casino Bets</code> - The customer total bet amount placed during the given date on the Casino Product.</p>\n</li>\n<li><p><code>Casino Wins</code> - The customer total win amount settled during the given date on the Casino Product.</p>\n</li>\n<li><p><code>Bonus Cost</code> - This is the total Bonus Cost which the customer accumulated during the given date.</p>\n</li>\n<li><p><code>Brand</code> - The brand name on which the activity happened</p>\n</li>\n</ul>\n<blockquote>\n<p>These are the default headers. As per our Client Integration documentation, their names can be completely and freely customized by communicating this need with our administration team. </p>\n</blockquote>\n<h4 id=\"response\">Response</h4>\n<h4 id=\"200-ok\">200 Ok</h4>\n<p>This response will be returned when the request was successfully, but we found no data to import. Either the file was empty, contained data that we could not recognize at all, or all of its contents are already present on our side.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"There is no new data to import from this file.\"\n}\n\n</code></pre>\n<h4 id=\"201-created\">201 Created</h4>\n<p>This response will be returned when all of the rows were valid and successfully imported. Further processing will happen in the background, and their ultimate registration and display in the platform might lag behind this.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Activity data imported successfully\"\n}\n\n</code></pre>\n<h4 id=\"206-partial-content\">206 Partial Content</h4>\n<p>This response will be returned when some rows have been found to contain invalid data values, but not all. In this case we'll parse and import all valid rows and collect error information for the invalid ones with details as to why the data is considered to be invalid and we are not able to import these rows.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Activity data partially imported\",\n  \"details\": {\n    \"imported\": 18,\n    \"skipped\": 2\n  },\n  \"errors\": [\n    {\n      \"column\": \"Customer\",\n      \"message\": \"Values are invalid or do not exist\",\n      \"rows\": [4, 9],\n      \"values\": [\"1005\", \"1006\"]\n    }\n  ]\n}\n\n</code></pre>\n<h4 id=\"400-bad-request\">400 Bad Request</h4>\n<p>This response will be returned upon receiving an invalid file or file that is missing information, such as a required column with data for the type of import.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n    \"error\": \"Invalid Customer values: 1005\",\n    \"column\": \"Customer\",\n    \"reason\": \"Invalid 'Customer' values 1005.\"\n}\n\n</code></pre>\n<h4 id=\"413-payload-too-large\">413 Payload Too Large</h4>\n<p>Returned when the upload exceeds the maximum size. Import files may be up to 20 MB, measured across the whole request body (including the file). Larger uploads are rejected before any rows are processed.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">  {\n      \"message\": \"request entity too large\"\n  }\n\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>This response will be returned when too many import requests have been sent in a short period of time among all import endpoints, including both customers and activities.</p>\n<p>Current rate allows for 10 requests per 1 minute.</p>\n","urlObject":{"path":["workspaces","api","activities","import"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"e6fa8737-0dd1-4775-9d81-c9ef97a931c3","name":"CSV Activity Import","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"multipart/form-data","type":"text"}],"body":{"mode":"formdata","formdata":[{"key":"file","type":"file","value":null},{"key":"platform","value":"<case_sensitive_platform_name>","description":"(Optional) A specific Platform configuration to use for the imported data.","type":"text","uuid":"89c8370c-7947-48a9-9c23-9d41ec937622","disabled":true}]},"url":"/workspaces/api/activities/import/"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"49"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 07:09:00 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"{\n    \"message\": \"Activity data imported successfully\"\n}"},{"id":"9bc1665b-2fe7-402b-a1d6-f701d4f7d507","name":"JSON Activity Import","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"  {\n      // \"platform\": \"<optional_case_sensitive_platform_name>\",\n\n      // This is the data that will be imported, and it is always REQUIRED.\n      \"data\": [\n          {\n              \"Customer\": \"12345\",\n              \"Date\": \"DD/MM/YYYY\",\n              \"FTD\": 0,\n              \"Ftd Amount\": 0,\n              \"Total Deposits\": 750,\n              \"Withdrawals\": 300,\n              \"Casino Bets\": 1689,\n              \"Casino Wins\": 1240,\n              \"Bonus Cost\": 0,\n              \"Brand\": \"brand_name\"\n          },\n          {\n              \"Customer\": \"123456\",\n              \"Date\": \"DD/MM/YYYY\",\n              \"FTD\": 1,\n              \"Ftd Amount\": 25,\n              \"Total Deposits\": 235,\n              \"Withdrawals\": 0,\n              \"Casino Bets\": 2972,\n              \"Casino Wins\": 2692,\n              \"Bonus Cost\": 44,\n              \"Brand\": \"brand_name\"\n          }\n      ]\n  }","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/activities/import/"},"status":"Created","code":201,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"49"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Wed, 21 Aug 2024 07:09:00 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"{\n    \"message\": \"Activity data imported successfully\"\n}"},{"id":"7d598533-e925-023f-ea3e-63a0fbca236b","name":"413 Payload Too Large (file over 20 MB)","originalRequest":{"auth":{"type":"bearer","bearer":{"token":"{{token}}"}},"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"type":"file","key":"file","value":""},{"key":"platform","value":"<case_sensitive_platform_name>","uuid":"ec0112ee-22eb-45a1-aa30-48059202c39a","type":"text","description":"(Optional) A specific Platform configuration to use for the imported data.","disabled":true}]},"url":"/workspaces/api/activities/import","description":"### Import Activities\n\nThis endpoint allows the user to import activities via `file` or `json body`.\n\n- `file` import\n    \n    - The request body should be of type `form-data`, with a key \"file\" of type `File`.\n        \n    - The format of the file could be either `csv`, `xlsx` or `json`\n        \n    - It is recommended that the `Content-Type` header is set to `multipart/form-data`\n        \n- `json body` import\n    \n    - The request body should be a json object in the file information format described below\n        \n    - It is recommended that the `Content-Type` header is set to `application/json`\n        \n\n### **Hybrid integration**\n\nYou may want to integrate some of the activity data as soon as possible. For example, we might want to reflect the FTD status and amount of the customer as soon as it is available. This endpoint can receive data multiple times. It means that data can be sent partially or incrementally in pieces.\n\nFor example, it can only receive FTD (or any other column) information, as soon as it is available, and then receive all other values later on. In order to do this, simply provide the values that are known at the time, and leave all other required values as 0 (optional values can be skipped, as always). As other values become available they can be sent via this endpoint in a similar manner.\n\n> Depending on the import mode configured on integration, subsequent imports will either merge (incremental import) or overwrite (partial import) existing data. \n  \n\n#### Request Body\n\n- `file` (file): The file containing the activities to be imported. It can either be `csv`, `xlsx` or `json`\n    \n\nOR\n\n- JSON body with the imported data, describing the file information as explained below. This is an alternative to directly import JSON, without first writing it to a JSON file.\n    \n\n``` json\n// An array of objects with imported data in described format\n[\n  {\n   \"Customer\": \"CustomerId\",\n   \"Affiliate\": \"AffiliateUsername\",\n    // ...rest of the keys configured based on the File Information section below\n  }\n]\n\n ```\n\n### File information\n\nRegardless of the format (`csv` or `xlsx`), the headers included in the file should be the following:\n\n- `Customer` - this field should contain the `id` of the customer. **Required when country and affiliate are missing, optional otherwise.**\n    \n- `Affiliate` - the `username` of the Affiliate under which the customer has been registered. **Required with Country when Customer value is missing, optional otherwise.**\n    \n- `Country` - the country of origin for the activity. When customer is present, it must match the country of the customer. **Required with Affiliate when Customer value is missing, optional otherwise.**\n    \n- `Date` - date on which the activity occurred. Format - `DD/MM/YYYY`.\n    \n- `FTD` (number) - Whether or not this is a First Time Deposit. `1` should be passed for `true`, and `0` for `false`.\n    \n- `Ftd Amount` - Amount of the first time deposit. If the activity is not FTD related, the imported value will be 0.\n    \n- `Total Deposits` - Total amount of deposits during the given date.\n    \n- `Deposit Count (optional)` - The number of deposits made within the current activity batch. Default: `1`.\n    \n- `Withdrawals` - The withdrawal amount made by the customer during the given date.\n    \n- `Casino Bets` - The customer total bet amount placed during the given date on the Casino Product.\n    \n- `Casino Wins` - The customer total win amount settled during the given date on the Casino Product.\n    \n- `Bonus Cost` - This is the total Bonus Cost which the customer accumulated during the given date.\n    \n- `Brand` - The brand name on which the activity happened\n    \n\n> These are the default headers. As per our Client Integration documentation, their names can be completely and freely customized by communicating this need with our administration team. \n  \n\n#### Response\n\n#### 200 Ok\n\nThis response will be returned when the request was successfully, but we found no data to import. Either the file was empty, contained data that we could not recognize at all, or all of its contents are already present on our side.\n\n``` json\n{\n  \"message\": \"There is no new data to import from this file.\"\n}\n\n ```\n\n#### 201 Created\n\nThis response will be returned when all of the rows were valid and successfully imported. Further processing will happen in the background, and their ultimate registration and display in the platform might lag behind this.\n\n``` json\n{\n  \"message\": \"Activity data imported successfully\"\n}\n\n ```\n\n#### 206 Partial Content\n\nThis response will be returned when some rows have been found to contain invalid data values, but not all. In this case we'll parse and import all valid rows and collect error information for the invalid ones with details as to why the data is considered to be invalid and we are not able to import these rows.\n\n``` json\n{\n  \"message\": \"Activity data partially imported\",\n  \"details\": {\n    \"imported\": 18,\n    \"skipped\": 2\n  },\n  \"errors\": [\n    {\n      \"column\": \"Customer\",\n      \"message\": \"Values are invalid or do not exist\",\n      \"rows\": [4, 9],\n      \"values\": [\"1005\", \"1006\"]\n    }\n  ]\n}\n\n ```\n\n#### 400 Bad Request\n\nThis response will be returned upon receiving an invalid file or file that is missing information, such as a required column with data for the type of import.\n\n``` json\n{\n    \"error\": \"Invalid Customer values: 1005\",\n    \"column\": \"Customer\",\n    \"reason\": \"Invalid 'Customer' values 1005.\"\n}\n\n ```\n\n#### 413 Payload Too Large\n\nReturned when the upload exceeds the maximum size. Import files may be up to 20 MB, measured across the whole request body (including the file). Larger uploads are rejected before any rows are processed.\n\n``` json\n  {\n      \"message\": \"request entity too large\"\n  }\n\n ```\n\n#### 429 Too Many Requests\n\nThis response will be returned when too many import requests have been sent in a short period of time among all import endpoints, including both customers and activities.\n\nCurrent rate allows for 10 requests per 1 minute.\n"},"status":"Payload Too Large","code":413,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json; charset=utf-8"}],"cookie":[],"responseTime":null,"body":"{\n    \"message\": \"request entity too large\"\n}"}],"_postman_id":"799ceec7-e632-4e3b-a8e2-e733fbe2b9c1"}],"id":"263487b4-5195-41b3-b57a-d0ef275ce809","_postman_id":"263487b4-5195-41b3-b57a-d0ef275ce809","description":""},{"name":"TRACKING","item":[{"name":"Tracking Link","id":"e9e6708b-198d-9908-4eca-258fb9e04ea5","request":{"auth":{"type":"noauth","isInherited":false},"method":"GET","header":[],"url":"/t/LINK_ID","description":"<p>Reference only — your integration never calls this endpoint. The customer's browser calls it when the customer clicks an affiliate's tracking link.</p>\n<p>iGsuite records the click, generates the customer token, and answers with a <code>301 Moved Permanently</code> to the landing page configured on the tracking link. Read this to know exactly what arrives on your landing page URL.</p>\n<h3 id=\"the-tracking-link\">The tracking link</h3>\n<p>Every tracking link is a single URL carrying a unique link identifier:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-text\">https://www.Affiliate-Platform-Domain.com/t/1f5c9e04-8a3d-4c77-9b21-6de0f2a7c418\n\n</code></pre>\n<p>Affiliates take the link from the Affiliate Platform as it is given to them. This is the format you should expect to see in circulation.</p>\n<h3 id=\"request\">Request</h3>\n<ul>\n<li><p>Link identifier (required): the path segment after <code>/t/</code>. Nothing needs to be added to it or read out of it.</p>\n</li>\n<li><p><code>clickId</code> (string, optional): the affiliate's own click identifier, up to 2048 characters. Returned as <code>externalAffiliateClickId</code> when you decode the customer token.</p>\n</li>\n</ul>\n<h3 id=\"dynamic-variables\">Dynamic variables</h3>\n<p>A tracking link also accepts any dynamic variables configured for your platform, passed as query parameters named exactly as the variable. Query parameters that do not match a configured variable are ignored.</p>\n<p>Say a dynamic variable named <code>subid</code> is configured for your platform, and an affiliate wants to mark the clicks coming from their newsletter. They append it to their tracking link:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-text\">https://www.Affiliate-Platform-Domain.com/t/1f5c9e04-8a3d-4c77-9b21-6de0f2a7c418?subid=newsletter-42\n\n</code></pre>\n<p>The click is then recorded with <code>subid</code> set to <code>newsletter-42</code>, and the value becomes available in iGsuite reporting.</p>\n<p>Dynamic variables are recorded against the click only. They are not forwarded to your landing page URL and are not part of the decoded customer token, so there is nothing for you to read or store on your side.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"301-moved-permanently\">301 Moved Permanently</h4>\n<p>The <code>Location</code> header carries the landing page URL with the parameters below appended. Query parameters and the hash fragment already present on the configured landing page are preserved.</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Parameter</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Customer token</strong></td>\n<td>The parameter name is the token parameter agreed for your platform during integration — <code>btag</code> in the example below. The value is the customer token for this click, unique per click.</td>\n</tr>\n<tr>\n<td><strong>Affiliate id</strong></td>\n<td>Named <code>ai</code> by default, and configurable for your platform on request. The value is the iGsuite ID of the affiliate the click belongs to.</td>\n</tr>\n<tr>\n<td><strong>Tracking system</strong></td>\n<td>Appended only when a tracking system parameter is configured for your platform. The value is always <code>igs</code>.</td>\n</tr>\n</tbody>\n</table>\n</div><pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-text\">https://www.operator-brand.com/signup?btag=15bedea716ee7a35279520b61aa3d2a5&amp;ai=142\n\n</code></pre>\n<p>Reading that example parameter by parameter:</p>\n<ul>\n<li><p><code>btag</code> is the token parameter name configured for this platform, so <code>15bedea716ee7a35279520b61aa3d2a5</code> is <strong>the customer token</strong>. This is the value to store against the customer on registration and return in the Registration File, and the value to pass to <code>Decode Customer Token</code>.</p>\n</li>\n<li><p><code>ai</code> is the affiliate id, so the click belongs to affiliate <code>142</code>.</p>\n</li>\n</ul>\n<p><code>btag</code> is only an example. Your token parameter name is agreed with us during integration and is most likely something else — if you are not certain which parameter on your landing page carries the token, ask us and we will confirm it for your platform.</p>\n<p>The affiliate id lets you attribute a landing page visit to an affiliate immediately, without decoding the customer token first. It does not replace the token: the customer token stays the value you store on registration and return in the Registration File.</p>\n<h4 id=\"404-not-found\">404 Not Found</h4>\n<p>Returned when the link identifier does not match a tracking link.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n    \"error\": \"Invalid tracking link\"\n}\n\n</code></pre>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>Returned when <code>clickId</code> is longer than 2048 characters.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n    \"errors\": [\n        { \"field\": \"clickId\", \"message\": \"...\", \"rule\": \"...\" }\n    ]\n}\n\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>Returned when too many clicks are recorded from the same client in a short period. Current rate allows for 15 requests per 15 seconds.</p>\n<h3 id=\"deprecated-link-format\">Deprecated link format</h3>\n<p>You may still come across an older link format:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-text\">https://www.Affiliate-Platform-Domain.com/api/tracking-links/record?trackingLinkId=1&amp;affiliateId=1\n\n</code></pre>\n<p>Those links keep working and append the same landing page parameters, so there is nothing to fix on your side. New integrations should use the link format above. The only difference you can observe is the 404 body, which reads <code>Invalid tracking link ID</code>.</p>\n","urlObject":{"path":["t","LINK_ID"],"query":[],"variable":[]}},"response":[],"_postman_id":"e9e6708b-198d-9908-4eca-258fb9e04ea5"},{"name":"Decode Customer Token","id":"5ddce0c7-c486-4ebc-8fb5-8129ad6326d3","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"noauth","isInherited":false},"method":"GET","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/tracking-links/decode-token?token=a3a785a961b603b03c738cb8136055bb","description":"<p>This endpoint retrieves information about a customer based on an encoded token associated with a tracking link click.</p>\n<p>Leave a short delay between the click and your call. Clicks are processed through a queue rather than stored the moment the customer is redirected, so a token asked for in the first seconds may not be resolvable yet. About a minute after the click is enough in practice.</p>\n<h4 id=\"request\">Request</h4>\n<ul>\n<li>Query Parameters:<ul>\n<li>token (string, required): The token used to retrieve customer information.</li>\n</ul>\n</li>\n</ul>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>The response will be in JSON format and will represent the decoded information the token holds. See the example for reference.</p>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>Returned when the token query parameter is missing or does not match a recorded click.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n    \"errors\": [\n        { \"field\": \"token\", \"message\": \"...\", \"rule\": \"...\" }\n    ]\n}\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>Lookups for a token we can already resolve are not rate limited. Lookups for a token we cannot resolve — one that has not been through the queue yet, or one that does not exist — are limited to 100 requests per day.</p>\n<p>This is the reason for the short delay above. Polling immediately after a click, or retrying a token in a tight loop, spends that daily allowance on clicks that would have resolved on their own a moment later.</p>\n","urlObject":{"path":["workspaces","api","tracking-links","decode-token"],"host":[""],"query":[{"key":"token","value":"a3a785a961b603b03c738cb8136055bb"}],"variable":[]}},"response":[{"id":"6219ee6e-df04-4d7e-8ef0-d582c55c0ad3","name":"Decode Customer Token","originalRequest":{"method":"GET","header":[],"body":{"mode":"raw","raw":"","options":{"raw":{"language":"json"}}},"url":{"raw":"/workspaces/api/tracking-links/decode-token?token=a3a785a961b603b03c738cb8136055bb","path":["workspaces","api","tracking-links","decode-token"],"query":[{"key":"token","value":"a3a785a961b603b03c738cb8136055bb"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"content-length","value":"202"},{"key":"content-type","value":"application/json; charset=utf-8"},{"key":"Date","value":"Tue, 24 Sep 2024 12:28:01 GMT"},{"key":"Connection","value":"keep-alive"},{"key":"Keep-Alive","value":"timeout=5"}],"cookie":[],"responseTime":null,"body":"  {\n      \"affiliateId\": 1,\n      \"username\": \"niki\",\n      \"ipAddress\": \"123.45.67.89\",\n      \"referralLink\": null,\n      \"externalAffiliateClickId\": \"abc123def456\",\n      \"trafficSource\": \"SEO\",\n      \"trackingLinkId\": 1,\n      \"brandId\": 2,\n      \"brandName\": \"brandCool\",\n      \"campaignId\": 5,\n      \"campaignName\": \"Spring Promo\"\n  }"}],"_postman_id":"5ddce0c7-c486-4ebc-8fb5-8129ad6326d3"}],"id":"9cbdac48-55d0-44be-8d4f-f0c75af64b41","description":"<p>Links and clicks are the primary tracking instrument. Every tracking link click tells you two things: <strong>which affiliate</strong> sent the customer, and the <strong>client details</strong> (campaign, referring URL, traffic source, dynamic variables, etc.). How to collect each of them?  </p>\n<p><strong>1. Affiliate ID → ✅ Best is to read it from the landing page URL</strong></p>\n<ul>\n<li><p>Each redirect appends it as the <code>ai</code> parameter on your landing page URL as part of its query parameters (search string), e.g. <code>ai=277</code> -&gt; <code>277</code> is the affiliate ID.</p>\n</li>\n<li><p>This is our recommended way to attribute a customer to an affiliate.</p>\n</li>\n</ul>\n<p><strong>2. Everything else → decode the customer token</strong></p>\n<ul>\n<li><p>Resolves to campaign, referring URL, traffic source, brand, the affiliate's click ID, and more.</p>\n</li>\n<li><p>Endpoint: <a href=\"https://documenter.getpostman.com/view/28572506/2sA3s7k9k4#5ddce0c7-c486-4ebc-8fb5-8129ad6326d3\">Decode Customer Token</a></p>\n</li>\n<li><p><strong>ℹ️ Tip</strong>: Decoding of the token is not immediately avaialbe. You should set up your script to start with a delay of 2-3 minutes or to run after the registration customer registration process is complete on your end.</p>\n<ul>\n<li><p>Reason for the delay: Clicks are processed in a queue, and not immediately at redirect time — a token requested immediately after the landing page is hit, or in the first few seconds afte rthat may not yet resolve to the full details; failed lookups are rate limited for security reasons so be warned.</p>\n</li>\n<li><p>Normally clicks are completely processed within no more than a few seconds; under heavy traffic it can take a bit longer.</p>\n</li>\n</ul>\n</li>\n</ul>\n","_postman_id":"9cbdac48-55d0-44be-8d4f-f0c75af64b41"},{"name":"REPORTS","item":[{"name":"Traffic Report","event":[{"listen":"test","script":{"id":"634f8ec3-3ed7-48ab-a0d2-db21b4592cd0","exec":["var token = pm.response.json().token","pm.environment.set(\"token\", token)","postman.setEnvironmentVariable(\"token\", token);",""],"type":"text/javascript","packages":{},"requests":{}}}],"id":"1b69eeb1-8919-4018-bb92-ebe845f0d2cd","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"GET","header":[{"key":"Content-Type","value":"application/json"}],"url":"/workspaces/api/reports/traffic-report","description":"<p>This API endpoint is used to obtain traffic (tracking link) data.</p>\n<h3 id=\"request-query-params\">Request Query Params</h3>\n<ul>\n<li><p><code>page</code> (integer) - The page of results that is being requested. Can be any number &gt;= 1.</p>\n<ul>\n<li><code>Default: 1</code></li>\n</ul>\n</li>\n<li><p><code>perPage</code> (integer) - How many results to show per page. Can be any number &gt;= 1.</p>\n<ul>\n<li><code>Default: 15</code></li>\n</ul>\n</li>\n<li><p><code>start</code> (ISO Date) - The starting date to show results from. Can be any valid date in ISO Format e.g. <code>yyyy/mm/dd</code>.</p>\n<ul>\n<li><code>Default: start of current month</code></li>\n</ul>\n</li>\n<li><p><code>end</code> (ISO Date) - The ending date to show results until. Can be any valid date in ISO Format e.g. <code>yyyy/mm/dd</code>.</p>\n<ul>\n<li><code>Default: today's date</code></li>\n</ul>\n</li>\n<li><p><code>affiliateId</code> (number) - A valid ID of an affiliate user to filter for. Only the results of the given affiliate will be shown.</p>\n</li>\n<li><p><code>groupByCountry</code> (boolean) - When <code>true</code> the results will be grouped by their country of origin. When <code>false</code> the results will be combined totals from all countries of origin, and country information will not be included in the results.</p>\n<ul>\n<li><code>Default: false</code></li>\n</ul>\n</li>\n</ul>\n<h3 id=\"200-ok\">200 OK</h3>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"meta\": {\n    \"total\": 24,\n    \"perPage\": 15,\n    \"currentPage\": 1,\n    \"lastPage\": 2,\n    \"firstPage\": 1\n  },\n  \"data\": [\n    {\n      \"country\": \"Germany\",\n      \"affiliateId\": 142,\n      \"affiliateUsername\": \"topblog-aff\",\n      \"affiliateLabel\": \"Top Blog Network\",\n      \"referralLink\": \"https://my-blog.example.com/casino-roundup\",\n      \"dynamicVariables\": { \"sub1\": \"blog-roundup\", \"sub2\": \"home-hero\" },\n      \"nrc\": 7,\n      \"ftd\": 3,\n      \"deposits\": 18,\n      \"withdrawals\": 4,\n      \"totalBonusCost\": 120,\n      \"totalGgr\": 2450,\n      \"totalNgr\": 1980,\n      \"clicks\": 412,\n      \"uniqueClicks\": 401,\n      \"campaign\": \"spring-2026-de-casino\",\n      \"trafficSource\": \"Blog network\",\n      \"casinoBonusCost\": 90,\n      \"casinoGgr\": 1700,\n      \"casinoNgr\": 1400,\n      \"sportsBonusCost\": 30,\n      \"sportsGgr\": 750,\n      \"sportsNgr\": 580\n    }\n  ]\n}\n\n</code></pre>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Returned when the request is missing or carries an invalid Bearer token.</p>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Returned when the authenticated user lacks the <code>view-traffic-report</code> permission.</p>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>Returned when query parameters fail validation — invalid date, unknown country/brand, or invalid <code>period</code> value.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">  {\n      \"errors\": [\n          { \"field\": \"start\", \"message\": \"...\", \"rule\": \"...\" }\n      ]\n  }\n\n</code></pre>\n","urlObject":{"path":["workspaces","api","reports","traffic-report"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"e7153da8-2e7a-445c-8978-c15aaff5ddf7","name":"Traffic","originalRequest":{"method":"GET","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"/workspaces/api/reports/traffic-report?groupByCountry=true&start=2025-01-01&end=2025-01-31&page=1&perPage=100","path":["workspaces","api","reports","traffic-report"],"query":[{"key":"groupByCountry","value":"true"},{"key":"start","value":"2025-01-01"},{"key":"end","value":"2025-01-31"},{"key":"page","value":"1"},{"key":"perPage","value":"100"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json","description":"","type":"text"}],"cookie":[],"responseTime":null,"body":"{\r\n    \"meta\": {\r\n        \"total\": 1,\r\n        \"perPage\": 15,\r\n        \"currentPage\": 1,\r\n        \"lastPage\": 1,\r\n        \"firstPage\": 1\r\n    },\r\n    \"data\": [\r\n        {\r\n            \"affiliateId\": 11,\r\n            \"affiliateUsername\": \"john_doe\",\r\n            \"affiliateLabel\": \"new\",\r\n            \"trafficSource\": \"SEO\",\r\n            \"referralLink\": \"\",\r\n            \"dynamicVariables\": {},\r\n            \"campaign\": \"N/A\",\r\n            \"nrc\": 0,\r\n            \"ftd\": 0,\r\n            \"deposits\": 0,\r\n            \"withdrawals\": 0,\r\n            \"totalBonusCost\": 0,\r\n            \"totalGgr\": 0,\r\n            \"totalNgr\": 0,\r\n            \"clicks\": 13,\r\n            \"uniqueClicks\": 12,\r\n            \"country\": \"All Countries\"\r\n        }\r\n    ]\r\n}"}],"_postman_id":"1b69eeb1-8919-4018-bb92-ebe845f0d2cd"},{"name":"Customer Report","event":[{"listen":"test","script":{"id":"634f8ec3-3ed7-48ab-a0d2-db21b4592cd0","exec":["var token = pm.response.json().token","pm.environment.set(\"token\", token)","postman.setEnvironmentVariable(\"token\", token);",""],"type":"text/javascript","packages":{},"requests":{}}}],"id":"f9fe56e4-17dc-4ea6-8223-1d69acb7e33c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"GET","header":[{"key":"Content-Type","value":"application/json"}],"url":"/workspaces/api/reports/customer-report","description":"<p>Returns customer data at the individual-customer grain — one row per customer, carrying the affiliate it belongs to, the deal it is attributed to, and its activity figures for the requested period.</p>\n<h3 id=\"request-query-params\">Request Query Params</h3>\n<ul>\n<li><p><code>page</code> (integer) - The page of results that is being requested. Can be any number &gt;= 1.</p>\n<ul>\n<li><code>Default: 1</code></li>\n</ul>\n</li>\n<li><p><code>perPage</code> (integer) - How many results to show per page. Can be any number &gt;= 1.</p>\n<ul>\n<li><code>Default: 15</code></li>\n</ul>\n</li>\n<li><p><code>start</code> (ISO Date) - The starting date to show results from. Can be any valid date in ISO Format e.g. <code>yyyy/mm/dd</code>.</p>\n<ul>\n<li><code>Default: start of current month</code></li>\n</ul>\n</li>\n<li><p><code>end</code> (ISO Date) - The ending date to show results until. Can be any valid date in ISO Format e.g. <code>yyyy/mm/dd</code>.</p>\n<ul>\n<li><code>Default: today's date</code></li>\n</ul>\n</li>\n<li><p><code>affiliateId</code> (number) - A valid ID of an affiliate user to filter for. Only the results of the given affiliate will be shown.</p>\n</li>\n</ul>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 OK</h4>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"meta\": {\n    \"total\": 142,\n    \"perPage\": 15,\n    \"currentPage\": 1,\n    \"lastPage\": 10,\n    \"firstPage\": 1\n  },\n  \"data\": [\n    {\n      \"id\": 8421,\n      \"ftdDate\": \"2026-05-03\",\n      \"externalCustomerId\": \"customer-abc-1\",\n      \"customerNickname\": \"playerOne\",\n      \"externalCustomerStatus\": \"active\",\n      \"registeredAt\": \"2026-05-01T09:14:00.000Z\",\n      \"externalAffiliateClickId\": \"ext-click-abc-1\",\n      \"dynamicVariables\": { \"sub1\": \"blog-roundup\", \"sub2\": \"home-hero\" },\n      \"affiliateId\": 142,\n      \"affiliateUsername\": \"topblog-aff\",\n      \"affiliateEmail\": \"ops@topblog.example\",\n      \"affiliateName\": \"Top Blog Network\",\n      \"affiliateLabel\": \"Top Blog Network\",\n      \"companyWebsite\": \"https://topblog.example\",\n      \"expectedVolumes\": \"100-500\",\n      \"trafficSource\": \"Blog network\",\n      \"businessModel\": \"CPA\",\n      \"paymentType\": \"wire\",\n      \"brandName\": \"BrandCool\",\n      \"country\": \"Germany\",\n      \"campaign\": \"spring-2026-de-casino\",\n      \"dealName\": \"DE — Casino CPA — 120\",\n      \"ttlCost\": 120,\n      \"activeCustomers\": 1,\n      \"qualifiedFtds\": 1,\n      \"nrc\": 0,\n      \"ftd\": 1,\n      \"ftdAmount\": 250,\n      \"cpaAmount\": 120,\n      \"deposits\": 12,\n      \"withdrawals\": 4,\n      \"totalBets\": 16240,\n      \"totalWins\": 15300,\n      \"totalBonusCost\": 85,\n      \"totalFees\": 40,\n      \"totalGgr\": 940,\n      \"totalNgr\": 712,\n      \"totalRsCost\": 36,\n      \"uniqueClicks\": 401\n    }\n  ]\n}\n\n</code></pre>\n<p><code>customerNickname</code> carries the value sent in the <code>Nickname</code> column of the registration file — see <a href=\"#515b2251-88b6-4007-bd56-68044b9ccc54\">Import Customers</a>. It is an empty string for customers created without one.</p>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Returned when the request is missing or carries an invalid Bearer token.</p>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Returned when the authenticated user lacks the <code>view-customer-report</code> permission.</p>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>Returned when query parameters fail validation — invalid date, unknown country/brand, or invalid <code>period</code> value.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">  {\n      \"errors\": [\n          { \"field\": \"start\", \"message\": \"...\", \"rule\": \"...\" }\n      ]\n  }\n\n</code></pre>\n","urlObject":{"path":["workspaces","api","reports","customer-report"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"8d81cfc1-4629-447b-86ee-39ef616fe913","name":"Customers","originalRequest":{"method":"GET","header":[{"key":"Content-Type","value":"application/json","type":"text"}],"url":{"raw":"/workspaces/api/reports/traffic-report?start=2025-01-01&end=2025-01-31&page=1&perPage=100","path":["workspaces","api","reports","traffic-report"],"query":[{"key":"start","value":"2025-01-01"},{"key":"end","value":"2025-01-31"},{"key":"page","value":"1"},{"key":"perPage","value":"100"}]}},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json","description":"","type":"text"}],"cookie":[],"responseTime":null,"body":"{\r\n    \"meta\": {\r\n        \"total\": 1,\r\n        \"perPage\": 15,\r\n        \"currentPage\": 1,\r\n        \"lastPage\": 1,\r\n        \"firstPage\": 1\r\n    },\r\n    \"data\": [\r\n        {\r\n            \"id\": 1,\r\n            \"ftdDate\": \"2023-02-22\",\r\n            \"externalCustomerId\": \"c1fb24f0-b994-444b-9344-228726d8551a\",\r\n            \"customerNickname\": \"\",\r\n            \"externalCustomerStatus\": \"Active\",\r\n            \"registeredAt\": \"2016-05-25\",\r\n            \"externalAffiliateClickId\": \"\",\r\n            \"dynamicVariables\": {},\r\n            \"affiliateId\": 11,\r\n            \"affiliateUsername\": \"john_doe\",\r\n            \"affiliateEmail\": \"john.doe@example.mail\",\r\n            \"affiliateName\": \"John Doe\",\r\n            \"affiliateRejected\": null,\r\n            \"affiliateLabel\": \"new\",\r\n            \"companyWebsite\": \"https://localhost.dev\",\r\n            \"expectedVolumes\": \"11-50 FTD's\",\r\n            \"trafficSource\": \"SEO\",\r\n            \"businessModel\": \"CPA\",\r\n            \"paymentType\": \"Wire Transfer\",\r\n            \"brandId\": 4,\r\n            \"brandName\": \"John Doe Inc.\",\r\n            \"country\": \"Malta\",\r\n            \"campaign\": \"N/A\",\r\n            \"dealName\": \"RS: 20% RS\",\r\n            \"ttlCost\": 0,\r\n            \"activeCustomers\": 0,\r\n            \"qualifiedFtds\": 0,\r\n            \"nrc\": 0,\r\n            \"ftd\": 0,\r\n            \"sameMonthFtd\": 0,\r\n            \"ftdAmount\": 0,\r\n            \"cpaAmount\": 0,\r\n            \"deposits\": 0,\r\n            \"multipleDepositsCustomers\": 0,\r\n            \"withdrawals\": 0,\r\n            \"totalBets\": 0,\r\n            \"totalWins\": 0,\r\n            \"totalBonusCost\": 0,\r\n            \"totalFees\": 0,\r\n            \"totalGgr\": 0,\r\n            \"totalNgr\": 0,\r\n            \"totalRsCost\": 0,\r\n            \"uniqueClicks\": 0\r\n        }\r\n    ]\r\n}"}],"_postman_id":"f9fe56e4-17dc-4ea6-8223-1d69acb7e33c"},{"name":"Affiliate Report","id":"70768632-9932-f45b-3191-9031f07bc774","request":{"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":"{{token}}"}]},"isInherited":false},"method":"GET","header":[{"key":"Content-Type","value":"application/json"}],"url":"/workspaces/api/reports/affiliate-report","description":"<p>Retrieve per-affiliate performance and cost for a date range — traffic, player activity, revenue, and the cost each affiliate generated.</p>\n<p>Use this for the affiliate-level view of a period. For the same traffic broken down by tracking link, use Traffic Report; for the per-customer breakdown, use Customer Report.</p>\n<h3 id=\"request-query-params\">Request Query Params</h3>\n<p>All parameters are optional.</p>\n<p><strong>Paging and sorting</strong></p>\n<ul>\n<li><code>page</code> (integer) - The page of results that is being requested. Can be any number &gt;= 1.<ul>\n<li><code>Default: 1</code></li>\n</ul>\n</li>\n<li><code>perPage</code> (integer) - How many results to show per page. Can be any number &gt;= 1.<ul>\n<li><code>Default: 15</code></li>\n</ul>\n</li>\n<li><code>sortBy</code> (string) - Column key to sort on. A key that is not part of the current result set is ignored.<ul>\n<li><code>Default: ftd</code></li>\n</ul>\n</li>\n<li><code>sortOrder</code> (string) - Either <code>asc</code> or <code>desc</code>.<ul>\n<li><code>Default: desc</code></li>\n</ul>\n</li>\n</ul>\n<p><strong>Date range</strong></p>\n<ul>\n<li><code>start</code> (ISO Date) - The starting date to count activity from. Can be any valid date in ISO format e.g. <code>yyyy-mm-dd</code>.<ul>\n<li><code>Default: start of current month</code></li>\n</ul>\n</li>\n<li><code>end</code> (ISO Date) - The ending date to count activity until.<ul>\n<li><code>Default: today's date</code></li>\n</ul>\n</li>\n</ul>\n<p><strong>Row grain</strong></p>\n<p>By default one row covers one affiliate for the whole range, totalled across every brand, country, campaign and tracking link. The parameters below subdivide that row, and they combine — each one you add multiplies the row count rather than adding to it.</p>\n<ul>\n<li><code>period</code> (string) - Split each affiliate by time. One of <code>day</code>, <code>week</code>, <code>month</code>, <code>quarter</code> or <code>year</code>. Adds <code>period</code> to every row.</li>\n<li><code>groupByCountry</code> (boolean) - Split by the country the activity came from. Adds <code>country</code>.<ul>\n<li><code>Default: false</code></li>\n</ul>\n</li>\n<li><code>groupByCampaign</code> (boolean) - Split by campaign. Adds <code>campaign</code>.<ul>\n<li><code>Default: false</code></li>\n</ul>\n</li>\n<li><code>groupByTrackingLink</code> (boolean) - Split by tracking link. Adds <code>linkId</code> and <code>linkTitle</code>.<ul>\n<li><code>Default: false</code></li>\n</ul>\n</li>\n<li><code>groupByBrand</code> (boolean) - Split by brand. Adds <code>brandName</code>.<ul>\n<li><code>Default: false</code></li>\n</ul>\n</li>\n</ul>\n<p><strong>Which affiliates are listed</strong></p>\n<ul>\n<li><code>affiliateId</code> (number) - Return only this affiliate.</li>\n<li><code>search</code> (string) - Match on affiliate username or affiliate ID.</li>\n<li><code>username</code> (string) - Match on affiliate username.</li>\n<li><code>email</code> (string) - Match on affiliate email address.</li>\n<li><code>status</code> (array of string) - Any of <code>approved</code>, <code>rejected</code>, <code>waiting</code>.</li>\n<li><code>accountType</code> (string) - Either <code>internal</code> or <code>external</code>.</li>\n<li><code>manager</code> (array of number) - IDs of the affiliate managers assigned to the affiliate.</li>\n<li><code>affiliateRegistrationStart</code> / <code>affiliateRegistrationEnd</code> (ISO Date) - Limit to affiliates who signed up inside this window. Independent of <code>start</code> and <code>end</code>, which continue to govern the activity counted.</li>\n<li><code>affiliateFirstTimeActiveStart</code> / <code>affiliateFirstTimeActiveEnd</code> (ISO Date) - Limit to affiliates whose first activity falls inside this window. Also independent of <code>start</code> and <code>end</code>.</li>\n<li><code>ignoreNoActivity</code> (boolean) - When <code>true</code>, drop affiliates with no activity in the range. When <code>false</code> they are returned with zeroed figures.<ul>\n<li><code>Default: false</code></li>\n</ul>\n</li>\n<li><code>ignoreNoNrcAndFtd</code> (boolean) - When <code>true</code>, keep only rows with at least one registration or first-time depositor.<ul>\n<li><code>Default: false</code></li>\n</ul>\n</li>\n<li><code>ignoreRejected</code> (boolean) - When <code>true</code>, drop rejected affiliates.<ul>\n<li><code>Default: false</code></li>\n</ul>\n</li>\n</ul>\n<p><strong>Which activity is counted</strong></p>\n<p>These narrow the figures without removing affiliates from the list. An affiliate who sent traffic from three countries still occupies one row when you filter to one of them — that row simply carries only that country's share.</p>\n<ul>\n<li><code>brand</code> (array of number) - Count only activity for these brands. Must be brands the token's account can see.</li>\n<li><code>country</code> (number) - Count only activity from this country. Must be a valid country ID.</li>\n</ul>\n<p><strong>Export</strong></p>\n<ul>\n<li><code>exportTo</code> (string) - Either <code>csv</code> or <code>xlsx</code>. Returns a file download instead of JSON.</li>\n<li><code>exportColumns</code> (object) - Map of column key to the header label to write. Required alongside <code>exportTo</code>, and decides which columns the file carries.</li>\n</ul>\n<h3 id=\"200-ok\">200 OK</h3>\n<p>Returns the requested page of rows, plus <code>meta.totals</code> — the same figures recalculated across the entire result rather than the page. Ratio columns such as <code>dpp</code> and <code>ppp</code> are recalculated there rather than summed, so <code>meta.totals</code> will not match a column added up by hand.</p>\n<p>The key set varies with the request. Split keys appear only alongside their <code>groupBy</code> parameter, and platforms without the multiproduct add-on return the <code>total</code> keys in place of the <code>casino</code> and <code>sports</code> pairs.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"meta\": {\n    \"total\": 24,\n    \"perPage\": 15,\n    \"currentPage\": 1,\n    \"lastPage\": 2,\n    \"firstPage\": 1,\n    \"totals\": {\n      \"clicks\": 18420,\n      \"uniqueClicks\": 17233,\n      \"nrc\": 486,\n      \"ftd\": 121,\n      \"deposits\": 154300,\n      \"withdrawals\": 42150,\n      \"totalNgr\": 78310,\n      \"ttlCost\": 31240\n    }\n  },\n  \"data\": [\n    {\n      \"affiliateId\": 142,\n      \"affiliateUsername\": \"topblog-aff\",\n      \"affiliateName\": \"Top Blog Network Ltd\",\n      \"affiliateEmail\": \"ops@topblog.example.com\",\n      \"affiliateLabel\": \"established\",\n      \"kybVerified\": \"Verified\",\n      \"internalAccount\": \"External\",\n      \"isMasterAffiliate\": \"Disabled\",\n      \"assignedToMasterAffiliate\": \"\",\n      \"firstManagerUsername\": \"j.novak\",\n      \"secondManagerUsername\": \"\",\n      \"companyWebsite\": \"https://topblog.example.com\",\n      \"trafficSource\": \"Blog network\",\n      \"businessModel\": \"Content\",\n      \"expectedVolumes\": \"100-500 FTD monthly\",\n      \"paymentType\": \"Bank transfer\",\n      \"clicks\": 412,\n      \"uniqueClicks\": 401,\n      \"nrc\": 37,\n      \"ftd\": 12,\n      \"sameMonthFtd\": 9,\n      \"qualifiedFtds\": 7,\n      \"ftdAmount\": 1450,\n      \"ftdDeposits\": 3120,\n      \"multipleDepositsCustomers\": 5,\n      \"activeCustomers\": 14,\n      \"deposits\": 18400,\n      \"withdrawals\": 4260,\n      \"totalBets\": 96200,\n      \"totalWins\": 88150,\n      \"totalBonusCost\": 640,\n      \"totalFees\": 310,\n      \"totalGgr\": 8050,\n      \"totalNgr\": 7100,\n      \"totalRsCost\": 2130,\n      \"cpaAmount\": 900,\n      \"ttlCost\": 3030,\n      \"masterCost\": 0,\n      \"balance\": 1840,\n      \"balanceCorrection\": 0,\n      \"dpp\": 1533.33,\n      \"wpp\": 355,\n      \"ngrpp\": 591.67,\n      \"cpp\": 252.5,\n      \"rawrpp\": 670.83,\n      \"ppp\": 339.17\n    }\n  ]\n}\n\n</code></pre>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Returned when the request is missing or carries an invalid Bearer token.</p>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Returned when the authenticated user lacks the <code>view-affiliate-report</code> permission.</p>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>Returned when query parameters fail validation — an invalid date, an unknown brand or country, an <code>exportTo</code> outside <code>csv</code> and <code>xlsx</code>, or a <code>period</code> outside the five accepted values.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">  {\n      \"errors\": [\n          { \"field\": \"start\", \"message\": \"...\", \"rule\": \"...\" }\n      ]\n  }\n\n</code></pre>\n","urlObject":{"path":["workspaces","api","reports","affiliate-report"],"host":[""],"query":[],"variable":[]}},"response":[{"id":"3127b6ab-0a64-be62-6b20-0e9068162295","name":"Affiliates","originalRequest":{"auth":{"type":"bearer","bearer":{"token":"{{token}}"}},"method":"GET","header":[{"key":"Content-Type","value":"application/json"}],"url":"/workspaces/api/reports/affiliate-report","description":"Retrieve per-affiliate performance and cost for a date range — traffic, player activity, revenue, and the cost each affiliate generated.\n\nUse this for the affiliate-level view of a period. For the same traffic broken down by tracking link, use Traffic Report; for the per-customer breakdown, use Customer Report.\n\n### Request Query Params\n\nAll parameters are optional.\n\n**Paging and sorting**\n\n- `page` (integer) - The page of results that is being requested. Can be any number >= 1.\n    - `Default: 1`\n- `perPage` (integer) - How many results to show per page. Can be any number >= 1.\n    - `Default: 15`\n- `sortBy` (string) - Column key to sort on. A key that is not part of the current result set is ignored.\n    - `Default: ftd`\n- `sortOrder` (string) - Either `asc` or `desc`.\n    - `Default: desc`\n\n**Date range**\n\n- `start` (ISO Date) - The starting date to count activity from. Can be any valid date in ISO format e.g. `yyyy-mm-dd`.\n    - `Default: start of current month`\n- `end` (ISO Date) - The ending date to count activity until.\n    - `Default: today's date`\n\n**Row grain**\n\nBy default one row covers one affiliate for the whole range, totalled across every brand, country, campaign and tracking link. The parameters below subdivide that row, and they combine — each one you add multiplies the row count rather than adding to it.\n\n- `period` (string) - Split each affiliate by time. One of `day`, `week`, `month`, `quarter` or `year`. Adds `period` to every row.\n- `groupByCountry` (boolean) - Split by the country the activity came from. Adds `country`.\n    - `Default: false`\n- `groupByCampaign` (boolean) - Split by campaign. Adds `campaign`.\n    - `Default: false`\n- `groupByTrackingLink` (boolean) - Split by tracking link. Adds `linkId` and `linkTitle`.\n    - `Default: false`\n- `groupByBrand` (boolean) - Split by brand. Adds `brandName`.\n    - `Default: false`\n\n**Which affiliates are listed**\n\n- `affiliateId` (number) - Return only this affiliate.\n- `search` (string) - Match on affiliate username or affiliate ID.\n- `username` (string) - Match on affiliate username.\n- `email` (string) - Match on affiliate email address.\n- `status` (array of string) - Any of `approved`, `rejected`, `waiting`.\n- `accountType` (string) - Either `internal` or `external`.\n- `manager` (array of number) - IDs of the affiliate managers assigned to the affiliate.\n- `affiliateRegistrationStart` / `affiliateRegistrationEnd` (ISO Date) - Limit to affiliates who signed up inside this window. Independent of `start` and `end`, which continue to govern the activity counted.\n- `affiliateFirstTimeActiveStart` / `affiliateFirstTimeActiveEnd` (ISO Date) - Limit to affiliates whose first activity falls inside this window. Also independent of `start` and `end`.\n- `ignoreNoActivity` (boolean) - When `true`, drop affiliates with no activity in the range. When `false` they are returned with zeroed figures.\n    - `Default: false`\n- `ignoreNoNrcAndFtd` (boolean) - When `true`, keep only rows with at least one registration or first-time depositor.\n    - `Default: false`\n- `ignoreRejected` (boolean) - When `true`, drop rejected affiliates.\n    - `Default: false`\n\n**Which activity is counted**\n\nThese narrow the figures without removing affiliates from the list. An affiliate who sent traffic from three countries still occupies one row when you filter to one of them — that row simply carries only that country's share.\n\n- `brand` (array of number) - Count only activity for these brands. Must be brands the token's account can see.\n- `country` (number) - Count only activity from this country. Must be a valid country ID.\n\n**Export**\n\n- `exportTo` (string) - Either `csv` or `xlsx`. Returns a file download instead of JSON.\n- `exportColumns` (object) - Map of column key to the header label to write. Required alongside `exportTo`, and decides which columns the file carries.\n\n### 200 OK\n\nReturns the requested page of rows, plus `meta.totals` — the same figures recalculated across the entire result rather than the page. Ratio columns such as `dpp` and `ppp` are recalculated there rather than summed, so `meta.totals` will not match a column added up by hand.\n\nThe key set varies with the request. Split keys appear only alongside their `groupBy` parameter, and platforms without the multiproduct add-on return the `total` keys in place of the `casino` and `sports` pairs.\n\n``` json\n{\n  \"meta\": {\n    \"total\": 24,\n    \"perPage\": 15,\n    \"currentPage\": 1,\n    \"lastPage\": 2,\n    \"firstPage\": 1,\n    \"totals\": {\n      \"clicks\": 18420,\n      \"uniqueClicks\": 17233,\n      \"nrc\": 486,\n      \"ftd\": 121,\n      \"deposits\": 154300,\n      \"withdrawals\": 42150,\n      \"totalNgr\": 78310,\n      \"ttlCost\": 31240\n    }\n  },\n  \"data\": [\n    {\n      \"affiliateId\": 142,\n      \"affiliateUsername\": \"topblog-aff\",\n      \"affiliateName\": \"Top Blog Network Ltd\",\n      \"affiliateEmail\": \"ops@topblog.example.com\",\n      \"affiliateLabel\": \"established\",\n      \"kybVerified\": \"Verified\",\n      \"internalAccount\": \"External\",\n      \"isMasterAffiliate\": \"Disabled\",\n      \"assignedToMasterAffiliate\": \"\",\n      \"firstManagerUsername\": \"j.novak\",\n      \"secondManagerUsername\": \"\",\n      \"companyWebsite\": \"https://topblog.example.com\",\n      \"trafficSource\": \"Blog network\",\n      \"businessModel\": \"Content\",\n      \"expectedVolumes\": \"100-500 FTD monthly\",\n      \"paymentType\": \"Bank transfer\",\n      \"clicks\": 412,\n      \"uniqueClicks\": 401,\n      \"nrc\": 37,\n      \"ftd\": 12,\n      \"sameMonthFtd\": 9,\n      \"qualifiedFtds\": 7,\n      \"ftdAmount\": 1450,\n      \"ftdDeposits\": 3120,\n      \"multipleDepositsCustomers\": 5,\n      \"activeCustomers\": 14,\n      \"deposits\": 18400,\n      \"withdrawals\": 4260,\n      \"totalBets\": 96200,\n      \"totalWins\": 88150,\n      \"totalBonusCost\": 640,\n      \"totalFees\": 310,\n      \"totalGgr\": 8050,\n      \"totalNgr\": 7100,\n      \"totalRsCost\": 2130,\n      \"cpaAmount\": 900,\n      \"ttlCost\": 3030,\n      \"masterCost\": 0,\n      \"balance\": 1840,\n      \"balanceCorrection\": 0,\n      \"dpp\": 1533.33,\n      \"wpp\": 355,\n      \"ngrpp\": 591.67,\n      \"cpp\": 252.5,\n      \"rawrpp\": 670.83,\n      \"ppp\": 339.17\n    }\n  ]\n}\n\n ```\n\n#### 401 Unauthorized\n\nReturned when the request is missing or carries an invalid Bearer token.\n\n#### 403 Forbidden\n\nReturned when the authenticated user lacks the `view-affiliate-report` permission.\n\n#### 422 Unprocessable Entity\n\nReturned when query parameters fail validation — an invalid date, an unknown brand or country, an `exportTo` outside `csv` and `xlsx`, or a `period` outside the five accepted values.\n\n``` json\n  {\n      \"errors\": [\n          { \"field\": \"start\", \"message\": \"...\", \"rule\": \"...\" }\n      ]\n  }\n\n ```"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"value":"application/json","key":"Content-Type"}],"cookie":[],"responseTime":null,"body":"{\n  \"meta\": {\n    \"total\": 24,\n    \"perPage\": 15,\n    \"currentPage\": 1,\n    \"lastPage\": 2,\n    \"firstPage\": 1,\n    \"totals\": {\n      \"clicks\": 18420,\n      \"uniqueClicks\": 17233,\n      \"nrc\": 486,\n      \"ftd\": 121,\n      \"deposits\": 154300,\n      \"withdrawals\": 42150,\n      \"totalNgr\": 78310,\n      \"ttlCost\": 31240\n    }\n  },\n  \"data\": [\n    {\n      \"affiliateId\": 142,\n      \"affiliateUsername\": \"topblog-aff\",\n      \"affiliateName\": \"Top Blog Network Ltd\",\n      \"affiliateEmail\": \"ops@topblog.example.com\",\n      \"affiliateLabel\": \"established\",\n      \"kybVerified\": \"Verified\",\n      \"internalAccount\": \"External\",\n      \"isMasterAffiliate\": \"Disabled\",\n      \"assignedToMasterAffiliate\": \"\",\n      \"firstManagerUsername\": \"j.novak\",\n      \"secondManagerUsername\": \"\",\n      \"companyWebsite\": \"https://topblog.example.com\",\n      \"trafficSource\": \"Blog network\",\n      \"businessModel\": \"Content\",\n      \"expectedVolumes\": \"100-500 FTD monthly\",\n      \"paymentType\": \"Bank transfer\",\n      \"clicks\": 412,\n      \"uniqueClicks\": 401,\n      \"nrc\": 37,\n      \"ftd\": 12,\n      \"sameMonthFtd\": 9,\n      \"qualifiedFtds\": 7,\n      \"ftdAmount\": 1450,\n      \"ftdDeposits\": 3120,\n      \"multipleDepositsCustomers\": 5,\n      \"activeCustomers\": 14,\n      \"deposits\": 18400,\n      \"withdrawals\": 4260,\n      \"totalBets\": 96200,\n      \"totalWins\": 88150,\n      \"totalBonusCost\": 640,\n      \"totalFees\": 310,\n      \"totalGgr\": 8050,\n      \"totalNgr\": 7100,\n      \"totalRsCost\": 2130,\n      \"cpaAmount\": 900,\n      \"ttlCost\": 3030,\n      \"masterCost\": 0,\n      \"balance\": 1840,\n      \"balanceCorrection\": 0,\n      \"dpp\": 1533.33,\n      \"wpp\": 355,\n      \"ngrpp\": 591.67,\n      \"cpp\": 252.5,\n      \"rawrpp\": 670.83,\n      \"ppp\": 339.17\n    },\n    {\n      \"affiliateId\": 208,\n      \"affiliateUsername\": \"streamsquad\",\n      \"affiliateName\": \"Stream Squad Media\",\n      \"affiliateEmail\": \"partners@streamsquad.example.com\",\n      \"affiliateLabel\": \"new\",\n      \"kybVerified\": \"Unverified\",\n      \"internalAccount\": \"External\",\n      \"isMasterAffiliate\": \"Disabled\",\n      \"assignedToMasterAffiliate\": \"topblog-aff\",\n      \"firstManagerUsername\": \"m.iliev\",\n      \"secondManagerUsername\": \"\",\n      \"companyWebsite\": \"https://streamsquad.example.com\",\n      \"trafficSource\": \"Streaming\",\n      \"businessModel\": \"Influencer\",\n      \"expectedVolumes\": \"Under 100 FTD monthly\",\n      \"paymentType\": \"Crypto\",\n      \"clicks\": 1980,\n      \"uniqueClicks\": 1844,\n      \"nrc\": 63,\n      \"ftd\": 21,\n      \"sameMonthFtd\": 18,\n      \"qualifiedFtds\": 11,\n      \"ftdAmount\": 2100,\n      \"ftdDeposits\": 5480,\n      \"multipleDepositsCustomers\": 9,\n      \"activeCustomers\": 24,\n      \"deposits\": 26750,\n      \"withdrawals\": 7310,\n      \"totalBets\": 141300,\n      \"totalWins\": 129400,\n      \"totalBonusCost\": 1180,\n      \"totalFees\": 470,\n      \"totalGgr\": 11900,\n      \"totalNgr\": 10250,\n      \"totalRsCost\": 3075,\n      \"cpaAmount\": 1650,\n      \"ttlCost\": 4725,\n      \"masterCost\": 236.25,\n      \"balance\": 2410,\n      \"balanceCorrection\": -150,\n      \"dpp\": 1273.81,\n      \"wpp\": 348.1,\n      \"ngrpp\": 488.1,\n      \"cpp\": 225,\n      \"rawrpp\": 566.67,\n      \"ppp\": 263.1\n    }\n  ]\n}"}],"_postman_id":"70768632-9932-f45b-3191-9031f07bc774"}],"id":"4f084fc0-8cf4-4e5f-876c-9793889b183c","description":"<h2 id=\"ℹ️-introduction\"><strong>ℹ️ Introduction</strong></h2>\n<p>This document will guide you through the integration of both the <strong>Newton Documentation</strong> of <strong>iGsuite</strong>.</p>\n<p>In order to use any of the endpoints, first refer to the <a href=\"https://api-docs.igsuite.com/#59a78ccd-bc5b-484d-927b-3bd86b1b2494\">AUTH </a> documentation to understand how to obtain an API key. The key will then have to be passed with an <code>Authorization: Bearer</code> header.</p>\n","_postman_id":"4f084fc0-8cf4-4e5f-876c-9793889b183c"},{"name":"TOURNAMENTS","item":[{"name":"Tournaments","id":"3f78d42b-8523-6215-6ad5-ee70d03e9ca3","request":{"method":"GET","header":[],"url":"/workspaces/api/tournaments","description":"<p>List every tournament set up on the platform, newest-starting first.</p>\n<p>This is the Tournaments tab read as JSON: one row per tournament, with the window it runs in, what it competes on, the budget it commits and the number of winner positions it pays out. Reach for <code>Tournament</code> below when you need one tournament's full setup — the affiliate it runs for, its brand, country, landing page and prize ladder.</p>\n<h3 id=\"request\">Request</h3>\n<ul>\n<li><code>page</code> (number, optional): page to return. Defaults to <code>1</code>.</li>\n<li><code>perPage</code> (number, optional): rows per page. Defaults to <code>15</code>.</li>\n<li><code>search</code> (string, optional): matches anywhere in the tournament name, case-insensitively.</li>\n<li><code>sortBy</code> (string, optional): <code>name</code>, <code>type</code>, <code>startDate</code> (default), <code>endDate</code> or <code>status</code>. Any other value falls back to <code>startDate</code>.</li>\n<li><code>sortOrder</code> (string, optional): <code>asc</code> or <code>desc</code>. Defaults to <code>desc</code>.</li>\n</ul>\n<p>Sorting by <code>status</code> ranks by lifecycle rather than alphabetically — upcoming, then active, then ended — because status is derived from the dates rather than stored. <code>desc</code> reverses that order.</p>\n<p>Archived tournaments are never returned here. Rows are tie-broken by descending id, so the order is total and no row can appear on two pages while another appears on none.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>A page of tournaments.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"meta\": {\n    \"total\": 2,\n    \"perPage\": 15,\n    \"currentPage\": 1,\n    \"lastPage\": 1,\n    \"firstPage\": 1,\n    \"firstPageUrl\": \"/?page=1\",\n    \"lastPageUrl\": \"/?page=1\",\n    \"nextPageUrl\": null,\n    \"previousPageUrl\": null\n  },\n  \"data\": [\n    {\n      \"id\": 41,\n      \"name\": \"Autumn Clash\",\n      \"type\": \"turnover\",\n      \"startDate\": \"2026-08-01\",\n      \"endDate\": \"2026-09-30\",\n      \"subtitle\": \"Win a trip to Malta\",\n      \"affiliateId\": 142,\n      \"brandId\": 2,\n      \"audience\": \"new\",\n      \"campaignId\": 56,\n      \"activeTrackingLinkId\": \"a8b3e0bd-c0a6-43ac-a1bf-982ed89274c1\",\n      \"budget\": 1500,\n      \"maxWinners\": 3,\n      \"terms\": \"Minimum 50 EUR turnover to qualify. Prizes are paid within 14 days of the final standings.\",\n      \"createdAt\": \"2026-07-18T09:24:11.204+00:00\",\n      \"updatedAt\": \"2026-08-02T14:51:37.880+00:00\",\n      \"status\": \"active\",\n      \"archived\": false,\n      \"isEditable\": true\n    },\n    {\n      \"id\": 37,\n      \"name\": \"Summer Sprint\",\n      \"type\": \"winnings\",\n      \"startDate\": \"2026-06-01\",\n      \"endDate\": \"2026-06-30\",\n      \"subtitle\": null,\n      \"affiliateId\": 118,\n      \"brandId\": 2,\n      \"audience\": \"affiliated\",\n      \"campaignId\": 49,\n      \"activeTrackingLinkId\": \"c41d90fe-7b22-4a5f-8ce0-6d1b3a77e412\",\n      \"budget\": 800,\n      \"maxWinners\": 2,\n      \"terms\": null,\n      \"createdAt\": \"2026-05-12T11:02:45.117+00:00\",\n      \"updatedAt\": \"2026-05-12T11:02:45.117+00:00\",\n      \"status\": \"ended\",\n      \"archived\": false,\n      \"isEditable\": false\n    }\n  ]\n}\n</code></pre>\n<p>Field notes:</p>\n<ul>\n<li><code>type</code> — how players are ranked: <code>turnover</code> (what they staked) or <code>winnings</code> (what came back).</li>\n<li><code>audience</code> — which players the tournament competes over: <code>new</code> (players who sign up through the tournament's own link) or <code>affiliated</code> (the affiliate's existing players).</li>\n<li><code>status</code> — <code>upcoming</code>, <code>active</code> or <code>ended</code>. Derived from the date window rather than stored, so it is always consistent with <code>startDate</code> and <code>endDate</code>; a tournament ending today still counts as <code>active</code>.</li>\n<li><code>isEditable</code> — <code>false</code> once the window has closed. <code>Update Tournament</code> refuses an ended tournament, so this is what tells you whether an edit is worth attempting.</li>\n<li><code>archived</code> — always <code>false</code> on this list, which never returns retired tournaments.</li>\n<li><code>budget</code> and <code>maxWinners</code> — both derived from the prizes on save rather than stated by the caller: the budget is their values summed, and <code>maxWinners</code> is how many there are.</li>\n<li><code>campaignId</code> and <code>activeTrackingLinkId</code> — the campaign every opt-in is attributed to, and the exclusive tracking link the tournament is published through. Both are provisioned when the tournament is created and stay behind when it is archived. <code>activeTrackingLinkId</code> doubles as the handle the tournament's public leaderboard is addressed by.</li>\n<li><code>startDate</code> and <code>endDate</code> are calendar dates (<code>yyyy-MM-dd</code>); <code>createdAt</code> and <code>updatedAt</code> are ISO 8601 timestamps.</li>\n</ul>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Your token is missing, invalid, or has not cleared two-factor verification.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access!\"\n}\n</code></pre>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Your account cannot read tournaments.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access\"\n}\n</code></pre>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>A parameter did not validate — a <code>sortOrder</code> that is neither <code>asc</code> nor <code>desc</code>, or a <code>page</code> / <code>perPage</code> that is not a positive number.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"errors\": [\n    {\n      \"message\": \"The selected sortOrder is invalid\",\n      \"rule\": \"enum\",\n      \"field\": \"sortOrder\"\n    }\n  ]\n}\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>You are sending too many requests. The limit is 600 requests per minute.</p>\n","urlObject":{"path":["workspaces","api","tournaments"],"host":[""],"query":[],"variable":[]}},"response":[],"_postman_id":"3f78d42b-8523-6215-6ad5-ee70d03e9ca3"},{"name":"Tournament","id":"c92874fa-57b9-d356-47d3-f02e3b63822c","request":{"method":"GET","header":[],"url":"/workspaces/api/tournaments/:id","description":"<p>Read one tournament's full setup — everything <code>Create Tournament</code> and <code>Update Tournament</code> take, read back in the same shape.</p>\n<p>Four of the fields are not tournament properties at all. The country, landing page and link description were spent on the exclusive tracking link the tournament is published through, and the traffic source on its campaign; they are read back off those two rows. That is also why each can come back <code>null</code> — a tournament whose campaign or link has since been detached has nowhere to read them from.</p>\n<h3 id=\"request\">Request</h3>\n<p>Path parameters:</p>\n<ul>\n<li><code>id</code> (number, required): the tournament's id, as <code>Tournaments</code> returns it.</li>\n</ul>\n<p>No query parameters and no request body.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>The tournament as its edit form fills itself from.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"id\": 41,\n  \"name\": \"Autumn Clash\",\n  \"subtitle\": \"Win a trip to Malta\",\n  \"type\": \"turnover\",\n  \"audience\": \"new\",\n  \"status\": \"active\",\n  \"isEditable\": true,\n  \"startDate\": \"2026-08-01\",\n  \"endDate\": \"2026-09-30\",\n  \"terms\": \"Minimum 50 EUR turnover to qualify. Prizes are paid within 14 days of the final standings.\",\n  \"affiliate\": {\n    \"id\": 142,\n    \"username\": \"northstar_media\"\n  },\n  \"brand\": {\n    \"id\": 2,\n    \"name\": \"MonteCryptos\"\n  },\n  \"country\": {\n    \"id\": 14,\n    \"name\": \"Finland\"\n  },\n  \"trafficSource\": {\n    \"id\": 3,\n    \"name\": \"SEO\"\n  },\n  \"landingPage\": \"https://montecryptos.example.com/autumn-clash\",\n  \"linkDescription\": \"Autumn Clash tournament traffic\",\n  \"campaignId\": 56,\n  \"activeTrackingLinkId\": \"a8b3e0bd-c0a6-43ac-a1bf-982ed89274c1\",\n  \"prizes\": [\n    {\n      \"id\": 88,\n      \"tournamentId\": 41,\n      \"position\": 1,\n      \"rewardType\": \"cash\",\n      \"reward\": null,\n      \"value\": 1000,\n      \"createdAt\": \"2026-07-18T09:24:11.204+00:00\",\n      \"updatedAt\": \"2026-07-18T09:24:11.204+00:00\"\n    },\n    {\n      \"id\": 89,\n      \"tournamentId\": 41,\n      \"position\": 2,\n      \"rewardType\": \"free_spins\",\n      \"reward\": \"250 spins on Book of Dead\",\n      \"value\": 500,\n      \"createdAt\": \"2026-07-18T09:24:11.204+00:00\",\n      \"updatedAt\": \"2026-07-18T09:24:11.204+00:00\"\n    }\n  ]\n}\n</code></pre>\n<p>Field notes:</p>\n<ul>\n<li><code>affiliate</code> — the affiliate the tournament runs for. Immutable once the tournament exists: moving it to a different affiliate would strand the campaign and tracking link provisioned under the first one, so <code>Update Tournament</code> does not accept the field at all.</li>\n<li><code>brand</code>, <code>country</code>, <code>trafficSource</code> — each <code>{ id, name }</code>, or <code>null</code>. <code>brand</code> is null once the brand has been archived; the other two are null when the tournament's attribution has been detached.</li>\n<li><code>landingPage</code> — where the tournament's tracking link sends a player. <code>null</code> for the same reason as above.</li>\n<li><code>campaignId</code> and <code>activeTrackingLinkId</code> — present so you can tell an unset field apart from one with nowhere to write back to. With neither, the country, traffic source and landing page you send on an update still validate and then land nowhere.</li>\n<li><code>prizes</code> — the winner positions in position order, one per prize. <code>position</code> is 1-based and contiguous.</li>\n<li><code>prizes[].rewardType</code> — <code>cash</code>, <code>bonus_money</code>, <code>free_spins</code> or <code>physical</code>.</li>\n<li><code>prizes[].reward</code> and <code>prizes[].value</code> — a currency prize (<code>cash</code>, <code>bonus_money</code>) is fully described by its <code>value</code> and carries a null <code>reward</code>; a described prize (<code>free_spins</code>, <code>physical</code>) carries its description in <code>reward</code> alongside the currency <code>value</code> the operator books it at. Both contribute to the tournament's budget.</li>\n<li><code>budget</code> and <code>maxWinners</code> are deliberately absent: both are derived from the prizes, and the form that writes this back recalculates them.</li>\n</ul>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Your token is missing, invalid, or has not cleared two-factor verification.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access!\"\n}\n</code></pre>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Your account cannot read tournaments.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access\"\n}\n</code></pre>\n<h4 id=\"404-not-found\">404 Not Found</h4>\n<p>The id names no tournament you can read. Archived tournaments answer the same as ones that never existed — they are not distinguishable from here.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Row not found\"\n}\n</code></pre>\n<p>A tournament that exists but belongs to an affiliate outside your reach answers 404 as well, under a different body. Treat both as the same outcome.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"User is inaccessible or cannot be found!\"\n}\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>You are sending too many requests. The limit is 600 requests per minute.</p>\n","urlObject":{"path":["workspaces","api","tournaments",":id"],"host":[""],"query":[],"variable":[{"type":"any","key":"id"}]}},"response":[],"_postman_id":"c92874fa-57b9-d356-47d3-f02e3b63822c"},{"name":"Create Tournament","id":"56de1a85-3312-3759-665f-11331d325b53","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"affiliateId\": 142,\n  \"name\": \"Autumn Clash\",\n  \"subtitle\": \"Win a trip to Malta\",\n  \"type\": \"turnover\",\n  \"audience\": \"new\",\n  \"startDate\": \"2026-08-01\",\n  \"endDate\": \"2026-09-30\",\n  \"brandId\": 2,\n  \"countryId\": 14,\n  \"trafficSourceId\": 3,\n  \"landingPage\": \"https://montecryptos.example.com/autumn-clash\",\n  \"linkDescription\": \"Autumn Clash tournament traffic\",\n  \"terms\": \"Minimum 50 EUR turnover to qualify. Prizes are paid within 14 days of the final standings.\",\n  \"prizes\": [\n    {\n      \"rewardType\": \"cash\",\n      \"value\": 1000\n    },\n    {\n      \"rewardType\": \"free_spins\",\n      \"reward\": \"250 spins on Book of Dead\",\n      \"value\": 500\n    }\n  ]\n}","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/tournaments","description":"<p>Set up a tournament and the attribution it is measured through.</p>\n<p>One call stands up three things in a single transaction: the tournament itself, a campaign for the affiliate, and an exclusive tracking link pointed at your landing page. A tournament without them could never be reported on, so either all of it lands or none of it does.</p>\n<p>Send the body as JSON with <code>Content-Type: application/json</code>.</p>\n<h3 id=\"request\">Request</h3>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"affiliateId\": 142,\n  \"name\": \"Autumn Clash\",\n  \"subtitle\": \"Win a trip to Malta\",\n  \"type\": \"turnover\",\n  \"audience\": \"new\",\n  \"startDate\": \"2026-08-01\",\n  \"endDate\": \"2026-09-30\",\n  \"brandId\": 2,\n  \"countryId\": 14,\n  \"trafficSourceId\": 3,\n  \"landingPage\": \"https://montecryptos.example.com/autumn-clash\",\n  \"linkDescription\": \"Autumn Clash tournament traffic\",\n  \"terms\": \"Minimum 50 EUR turnover to qualify. Prizes are paid within 14 days of the final standings.\",\n  \"prizes\": [\n    {\n      \"rewardType\": \"cash\",\n      \"value\": 1000\n    },\n    {\n      \"rewardType\": \"free_spins\",\n      \"reward\": \"250 spins on Book of Dead\",\n      \"value\": 500\n    }\n  ]\n}\n</code></pre>\n<ul>\n<li><code>affiliateId</code> (number, required): the affiliate the tournament runs for. Must be an affiliate account you can reach. Settled here for good — no later edit can move a tournament to a different affiliate.</li>\n<li><code>name</code> (string, required, max 55): one affiliate cannot hold two live tournaments of the same name. An archived tournament releases its name.</li>\n<li><code>subtitle</code> (string, optional, max 255): the line under the name on the tournament card.</li>\n<li><code>type</code> (string, required): how players are ranked — <code>turnover</code> (what they staked) or <code>winnings</code> (what came back).</li>\n<li><code>audience</code> (string, required): which players compete — <code>new</code> (players who sign up through the tournament's own link) or <code>affiliated</code> (the affiliate's existing players).</li>\n<li><code>startDate</code> (date, required): cannot be in the past. <code>yyyy-MM-dd</code> is the format to send; ISO 8601 and the common regional spellings are also accepted.</li>\n<li><code>endDate</code> (date, required): must fall on a later day than <code>startDate</code>.</li>\n<li><code>brandId</code> (number, required): the brand the tournament runs on. Must be one visible to you, and cannot be the Unknown brand.</li>\n<li><code>countryId</code> (number, required): the country the tracking link is registered for.</li>\n<li><code>trafficSourceId</code> (number, required): the traffic source the campaign is registered under.</li>\n<li><code>landingPage</code> (string, required, max 2048): where the tracking link sends a player. The scheme is mandatory — <code>montecryptos.example.com</code> is rejected, <code>https://montecryptos.example.com</code> is not.</li>\n<li><code>linkDescription</code> (string, optional, max 255): the tracking link's own description. Falls back to the tournament name when omitted.</li>\n<li><code>terms</code> (string, optional, max 5000): the terms shown on the tournament card and its public leaderboard.</li>\n<li><code>prizes</code> (array, required, 1 to 25 entries): the winner positions, in the order they are paid. The first entry is position 1.<ul>\n<li><code>rewardType</code> (string, required): <code>cash</code>, <code>bonus_money</code>, <code>free_spins</code> or <code>physical</code>.</li>\n<li><code>reward</code> (string, 1 to 255): what the winner receives, in words. Required for <code>free_spins</code> and <code>physical</code>; ignored and stored as null for <code>cash</code> and <code>bonus_money</code>, which their value already describes in full.</li>\n<li><code>value</code> (number, required, greater than 0, at most 1000000000): the currency amount. For a described prize this is the operator's own valuation of it — it feeds the budget but is never published on the leaderboard.</li>\n</ul>\n</li>\n</ul>\n<p><code>budget</code> and <code>maxWinners</code> are not accepted. Both are derived from the prizes: the budget is their values summed, <code>maxWinners</code> is how many there are.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"201-created\">201 Created</h4>\n<p>The tournament, with the campaign and exclusive link provisioned alongside it.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Tournament created successfully!\",\n  \"data\": {\n    \"id\": 41,\n    \"name\": \"Autumn Clash\",\n    \"type\": \"turnover\",\n    \"startDate\": \"2026-08-01\",\n    \"endDate\": \"2026-09-30\",\n    \"subtitle\": \"Win a trip to Malta\",\n    \"affiliateId\": 142,\n    \"brandId\": 2,\n    \"audience\": \"new\",\n    \"campaignId\": 56,\n    \"activeTrackingLinkId\": \"a8b3e0bd-c0a6-43ac-a1bf-982ed89274c1\",\n    \"budget\": 1500,\n    \"maxWinners\": 2,\n    \"terms\": \"Minimum 50 EUR turnover to qualify. Prizes are paid within 14 days of the final standings.\",\n    \"createdAt\": \"2026-07-18T09:24:11.204+00:00\",\n    \"updatedAt\": \"2026-07-18T09:24:11.204+00:00\",\n    \"status\": \"upcoming\",\n    \"archived\": false,\n    \"isEditable\": true,\n    \"prizes\": [\n      {\n        \"id\": 88,\n        \"tournamentId\": 41,\n        \"position\": 1,\n        \"rewardType\": \"cash\",\n        \"reward\": null,\n        \"value\": 1000,\n        \"createdAt\": \"2026-07-18T09:24:11.204+00:00\",\n        \"updatedAt\": \"2026-07-18T09:24:11.204+00:00\"\n      },\n      {\n        \"id\": 89,\n        \"tournamentId\": 41,\n        \"position\": 2,\n        \"rewardType\": \"free_spins\",\n        \"reward\": \"250 spins on Book of Dead\",\n        \"value\": 500,\n        \"createdAt\": \"2026-07-18T09:24:11.204+00:00\",\n        \"updatedAt\": \"2026-07-18T09:24:11.204+00:00\"\n      }\n    ],\n    \"campaign\": {\n      \"id\": 56,\n      \"name\": \"Tournament: Autumn Clash\",\n      \"description\": \"Autumn Clash tournament traffic\",\n      \"trafficSourceId\": 3,\n      \"affiliateId\": 142,\n      \"createdAt\": \"2026-07-18T09:24:11.180+00:00\",\n      \"updatedAt\": \"2026-07-18T09:24:11.180+00:00\"\n    },\n    \"activeTrackingLink\": {\n      \"id\": \"a8b3e0bd-c0a6-43ac-a1bf-982ed89274c1\",\n      \"createdAt\": \"2026-07-18T09:24:11.192+00:00\",\n      \"updatedAt\": \"2026-07-18T09:24:11.192+00:00\",\n      \"trackingLinkId\": 3120,\n      \"affiliateId\": 142,\n      \"campaignId\": 56,\n      \"trackingUrl\": \"https://api.example.com/t/a8b3e0bd-c0a6-43ac-a1bf-982ed89274c1\"\n    }\n  }\n}\n</code></pre>\n<p>Notes on what was provisioned:</p>\n<ul>\n<li><code>campaign.name</code> — always <code>Tournament: </code> followed by the tournament's name. Where an earlier run of the same name already took it, the new one is counted (<code>Tournament: Autumn Clash (2)</code>) rather than refused: campaigns are never deleted and their names are unique per affiliate, so a second annual cup cannot reuse the first one's campaign name. The campaign keeps this name for life — renaming the tournament later does not chase it.</li>\n<li><code>activeTrackingLink.id</code> — the tournament's public handle. It is the token in the opt-in URL, the id its leaderboard is addressed by, and the only thing the embeddable widget needs.</li>\n<li><code>activeTrackingLink.trackingUrl</code> — the opt-in link itself, ready to hand out.</li>\n<li>The tracking link is exclusive to this one affiliate, so it never surfaces in anyone else's list of links to promote.</li>\n</ul>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Your token is missing, invalid, or has not cleared two-factor verification.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access!\"\n}\n</code></pre>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Your account cannot create tournaments — or <code>affiliateId</code> names a real, visible account that is not an affiliate, which cannot promote a tournament and so cannot be given one.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"User is not an affiliate\"\n}\n</code></pre>\n<h4 id=\"404-not-found\">404 Not Found</h4>\n<p><code>affiliateId</code> names an account outside your reach. Deliberately not told apart from an id that does not exist — a validation error would confirm the account is there.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"User is inaccessible or cannot be found!\"\n}\n</code></pre>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>One or more fields failed validation. Every failure is reported, not just the first.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"errors\": [\n    {\n      \"message\": \"This affiliate already has a tournament with this name\",\n      \"rule\": \"tournament.name.unique\",\n      \"field\": \"name\"\n    },\n    {\n      \"message\": \"The endDate field must be a date after startDate\",\n      \"rule\": \"afterField\",\n      \"field\": \"endDate\"\n    },\n    {\n      \"message\": \"The reward field must be defined\",\n      \"rule\": \"requiredWhen\",\n      \"field\": \"prizes.0.reward\"\n    }\n  ]\n}\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>You are sending too many requests. The limit is 600 requests per minute.</p>\n","urlObject":{"path":["workspaces","api","tournaments"],"host":[""],"query":[],"variable":[]}},"response":[],"_postman_id":"56de1a85-3312-3759-665f-11331d325b53"},{"name":"Update Tournament","id":"cb4d6a45-569b-c1a0-2eab-d643cf9922b4","request":{"method":"PUT","header":[{"value":"application/json","key":"Content-Type"}],"body":{"mode":"raw","raw":"{\n  \"name\": \"Autumn Clash\",\n  \"subtitle\": \"Win a trip to Malta\",\n  \"type\": \"turnover\",\n  \"audience\": \"new\",\n  \"startDate\": \"2026-08-01\",\n  \"endDate\": \"2026-10-15\",\n  \"brandId\": 2,\n  \"countryId\": 14,\n  \"trafficSourceId\": 3,\n  \"landingPage\": \"https://montecryptos.example.com/autumn-clash\",\n  \"linkDescription\": \"Autumn Clash tournament traffic\",\n  \"terms\": \"Minimum 50 EUR turnover to qualify. Prizes are paid within 14 days of the final standings.\",\n  \"prizes\": [\n    {\n      \"rewardType\": \"cash\",\n      \"value\": 1200\n    },\n    {\n      \"rewardType\": \"free_spins\",\n      \"reward\": \"250 spins on Book of Dead\",\n      \"value\": 500\n    }\n  ]\n}","options":{"raw":{"language":"json"}}},"url":"/workspaces/api/tournaments/:id","description":"<p>Edit a tournament, and the campaign and tracking link it is published through.</p>\n<p><strong>Send the whole tournament, not a diff.</strong> Every field below is required, and an omitted optional field is read as cleared rather than unchanged. Read the tournament with <code>Tournament</code> first and send back what you get, amended.</p>\n<p>What a competition is decided on settles as it runs, so the edit narrows the closer a tournament gets to being over — see the 409 responses below, which is where those rules live rather than in validation.</p>\n<p>Send the body as JSON with <code>Content-Type: application/json</code>.</p>\n<h3 id=\"request\">Request</h3>\n<p>Path parameters:</p>\n<ul>\n<li><code>id</code> (number, required): the tournament's id.</li>\n</ul>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"name\": \"Autumn Clash\",\n  \"subtitle\": \"Win a trip to Malta\",\n  \"type\": \"turnover\",\n  \"audience\": \"new\",\n  \"startDate\": \"2026-08-01\",\n  \"endDate\": \"2026-10-15\",\n  \"brandId\": 2,\n  \"countryId\": 14,\n  \"trafficSourceId\": 3,\n  \"landingPage\": \"https://montecryptos.example.com/autumn-clash\",\n  \"linkDescription\": \"Autumn Clash tournament traffic\",\n  \"terms\": \"Minimum 50 EUR turnover to qualify. Prizes are paid within 14 days of the final standings.\",\n  \"prizes\": [\n    {\n      \"rewardType\": \"cash\",\n      \"value\": 1200\n    },\n    {\n      \"rewardType\": \"free_spins\",\n      \"reward\": \"250 spins on Book of Dead\",\n      \"value\": 500\n    }\n  ]\n}\n</code></pre>\n<p>The fields are exactly those of <code>Create Tournament</code>, with the same types and bounds, minus one:</p>\n<ul>\n<li><code>affiliateId</code> is <strong>not accepted</strong>. Handing a tournament to a different affiliate would strand the campaign and tracking link provisioned under the first one, so it is immutable by omission — sending it changes nothing.</li>\n<li><code>name</code> still has to be free within the affiliate's own tournaments, except that the tournament keeps its own name without that counting as a clash with itself.</li>\n<li><code>startDate</code> may be resubmitted as it stands at any time. Moving it is what the rules below govern.</li>\n<li><code>prizes</code> replaces the ladder wholesale. Nothing references a prize by id, so the old positions are dropped and the array you send becomes positions 1..n with <strong>new ids</strong>. Send the full ladder every time, in order.</li>\n</ul>\n<p>What follows the tournament, and what does not:</p>\n<ul>\n<li>The exclusive tracking link follows in everything — name, description, brand, country and landing page. It is the tournament's own handle and nothing else reads it.</li>\n<li>The campaign's description and traffic source follow, because this form is the only place left that can edit them.</li>\n<li><strong>The campaign's name does not follow a rename.</strong> That name records which tournament stood the campaign up, not what the tournament is called today; chasing a rename would either collide with an earlier run's campaign or quietly relabel a row the affiliate has been reading in their own reports.</li>\n</ul>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>The tournament re-read, in the same shape <code>Tournament</code> returns.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Tournament updated successfully!\",\n  \"data\": {\n    \"id\": 41,\n    \"name\": \"Autumn Clash\",\n    \"subtitle\": \"Win a trip to Malta\",\n    \"type\": \"turnover\",\n    \"audience\": \"new\",\n    \"status\": \"active\",\n    \"isEditable\": true,\n    \"startDate\": \"2026-08-01\",\n    \"endDate\": \"2026-10-15\",\n    \"terms\": \"Minimum 50 EUR turnover to qualify. Prizes are paid within 14 days of the final standings.\",\n    \"affiliate\": {\n      \"id\": 142,\n      \"username\": \"northstar_media\"\n    },\n    \"brand\": {\n      \"id\": 2,\n      \"name\": \"MonteCryptos\"\n    },\n    \"country\": {\n      \"id\": 14,\n      \"name\": \"Finland\"\n    },\n    \"trafficSource\": {\n      \"id\": 3,\n      \"name\": \"SEO\"\n    },\n    \"landingPage\": \"https://montecryptos.example.com/autumn-clash\",\n    \"linkDescription\": \"Autumn Clash tournament traffic\",\n    \"campaignId\": 56,\n    \"activeTrackingLinkId\": \"a8b3e0bd-c0a6-43ac-a1bf-982ed89274c1\",\n    \"prizes\": [\n      {\n        \"id\": 104,\n        \"tournamentId\": 41,\n        \"position\": 1,\n        \"rewardType\": \"cash\",\n        \"reward\": null,\n        \"value\": 1200,\n        \"createdAt\": \"2026-08-14T10:05:22.661+00:00\",\n        \"updatedAt\": \"2026-08-14T10:05:22.661+00:00\"\n      },\n      {\n        \"id\": 105,\n        \"tournamentId\": 41,\n        \"position\": 2,\n        \"rewardType\": \"free_spins\",\n        \"reward\": \"250 spins on Book of Dead\",\n        \"value\": 500,\n        \"createdAt\": \"2026-08-14T10:05:22.661+00:00\",\n        \"updatedAt\": \"2026-08-14T10:05:22.661+00:00\"\n      }\n    ]\n  }\n}\n</code></pre>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Your token is missing, invalid, or has not cleared two-factor verification.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access!\"\n}\n</code></pre>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Your account cannot update tournaments.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access\"\n}\n</code></pre>\n<h4 id=\"404-not-found\">404 Not Found</h4>\n<p>The id names no tournament you can read. Archived tournaments answer the same as ones that never existed.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Row not found\"\n}\n</code></pre>\n<h4 id=\"409-conflict\">409 Conflict</h4>\n<p>The edit is refused on the tournament's own terms rather than on the shape of what you sent. Three rules, checked in this order, each answering under its own message:</p>\n<p><strong>The window has closed.</strong> There is no honest edit to make to a competition that has already been decided. <code>isEditable</code> on the tournament tells you this before you try.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Ended tournaments cannot be edited.\"\n}\n</code></pre>\n<p><strong>The tournament is running and you changed its terms.</strong> What it competes on, who it competes between and the day it opened are what the players entered under, so <code>type</code>, <code>audience</code> and <code>startDate</code> are settled once it is active. Resubmitting the values it already holds is not a change and stays allowed — which is what lets you send the whole tournament back rather than a diff.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Audience, type and start date cannot change while a tournament is active.\"\n}\n</code></pre>\n<p><strong>The window was moved into the past.</strong> A competition cannot be made to have run when it did not, and one closed retroactively would be decided after the fact. <code>endDate</code> gets no slack at all; <code>startDate</code> is exempt on a running tournament, because the rule above already pins it to the day it really opened.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"A tournament cannot be moved into the past. Pick today or a later date.\"\n}\n</code></pre>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>One or more fields failed validation. Same rules and messages as <code>Create Tournament</code>.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"errors\": [\n    {\n      \"message\": \"This affiliate already has a tournament with this name\",\n      \"rule\": \"tournament.name.unique\",\n      \"field\": \"name\"\n    }\n  ]\n}\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>You are sending too many requests. The limit is 600 requests per minute.</p>\n","urlObject":{"path":["workspaces","api","tournaments",":id"],"host":[""],"query":[],"variable":[{"type":"any","key":"id"}]}},"response":[],"_postman_id":"cb4d6a45-569b-c1a0-2eab-d643cf9922b4"},{"name":"Archive Tournament","id":"4af9b966-c7dd-92d2-9c99-4f8bc1d94fa7","request":{"method":"DELETE","header":[],"url":"/workspaces/api/tournaments/:id","description":"<p>Retire a tournament.</p>\n<p>The row is kept and marked archived rather than deleted, so everything already attributed to the tournament stays attributed and its history keeps reading correctly. What changes is that it stops being reachable: it leaves the tournament list, it leaves the analysis report, <code>Tournament</code> answers 404 for it, and its public leaderboard answers 404 to the widget. Its name becomes free for a new tournament under the same affiliate.</p>\n<blockquote>\n<p>⚠️ <strong>There is no un-archive over the API.</strong> Archive a tournament only when you mean it.</p>\n</blockquote>\n<p>The campaign and the exclusive tracking link are deliberately left behind. Campaigns cannot be deleted through the API, and the link outlives the tournament that provisioned it — retiring either is a separate, manual decision.</p>\n<h3 id=\"request\">Request</h3>\n<p>Path parameters:</p>\n<ul>\n<li><code>id</code> (number, required): the tournament's id.</li>\n</ul>\n<p>No query parameters and no request body.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>The tournament is archived. Nothing is returned beyond the confirmation.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Tournament archived successfully!\"\n}\n</code></pre>\n<p>Archiving an already-archived tournament is not possible — it answers 404, since an archived tournament is unreachable by id.</p>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Your token is missing, invalid, or has not cleared two-factor verification.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access!\"\n}\n</code></pre>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Your account cannot archive tournaments.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access\"\n}\n</code></pre>\n<h4 id=\"404-not-found\">404 Not Found</h4>\n<p>The id names no tournament you can read — one that never existed, or one already archived.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"message\": \"Row not found\"\n}\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>You are sending too many requests. The limit is 600 requests per minute.</p>\n","urlObject":{"path":["workspaces","api","tournaments",":id"],"host":[""],"query":[],"variable":[{"type":"any","key":"id"}]}},"response":[],"_postman_id":"4af9b966-c7dd-92d2-9c99-4f8bc1d94fa7"},{"name":"Tournament Analysis Report","id":"da0feaf9-dc5e-5420-c0ca-1bc5464312d5","request":{"method":"GET","header":[],"url":"/workspaces/api/tournaments/analysis?start=2026-08-01&end=2026-09-30","description":"<p>What each tournament acquired over a period — one row per tournament, with the clicks, registrations, depositors, revenue and cost attributed to it.</p>\n<p>Two rules decide what you get back, and they are not the same rule:</p>\n<ul>\n<li><strong>The row set is window overlap.</strong> Every tournament whose own start-to-end window overlaps the period you asked for is a row. One that starts after the period, or ended before it, is absent rather than zeroed; one inside it that attracted nothing at all is still a row of zeros, because \"did this tournament work\" has an answer for a tournament that did nothing.</li>\n<li><strong>The period clamps the measures.</strong> The figures on a row count only what fell inside your <code>start</code> and <code>end</code>, even where the tournament ran longer.</li>\n</ul>\n<blockquote>\n<p>⚠️ <strong>This report measures acquisition, and only acquisition.</strong> Every customer-grained figure keys off the stamp on that customer's registration click, so it counts the players a tournament brought in. A tournament whose <code>audience</code> is <code>affiliated</code> — one competing over players the affiliate already had — therefore reads zeros here and <strong>deliberately disagrees with its own leaderboard</strong>, which is ranking those existing players just fine. Read <code>Tournament Standings</code> for who is winning; read this for what the campaign recruited.</p>\n</blockquote>\n<p>Archived tournaments never appear.</p>\n<h3 id=\"request\">Request</h3>\n<p>All parameters are optional.</p>\n<ul>\n<li><code>start</code> (date): start of the period. Defaults to the first day of the current month.</li>\n<li><code>end</code> (date): end of the period. Defaults to now. Send dates as <code>yyyy-MM-dd</code>.</li>\n<li><code>lifecycle</code> (string): narrow to a slice of the lifecycle — <code>live-upcoming</code> (anything not yet ended), <code>completed</code> (ended only), or <code>all</code>. Omitted means no lifecycle predicate.</li>\n<li><code>tournamentId</code> (number array): restrict to specific tournaments. Repeat the parameter — <code>tournamentId[]=41&amp;tournamentId[]=37</code>.</li>\n<li><code>affiliateId</code> (number): restrict to one affiliate's tournaments.</li>\n<li><code>brand</code> (number array): restrict to brands, by id. Must be brands visible to you.</li>\n<li><code>page</code> (number): page to return. Defaults to <code>1</code>.</li>\n<li><code>perPage</code> (number): rows per page. Defaults to <code>15</code>.</li>\n<li><code>sortBy</code> (string): any column name from the row below — for example <code>uniqueClicks</code>, <code>ftd</code>, <code>totalNgr</code>, <code>tournamentName</code>. Defaults to <code>tournamentStartDate</code>. A name that is not a column of this report is ignored rather than rejected, and you get the default order.</li>\n<li><code>sortOrder</code> (string): <code>asc</code> or <code>desc</code>. Defaults to <code>desc</code>.</li>\n<li><code>exportTo</code> (string): <code>csv</code> or <code>xlsx</code>. See <strong>Exporting</strong> below.</li>\n<li><code>exportColumns</code> (object): required alongside <code>exportTo</code>.</li>\n</ul>\n<h4 id=\"exporting\">Exporting</h4>\n<p>Send <code>exportTo</code> <strong>and</strong> <code>exportColumns</code> together and the response stops being JSON: you get the file itself, streamed, with <code>Content-Disposition: attachment; filename=report.csv</code> (or <code>.xlsx</code>). The export ignores <code>page</code> and <code>perPage</code> and streams every row the filters match.</p>\n<p><code>exportColumns</code> maps a field name to the heading you want above it, and its order is the column order:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>exportColumns[tournamentName]=Tournament Campaign\nexportColumns[tournamentStartDate]=Start Date\nexportColumns[uniqueClicks]=Unique Clicks\nexportColumns[ftd]=FTD\n</code></pre><p>A field the report did not produce is dropped from the file rather than emitted empty.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>A page of tournaments, with the same measures totalled across the whole filtered set in <code>meta.totals</code>.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"meta\": {\n    \"total\": 2,\n    \"perPage\": 15,\n    \"currentPage\": 1,\n    \"lastPage\": 1,\n    \"firstPage\": 1,\n    \"totals\": {\n      \"cpaAmount\": 900,\n      \"ftd\": 46,\n      \"multipleDepositsCustomers\": 19,\n      \"nrc\": 312,\n      \"roi\": 203.06,\n      \"totalGgr\": 14820.5,\n      \"totalNgr\": 11260.75,\n      \"totalRsCost\": 2815.19,\n      \"ttlCost\": 3715.19,\n      \"uniqueClicks\": 8941\n    }\n  },\n  \"data\": [\n    {\n      \"affiliateId\": 142,\n      \"affiliateUsername\": \"northstar_media\",\n      \"cpaAmount\": 600,\n      \"ftd\": 31,\n      \"multipleDepositsCustomers\": 14,\n      \"nrc\": 208,\n      \"roi\": 206.29,\n      \"totalGgr\": 10120.4,\n      \"totalNgr\": 7845.2,\n      \"totalRsCost\": 1961.3,\n      \"tournamentEndDate\": \"2026-09-30\",\n      \"tournamentId\": 41,\n      \"tournamentName\": \"Autumn Clash\",\n      \"tournamentStartDate\": \"2026-08-01\",\n      \"tournamentStatus\": \"active\",\n      \"ttlCost\": 2561.3,\n      \"uniqueClicks\": 6204\n    },\n    {\n      \"affiliateId\": 118,\n      \"affiliateUsername\": \"reeljump\",\n      \"cpaAmount\": 300,\n      \"ftd\": 15,\n      \"multipleDepositsCustomers\": 5,\n      \"nrc\": 104,\n      \"roi\": 195.72,\n      \"totalGgr\": 4700.1,\n      \"totalNgr\": 3415.55,\n      \"totalRsCost\": 853.89,\n      \"tournamentEndDate\": \"2026-08-31\",\n      \"tournamentId\": 44,\n      \"tournamentName\": \"Late Summer Cup\",\n      \"tournamentStartDate\": \"2026-08-10\",\n      \"tournamentStatus\": \"ended\",\n      \"ttlCost\": 1153.89,\n      \"uniqueClicks\": 2737\n    }\n  ]\n}\n</code></pre>\n<p>Field notes:</p>\n<ul>\n<li><code>tournamentId</code>, <code>tournamentName</code> — the tournament the row is about. <code>tournamentName</code> is the tournament's own name, not its campaign's.</li>\n<li><code>tournamentStatus</code> — <code>upcoming</code>, <code>active</code> or <code>ended</code>, read off the database's clock so it always agrees with the dates beside it.</li>\n<li><code>tournamentStartDate</code>, <code>tournamentEndDate</code> — the tournament's own window, <code>yyyy-MM-dd</code>. Not the period you asked for, which the measures are clamped to instead.</li>\n<li><code>affiliateId</code>, <code>affiliateUsername</code> — the affiliate the tournament runs for.</li>\n<li><code>uniqueClicks</code> — clicks on the tournament's link, deduplicated.</li>\n<li><code>nrc</code> — newly registered customers acquired through it.</li>\n<li><code>ftd</code> — of those, the ones who made a first deposit.</li>\n<li><code>multipleDepositsCustomers</code> — of those, the ones who deposited more than once.</li>\n<li><code>totalGgr</code>, <code>totalNgr</code> — gross and net gaming revenue from the customers it acquired.</li>\n<li><code>totalRsCost</code> — the revenue-share the affiliate earned on them; <code>cpaAmount</code> — the CPA earned; <code>ttlCost</code> — what the tournament cost in total, which is what <code>roi</code> is measured against.</li>\n<li><code>roi</code> — <code>(totalNgr / ttlCost - 1) × 100</code>, already a percentage, and <code>0</code> when <code>ttlCost</code> is <code>0</code>.</li>\n<li><code>meta.totals</code> — the same measures over every row the filters match, not just this page. It carries no tournament identity, since a grand total is no single tournament.</li>\n</ul>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Your token is missing, invalid, or has not cleared two-factor verification.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access!\"\n}\n</code></pre>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Your account cannot read the tournament analysis. Reading tournaments and reading their analysis are separate — holding the first does not imply the second.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access\"\n}\n</code></pre>\n<h4 id=\"422-unprocessable-entity\">422 Unprocessable Entity</h4>\n<p>A parameter did not validate — an unrecognised <code>lifecycle</code>, an <code>exportTo</code> that is neither <code>csv</code> nor <code>xlsx</code>, a <code>brand</code> id you cannot see, or a malformed date.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"errors\": [\n    {\n      \"message\": \"The selected lifecycle is invalid\",\n      \"rule\": \"enum\",\n      \"field\": \"lifecycle\"\n    }\n  ]\n}\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>You are sending too many requests. The limit is 600 requests per minute.</p>\n","urlObject":{"path":["workspaces","api","tournaments","analysis"],"host":[""],"query":[{"key":"start","value":"2026-08-01"},{"key":"end","value":"2026-09-30"}],"variable":[]}},"response":[],"_postman_id":"da0feaf9-dc5e-5420-c0ca-1bc5464312d5"},{"name":"Tournament Standings","id":"b1bd50e6-c264-af00-0a27-25956f15617b","request":{"method":"GET","header":[],"url":"/workspaces/api/tournaments/:id/standings","description":"<p>Read a tournament's current standings — the prize ladder with whoever is holding each rung, and when the board was last rebuilt.</p>\n<p>This is what you decide payouts from. Every prize the tournament offers has a row whether or not anyone has qualified for it yet, in position order, so the board reads as a full ladder rather than as a list of current winners — an unclaimed prize is a prize still open, not a row to leave out.</p>\n<p>Addressed by the tournament's <strong>id</strong>, not by the public handle the embeddable widget uses. A tournament that was never published through a link of its own has no handle and is still a row in the table, so this reaches it either way.</p>\n<blockquote>\n<p>This read is never cached. The public leaderboard is served from a one-minute cache; this one is not, because a copy up to a minute old served under a <code>computedAt</code> saying otherwise is the one wrong answer to give someone deciding what to pay out.</p>\n</blockquote>\n<p><strong>The board and the analysis report can disagree, and both are right.</strong> <code>Tournament Analysis Report</code> counts only the players a tournament acquired; the board ranks everyone competing in it. A tournament whose <code>audience</code> is <code>affiliated</code> — competing over the affiliate's existing players — reads zeros in that report while showing a full ladder here.</p>\n<h3 id=\"request\">Request</h3>\n<p>Path parameters:</p>\n<ul>\n<li><code>id</code> (number, required): the tournament's id, as <code>Tournaments</code> or <code>Tournament Analysis Report</code> returns it.</li>\n</ul>\n<p>No query parameters and no request body.</p>\n<h3 id=\"response\">Response</h3>\n<h4 id=\"200-ok\">200 OK</h4>\n<p>The ladder, and its age.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"computedAt\": \"2026-08-11T06:00:04.512+00:00\",\n  \"rows\": [\n    {\n      \"position\": 1,\n      \"rewardType\": \"cash\",\n      \"reward\": null,\n      \"value\": 1000,\n      \"player\": \"joh*****\",\n      \"points\": 184320\n    },\n    {\n      \"position\": 2,\n      \"rewardType\": \"cash\",\n      \"reward\": null,\n      \"value\": 500,\n      \"player\": \"mar*****\",\n      \"points\": 96140\n    },\n    {\n      \"position\": 3,\n      \"rewardType\": \"physical\",\n      \"reward\": \"Weekend for two in Malta\",\n      \"value\": null,\n      \"player\": null,\n      \"points\": null\n    }\n  ]\n}\n</code></pre>\n<p>Field notes:</p>\n<ul>\n<li><code>computedAt</code> — when the board was last rebuilt, as an ISO 8601 timestamp. <code>null</code> until a board has been built at all, which is indistinguishable from one nobody has qualified for. The figures move when the operator imports and at no other time, so this is the only honest freshness marker there is — a board is rebuilt on the import schedule, not on the clock.</li>\n<li><code>rows[].position</code> — the prize's rung, 1-based and contiguous.</li>\n<li><code>rows[].rewardType</code> — <code>cash</code>, <code>bonus_money</code>, <code>free_spins</code> or <code>physical</code>.</li>\n<li><code>rows[].reward</code> and <code>rows[].value</code> — a currency prize (<code>cash</code>, <code>bonus_money</code>) publishes its amount in <code>value</code> and leaves <code>reward</code> null; a described prize (<code>free_spins</code>, <code>physical</code>) publishes its description in <code>reward</code> and leaves <code>value</code> null. The currency figure behind a described prize is the operator's own valuation of it, and it is withheld here as it is on the public board.</li>\n<li><code>rows[].player</code> and <code>rows[].points</code> — both <code>null</code> on a position nobody has qualified for yet, which is how an open prize reads. <code>points</code> is the tournament's ranking measure: turnover staked or winnings returned, depending on the tournament's <code>type</code>.</li>\n<li><code>player</code> is <strong>masked</strong> — at most the first three characters of the nickname, then a fixed run of five asterisks. A short nickname gives up fewer characters and a very short one gives up none at all (<code>*****</code>), so the visible prefix can never amount to most of a name. The masking is applied in the stored column rather than on the way out, so no read path holds the full nickname: it is free text an operator imported and may well be an email address. The asterisk run is a fixed length, so the mask does not publish how long the nickname is.</li>\n</ul>\n<h4 id=\"401-unauthorized\">401 Unauthorized</h4>\n<p>Your token is missing, invalid, or has not cleared two-factor verification.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access!\"\n}\n</code></pre>\n<h4 id=\"403-forbidden\">403 Forbidden</h4>\n<p>Your account cannot read the tournament analysis.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Unauthorized access\"\n}\n</code></pre>\n<h4 id=\"404-not-found\">404 Not Found</h4>\n<p>The id names no tournament you can read. One answer covers all of it — never existed, archived since, or belonging to an affiliate or a brand outside your reach. They are deliberately not told apart: a reader who could distinguish them could sort the tournaments they are kept away from out of a list of ids.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"error\": \"Tournament not found\"\n}\n</code></pre>\n<h4 id=\"429-too-many-requests\">429 Too Many Requests</h4>\n<p>You are sending too many requests. The limit is 600 requests per minute.</p>\n","urlObject":{"path":["workspaces","api","tournaments",":id","standings"],"host":[""],"query":[],"variable":[{"type":"any","key":"id"}]}},"response":[],"_postman_id":"b1bd50e6-c264-af00-0a27-25956f15617b"}],"id":"1e458c8f-dcc7-4920-9181-55971e4d6393","description":"<p>Endpoints for setting up and running tournaments, and for reading how they performed — the campaigns themselves, and the analysis behind them: what each tournament acquired over a period, the month-to-date figures above it, and the live prize ladder with whoever is currently holding each position.</p>\n","_postman_id":"1e458c8f-dcc7-4920-9181-55971e4d6393"}],"event":[{"listen":"prerequest","script":{"id":"316db919-6cd1-40b8-a1b9-f0dcda29e55d","type":"text/javascript","packages":{},"requests":{},"exec":[""]}},{"listen":"test","script":{"id":"de58fcd6-941b-4f80-987e-4118ba0f3f0c","type":"text/javascript","packages":{},"requests":{},"exec":[""]}}],"variable":[{"key":"baseUrl","value":"","type":"default"}]}