Send spend
Sends daily ad spend for platforms MTA has no connector for, such as podcasts or a regional network.
https://mta.growthdept.ai/api/v1/spendRequest#
curl -X POST "https://mta.growthdept.ai/api/v1/spend" \
-H "Authorization: $MTA_API_KEY" \
-H "Data-Client-ID: $MTA_BRAND_ID" \
-H "Content-Type: application/json" \
-d '[
{
"platform": "podcast",
"date": "2026-11-05",
"currency": "USD",
"spend": "350.00",
"campaign_id": "fall-reads",
"campaign_name": "Fall reads"
},
{
"platform": "podcast",
"date": "2026-11-06",
"currency": "USD",
"spend": "350.00",
"campaign_id": "fall-reads",
"campaign_name": "Fall reads"
}
]'Response#
{ "message": "Spend rows upserted", "count": 2 }Authentication#
Send the API key in Authorization and the brand id in Data-Client-ID. The key needs the spend:write scope.
| Header | Type | Description |
|---|---|---|
Authorizationrequired | string | The API key. See Authentication. |
Data-Client-IDrequired | uuid | The brand id. |
Content-Type | string | application/json. |
Body#
A JSON array of 1 to 10,000 daily rows. A field this table does not list is refused.
| Field | Type | Description |
|---|---|---|
platformrequired | string | A lower-case platform key of 2 to 40 characters: letters, digits and _, starting with a letter. For example podcast or snapchat, or one of the Growth Dept platform keys. |
daterequired | string (date) | The day the spend belongs to, YYYY-MM-DD. |
currencyrequired | string | Three capital letters: the currency the amounts are in. |
spendrequired | number or string | Spend for the day. A decimal string such as "350.00" is accepted. |
impressions, | integer | Delivery counts, 0 or more. |
conversions, | number or string | The platform's own results. They are stored with the row and do not change MTA's attribution. |
conversion_ | string | Three capital letters: the currency conversion_value is in, when it is not currency. MTA converts the value into currency at the row's date. Leave it out when they are the same. |
account_ | string | Ids, up to 200 characters each, so spend can be read by account, campaign, ad set and ad. |
campaign_ | string | Names, up to 500 characters each, shown in reports beside the ids. |
campaign_ | string | The campaign's, ad set's or ad's status on the platform now, such as ACTIVE or PAUSED, up to 40 characters. ACTIVE, ENABLED and ENABLE read as Active in reports; any other value reads as Inactive. Leave it out to keep the status sent before. |
Response fields#
| Field | Type | Description |
|---|---|---|
count | integer | Rows written. |
Status codes#
| Status | Meaning | Body |
|---|---|---|
| 200 | The rows were written. | |
| 400 | The body is not a JSON array of valid rows. issues lists the first 50 problems, each with its path, such as 0.date. | {
"detail": "invalid input",
"error": "invalid input",
"issues": [{ "path": "0.date", "message": "Invalid ISO date" }]
} |
| 401 | No key, an unknown or revoked key, or a key that belongs to another brand than Data-Client-ID. A key with the right brand but without spend:write gets 403 instead. | {
"detail": "unauthenticated: send the API key in Authorization and the brand id in Data-Client-ID",
"error": "unauthenticated: send the API key in Authorization and the brand id in Data-Client-ID"
} |
| 403 | The key is valid but lacks spend:write. | {
"detail": "this API key lacks the spend:write scope",
"error": "this API key lacks the spend:write scope"
} |
| 409 | A platform in the request has a live connection for the brand, so its spend already comes from the connector. Nothing was written. | {
"detail": "spend for snapchat comes from the brand's connected ad account; it is not taken through this API",
"error": "spend for snapchat comes from the brand's connected ad account; it is not taken through this API"
} |
| 422 | No exchange rate yet for a row's currency, or its conversion_value_currency, on its date. MTA has asked for the rate; send the same rows again later. Nothing was written. | {
"detail": "no ECB rate for GBP to USD on or up to 7 days before 2026-11-05; run fx_pull for that date or add the rate to ops.fx_rates",
"error": "no ECB rate for GBP to USD on or up to 7 days before 2026-11-05; run fx_pull for that date or add the rate to ops.fx_rates",
"retry": "the rate has been requested; send the rows again later"
} |
| 500 | Something failed on our side. Send the same request again later; spend rows upsert, so a retry is safe. | { "detail": "Internal server error.", "error": "Internal server error." } |
Sending a row again#
Rows upsert on platform, date, account_id, campaign_id, adset_id and ad_id, with a missing id counting as empty. To correct a day, send its row again with the same keys. Rows you do not send are kept.
Currency#
Amounts are converted once to the brand's currency, at the European Central Bank rate for the row's date or the latest rate up to 7 days before it. When there is none, the request gets 422 and MTA asks for the rate.
Every amount on a row is in its currency. When the conversion value is in another currency, such as orders paid in AUD on a row whose spend is in USD, name that currency in conversion_value_currency: MTA first converts the value into currency at the same day's rate, with the same 422 when there is none.
Growth Dept platforms#
Growth Dept's own products send their costs with these platform keys. Each shows under its own name and category, and its visits count as lower funnel, as other email and SMS visits do, so Clicks only leaves them out when the journey has an upper-funnel touch.
| platform | Shown as | Category |
|---|---|---|
growthdept_acr | Growth Dept ACR | SMS |
growthdept_email | Growth Dept Email | |
growthdept_sms | Growth Dept SMS | SMS |
Their links carry utm_source set to the platform key and utm_id set to the row's campaign_id, such as acr_<campaign id>. A visit from such a link sits on that campaign's row, beside its spend, whatever its utm_medium. A visit whose utm_id no row has named yet counts under the platform's name until that campaign's first row arrives, and then moves to the campaign's row.
A daily share of a fixed fee can go in a row of its own, with its own campaign_id, such as acr_platform_fee, and clicks and conversions at 0. It shows as one more campaign under the platform, so the platform's ROAS includes the fee.
Platforms with a connector#
A platform the brand pulls through a live connection is refused with 409, so its spend is never counted twice.
Spend you sent through this API stays when you later connect that platform, and it would then count alongside the connector's. Email us before you connect it so the API rows can be removed first.
Failed requests#
When a request with a valid key fails, MTA keeps the status and error message for 30 days, and the brand's team can read them under Settings > Data Diagnostics. The error response carries the record's id in X-Request-Id.
Questions about the API: support@growthdept.ai.