Campaign returns compare what each advertising campaign cost with the store sales it brought. You can type the daily cost by hand in the app, or send it automatically from a script in your ad platform with this endpoint. Sending the same day again replaces the earlier figure, so a script can safely resend the last few days every time it runs.
Before you start
- The plan must include both API access and Campaign returns. Without Campaign returns the endpoint answers
403withCampaign returns are not in this plan. - Use a Read and write key created by an owner or administrator. A key made by a member or viewer gets
403. - If the key's creator has a custom role that blocks Campaign returns, the endpoint answers
403withCampaign returns are not allowed for this role. - Use the same campaign names your store orders carry in their campaign tags, such as the
utm_campaignvalue, so spend and sales line up.
Send ad spend
POST /ad-spend
The body has one field, rows, a list of 1 to 1000 daily figures:
| Field | Required | Rules |
|---|---|---|
campaign |
Yes | The campaign name, up to 120 characters |
source |
No | Where the ad ran, such as google, up to 120 characters |
spent_on |
Yes | The day, YYYY-MM-DD. Not in the future, and no more than 399 days before today |
amount |
Yes | What the day cost, as a string such as "120.50" or a number. Not negative. A string may have at most two decimals; a number is rounded to two decimals |
currency |
Yes | USD, EUR, GBP, TRY, SAR or AED |
curl -X POST https://rutbacrm.com/api/v1/ad-spend \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"rows": [
{"campaign": "ramadan-sale", "source": "google", "spent_on": "2026-11-06", "amount": "120.50", "currency": "SAR"},
{"campaign": "ramadan-sale", "source": "google", "spent_on": "2026-11-07", "amount": "30.00", "currency": "SAR"}
]}'
The response is 200 with two counts:
{
"data": {
"saved": 2,
"unchanged": 0
}
}
| Count | Meaning |
|---|---|
saved |
Rows that added a new figure, replaced an earlier one, or removed one. A 0 for a day with no figure is also counted here, though nothing is stored |
unchanged |
Rows whose figure was already stored with the same amount |
How rows are matched
A row is the same as an earlier figure when the campaign, the day, the currency and the source all match. Campaign names are compared without regard to capital letters or surrounding spaces, so Ramadan-Sale and ramadan-sale are one campaign. A row with no source only matches earlier figures with no source.
| You send | Result |
|---|---|
| A day that has no figure yet | The figure is added |
| The same day with the same amount | Nothing changes. Counted as unchanged |
| The same day with a new amount | The old figure is replaced. Counted as saved |
The same day with an amount of 0 |
The old figure is removed. Counted as saved |
A day that has no figure yet, with an amount of 0 |
Nothing is stored. Counted as saved |
Replaced and removed figures are kept as voided entries rather than deleted.
Errors
The request is all or nothing. If any row is invalid, nothing is saved and the response is 422:
| Error key | Cause |
|---|---|
rows |
rows is missing, empty, not a list, or has more than 1000 rows. The message is Send between 1 and 1000 rows. |
rows.N.amount |
The amount in row N (counting from 0) is negative, or is a string with more than two decimals |
campaign, source, spent_on, amount, currency |
A required field is missing, a text is too long, the date is in the future or more than 399 days old, or the currency is not supported. These keys carry no row number |
Fix the row and send the whole request again.
A daily script
A typical setup runs once a day in your ad platform's scripting tool and sends yesterday and the six days before it, so late corrections from the ad platform are picked up. Because each day replaces itself, resending costs nothing. Keep each run under 1000 rows, and if you track many campaigns, split the days across several requests.
Good to know
- Spend is stored per day. There is no hourly figure.
- The figures you send appear in the app's campaign returns report next to the spend entered by hand, and can be removed there by owners and administrators.
- A figure is never converted between currencies. Send spend in the currency your store sells in, or the report will show the currencies separately.
Last updated 2026-11-08