Skip to content
GROWTH DEPT.MTA
DocsSend spend

API reference

Send spend

Sends daily ad spend for platforms MTA has no connector for, such as podcasts or a regional network.

POSThttps://mta.growthdept.ai/api/v1/spend

Request#

Request
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#

Response 200
{ "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.

HeaderTypeDescription
AuthorizationrequiredstringThe API key. See Authentication.
Data-Client-IDrequireduuidThe brand id.
Content-Typestringapplication/json.

Body#

A JSON array of 1 to 10,000 daily rows. A field this table does not list is refused.

FieldTypeDescription
platformrequiredstringA 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.
daterequiredstring (date)The day the spend belongs to, YYYY-MM-DD.
currencyrequiredstringThree capital letters: the currency the amounts are in.
spendrequirednumber or stringSpend for the day. A decimal string such as "350.00" is accepted.
impressions, clicksintegerDelivery counts, 0 or more.
conversions, conversion_valuenumber or stringThe platform's own results. They are stored with the row and do not change MTA's attribution.
conversion_value_currencystringThree 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_id, campaign_id, adset_id, ad_idstringIds, up to 200 characters each, so spend can be read by account, campaign, ad set and ad.
campaign_name, adset_name, ad_namestringNames, up to 500 characters each, shown in reports beside the ids.
campaign_status, adset_status, ad_statusstringThe 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#

FieldTypeDescription
countintegerRows written.

Status codes#

StatusMeaningBody
200The rows were written.
400The 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" }] }
401No 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" }
403The 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" }
409A 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" }
422No 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" }
500Something 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.

platformShown asCategory
growthdept_acrGrowth Dept ACRSMS
growthdept_emailGrowth Dept EmailEmail
growthdept_smsGrowth Dept SMSSMS

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.