Admin API: offers
Admin API methods for offers: list, create and edit, copies for team members, groups, archive upload, and the files and versions of a hosted offer.
This page covers 22 methods for working with offers. All of them require a token with Full access («Полный доступ») (full). Authorization is described on the Admin API: overview and authorization page, and the request format and common error codes on the Request, response and error format page.
Offer object
The list, read, create, edit and copy methods return an offer in the same form.
| Field | Type | Description |
|---|---|---|
id | string, UUID | Offer ID. Used in method addresses |
seq | number | The offer's sequence number in the panel |
name | string | Name |
network_id | string or null | Affiliate network ID |
url | string | Link of an external offer |
storage | string | external — an external link, local — the files are stored in the tracker. The default is external |
delivery_type | string | Delivery type: redirect, meta, js, direct, double_meta. The default is redirect |
payout | number | Fixed payout. The default is 0 |
payout_type | string | Payout model: cpa, cpc, crg, revshare. The default is cpa |
payout_from_param | boolean | true — take the payout from a postback parameter. The default is false |
payout_param | string | Name of the postback parameter with the payout. The default is payout. Does not apply to conversion import from CSV |
currency | string | Offer currency, a three-letter code. The fixed payout payout is recorded in it. The default is USD |
country | string or null | Two-letter country code |
daily_cap | number or null | Daily sales cap, an integer from 0. null — no cap |
cap_timezone | string | Time zone of the cap in IANA format, for example Europe/Warsaw. The default is UTC |
overflow_offer_id | string or null | Fallback offer that receives clicks after the cap is reached |
state | string | Status: active, paused, archived. The default is active |
values | array | Offer values: objects {"name": "...", "value": "..."} for the {offer_value:name} macro. The default is [] |
group_name | string | Group name. An empty string — no group |
owner_id | string or null | The user the offer belongs to |
php_network | boolean | PHP with network access. When a hosted offer is created, the default is true; an explicit false turns the network off. For an external offer — false. When the offer is edited without this field, the previous value is kept |
Delivery types, payout models and statuses are explained on the Offers page, the cap and the fallback offer on the Daily sales cap and fallback offer page, and php_network on the PHP in hosted pages and network access page.
The fixed payout payout is recorded in the currency from the currency field and does not depend on the currency parameter of the postback. For an amount from the postback, the offer currency is used only if the network did not send its own currency. If currency holds a currency other than USD, it needs an exchange rate: without one, postbacks for an offer with a fixed payout get 422 with code currency_rate. Rates and conversion are covered on the Currencies and rates page.
payout_param applies to the incoming postback. Conversion import from CSV always takes the amount from the payout column.
If there is no offer with the given id, any method with {id} in its address responds 404 with code not_found. For an id that is not in UUID format, the response is 400 with code bad_request.
Offers
List offers
GET /api/v1/offers
Returns an array of offers, newest first. There are no parameters and no pagination. Deleted offers are not included. Response — code 200.
Get an offer
GET /api/v1/offers/{id}
Returns one offer. Response — code 200.
Create an offer
POST /api/v1/offers
Creates an offer. The user who created the token becomes its owner.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Name |
url | string | yes, if storage is not local | Offer link |
storage | string | no | external or local |
daily_cap | number or null | no | At least 0 |
cap_timezone | string | no | IANA time zone, up to 64 characters. The value Local is not accepted |
overflow_offer_id | string or null | no | Another offer of this team with the active status |
The other fields from the Offer object section, except id, seq and owner_id, are optional. Omitted fields get their default values.
curl -X POST https://panel.example.com/api/v1/offers \
-H "Api-Key: <token>" \
-H "Content-Type: application/json" \
-d '{"name":"Offer PL","url":"https://offer.example.com/?sub={subid}","payout":12.5,"country":"PL","daily_cap":50,"cap_timezone":"Europe/Warsaw"}'Response — code 201 and the created offer.
Errors with code 422 and validation:
| Message | Reason |
|---|---|
name is required | The name is empty |
url is required for external offer | An external offer has no link |
daily_cap must be non-negative | Negative cap |
cap_timezone must be a valid IANA time zone | Unknown time zone |
overflow_offer_id must identify another offer | The fallback is the offer itself or not a UUID |
overflow offer must belong to this workspace | The fallback offer was not found |
overflow offer must be active | The fallback offer does not have the active status |
Edit an offer
PUT /api/v1/offers/{id}
Changes the fields you pass. Fields that are not in the body keep their current values. A null value clears country, daily_cap, network_id and overflow_offer_id. The group is removed with an empty string in group_name, and the values with an empty array in values. An empty string in storage, delivery_type, payout_type, payout_param, currency and state keeps the previous value, and in cap_timezone it sets UTC back.
The fields and errors are the same as when creating. The checks apply to the resulting offer. Response — code 200 and the updated offer.
Delete an offer
DELETE /api/v1/offers/{id}
Moves the offer and its files to the trash. Response — code 204 with no body. Restoring is described on the Admin API: trash page.
Copies and access
Clone an offer
POST /api/v1/offers/{id}/clone
Creates a copy of the offer. The suffix “ (copy)” (« (копия)») is added to the name. For a hosted offer, the files are copied too.
| Field | Type | Required | Description |
|---|---|---|---|
owner_id | string | no | Who to assign the copy to. Without the field, the copy goes to the token's creator |
Response — code 201 and the new offer.
Give copies to team members
POST /api/v1/offers/{id}/share-clone
Creates a separate copy of the offer for each team member in the list. The copy's name is the same as the original's, and the files of a hosted offer are copied.
| Field | Type | Required | Description |
|---|---|---|---|
user_ids | array of strings | yes | User IDs, at least one |
Response — code 200 and an object with a results array. Each element has user_id, login and the offer_id of the new copy. If a copy could not be created, error comes instead of offer_id: “user not found” («пользователь не найден»), “could not clone” («не удалось клонировать») or “could not copy the files” («не удалось скопировать файлы»).
An empty list gives 422 with the message “no team member selected” («не выбран ни один сотрудник»).
Who the offer is shared with
GET /api/v1/offers/{id}/shares
Returns an object with a user_ids array — the users the offer is shared with for viewing. Response — code 200.
Share the offer with users
POST /api/v1/offers/{id}/shares
Replaces the list entirely. An empty array closes access for everyone.
| Field | Type | Required | Description |
|---|---|---|---|
user_ids | array of strings | yes | The new full list. Users of another team are skipped |
Response — code 204 with no body.
Offer groups
A group is the value of the group_name field. To add an offer to a group, pass the group name in group_name when you create or edit the offer. Separate methods rename and delete a group across all its offers at once.
Rename a group
PUT /api/v1/offer-groups?name=<current name>
| Field | Type | Required | Description |
|---|---|---|---|
name in the address | string | yes | Current group name |
name in the body | string | yes | New name |
curl -X PUT "https://panel.example.com/api/v1/offer-groups?name=Nutra" \
-H "Api-Key: <token>" \
-H "Content-Type: application/json" \
-d '{"name":"Nutra PL"}'Response — code 200 and {"updated": 4}, where the number is how many offers were changed.
Delete a group
DELETE /api/v1/offer-groups?name=<name>
Removes the group from the offers. The offers themselves stay. No body is needed. Response — code 200 and {"updated": N}.
Errors of both methods — 422 with code validation: “name is required” («name обязателен») if the parameter is missing from the address. Renaming has two more: “invalid request body” («некорректное тело запроса») if the body is not a JSON object, and “new group name is required” («новое имя группы обязательно») if the name in the body is empty.
Hosted offer files
File paths are given relative to the offer folder, separated by /, for example css/style.css. After the first upload of an archive or file, or the first file save, the offer switches to storage: local. How the tracker serves such pages is described on the Hosted pages page.
Upload an archive
POST /api/v1/offers/{id}/upload
Accepts a ZIP as multipart/form-data and replaces all of the offer's files with it. The offer's lead log is kept.
| Field | Type | Required | Description |
|---|---|---|---|
archive | file | yes | ZIP without a password |
curl -X POST https://panel.example.com/api/v1/offers/7c9e6679-7425-40de-944b-e07fc1f90ae7/upload \
-H "Api-Key: <token>" \
-F "[email protected]"If the offer already had files, they are saved as a version before the replacement.
Response — code 200 and an object with a files array. A list element: path — the path, size — the size in bytes, dir — always false. The list is sorted by path; folders are not listed as separate rows.
If the archive is not accepted, the offer's previous files stay in place. Limits and errors are listed in the Upload limits and errors section.
List files
GET /api/v1/offers/{id}/files
Returns an object with a files array in the same form. For an offer without files, the array is empty. Response — code 200.
Add more files
POST /api/v1/offers/{id}/files
Adds files to the ones already uploaded, format — multipart/form-data. A file with the same path is replaced; the others are not affected.
| Field | Type | Required | Description |
|---|---|---|---|
files | file, several allowed | yes | Files. Only the file name is kept, without the folders from the original path |
dir | string | no | Folder inside the offer. Without the field, the files go to the root |
Response — code 200 and an object: files — the full list of files, saved — the paths of the saved files, storage — local.
Read a file
GET /api/v1/offers/{id}/file?path=<path>
Returns the contents of a file up to 5 MB in size.
| Field | Type | Required | Description |
|---|---|---|---|
path in the address | string | yes | File path |
Response — code 200 and an object: path, content — the contents as a string, utf8 — false if the file is not UTF-8 text.
Save a file
PUT /api/v1/offers/{id}/file
Creates or overwrites a text file. The body is JSON.
| Field | Type | Required | Description |
|---|---|---|---|
path | string | yes | File path. Missing folders are created |
content | string | no | Contents, up to 5 MB |
create | boolean | no | true — create only: if the file already exists, the response is 409 “a file with this path already exists” («файл с таким путём уже есть») |
Response — code 200 and {"ok": true, "storage": "local"}.
Delete a file
DELETE /api/v1/offers/{id}/file?path=<path>
Deletes one file. Response — code 204 with no body.
Rename a file
POST /api/v1/offers/{id}/rename
Renames a file or moves it to another folder. A file that already exists at the new path is not overwritten. The leads.php lead log and names starting with a dot are not renamed: the response is 400.
| Field | Type | Required | Description |
|---|---|---|---|
from | string | yes | Current path |
to | string | yes | New path |
Response — code 200 and {"ok": true}. Without one of the fields — 400 with the message “the source and new path are required” («нужны исходный и новый путь»).
File method errors
| Code | Message | Reason |
|---|---|---|
422 | path is required | The path was not passed |
404 | “file not found” («файл не найден») | Reading or deleting: there is no file at this path |
400 | “invalid path” («недопустимый путь») | The path goes outside the offer folder |
400 | “hidden files (starting with a dot) are not supported” («скрытые файлы (начинающиеся с точки) не поддерживаются») | The path contains a name that starts with a dot |
413 | “file larger than 5 MB — the editor does not open or save such files; replace it by uploading a file or archive” («файл больше 5 МБ — Редактор такие не открывает и не сохраняет; замените его загрузкой файла или архива») | Reading or saving a file larger than 5 MB |
413 | “too many files” («слишком много файлов») | A new file when the offer already has 10,000 files |
Versions and archive
A version is a saved copy of the offer's files. The tracker keeps the offer's 10 latest versions; older ones are deleted when a new one is saved. The lead log and files whose names start with a dot are not included in versions or in the archive. Saving a version and downloading the archive are meant for an offer that has files. Working with versions in the panel is described on the File editor, versions and export page.
List versions
GET /api/v1/offers/{id}/versions
Returns an array of versions, newest first. Fields: id, created_at — creation time in UTC, size — size in bytes. If there are no versions, the array is empty. Response — code 200.
Save a version
POST /api/v1/offers/{id}/versions
Saves the current files as a new version. No body is needed. Response — code 201 and the version object.
Restore a version
POST /api/v1/offers/{id}/versions/{version}/restore
Replaces the offer's files with the files of the version. Put the id from the version list into {version}. Before the replacement, the current files are saved as a new version. The lead log stays in place. No body is needed. Response — code 200 and {"ok": true}.
Restoring replaces all of the offer's current files. Only 10 versions are kept: after several restores and uploads in a row, old versions are deleted.
Download the archive
GET /api/v1/offers/{id}/archive
Returns the offer's current files as a single ZIP file, page.zip. Response — code 200 with the application/zip type.
curl https://panel.example.com/api/v1/offers/7c9e6679-7425-40de-944b-e07fc1f90ae7/archive \
-H "Api-Key: <token>" \
-o page.zipSaving a version and downloading the archive respond 413 if the offer has more than 10,000 files or more than 1 GB.
Upload limits and errors
| What is limited | Limit |
|---|---|
| Size of one upload request | 1 GB |
| One file | 1 GB |
| All archive files after extraction | 1 GB |
| Number of files in the archive and in the offer | 10,000 |
File for reading and saving through /file | 5 MB |
Archive requirements are on the Hosted pages page.
| Code | Message | Reason |
|---|---|---|
413 | “1 GB upload limit exceeded” («превышен лимит загрузки 1 ГБ») | The request is larger than 1 GB |
413 | “file too large (limit 1 GB per file, 1 GB per extracted archive)” («файл слишком большой (лимит 1 ГБ на файл, 1 ГБ на распакованный архив)») | A file or the extracted archive exceeds the limit |
413 | “too many files” | The archive or the offer has more than 10,000 files |
422 | “expected a file in the archive field” («ожидался файл в поле archive») | The form has no archive field |
422 | “expected files in the files field” («ожидались файлы в поле files») | The form has no files field |
422 | “nothing to save” («нечего сохранять») | None of the files has a name |
422 | “the file is not a valid .zip” («файл не является корректным .zip») | The uploaded file is not a ZIP |
422 | “the archive uses an unsupported compression method (LZMA, AES encryption, etc.) — rebuild it as a regular ZIP without a password” («архив сжат неподдерживаемым методом (LZMA, AES-шифрование и т.п.) — пересоберите обычным ZIP без пароля») | The archive has a password or non-standard compression |
422 | “the archive is damaged — rebuild it and upload it again” («архив повреждён — пересоберите его и загрузите заново») | The archive cannot be read to the end |
400 | “could not read the form (the upload was interrupted or damaged)” («не удалось прочитать форму (загрузка прервана или повреждена)») | The transfer broke off or the body is not multipart/form-data |