REST API
The AdvocateLoop API lets you record claims, conversions, refunds, and visits with a simple HTTP POST — from your own backend, or from any tool that can send an HTTP request.
Prefer a no-code tool? See Zapier or Make; both send events through this same API.
What you’ll need
Before configuring anything, gather two values from your AdvocateLoop dashboard:
- API Key — found under Settings → API Keys. Keep this secret; treat it like a password.
- Brand ID — found alongside the API key on the same page.
You’ll send both as HTTP headers on every request.
Plan tiers
Some event types are available on all plans; others require Premier:
| Event | Starter | Growth | Premier |
|---|---|---|---|
Conversions (/track/conversion) | ✓ | ✓ | ✓ |
Refunds (/track/refund) | ✓ | ✓ | ✓ |
Claims (/track/claim) | — | — | ✓ |
Visits (/track/visit) | — | — | ✓ |
If you send a Premier-only event type on a lower-tier plan, the API returns a 403 response with a clear message.
The basics
Every request follows the same shape:
- URL:
https://api.advocateloop.com/api/v1/track/<event>(e.g./api/v1/track/conversion) - Method:
POST - Headers:
Content-Type: application/jsonX-API-Key: <your API key>X-Brand-ID: <your brand ID>
- Body: JSON payload (shape depends on the event type — see below)
If the request succeeds, you’ll get a 200 response with a JSON body like:
{ "success": true, "data": { /* event-specific details */ } }If something goes wrong, you’ll get a 4xx response with an explanation:
{ "success": false, "error": "what went wrong" }Recording a conversion
Use this when someone completes a purchase or other tracked outcome that should count against a referral.
Request
POST https://api.advocateloop.com/api/v1/track/conversionContent-Type: application/jsonX-API-Key: <your API key>X-Brand-ID: <your brand ID>
{ "customer_email": "buyer@example.com", "customer_name": "Jane Doe", "order_id": "ORD-12345", "order_total": 108.00, "tax": 8.00, "shipping": 10.00, "discount": 5.00, "referral_code": "JOHN123", "items": [ { "name": "Vitamin C Serum", "quantity": 2, "unit_price": 32.00, "sku": "VITC-30", "category_path": ["Skincare", "Serums"] }, { "name": "Gift Wrapping", "quantity": 1, "unit_price": 4.00 } ]}Fields
| Field | Required | Notes |
|---|---|---|
customer_email | yes | Customer’s email address |
order_total | yes | What the customer actually paid — items + tax + shipping, after discounts |
tax, shipping, discount | yes | Use 0 if not applicable |
customer_name | no | Customer’s name |
order_id | no | Your internal order identifier, for cross-referencing |
referral_code | no | Include if you know it |
items | no | Line items in the order. Fields below. |
Each entry in items:
| Field | Required | Notes |
|---|---|---|
name | yes | Item or service name |
quantity | no | Units. Defaults to 1 |
unit_price | no | Price per unit. Or send line_total for the line’s total |
sku | no | SKU, plan code, or service code |
category_path | no | Ordered category path, top level first — e.g. ["Skincare", "Serums"] |
image_url | no | Image URL for the item |
Available on all plans.
Recording a refund
Use this when an order is refunded (in full or in part) so the conversion is updated accordingly.
Request
POST https://api.advocateloop.com/api/v1/track/refundContent-Type: application/jsonX-API-Key: <your API key>X-Brand-ID: <your brand ID>
{ "order_id": "ORD-12345", "refund_amount": 50.00, "refund_reason": "Customer changed mind on one item"}Fields
| Field | Required | Notes |
|---|---|---|
order_id | one of¹ | The order_id from the original conversion |
conversion_id | one of¹ | AdvocateLoop conversion ID — an alternative way to identify the conversion |
claim_id | one of¹ | Associated claim ID — another alternative identifier |
refund_amount | no | The amount refunded in this transaction. Omit for a full refund of the remaining amount. |
refund_id | no | Your refund identifier, for cross-referencing |
refund_reason | no | Free-text reason for the refund |
¹ Provide at least one of order_id, conversion_id, or claim_id to identify the conversion.
Refunds don’t include an items array.
Available on all plans.
Recording a claim — Premier only
Use this when a customer first claims a referral (signs up, requests a discount code, etc.) from a system that captures sign-ups outside the widget. Most integrations won’t need it.
Request
POST https://api.advocateloop.com/api/v1/track/claimContent-Type: application/jsonX-API-Key: <your API key>X-Brand-ID: <your brand ID>
{ "referral_code": "JOHN123", "email": "newuser@example.com", "name": "Sam Smith", "phone": "+1-555-0100"}Fields
| Field | Required | Notes |
|---|---|---|
referral_code | yes | The code being claimed |
email | yes | The new user’s email |
name | no | Display name |
phone | no | Contact phone |
items | no | Line items, same fields as the conversion items above |
Premier plan only.
Recording a visit — Premier only
Track a click on a referral link from an external system — e.g. logging clicks from a custom email tool. Most integrations won’t need it.
Request
POST https://api.advocateloop.com/api/v1/track/visitContent-Type: application/jsonX-API-Key: <your API key>X-Brand-ID: <your brand ID>
{ "referral_code": "JOHN123", "landing_page": "https://example.com/products/widget", "utm_source": "newsletter", "utm_medium": "email", "utm_campaign": "spring-sale"}Premier plan only.
Sending from your backend
If you’re sending from your own server (Node, Python, PHP, Ruby, anything that can do HTTP), it’s just a POST with the headers and body above. There’s no SDK — the API is small enough that direct HTTP is the simplest path.
Node.js example
const response = await fetch('https://api.advocateloop.com/api/v1/track/conversion', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.ADVOCATELOOP_API_KEY, 'X-Brand-ID': process.env.ADVOCATELOOP_BRAND_ID }, body: JSON.stringify({ customer_email: 'buyer@example.com', order_id: 'ORD-12345', order_total: 108.00, tax: 8.00, shipping: 10.00, discount: 5.00, referral_code: 'JOHN123' })});
const result = await response.json();if (!result.success) { console.error('Failed to record conversion:', result.error);}Python example
import requestsimport os
response = requests.post( 'https://api.advocateloop.com/api/v1/track/conversion', headers={ 'Content-Type': 'application/json', 'X-API-Key': os.environ['ADVOCATELOOP_API_KEY'], 'X-Brand-ID': os.environ['ADVOCATELOOP_BRAND_ID'] }, json={ 'customer_email': 'buyer@example.com', 'order_id': 'ORD-12345', 'order_total': 108.00, 'tax': 8.00, 'shipping': 10.00, 'discount': 5.00, 'referral_code': 'JOHN123' })
result = response.json()if not result.get('success'): print(f"Failed: {result.get('error')}")Common issues
“401 Unauthorized” / “Invalid API key” — double-check the X-API-Key header value. Make sure there’s no leading/trailing whitespace and that you’re using the key from the right environment (production vs sandbox if you have separate keys).
“403 Feature not available” on claim/visit — these event types require Premier. Either upgrade your plan, or restructure your integration to use conversions/refunds (which are available on all plans).
“400 Bad Request” with field-specific error — the API tells you which field is missing or invalid. Read the error field of the response.
Numbers arriving as strings — common with some no-code HTTP tools. JSON numbers must not be quoted. If order_total: "108.00" shows up in your request body, your tool is escaping the number. Check your tool’s settings for “data pass-through” or “raw JSON” options.
Webhook fires multiple times — most automation tools retry failed deliveries. Use the order_id field for conversions: if you send the same order_id twice, AdvocateLoop won’t double-count it.
Security best practices
- Treat your API key like a password. Don’t commit it to source control. Don’t share it in support tickets unless explicitly asked (and rotate it after).
- Use HTTPS. All AdvocateLoop endpoints require it; HTTP requests are rejected.
- Rotate keys if you suspect a leak or when a team member with access leaves.
- Limit access. Give each integration its own API key if you can, so you can revoke one without breaking others.