Discount Coupons

8 Aufrufe Markdown

The seven endpoints that define discount coupons, copy them and set their conditions.

Overview

Coupons are the discount codes a client uses in the basket or on an invoice. A coupon takes off either a share or a fixed amount. The type field names which of the two, and the matching field is the one to fill.

When a coupon is valid rests on three things: its stored status, its date range and its use limit. The listing folds the three together and returns the real state as well, and that is what a client meets.

The conditions cover the rest: which products, which cycles, which kind of client and the smallest basket it works on. The list of product values comes from an endpoint of its own.

Reference

Listing the Coupons

get/api/v1/admin/financial/coupons
Financial/GetCoupons admin

Returns the discount coupons with the state they are really in.

Query 4
pageintWhich page.
limitintRecords per page. Clamped between one and a hundred.
searchstringSearches the code and the notes.
statusstringFilters by the real state: live, off, not yet started, expired or used up.
Response fields data[] — 15 + meta — 4
idintThe coupon id.
codestringThe code a client types in.
statusstringThe stored status: live or off.
effective_statusstringThe state it is really in. Worked out with the dates and the use limit taken in.
typestringThe discount type.
ratefloatThe share taken off.
amountfloatThe fixed amount taken off.
currency_idintThe currency of the fixed amount.
auto_applyboolWhether it applies itself.
max_usesintThe use limit.
usesintHow often it was used.
start_datestring | nullWhen it becomes valid.
due_datestring | nullWhen it stops being valid.
created_atstring | nullWhen it was created.
totalintHow many match the filter. It comes back under meta.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page. Zero means you are on the last one.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/financial/coupons?status=active' \
  -H "Authorization: Bearer $API_KEY"
const url = new URL('https://panel.example.com/api/v1/admin/financial/coupons');
url.searchParams.set('status', 'active');

