Spend intake by key
Qubix imports Facebook spend on its own. You can send Qubix the spend of any other platform that has no import of its own yourself: with an external program using a key, through the assistant, or from a Qubix script. This article is mainly about the key: you issue a key on the MCP/API page, and your program uses it to send hourly amounts — per ad or for a whole website. The assistant and scripts write spend through the same intake — see Submitting spend through the assistant and Submitting spend from a script. Accepted spend shows in the same reports as Facebook spend.
What you need
- The Trafik kaynaklarını yönet permission in your permission template — to issue a key for yourself. If you do not have this permission, an administrator issues the key for you.
- A traffic source for the platform — one not based on the template of a platform whose spend Qubix imports on its own (for example,
facebook). More — Creating a source. - For spend per ad — the platform's ad IDs, and for ads Qubix does not have yet, their campaign IDs as well. For spend per website — the website in the Web siteleri section.
How to issue a key
- Open Ayarlarım → Integrations → MCP/API. More about the page — MCP/API.
- Find the Spend intake by key block. The Endpoint line shows the intake address with your panel's domain — this is the address your program calls.
- If you are an administrator, choose in the drop-down list above the button whom to issue the key for: For myself (the list's default) or another person. Other users have no such list — they issue the key for themselves.
- Click Issue a key.
- Copy the key with the Kopyala button and save it: the key is not stored anywhere and is not shown again.
The block is visible to administrators and to those whose permission template has Trafik kaynaklarını yönet. Below the button is the Request body: a sample request with your key filled in once it is issued. The Issue another key button issues one more key, and the earlier ones keep working.
When a key stops working:
- temporarily — while its owner's account is disabled (the aktif switch is off in the user card). If the account is turned back on, the earlier keys work again;
- permanently — after Tüm oturumları sonlandır in that user's card (all keys issued before the reset are revoked) and after İşten çıkar. After the sessions are reset, issue a new key.
More about the user card — Users.
The key writes spend on behalf of the person it was issued for, and with that person's write permissions: whoever holds the key writes to the sources, ads and websites that person may write to. Keep the key like a password.
To a key that does not work (revoked, mistyped or not a key at all), the intake answers the same way as to an accepted batch — with the number of rows in the request body, even if the batch contains errors — and stores nothing. With such a key, a refusal comes only if the body could not be parsed as JSON, the batch has more than 20,000 rows, the body is over the size limit, or the subscription is inactive: all of this is checked before the key. So check the result in the reports — see What you will see after intake.
How to send spend
The program sends a POST request to the address from the block — of the form https://<your-domain>/cost-intake — with a JSON body. The request body is a batch: the key, the source, the currency, the time zone and the spend rows. There is no need to sign in to the panel: the key in the request body identifies the sender.
curl -X POST 'https://your-domain.com/cost-intake' \
-H 'Content-Type: application/json' \
-d '{
"key": "YOUR_INTAKE_KEY",
"source": "a1b2c3d4-0000-4000-8000-000000000001",
"currency": "EUR",
"tz": "Europe/Berlin",
"rows": [
{"date": "2026-09-28", "hour": 10, "ad_id": "84512", "campaign_id": "3071",
"campaign_name": "push RU 28.09 anna", "ad_name": "banner 1",
"spend": 12.5, "impressions": 1000, "clicks": 40},
{"date": "2026-09-28", "hour": 10, "website_id": "a1b2c3d4-0000-4000-8000-000000000002",
"spend": 3}
]
}'
In the example, the first row is the spend of ad 84512, which Qubix does not have yet: this same batch registers it in campaign ext-3071, provided the campaign gets an owner whose ads you may write to — for example, the campaign name carries your binding pattern (in the example, anna). How Qubix picks the owner of such a campaign — see Spend per ad. The second row is the spend of a whole website. Both amounts are in euros for 10:00–11:00 Berlin time.
Batch fields
| Field | Required | What to pass |
|---|---|---|
key | yes | The intake key. |
source | yes | The ID of the traffic source the spend is written to. |
currency | no | The three-letter code of the currency of all amounts in the batch: USD, EUR, RUB… If omitted — US dollars. |
tz | no | The time zone in which the rows' dates and hours are given, for example Europe/Moscow. If omitted — UTC. |
rows | yes | The spend rows — no more than 20,000 in one batch. |
The source ID is a long string of the form a1b2c3d4-…. It is in the address of the open source card (the Kaynaklar section), and over MCP the get_traffic_sources_list method returns it. If the sources section is not open to you, an administrator gives you the ID together with the key. The website ID is likewise in the address of the open website card.
Row fields
| Field | Required | What to pass |
|---|---|---|
date | yes | The day as YYYY-MM-DD — in the batch's time zone. |
hour | yes | The hour, from 0 to 23 — in the batch's time zone. |
ad_id | one of the two | The platform's ad ID. |
website_id | one of the two | The website ID — if the spend belongs to the website as a whole rather than to its ads. |
spend | yes | The amount in the batch currency. It cannot be negative. |
impressions | no | Impressions. If omitted — 0. They go into the "Impr" column of the reports. |
clicks | no | The platform's clicks. If omitted — 0. They are stored with the spend but do not go into the "Clicks" column of the reports: that column shows the clicks Qubix counted itself. |
campaign_id | for a new ad | The platform's campaign ID. For an ad Qubix already knows, leave the field empty or name that ad's own campaign. |
campaign_name | no | The name of the new campaign. |
ad_name | no | The name of the new ad. |
Each row names exactly one of the two — ad_id or website_id. A website row must not have the campaign fields or the ad name.
Hour and cell
One row is one cell: an hour plus an ad (or a website). Two rows for the same cell in one batch are not accepted.
- A repeat replaces, it does not add. The same cell sent again under the same source and in the same time zone replaces the earlier numbers. So a batch that got no response can be sent again — the spend will not be doubled.
- Keep to one time zone. The same rows in another time zone land in other hours and are counted twice.
- A daily total only — send it as one row at hour 12 of that day; for today — at an hour that has already begun.
- The day of a clock change — send it in UTC: otherwise two local hours can fall into one cell, and the batch is refused.
Rows later than the next hour and rows dated before 1970 are not accepted.
Currency and exchange rate
Amounts are converted to US dollars at the exchange rate for the date named in the row. The rate is fixed for the cell at its first intake: sending the same cell again in the same currency uses the same rate, and the dollar amount does not shift. A rate from another day is never substituted:
- the rate for that date has not been published yet (this happens with today's date) or the rate sources did not answer — the batch is refused with a request to send it later;
- no rate source has a rate for that date — send these rows in US dollars;
- one batch can request rates for no more than 92 different days — split a larger batch into several.
US dollars are not converted.
Spend per ad
The ad is already in Qubix — for example, imported from Facebook or registered by an earlier batch. The spend you send lands next to what is already there: the ad will have the total of both sources. If a row names campaign_id, it must be the ad's own campaign; for an ad registered by the intake, its number can be written with or without the ext- prefix.
The ad is not in Qubix yet — the first batch it appears in registers it:
- all rows of such an ad carry the same
campaign_id(the number can be written with or without theext-prefix),campaign_nameandad_name: a name left out in one row must be left out in the others too; - the campaign is stored under its number with the
ext-prefix, so it never takes the number of the platform's own campaign. A number that already starts withext-does not get the prefix a second time; - a new ad in an already registered campaign gets the campaign name from Qubix, and the name you send is not written;
- the IDs of new ads and campaigns may contain only Latin letters, digits,
_and-; the number of a new campaign without theext-prefix is no longer than 124 characters.
Qubix determines the owner of a new campaign the same way as for Facebook campaigns: the owner an administrator assigned to the number with the ext- prefix, and if there is no assignment — the person whose name pattern appears in the campaign name (the Desenler tab in the user card). If the name contains the patterns of several people, the campaign has no owner. The campaign is registered only if its owner is a person whose ads you may write to. A campaign without an owner is registered only by an administrator's key.
The intake refuses if the campaign number (without the prefix) already belongs to a campaign of another platform in Qubix, and also if a campaign with this number is already registered under another source.
Such ads have no status: they are turned on and stopped only in the platform's own ad account.
Spend for a whole website
A row with website_id instead of ad_id is spend for a whole website that is not split by ads. The website must be in the Web siteleri section, and its owner must be a person whose ads you may write to.
Such spend has no ad, and so no campaign either. That is why it stands as a separate Site spend row — in the lists of campaigns, offers and networks, and also on the Kampanyalar and Affiliate ağları tabs of the source card. It does not go into the Kampanya yok, Teklif yok and Ağsız rows, but it is included in the dashboard total.
This spend is seen by the website's owner and by those who can see the owner's data. The website's visibility in the Web siteleri section does not affect this.
Where you may write
The intake checks the permissions of the person the key was issued for:
- source — one this person may edit, and not the source of Facebook or of another platform with an import of its own: Qubix receives that platform's spend itself, and the spend you send would land next to it and double it. A source without an owner accepts spend only through an administrator's key;
- ad — if the owner of its campaign is among the people whose ads this person may write to;
- website — if the website's owner is among the same people.
A batch is accepted as a whole or not at all: if even one row fails the check, spend is not written for any of the rows.
What you will see after intake
Spend appears in the reports within a few minutes — after the summary data is refreshed, just like the Facebook import.
- On the Ads tab of the Facebook section, the platform's new ads are marked External in the Kaynak column; you can filter them by the same column. Their status column shows a dash, and there is no pause button.
- In the card of such an ad, the status is replaced by a dash with the tooltip The status is changed in the platform's own cabinet: Qubix receives only the spend of this platform. There are no ⏸ Duraklat, ▶ Başlat and ↻ FB'den al buttons. The card of its ad campaign with the
ext-…number looks the same. - If you ask the assistant to stop or start such an ad or campaign, it gets a refusal: the ad came from a platform not connected to Qubix and can be managed only in that platform's ad account.
- The spend is included in the dashboard total and in the metrics of the source named in the batch — in the list of sources and in its card.
- Spend for a whole website — as the Site spend row (see above).
An ad's spend is seen by the same people as for Facebook ads — according to the owner of its campaign.
Response and refusals
To an accepted batch, the intake answers with the number of rows written:
{"success": true, "data": {"written": 2}}
A refusal comes with a response code and text in the error field:
- refusals of the intake itself — codes 400, 403, 422, 503, and 413 for the number of rows or rate days — carry the reason in English; rows in it are numbered from zero;
- the 402 refusal and the 413 refusal for body size are a ready phrase in the language from the request's
Accept-Languageheader (Russian or English; without the header — English); - a 500 failure is a ready phrase in English: "The spend was not stored: the server could not complete the request. The details are in the server log."
{"success": false, "error": "cost intake: invalid batch: row 1 must name exactly one of ad_id and website_id"}
| Code | When | What to do |
|---|---|---|
| 400 | The batch is built incorrectly: the body could not be parsed as JSON or a field came in the wrong type; a row has no date, hour or amount; a row names both ad_id and website_id, or neither; a website row names a campaign or an ad; two rows for the same cell; the amount is negative or above the limit, impressions are above the limit; the amount after conversion to US dollars is beyond the limit; the currency code is not three Latin letters; an unknown time zone; a row later than the next hour or before 1970; an ID longer than 128 characters, with spaces at the edges, with control characters or with a comma; a campaign or ad name longer than 512 bytes (a Cyrillic one — from 257 letters already) or with control characters; a new ad is described differently in different rows; one new campaign is named differently in the rows of different ads; a known ad names a campaign that is not its own; the ID of a new ad or campaign contains invalid characters, or the number of a new campaign without the ext- prefix is longer than 124 characters. | Fix the batch according to the refusal text. |
| 402 | The subscription is inactive: until it is renewed, Qubix accepts no changes, spend included. | Renew the subscription in your personal account. |
| 403 | The write is outside your permissions: the source is not open to you for editing, does not exist, or is the source of a platform with an import of its own; the ad belongs to someone else, or a new ad is named in a campaign you may not write to; the ad is not in Qubix and campaign_id is not given; the website belongs to someone else or does not exist; the campaign number belongs to another platform, or the campaign is registered under another source; the new campaign has no owner whose ads you may write to; Qubix named as the owner of the new campaign a person other than the one the write was checked against. | Check the source, the IDs and the permissions of the key's owner. |
| 413 | Intake refusal: the batch has more than 20,000 rows, or rates are needed for more than 92 different days. Size refusal: the request body is larger than the Default request body limit, MB value in the system settings. | Split the batch into several. |
| 422 | No rate source has a rate for the row's date. | Send these rows in US dollars. |
| 503 | The rate for the date has not been published yet, the rate sources did not answer, or they took longer than a minute to answer. The response has a Retry-After header. | Send the batch later: the rates that have already arrived are kept. |
| 500 | A failure on the server. | Repeat the batch later; the cause is in the server log. |
With any refusal, no spend is written. New ads the batch has already registered do stay, though, if the refusal came after they were registered: with the 403 refusal because Qubix named as the owner of the new campaign a person other than the one the write was checked against (the refusal text says so directly), and with a 500 failure that happened after the ads were registered.
Submitting spend through the assistant
The same intake is available to the built-in assistant and to an external AI client over MCP — the Submit spend tool (cost_intake_submit). It is open to the same people as issuing a key for yourself: you need the Trafik kaynaklarını yönet permission. Those without this permission are not offered the tool — neither to the built-in assistant nor in the external client's search — and they cannot call it; such a person writes spend only with a key issued by an administrator. In the method catalog on the MCP/API page the tool is shown to everyone, like the other methods: the catalog is a reference, and permissions are checked at call time.
How the tool differs from a request with a key:
- the source is named in the
traffic_source_idfield; - the currency is required — without it the write does not go through;
- the time zone can be omitted — then your profile's time zone is used (and if it is not set — UTC).
The tool writes data, so the built-in assistant first shows a confirmation card and writes the spend only after you click Onayla, while an external client needs the explicit confirmation flag confirm=true. Refusals come in the same English text as for a request with a key, and a server failure comes as the phrase "The spend was not stored: the server could not complete the request…" in the language of your profile, if that is Russian or English. More about connecting — Connecting an external AI (MCP).
Submitting spend from a script
Spend can also be submitted from a Qubix script: in a script that runs on a schedule or is started by hand, the QubixApp.submitSpend call writes spend through the same intake. Its rows have the same shape as in a request with a key, and the source is named in the traffic_source_id field, as with the assistant. How the call works — see Writing a script.
How this path differs:
- The script owner's permissions. Spend is written on behalf of the script's owner, no matter who runs the script: the owner must have the Trafik kaynaklarını yönet permission, and the source, ads and websites are checked against the owner's permissions — the same as with a key. If the owner is no longer among the users or their account is disabled, the call is refused.
- Only code saved by the owner. The call works only if the script's code was last changed and saved by its owner or an administrator — in the panel or with the
script_savetool over MCP. Code saved by someone else (for example, by a team lead in another person's script) does not submit spend until the owner or an administrator changes and saves it. With the "Run now" button, spend is submitted only by code that matches the last confirmed version, so an unsaved edit does not submit spend. Editing the code from Qubix Drive does not confirm it. - The currency and the time zone are always named. A request with a key has defaults for both fields, the assistant has one for the time zone, and here there are no defaults.
- The number of calls per run is limited. The limit is set by an administrator: Qubix ayarları → JavaScript, the Spend submission (QubixApp.submitSpend) group, the submitSpend calls per run field. One call carries one traffic source.
- A refusal comes as an exception in the script itself. Intake refusals and refusals due to the owner, the owner's permissions, the saved code, the call limit and a value that is too long are written in the script owner's language: in Russian if the owner's profile has Russian selected, otherwise in English. Always in English come the message about a value of the wrong kind (
TypeError) — for example,rowsis not an array or a field has the wrong type — and the refusal when the script's owner is not among the users. For a request with a key and for the assistant's tool, by contrast, the intake's own refusals come in English. - System scripts do not submit spend.
The reports do not show how spend was submitted — by key, through the assistant or from a script: such spend is seen by source, like spend sent in any other way.