Promotions

The admin side of promotions on revenexx, the overview, overlap check, timeline, copy, end and batch disable calls, records by id, lists, exports and removals, condition checks, voucher reservations, and the active flag on stacking groups and custom effect types.

The Promotions app holds the rules that reduce a cart, the voucher codes that unlock them, the stacking groups that decide how several promotions combine, and the custom effect types a promotion can carry. This page covers the calls that manage those rows once they exist.

Base path: https://api.revenexx.com/v1/promotions.

Each call needs its capability key as a scope, for example promotions.promotions.copy, or the promotions.* scope. A key without it is refused with 403. See Scopes.

For checkout use, meaning evaluate, reserve, commit, release and how a shopper enters a code, see Run promotions and voucher codes.

Reading a promotion

In this section, "live" means a promotion that is running or scheduled and not in the trash. Each read takes a promotion id from the list, except the overlap check, which is tenant-wide.

RouteAnswers
GET /v1/promotions/promotions/overlap-checkIs any promotion running now? It counts only promotions that are running at the time of the call (scheduled ones do not count), and it applies no market or goods/shipping test. ok is false while any is running, and overlaps is a list of their names (strings), so you can check priorities before you create one.
GET /v1/promotions/promotions/{id}/overlapsWhich live promotions can meet this one in a single cart. Each entry says when and where they meet, and how they combine by exclusivity and priority. At most 10 entries, and their text is in German.
GET /v1/promotions/promotions/{id}/overviewWhat the promotion does and how it stands. The effect and runtime sentences are always German. The usage rows (discount budget, redemption ceiling, runtime, carts holding it) are English only when the first language tag in Accept-Language starts with en, and German otherwise.
GET /v1/promotions/promotions/{id}/timelineRedemptions committed per day, and of those, the ones taken back by a cancellation or a return, counted on the day they were taken back. Days are in the promotion's time zone (Europe/Berlin when it names none). The answer covers the first day to the last with every day between, and at most the latest 60 days.

overlaps only counts live promotions (running or scheduled) whose window overlaps this one, that act on the same kind of amount (goods or shipping) and share a market. A promotion in no market is in every one.

Copy a promotion

POST /v1/promotions/promotions/{id}/copy makes a draft under a new code.

  • code (required): unique per tenant, the trash included.
  • parts (optional): any of conditions (the whole tree) and effects. Omitted means both.

The copy keeps what the promotion says about itself. Vouchers, redemptions and the budget used are not copied, because they describe what happened to the original. A bundle effect is copied with bundle_id: null, since the copy has no bundle of its own. The 201 answer is the new promotion, a draft, and copied counts what came along. A 400 means no code or an unknown part. An unknown promotion answers 404. A 409 means the code is taken (code_taken) or held by a promotion in the trash (in_trash).

Copy
curl -s -X POST https://api.revenexx.com/v1/promotions/promotions/<PROMOTION_ID>/copy \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT" \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "code": "SUMMER-2027", "parts": ["effects"] }'

End a promotion

POST /v1/promotions/promotions/{id}/end sets the end of a running, scheduled or paused promotion. Committed redemptions are not touched. Open holds follow honour_reservations (see below).

  • when: now (the default) or at.
  • at: the date and time to end at, used with when: at. It may not be in the past.
  • honour_reservations: default true. Holds that open carts carry keep counting. With false and an end now, those holds are given back as expired, and the answer's released_holds counts them.
End now
curl -s -X POST https://api.revenexx.com/v1/promotions/promotions/<PROMOTION_ID>/end \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT" \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "when": "now", "honour_reservations": false }'

A 400 means when is neither now nor at, or at is missing or in the past. A 409 means the promotion is not running, scheduled or paused (not_endable), or it is in the trash (in_trash). An unknown promotion answers 404.

Switch a voucher batch off

POST /v1/promotions/batches/{id}/disable switches a batch of voucher codes off, with every code it generated. The batch and each of its active codes become disabled. Redemptions already committed stay. evaluate refuses a disabled code. reserve and commit do not re-check the code, so do not send a disabled code's voucher_id to them. The 200 answer is the batch, now disabled, with disabled_codes set to the number of codes that were switched off. An unknown batch answers 404.

The call takes no body.

Records by id

