OVERVIEW
What this API is for
A brand runs an affiliate program on Trendly. Creators join, the brand approves them, and each approved creator gets a personal discount code. When a customer uses that code in your store, the creator earns commission, and Trendly pays it out on the schedule the brand sets.
Trendly needs one thing from you: the orders.
What you build
POST /v1/orders when an order is placed, and again whenever it changes. That is the integration. Approvals, codes, commission rules, caps, hold periods, clawbacks, payout runs, statements and both dashboards are handled for you.Two rules apply to everything you send.
| RULE | WHY |
|---|---|
Money is a decimal string. "240.00", never 240.00. | JSON numbers are doubles. 240.10 round-trips as 240.09999999999999. Across a few thousand orders a month that becomes a gap between your books and ours that nobody can trace. |
| Writes are matched on your order id. | Retry freely. Replay a whole day. Run a webhook and a nightly job side by side. The same order is stored once and earns commission once. That is a database constraint, not careful code. |
Not writing code?
QUICKSTART
From nothing to a working call
Step 1: Get a test key
In the dashboard, open Developers, API keys and create a test key. It starts with
trk_test_and is shown once, so copy it somewhere safe. No Trendly login? Your Trendly contact can issue one.Step 2: Check it works
GET /v1/pingsays which program the key reached and the three settings that decide what a creator takes home.cURL
curl https://api.trendly.com.sa/v1/ping \ -H "Authorization: Bearer trk_test_YOUR_KEY"Response
{ "ok": true, "environment": "test", "key_name": "Calo checkout", "scopes": ["codes:read", "orders:read", "orders:write", "program:read"], "program": { "id": "3b0c8f14-0a2e-4c77-9b31-6f5f2a1d4e88", "name": "Calo Market Partners", "currency": "SAR", "status": "active", "commission_basis": "net_ex_vat_ex_shipping", "hold_period_days": 14, "payout_cycle_days": 30 }, "server_time": "2026-09-18T09:14:22Z" }Step 3: Report an order
This is the whole integration.
cURL
curl -X POST https://api.trendly.com.sa/v1/orders \ -H "Authorization: Bearer trk_test_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "order_id": "ord_1001", "discount_code": "SARA10", "status": "confirmed", "currency": "SAR", "subtotal_amount": "240.00", "discount_amount": "24.00", "shipping_amount": "15.00", "tax_amount": "34.65", "total_amount": "265.65", "placed_at": "2026-09-08T11:42:03+03:00" }'Response
{ "mode": "test", "recorded": true, "created": true, "updated": false, "order_id": "ord_1001", "status": "confirmed", "attribution": { "attributed": true, "code": "SARA10", "creator_name": "Sara A.", "membership_id": "9c1e77a2-4b3d-4e91-8f22-11c0a7d5b6e3" }, "commission": { "basis_amount": "216.00", "basis": "net_ex_vat_ex_shipping", "rate_type": "percentage", "rate_value": "10.00", "rate_source": "program_default", "amount": "21.60", "currency": "SAR", "capped": false, "payable_after": "2026-09-22T08:42:03Z" }, "warnings": [ "Recorded as a test order. It appears in Trendly marked as test, and is excluded from the program's figures and from every payout. Send the same request with a trk_live_ key to record it for real." ] }Check the commission block
basis_amountis what the rate was applied to: 240.00 of goods minus the 24.00 discount, because this program leaves out VAT and shipping.rate_sourcesays which rate won, andpayable_afteris when the hold period ends. These are the numbers that get disputed at month end, so check them now against an order you can work out by hand.A test key stores the order marked as test. Open the program's Orders in the dashboard and you will see it there, left out of every figure and payout.
Step 4: Go live
Create a live key (
trk_live_) and swap it in. The same request now counts for real. That is the only difference between the two environments.
AUTHENTICATION
Keys
Send your key on every request as a bearer token. If your gateway strips the Authorization header, send the same value as X-API-Key instead.
Either header
Authorization: Bearer trk_live_7Qa4xN2pLmK8vR3sT1wY6z
X-API-Key: trk_live_7Qa4xN2pLmK8vR3sT1wY6z| KEY | WHAT IT DOES |
|---|---|
trk_test_… | Validates, attributes and stores the order, marked as test. Same parser, code lookup and commission calculation as live. Test orders show in Trendly and are left out of every figure and payout. The brand can clear them from the dashboard at any time. |
trk_live_… | Counts for real. Creators get paid from these calls. |
Keys are shown once
Scopes
A key carries some or all of four scopes. A new key gets all four unless you narrow it. Use codes:read on its own for anything that runs in a checkout: that is the most exposed place a key lives, and a key that can only answer "is this code valid?" is worth little to whoever takes it.
| SCOPE | GRANTS |
|---|---|
orders:write | Report orders, one at a time or in batches. This is the one that moves money. |
orders:read | Read back an order and its commission. |
codes:read | List codes, read one, and validate one against a cart. |
program:read | Reading the program's settings. GET /v1/ping returns them to any valid key today. |
Response headers
| HEADER | MEANS |
|---|---|
X-Request-Id | The id of this call. Also in every error body as request_id. Quote it to Trendly, or look the call up with GET /v1/events/{request_id}. |
X-Trendly-Environment | test or live, after the key is accepted. A quick way to see which key a deployed service is really using. |
PROGRAMS
Which program a call is about
A key belongs to your Trendly account, and an account can run more than one affiliate program. Every call lands in exactly one program, chosen in this order.
| WHEN | THEN |
|---|---|
| 1. The key is pinned to a program | Every call uses that program. You send nothing. Naming a different program returns 403 program_not_allowed. |
| 2. The call names a program | program_id in the body (orders and batches), ?program= on the query string, or an X-Trendly-Program header. A program your account cannot reach returns 404. |
| 3. The account has one program | That one. Most integrations never send a program id. |
Otherwise the call is refused with 400 program_required, and the hint lists your programs with their ids. An account with no program yet gets 400 no_program.
Naming the program
# In the body, for POST /v1/orders and /v1/orders/batch:
# { "program_id": "3b0c8f14-0a2e-4c77-9b31-6f5f2a1d4e88", "order_id": "ord_1001", ... }
# Or as a header, on any call:
curl https://api.trendly.com.sa/v1/codes \
-H "Authorization: Bearer trk_live_YOUR_KEY" \
-H "X-Trendly-Program: 3b0c8f14-0a2e-4c77-9b31-6f5f2a1d4e88"
# Or on the query string:
curl "https://api.trendly.com.sa/v1/codes?program=3b0c8f14-0a2e-4c77-9b31-6f5f2a1d4e88" \
-H "Authorization: Bearer trk_live_YOUR_KEY"Find the id with ping
GET /v1/ping returns the program a key reached, with its id. Call it with the same header or query string you plan to use and check the name.ORDERS
Reporting an order
/v1/orders
Call this when an order is placed, and again whenever it changes: confirmed, cancelled, refunded, partially refunded, flagged. Same call, same order_id. Trendly matches on it and updates the order it already has.
Reporting changes is not optional
confirmed and never update it, Trendly pays commission on it after a refund you knew about. This is the most common way an affiliate integration loses a merchant money.Fields
| FIELD | REQUIRED | NOTES |
|---|---|---|
order_id | Yes | Your permanent order identifier. Never reused. This is what makes a retry safe. |
status | Yes | One of the six under Order statuses. Anything else returns 400. |
subtotal_amount | Yes | Goods value before discount, without VAT and shipping. |
total_amount | Yes | What the customer actually paid. |
discount_code | Strongly | The code as applied. Matched without regard to case. Without it the order is stored but belongs to nobody. |
currency | Strongly | ISO 4217. Defaults to the program's currency, with a warning. An order in another currency returns 400 currency_mismatch rather than being converted. |
discount_amount | Strongly | What the code took off. Part of the commission basis under most program settings. |
shipping_amount | Strongly | "0.00" if none. |
tax_amount | Strongly | VAT charged. |
placed_at | Strongly | ISO 8601 with an offset. Needed for backfills. Without it Trendly uses the time of the call. |
refunded_amount | On refund | Total refunded so far, not the latest refund. |
status_changed_at | On updates | When the status changed on your side. An update older than what Trendly holds is ignored. updated_at works too. |
order_number | No | Human-readable reference, shown in support screens. |
customer_ref | No | Pseudonymous only. See Privacy. |
is_new_customer | No | True for the customer's first order with you. A program that pays more for a new customer pays that rate on it; false or absent earns the standard rate. Also drives the new and returning split in the dashboard. |
commissionable_amount | No | Only read when the program's commission basis is merchant_declared. |
program_id | Sometimes | Only when your key can reach more than one program. See Which program. |
metadata | No | Any object. Stored with the order, never parsed. |
Common aliases are accepted
id for order_id; code, coupon, coupon_code or promo_code for discount_code; total for total_amount; subtotal for subtotal_amount. Unknown fields are ignored, so your own extras will not break the call.Warnings
A successful response may include a warnings array. The order was accepted, so never retry because of a warning, but each one is worth fixing. Trendly warns when amounts do not add up to subtotal − discount + shipping + tax = total, when a timestamp has no UTC offset (Asia/Riyadh is assumed), when the currency was missing, when placed_at was missing, and when a refunded order arrives without a refunded amount.
STATUSES
Order statuses
Map your own statuses onto these six and send your Trendly contact the mapping. confirmed matters most: it starts the clock on a creator's earnings.
| STATUS | MEANS | EFFECT ON COMMISSION |
|---|---|---|
pending | Placed, not yet paid or accepted | Nothing accrues |
confirmed | Paid and accepted. The state that counts. | Accrues, held until the hold period ends |
cancelled | Cancelled before fulfilment | Reversed |
refunded | Fully refunded after fulfilment | Reversed in full |
partially_refunded | Partly refunded | Reversed in proportion |
fraud | Flagged as fraudulent | Reversed. The creator is flagged for review. |
Unknown statuses return 400
REFUNDS
Refunds and cancellations
Send the same order_id with the new status. Commission is held through the program's hold period before it can be paid, so most refunds arrive before any money moves.
Partial refund
# Same order_id, new status. Trendly reverses the refunded share of the commission.
# If it was already paid out, the reversal comes off the creator's next payout.
curl -X POST https://api.trendly.com.sa/v1/orders \
-H "Authorization: Bearer trk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"order_id": "ord_1001",
"status": "partially_refunded",
"currency": "SAR",
"subtotal_amount": "240.00",
"discount_amount": "24.00",
"shipping_amount": "15.00",
"tax_amount": "34.65",
"total_amount": "265.65",
"refunded_amount": "120.00",
"placed_at": "2026-09-08T11:42:03+03:00",
"status_changed_at": "2026-09-14T10:02:00+03:00"
}'If a refund arrives after a payout has gone out, nothing is rewritten. The original commission stands and a reversal is added, which comes off the creator's next payout. That is what keeps a statement from three months ago reconciling.
Order of delivery does not matter
status_changed_at with each update. An update that describes an older state than the one Trendly holds is ignored, so a late retry cannot undo a refund. Sending the same update twice changes nothing.BATCH
Batches and backfill
/v1/orders/batch
Up to 500 orders per call, for backfills and nightly reconciliation. Every order gets its own result, so one malformed row does not reject the rest, and the response names the row to fix. A batch belongs to one program: put program_id at the top level of the body if your key needs one.
Request
# For backfills and nightly reconciliation. Up to 500 orders per call.
# Each order gets its own result, so one bad row does not reject the rest.
curl -X POST https://api.trendly.com.sa/v1/orders/batch \
-H "Authorization: Bearer trk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"orders": [
{
"order_id": "ord_1002",
"discount_code": "SARA10",
"status": "confirmed",
"currency": "SAR",
"subtotal_amount": "180.00",
"discount_amount": "18.00",
"shipping_amount": "0.00",
"tax_amount": "24.30",
"total_amount": "186.30",
"placed_at": "2026-09-09T19:05:00+03:00"
},
{
"order_id": "ord_1003",
"discount_code": "NOURA10",
"status": "pending",
"currency": "SAR",
"subtotal_amount": "95.00",
"total_amount": "SAR 95",
"placed_at": "2026-09-09T21:40:00+03:00"
}
]
}'The response is 200 whenever the batch itself was read, even if some orders were rejected. Each entry carries orderId and an HTTP-style status, then either the result a single call would have returned, attribution and commission included, or errorCode, message and hint. The entry's own keys are camelCase; the keys inside result are snake_case, as in the single-order answer.
Response
{
"mode": "live",
"accepted": 1,
"rejected": 1,
"results": [
{
"orderId": "ord_1002",
"status": 200,
"result": {
"mode": "live",
"recorded": true,
"created": true,
"updated": false,
"order_id": "ord_1002",
"status": "confirmed",
"attribution": { "attributed": true, "code": "SARA10", "creator_name": "Sara A.", "membership_id": "9c1e77a2-4b3d-4e91-8f22-11c0a7d5b6e3" },
"commission": { "basis_amount": "95.00", "basis": "net_ex_vat_ex_shipping", "rate_type": "percentage", "rate_value": "10.00", "rate_source": "program_default", "amount": "9.50", "currency": "SAR", "capped": false, "payable_after": "2026-09-23T21:40:00Z" }
}
},
{
"orderId": "ord_1003",
"status": 400,
"errorCode": "invalid_amount",
"message": "'total_amount' is not a decimal amount: SAR 95",
"hint": "Send money as a decimal string. Use \"95.00\", not 95.0 and not \"SAR 95\"."
}
]
}More than 500 orders returns 400 batch_too_large, and an empty list returns 400 empty_batch.
Backfilling is safe
CODES
Discount codes
/v1/codes
When the brand approves a creator, Trendly issues their code as pending: the code exists in Trendly, but a customer cannot use it until it exists at your checkout. Your part is three calls, and until you make the last one the creator is told their code is on its way, not the code itself, so nobody shares a code that fails at checkout.
Step 1: List the codes to add
GET /v1/codes?status=pendingreturns each code with its discount and creator.Step 2: Create them in your discount engine
Use the code text exactly, with its
discount_typeanddiscount_value.Step 3: Confirm each one
POST /v1/codes/{code}/confirm. The code goes live, any orders that already used it are credited to its creator, and the creator is told it is ready to share. Confirming twice is harmless. A real order reported with a pending code confirms it too.
The three calls
# 1. The codes Trendly issued to newly approved creators, not yet in your checkout.
curl "https://api.trendly.com.sa/v1/codes?status=pending&limit=200" \
-H "Authorization: Bearer trk_live_YOUR_KEY"
# 2. Create each one in your discount engine, with its discount_type and discount_value.
# 3. Tell Trendly it exists. The code goes live and its creator is told it is ready.
curl -X POST https://api.trendly.com.sa/v1/codes/SARA10/confirm \
-H "Authorization: Bearer trk_live_YOUR_KEY"/v1/codes/{code}/confirm
Needs codes:read, the same key that lists the codes. Answers with the code, now active. A code that is disabled or failed cannot go live and answers 409 code_not_confirmable. If nobody on the brand's side writes code, the dashboard does the same: each program's Creators tab lists the codes to add, with Mark added.
Keeping your engine in step
To mirror every change (new codes, and codes switched off when a creator leaves or a code is replaced), poll GET /v1/codes with updated_since set to the time of your last successful run.
Request
# Mirror Trendly's codes into your own discount engine.
# updated_since, not a creation date: a code disabled this morning comes back
# even though it was created months ago.
curl "https://api.trendly.com.sa/v1/codes?updated_since=2026-09-01T00:00:00%2B03:00&limit=200" \
-H "Authorization: Bearer trk_live_YOUR_KEY"updated_since, not a creation date
limit defaults to 100, up to 200. When has_more is true, pass next_cursor back unchanged as cursor. The cursor is stable, so codes changing while you page cannot make you skip or repeat one.
Kinds
| KIND | MEANING |
|---|---|
member | A creator's permanent personal code. Shared publicly, no usage limit. |
campaign | A code for one campaign, usually at a better discount than the personal one, often with an end date. |
gift_single_use | A one-time code for a free product on a campaign. Treat it as a separate object. Handling it like a member code is how a free-product code ends up posted to 40,000 followers. |
Filter with ?kind=, or leave it out to get all three. Filter by state with ?status=.
Statuses
| STATUS | MEANING |
|---|---|
pending | Issued to an approved creator, waiting for you. Create it in your engine with its discount, then confirm it. |
active | Live at checkout. If you mirror codes, it exists in your engine. |
disabled | Its creator was removed from the program, or the code was replaced. It stays in this list. Switch it off in your engine too. |
failed | Could not be created in the brand's store. Validation reports it as not_provisioned. |
/v1/codes/{code}
One code, by its text. Returns 404 not_found if the program has no such code.
CODES
Where codes come from
Every approved creator gets one personal code. It can start in Trendly or in your own coupon engine, and you can mix the two within a program.
| WHO MAKES IT | HOW | WHAT YOU BUILD |
|---|---|---|
| Trendly | When the brand approves a creator, Trendly makes their code from the program's code template, usually from the creator's handle. | List GET /v1/codes?status=pending, create each code in your engine, then confirm it with POST /v1/codes/{code}/confirm. Or check codes live at checkout with POST /v1/codes/validate. |
| Your coupon engine | You create the code in your own engine, then register it on the creator in the Trendly dashboard. | Nothing in the API. Orders that already used the code are credited to the creator when it is registered. |
A code Trendly does not know yet
Replacing a code
The brand can reissue a creator's code from the dashboard. The old code is disabled, not deleted, and stays in GET /v1/codes with status disabled. Orders keep the code that brought them in, so last month's figures do not move when a code is replaced today. If you mirror codes, switch off the disabled ones.
Removing a creator from the program disables every code they hold the same way: their personal code and any code a campaign gave them. A code's text is unique within a program across every status, disabled included, so it is never issued again, to anyone. A returning creator or a replaced code always gets new text.
The API does not make or change codes
codes:write scope. Confirming only says a code now exists at your checkout. Codes are replaced in the dashboard and reach you through GET /v1/codes.CHECKOUT
Validating a code at checkout
/v1/codes/validate
If your checkout does not keep its own copy of the codes, ask Trendly in real time. Send the code and, if you have it, the cart total as cart_total. The answer says whether the code can be used and how much to take off.
cURL
curl -X POST https://api.trendly.com.sa/v1/codes/validate \
-H "Authorization: Bearer trk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "code": "SARA10", "cart_total": "240.00" }'Always 200, even for a bad code
valid: false with a reason to branch on and a message you can show the shopper. A 404 would mean wrapping this in a try/catch, which is how a network blip ends up telling a real customer their real code is bad.Each rejection has its own reason: unknown_code, disabled, not_provisioned, not_yet_valid, expired, below_minimum. A shopper who mistyped one character and one whose code genuinely ran out should not be sent down the same path.
Use the discount_amount in the answer rather than working out your own. It is rounded half up to the halala. Rounding the other way puts you a halala out on some orders, which shows up later as a reconciliation gap nobody can explain.
ERRORS
When something is wrong
Every failure has the same shape, including authentication failures.
400 Bad Request
{
"error": {
"code": "invalid_amount",
"message": "'total_amount' is not a decimal amount: SAR 240",
"field": "total_amount",
"hint": "Send money as a decimal string. Use \"240.00\", not 240.0 and not \"SAR 240\". JSON numbers are doubles and lose halalas at volume.",
"documentation_url": "https://trendly.com.sa/developers/affiliate#orders"
},
"request_id": "8f14d2a1-7c3b-4e55-9a20-3d6b1f0c9e47"
}Branch on error.code; it is stable. error.message and error.hint are written for whoever reads your logs, error.field names the part of your payload that is wrong, and documentation_url links to the section of this page that explains it.
| HTTP | MEANS | RETRY? |
|---|---|---|
| 400 | The request cannot be used | No. Fix it and resend. |
| 401 | Key missing, malformed, unknown, revoked or expired | No. The code says which. Fix the key. |
| 403 | Valid key, not allowed to do this | No. Use a key with the right scope or program. |
| 404 | No such object for this key | No. Check the id, the key and the program. |
| 409 | Idempotency-Key reused with a different body | Not with that key. Use a new one. |
| 5xx | Trendly failed | Yes. Every write is safe to repeat. Quote the request id if it persists. |
Error codes
| ERROR.CODE | HTTP | WHAT TO DO |
|---|---|---|
invalid_json | 400 | The body is not valid JSON, or a field has the wrong JSON type. Check for trailing commas and unquoted money. |
empty_body | 400 | Send a JSON object describing one order. |
missing_field | 400 | Add the field named in error.field. order_id, status, subtotal_amount and total_amount are required. |
invalid_amount | 400 | Send money as a decimal string: "240.00". |
negative_amount | 400 | Amounts are never negative. Report a refund with a refund status and a positive refunded_amount. |
amount_too_large | 400 | At most ten digits before the decimal point. |
invalid_timestamp | 400 | Use ISO 8601 with an offset, such as 2026-09-08T11:42:03+03:00. In a query string, encode + as %2B. |
unknown_status | 400 | Use one of the six order statuses. |
currency_mismatch | 400 | The order's currency is not the program's. A program settles in one currency; orders are not converted. |
pii_in_customer_ref | 400 | Send a hash or an internal id, not an email or phone number. See Privacy. |
order_rejected | 400 | The order could not be stored. The message says why; fix it and resend. |
empty_batch | 400 | Send {"orders": [ ... ]} with at least one order. |
batch_too_large | 400 | Split it into pages of 500. |
program_required | 400 | Your account has several programs. Name one; see Which program. |
no_program | 400 | The account has no affiliate program yet. Create one in Trendly; the key is fine. |
invalid_program | 400 | Program ids are UUIDs. |
invalid_kind | 400 | Use member, campaign or gift_single_use, or leave kind out. |
invalid_cursor | 400 | Pass next_cursor back exactly as you got it. |
invalid_parameter | 400 | A query parameter is the wrong type. Check it against the docs. |
idempotency_key_too_long | 400 | Keep Idempotency-Key to 128 characters. A UUID is ideal. |
missing_credentials | 401 | Send the key as Authorization: Bearer trk_… or X-API-Key. |
malformed_token | 401 | Keys start with trk_live_ or trk_test_. Check you did not send a dashboard session token or half a key. |
invalid_token | 401 | No such key. Check it was copied whole and has not been rotated away. |
revoked_token | 401 | The key was revoked. Create a new one and swap it in. |
expired_token | 401 | The key expired. Create a replacement. |
insufficient_scope | 403 | Use a key with the scope named in the message. |
program_not_allowed | 403 | This key is pinned to another program. Drop program_id, or use a key that is not pinned. |
not_found | 404 | No such order, code, request or program for this key. Check you are using the right key and program. |
idempotency_key_reused | 409 | The Idempotency-Key was used before with a different body. Use a new key for a new request. |
internal_error | 500 | Trendly failed. Safe to retry. If it keeps failing, send the request id. |
Idempotency
Optional. Send an Idempotency-Key header (up to 128 characters) on a write and a retry with the same key and the same body gets the original response back. The same key with a different body returns 409. Writes are already matched on order_id, so this is for your retry logic, not for the ledger: without it a retry still cannot pay twice, it just reports created: false the second time.
TESTING
Testing, and what go-live looks like
A test key runs the same code as a live one: the same parser, code lookup, commission basis and rate chain. There is no fixture data and no mock. A clean 200 from a test key proves your payload works, not that a sandbox was willing to accept it.
Test orders are stored and appear in Trendly marked as test. Send one, then open the program's Orders in the dashboard and look at it. Test orders are left out of the program's figures and every payout, and the brand can clear them from Developers whenever they want a clean slate.
Replay from the dashboard
/v1/events
Your own call history for the last 30 days, with request and response bodies, for the key's environment. It is the same log the brand sees in Trendly, reachable with your key, so you do not need a Trendly login to debug your integration. Filter with failures_only=true and search; limit defaults to 25. To page back, pass the next_before you got as before.
Your last ten failures
curl "https://api.trendly.com.sa/v1/events?failures_only=true&limit=10" \
-H "Authorization: Bearer trk_test_YOUR_KEY"/v1/events/{request_id}
One call in full, by the id from its X-Request-Id header.
Before you switch to a live key
| CHECK | WHY IT MATTERS |
|---|---|
| An order reports and attributes to the right creator | Confirms your code field reaches Trendly and matches. |
| The commission block matches what you expect | Agree the basis, rate and rounding while it is still cheap to change. |
| A cancellation reverses it | Proves your change events reach Trendly, not just your new orders. |
| A partial refund reverses in proportion | The case most integrations forget. |
| The same order sent twice changes nothing | Proves your retries are safe. |
| An order placed at 23:50 lands on the right day | Catches timestamps sent without an offset. |
| A lowercase code still attributes | Customers do not type codes the way your admin stores them. |
Run one cycle without paying
PRIVACY
What Trendly does and does not want
Trendly does not want customer personal data. No names, emails, phone numbers or addresses, in any field. A customer_ref that looks like an email or a phone number is rejected with 400 pii_in_customer_ref, not stored.
customer_ref must be pseudonymous: a hash or an internal key, the same for each customer and meaningless outside your system. It is used for the new and returning split and to spot a creator buying through their own code. Leave it out if you prefer; you lose those two features and nothing else.
Order data reaches the brand that owns the program. The creator whose code was used sees their own orders only: date, status, order value and their commission. Never another creator's orders, and never anything about the customer. Keys are stored hashed and kept out of logs.
REFERENCE
Everything, in one place
| ENDPOINT | SCOPE | DOES |
|---|---|---|
GET /v1/ping | Any key | Check a key and read the program it reached. |
POST /v1/orders | orders:write | Report one order. The only endpoint you must use. |
POST /v1/orders/batch | orders:write | Report up to 500, with a result for each. |
GET /v1/orders/{order_id} | orders:read | Read back what Trendly stored, with its commission. |
GET /v1/codes | codes:read | List codes to mirror into your store. |
GET /v1/codes/{code} | codes:read | Read one code. |
POST /v1/codes/{code}/confirm | codes:read | Say a pending code now exists at your checkout. |
POST /v1/codes/validate | codes:read | Check a code against a cart, at checkout. |
GET /v1/events | Any key | Your own call history, with bodies. |
GET /v1/events/{request_id} | Any key | One call in full. |
GET /v1/openapi.json | No key | The machine-readable spec. |
Something not covered here?
Send your Trendly contact the request id from a failing call. Trendly can then see exactly what you sent and exactly what came back, which usually settles it in one message. The full contract is in the OpenAPI spec, where the Sales API goes by its older name, the Trendly Partner API.
