Rutba CRM

Ad spend API

Send daily advertising cost per campaign so campaign returns compare spend with the sales each campaign brought.

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 403 with Campaign 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 403 with Campaign returns are not allowed for this role.
  • Use the same campaign names your store orders carry in their campaign tags, such as the utm_campaign value, 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