Batches, bundles, conditions, effects, vouchers and redemptions can each be read by id. The first five can also be corrected and removed; stacking groups and custom effect types have their own rows in Lists, exports and removals. Use the id the list returned. A 404 means no row has that id.

RouteWhat it does
GET /v1/promotions/batches/{id}Read one voucher batch.
PUT /v1/promotions/batches/{id}Correct a batch. With status set to disabled, evaluate refuses every code in it; see Switch a voucher batch off for what reserve and commit still accept. A refused body answers 400.
DELETE /v1/promotions/batches/{id}Delete a batch.
GET /v1/promotions/bundles/{id}Read one bundle.
PUT /v1/promotions/bundles/{id}Correct a bundle. A refused body answers 400.
DELETE /v1/promotions/bundles/{id}Remove a bundle.
GET /v1/promotions/conditions/{id}Read one condition.
PUT /v1/promotions/conditions/{id}Correct a condition. An unknown subject answers 400.
DELETE /v1/promotions/conditions/{id}Remove a condition.
GET /v1/promotions/effects/{id}Read one effect.
PUT /v1/promotions/effects/{id}Correct an effect. Moving it onto an inactive custom type answers 409 with custom_type_inactive.
DELETE /v1/promotions/effects/{id}Remove an effect.
GET /v1/promotions/vouchers/{id}Read one voucher.
PUT /v1/promotions/vouchers/{id}Correct a voucher. To stop a leaked code, set its status to disabled rather than deleting it: evaluate then refuses it, but reserve and commit do not re-check it (see Switch a voucher batch off).
DELETE /v1/promotions/vouchers/{id}Delete a voucher that was never redeemed.
GET /v1/promotions/redemptions/{id}Read one redemption.
  • A voucher that has been redeemed answers 409 to DELETE. Disable it instead, so the evidence stays.
  • Redemptions are a ledger. There is no PUT or DELETE for them, because each one records a reserve, commit, release or return.

Lists, exports and removals

The list calls answer one page of the rows of their kind, as items, with page.total and page.hasMore. Each takes limit (default 50; a larger value is clamped to 200, not refused), offset (default 0) and order, one column with an optional .asc or .desc. The filters are the row's own fields, named in the table. A filter matches the value exactly. An unknown filter answers 400 with unknown_filter, and a value that starts with an operator such as gt. answers 400 with unsupported_filter_operator. The API reference lists 200 as the maximum for limit, so send 200 or less.

RouteWhat it does
GET /v1/promotions/batchesList voucher batches. Filter by id, promotion_id, name, pattern, alphabet, requested, created_count, redeemed_count, status or request_ref.
GET /v1/promotions/bundlesList bundles. Filter by id, promotion_id, name, units_required, max_per_cart or allocation.
GET /v1/promotions/effectsList effects. Filter by id, promotion_id, position, kind, value_type, target_scope, bundle_id, unit_choice, unit_position, spread, free_item_quantity or requires_choice.
GET /v1/promotions/vouchersList voucher codes. Filter by id, promotion_id, batch_id, code, code_key, status, usage_limit, usage_count, currency, contact_id, organization_id or reservation_required.
GET /v1/promotions/groupsList stacking groups. Filter by id, code, name, mode, parent_id, position or active.
GET /v1/promotions/groups/{id}Read one stacking group. An unknown id answers 404.
DELETE /v1/promotions/groups/{id}Delete a stacking group. Its promotions are not deleted: they are weighed by default_stacking_mode, the market's setting when it has one and the tenant's otherwise (see Run promotions and voucher codes). Groups nested in it move to the top level. A 200 answer is { "deleted": true, "id": "<id>" }. An unknown id answers 404.
GET /v1/promotions/custom-effect-typesList registered effect types. Filter by id, name, shape_version, carries_amount or active.
GET /v1/promotions/custom-effect-types/{id}Read one effect type. An unknown id answers 404.
DELETE /v1/promotions/custom-effect-types/{id}Delete an effect type that no effect uses. While one does, the call answers 409, and the message names the promotions that use it. A 200 answer is { "deleted": "<id>" }.

Export a batch's codes

GET /v1/promotions/batches/{id}/export answers with the batch's codes as JSON: batch_id, name, created (how many codes the batch generated, not a date), redeemed (the uses of all its codes added up) and codes. Each entry in codes has code, status, usage_count and contact_id.

