Skip to content
GROWTH DEPT.MTA
DocsSend orders

API reference

Send orders

Sends orders from a store that is not on Shopify, or from your own backend, so MTA can attribute them.

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

Request#

Request
curl -X POST "https://mta.growthdept.ai/api/v1/orders" \
  -H "Authorization: $MTA_API_KEY" \
  -H "Data-Client-ID: $MTA_BRAND_ID" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "order_id": "1001",
      "customer_id": "c-42",
      "time_of_purchase": "2026-11-05T14:03:00-08:00",
      "currency": "USD",
      "purchase_total": 64.5,
      "tax": 4.5,
      "shipping_cost": 5,
      "products": [
        { "id": "sku-1", "name": "Canvas tote", "quantity": 1, "price": 55 }
      ],
      "order_tags": ["wholesale"],
      "visitor_keys": ["gd_7f3a9c2e41b8"]
    }
  ]'

Response#

Response 200
{
  "message": "Orders successfully upserted",
  "count": 1,
  "created": 1,
  "changed": 1
}

Authentication#

Send the API key in Authorization and the brand id in Data-Client-ID. The key needs the orders:write scope.

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

Body#

A JSON array of 1 to 1,000 orders, at most 20 MB. Each order_id may appear once per request, and a field this table does not list is refused.

FieldTypeDescription
order_idrequiredstringYour order id, up to 200 characters. Orders upsert on it.
customer_idrequiredstringYour customer id, up to 200 characters. It is stored only as a hash with the brand's key, and tells first-time customers from returning ones.
time_of_purchaserequiredstring (date-time)ISO 8601 with a time zone offset or Z.
currencyrequiredstringThree capital letters, such as USD.
purchase_totalrequirednumberThe order's revenue, with tax, shipping and fees included.
taxrequirednumberTax on the order.
shipping_costnumberShipping charged. Counts as 0 when left out.
discount_amountnumberDiscounts on the order.
productsrequiredarrayAt least one product: id, name, quantity (0 or more) and price, and optionally variant_id and variant_name.
refundsarrayEach refund's product_id, quantity, refund_amount, refund_cost and refund_made_at (date-time), and optionally variant_id. No two may be identical.
discount_codesarray of stringsCodes used on the order, each once.
order_tagsarray of stringsTags, each once. Tags the brand excludes in Settings > Exclude orders by tag keep the order out of attribution.
is_recurring_orderbooleanMarks a subscription order.
cancelled_atstring (date-time) or nullWhen the order was cancelled. A cancelled order keeps its attribution credit, as the store's own totals count it.
visitor_keysarray of stringsUp to 10 visitor ids that made the purchase: the pixel's gd_vid or Shopify's client id. 8 to 128 characters of letters, digits, ., _, : and -. They tie the order to its visits.
customer_emailstringThe buyer's email. It becomes a key for matching partner views and is not stored.
hashed_customer_emailstringThe SHA-256 hex digest of the trimmed, lower-cased email, in place of customer_email. A value that is not 64 hex characters is ignored.
customer_phone_number, hashed_customer_phone_number, customer_name, customer_ip_address, customer_shipping_addressvariousAccepted so existing clients keep working, and dropped unread.
alternate_order_idsarrayObjects with type and id. Accepted; not used today.

Response fields#

FieldTypeDescription
countintegerOrders written.
createdintegerOf those, orders MTA did not have before.
changedintegerOrders that are new or whose attributed values changed, which MTA will recompute.

Status codes#

StatusMeaningBody
200The orders were written.
400The body is not a JSON array of valid orders, or repeats an order_id. issues lists the first 50 problems, each with its path, such as 0.currency.{ "detail": "invalid input", "error": "invalid input", "issues": [ { "path": "0.currency", "message": "Invalid string: must match pattern /^[A-Z]{3}$/" } ] }
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 orders: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 orders:write.{ "detail": "this API key lacks the orders:write scope", "error": "this API key lacks the orders:write scope" }
409An order_id belongs to one of the brand's Shopify orders. Nothing in the request was written; order_ids lists the ids.{ "detail": "these order ids belong to the brand's Shopify orders", "error": "these order ids belong to the brand's Shopify orders", "order_ids": ["1001"] }
413The body is over 20 MB. Send fewer orders per request.{ "detail": "body over 20 MB; send fewer orders per request", "error": "body over 20 MB; send fewer orders per request" }
422No exchange rate yet for an order's currency on its date. MTA has asked for the rate; send the same orders again later. Nothing was written.{ "detail": "no ECB rate for CAD 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 CAD 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 orders again later" }
500Something failed on our side. Send the same request again later; orders upsert, so a retry is safe.{ "detail": "Internal server error.", "error": "Internal server error." }

Sending an order again#

Each order upserts on order_id, so send the whole order every time it changes. The products you send replace the order's stored products, and the refunds you send replace its stored refunds: a refund you leave out is removed.

Sending an order without customer_email or hashed_customer_email removes any email key it had.

Revenue and currency#

Revenue is purchase_total. Gross sales are purchase_total - tax - shipping_cost + discount_amount. An order whose revenue is 0 is stored but takes no attribution credit.

Amounts are converted to the brand's currency at the European Central Bank rate for the order's date in the brand's time zone, or the latest rate up to 7 days before it. When there is none, the request gets 422 and MTA asks for the rate.

Personal data#

MTA keeps no contact details from this API. customer_id is stored as a hash with the brand's key. An email becomes a key only this brand's data can match, used to match partner views sent with an email (Send matched views), and the address is discarded. Phone, name, IP address and shipping address fields are accepted and dropped unread.

Shopify stores#

Orders from a connected Shopify store come from the Shopify connection. An order_id that belongs to one of those orders is refused with 409, so this API never overwrites them.

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; quote it when you ask us about a failure.

Questions about the API: support@growthdept.ai.