const res  = await fetch(url, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/coupons?' . http_build_query(['status' => 'active']));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// There are two status fields: the stored one and the REAL one. Clients meet the second.
$rows = Api::Financial()->GetCoupons()['data'];
$live = array_filter($rows, fn ($c) => $c['effective_status'] === 'active');

Creating a Coupon

post/api/v1/admin/financial/coupons
Financial/CreateCoupon admin born live

Defines a new discount coupon.

Body 26
codestringreqThe code a client types in. It has to be unique on the installation.
typestringThe discount type: percentage or amount. A share by default.
ratefloatThe share taken off.
amountfloatThe fixed amount taken off.
currency_idintThe currency of the fixed amount.
product_servicesstring[]The products, categories, domain endings and add-ons the coupon applies to.
required_productsstring[]What the basket has to hold for the coupon to work.
validity_cyclesobjectThe billing cycles the coupon applies to.
required_product_cyclesobjectThe cycles the required products have to carry.
min_amountfloatThe smallest basket the coupon works on.
min_amount_currency_idintThe currency that smallest basket is in.
max_usesintHow many times it can be used. Zero means without limit.
recurringboolWhether the discount carries on into renewals.
recurring_numintHow many renewals it carries on.
auto_applyboolApplies itself to the basket.
apply_onceboolApplies once per client.
onetime_use_per_orderboolApplies once per order.
tax_freeboolCounts the discount as free of tax.
new_signups_onlyboolOpens it to new sign-ups alone.
existing_customers_onlyboolOpens it to existing clients alone.
dealership_onlyboolOpens it to resellers alone.
allow_mergeboolLets it stack with other coupons.
used_in_invoicesboolLets it be used on invoices too.
start_datestringWhen it becomes valid.
due_datestringWhen it stops being valid. Left empty, it never expires.
notesstringA note on the coupon.
Response fields 201 — data
dataobjectThe coupon created. Same shape as the detail endpoint.
Errors 4
code_required422The coupon code is empty.
coupon_save_failed422The code is taken, or the rate is not valid.
blocked_by_gate422A hook refused the save.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/financial/coupons' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"code":"HOSGELDIN30","type":"percentage","rate":30,"max_uses":50}'
const res = await fetch('https://panel.example.com/api/v1/admin/financial/coupons', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    code: 'WELCOME30',
    type: 'percentage',
    rate: 30,
    max_uses: 50,
    due_date: '2026-12-31',
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/coupons');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'code'     => 'WELCOME30',
        'type'     => 'percentage',
        'rate'     => 30,
        'max_uses' => 50,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A coupon is born LIVE, and with no end date it NEVER EXPIRES; weigh the two together.
Api::Financial()->CreateCoupon([
    'code'     => 'WELCOME30',
    'rate'     => 30,
    'max_uses' => 50,
    'due_date' => '2026-12-31',
]);

Reading the Product Tree

get/api/v1/admin/financial/coupons/products-hierarchy
Financial/GetCouponProductsHierarchy admin

Returns the products and categories a coupon can be tied to.

Response fields data
dataarrayA flat list of the products, categories, domain endings and add-ons you can pick. The values for the coupon fields come from here.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/financial/coupons/products-hierarchy' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/financial/coupons/products-hierarchy', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/coupons/products-hierarchy');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Do not write the values into your code: the list follows the installation's own products.
$options = Api::Financial()->GetCouponProductsHierarchy()['data'];

Reading One Coupon

get/api/v1/admin/financial/coupons/{id}
Financial/GetCoupon admin

Returns one coupon with all of its conditions.

Response fields data — 29
idintThe coupon id.
codestringThe code a client types in.
statusstringThe stored status.
typestringThe discount type.
ratefloatThe share taken off.
amountfloatThe fixed amount taken off.
currency_idintThe currency of the fixed amount.
product_servicesstring[]The products the coupon applies to.
validity_cyclesobjectThe billing cycles it applies to.
required_productsstring[]What the basket has to hold.
required_product_cyclesobjectThe cycles those have to carry.
min_amountfloatThe smallest basket.
min_amount_currency_idintThe currency that is in.
auto_applyboolWhether it applies itself.
max_usesintThe use limit.
usesintHow often it was used.
recurringboolWhether it carries on into renewals.
recurring_numintHow many renewals it lasts.
apply_onceboolWhether it applies once per client.
onetime_use_per_orderboolWhether it applies once per order.
tax_freeboolWhether the discount counts as free of tax.
new_signups_onlyboolWhether it is open to new sign-ups alone.
existing_customers_onlyboolWhether it is open to existing clients alone.
dealership_onlyboolWhether it is open to resellers alone.
allow_mergeboolWhether it stacks with other coupons.
used_in_invoicesboolWhether it can be used on invoices.
notesstringThe note on the coupon.
start_datestring | nullWhen it becomes valid.
due_datestring | nullWhen it stops being valid.
created_atstring | nullWhen it was created.
Errors 2
not_found404No such coupon.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/financial/coupons/19' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch(`https://panel.example.com/api/v1/admin/financial/coupons/${id}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/coupons/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The detail carries NO real-state field; only the list works that out.
$coupon = Api::Financial()->GetCoupon(['id' => $id])['data'];

Updating a Coupon

patch/api/v1/admin/financial/coupons/{id}
Financial/UpdateCoupon admin

Changes the coupon fields you send.

Body 26
codestringThe code a client types in.
typestringThe discount type: percentage or amount. A share by default.
ratefloatThe share taken off.
amountfloatThe fixed amount taken off.
currency_idintThe currency of the fixed amount.
product_servicesstring[]The products, categories, domain endings and add-ons the coupon applies to.
required_productsstring[]What the basket has to hold for the coupon to work.
validity_cyclesobjectThe billing cycles the coupon applies to.
required_product_cyclesobjectThe cycles the required products have to carry.
min_amountfloatThe smallest basket the coupon works on.
min_amount_currency_idintThe currency that smallest basket is in.
max_usesintHow many times it can be used. Zero means without limit.
recurringboolWhether the discount carries on into renewals.
recurring_numintHow many renewals it carries on.
auto_applyboolApplies itself to the basket.
apply_onceboolApplies once per client.
onetime_use_per_orderboolApplies once per order.
tax_freeboolCounts the discount as free of tax.
new_signups_onlyboolOpens it to new sign-ups alone.
existing_customers_onlyboolOpens it to existing clients alone.
dealership_onlyboolOpens it to resellers alone.
allow_mergeboolLets it stack with other coupons.
used_in_invoicesboolLets it be used on invoices too.
start_datestringWhen it becomes valid.
due_datestringWhen it stops being valid. Left empty, it never expires.
notesstringA note on the coupon.
Response fields data
dataobjectThe coupon as it now stands. Same shape as the detail endpoint.
Errors 4
not_found404No such coupon.
coupon_save_failed422The code is taken, or the rate is not valid.
blocked_by_gate422A hook refused the save.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/financial/coupons/19' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"rate":25,"due_date":"2027-01-31"}'
const res = await fetch(`https://panel.example.com/api/v1/admin/financial/coupons/${id}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ rate: 25, due_date: '2027-01-31', auto_apply: true }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/coupons/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['rate' => 25, 'due_date' => '2027-01-31']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Changing the rate does not reach PAST orders; it touches later uses alone.
Api::Financial()->UpdateCoupon(['id' => $id, 'rate' => 25]);

Deleting a Coupon

delete/api/v1/admin/financial/coupons/{id}
Financial/DeleteCoupon admin

Removes a coupon.

Response fields data — 2
deletedboolWhether the delete ran.
idintThe id of the coupon removed.
Errors 2
not_found404No such coupon.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/financial/coupons/19' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/financial/coupons/${id}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/coupons/' . $id);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Switch it off rather than delete: the code stays taken and its history survives.
Api::Financial()->UpdateCoupon(['id' => $id, 'status' => 'inactive']);

Copying a Coupon

post/api/v1/admin/financial/coupons/{id}/duplicate
Financial/DuplicateCoupon admin the copy is born off

Opens a new coupon carrying an existing one's settings.

Body
No body is needed. The coupon comes from the path; send an empty body.
Response fields 201 — data
dataobjectThe copy created. Its code gains a copy suffix, its status is off, its use count is cleared and its start date is dropped.
Errors 2
not_found404No such coupon.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/financial/coupons/19/duplicate' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/financial/coupons/${id}/duplicate`, {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const { data } = await res.json();
console.log(data.code);   // WELCOME30-COPY
$ch = curl_init('https://panel.example.com/api/v1/admin/financial/coupons/' . $id . '/duplicate');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The copy is born OFF: fix its code and dates, then switch it on yourself.
$copy = Api::Financial()->DuplicateCoupon(['id' => $id])['data'];
Api::Financial()->UpdateCoupon([
    'id' => $copy['id'], 'code' => 'SUMMER30', 'status' => 'active',
]);

Pitfalls

There are two status fields

The stored status is the switch an operator flips. The real state is worked out with the date range and the use limit. A coupon that reads live may not work because it expired or was used up. The second field is what tells you what a client will meet, and it comes back on the listing alone.

With no end date a coupon never expires

A coupon left without an end date never stops. A code opened for a campaign and forgotten still takes money off months later. With the use limit at zero as well it is both endless and unlimited. Fill in at least one of the two on a campaign code.

A copy is born off and under a new code

The duplicate endpoint opens the new coupon switched off. It also adds a copy suffix to the code, clears the use count and drops the start date. The copy is not ready to use: fix its code and switch it on. Duplicating the same coupon twice numbers the suffix upward.

A change does not reach the past

Changing a coupon's rate or its conditions touches later uses. Orders and invoices already cut with it keep the old rate. That is right, because those documents tell the story of a moment. Correcting a mistake means going to the invoice itself as well.

The product values belong to the installation

The values naming which products a coupon applies to come from a separate endpoint and follow the installation's own product tree. Writing them into your code leaves a coupon quietly matching nothing on another installation, or once the products change. Read the list each time.

War das hilfreich?

Vielen Dank für Ihre Rückmeldung!

Brauchen Sie weitere Hilfe?

Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.