POST /v1/promotions/batches/{id}/export answers with JSON, not a file: batch_id, filename (the batch name with spaces and other symbols turned into hyphens, followed by -codes.csv), content_type (text/csv) and content. content is the CSV text with the same codes. Its first line is the header code;status;usage_limit;usage_count;ends_at. Then one code per line, sorted by code, with the fields separated by semicolons. ends_at is a date (YYYY-MM-DD), empty when the code has no end. A value that contains a semicolon, a quote or a line break is quoted. The call takes no body. An unknown batch answers 404 to both.

The API reference has no response schema for either export call yet, so these shapes may change.

Export as CSV
curl -s -X POST https://api.revenexx.com/v1/promotions/batches/<BATCH_ID>/export \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT" \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  | jq -j .content > codes.csv

Conditions

POST /v1/promotions/conditions/validate checks one condition without saving it. An editor calls it while a merchant is still typing, so an unknown subject, or a comparison that needs a second value, is caught before save. It takes a condition in the same fields as a condition row (see PUT /v1/promotions/conditions/{id}). It answers 200 when the condition is valid, and 400 with a message that names what is wrong; for an unknown subject or comparison it also lists what is available.

GET /v1/promotions/promotions/{id}/conditions returns a promotion's condition rows built into the tree the engine evaluates, with the depth it reaches. Use it to check an offer the way the engine reads it. An unknown promotion answers 404.

Subjects and comparisons come from a closed list. GET /v1/promotions/vocabularies lists every vocabulary by name, title and description, without its values. GET /v1/promotions/vocabularies/{name} returns one vocabulary with its values. An unknown name answers 404. Read the list rather than copying it from this page.

Voucher reservations

A reservation records that a voucher code is held for a buyer. It is not the checkout reserve call, which holds what a cart redeems. Use one to hold a limited code for a buyer, for example a code for the first hundred people who ask. A reservation alone does not keep the code from anyone else: evaluate refuses other buyers only when the voucher itself sets reservation_required, a contact_id or an organization_id. A live reservation for the buyer's contact_id is what lets them past those checks, and only when the evaluate request sends that contact_id: a reservation that sets only organization_id admits nobody. An expired reservation stops counting when the code is next looked at.

RouteWhat it does
GET /v1/promotions/voucher-reservationsList reservations. Filter by id, voucher_id, contact_id or organization_id. Page with limit (default 50, max 200) and offset, and use page.total and page.hasMore.
POST /v1/promotions/voucher-reservationsHold a code. voucher_id is required. Answers 201 with the reservation.
GET /v1/promotions/voucher-reservations/{id}Read one reservation.
PUT /v1/promotions/voucher-reservations/{id}Correct a reservation.
DELETE /v1/promotions/voucher-reservations/{id}Release a reservation.

A reservation takes voucher_id (required), contact_id, organization_id and expires_at. Leave expires_at empty to hold the code until the reservation is deleted, unless the tenant has set a reservation lifetime (reservation_lifetime_days, 0 by default). A new reservation on a voucher whose reservation_limit is set answers 409 once that many live reservations already hold it. A POST with a voucher_id that names no voucher answers 400.

The active flag on groups and effect types

Stacking groups and custom effect types take an optional boolean active on create (POST) and on change (PUT). Every group and every effect type is active until you set it to false.

RowWith active: false
Stacking group: POST /v1/promotions/groups, PUT /v1/promotions/groups/{id}The promotions already in the group keep being weighed by its mode. Putting a promotion or a group into it is refused with 409. A group whose parent_id names an inactive group answers 409 with group_inactive.
Custom effect type: POST /v1/promotions/custom-effect-types, PUT /v1/promotions/custom-effect-types/{id}Every effect already written against the type keeps applying and keeps its title. A new effect of the type, or an effect moved onto it, is refused with 409 and the code custom_type_inactive.

To retire a group without touching its members, change only the flag:

Deactivate a group
curl -s -X PUT https://api.revenexx.com/v1/promotions/groups/<GROUP_ID> \
  -H "X-Revenexx-Tenant: $REVENEXX_TENANT" \
  -H "X-Revenexx-Api-Key: $REVENEXX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "active": false }'

Where to go next

Was this page helpful?