Worked incentives samples
The endpoint list below documents every operation’s parameters; this overlay walks an incentive the way a promotions API actually uses it: browse the provider catalog → issue an incentive → list the issued ledger → handle a redeem or void. Success envelopes are{ data, meta },
error envelopes { error, meta } — see How to read a worked
sample. The standalone promotions engine is
state-projected over the tenant cdp_events ledger (the same append-only
store the loyalty layer uses), so issuance is migration-free and a read
always converges. For the console-side accounting treatment, see the
incentives ledger guide.
Every response carries meta.request_id. Quote the request id when you
report a credit that landed on the wrong ledger — a support trace needs
both the row id and the request id.
1. Browse the provider catalog
GET /api/v1/incentives/providers returns every incentive fulfillment
provider the engine can call: the internal Orbit reward-code minter and
manual-fulfillment recorder, plus external gift-card networks (Tremendous,
Tango) that stay descriptors until you connect credentials. Use this to
drive the reward-source picker or to decide which provider_id to pin on
the issue call. Each row carries configured — true when the engine can
dispatch right now, false for an external network awaiting credentials.
200
2. Get a single provider
GET /api/v1/incentives/providers/{id} returns one provider descriptor —
the same shape each catalog row carries. Use it to render the detail behind
a reward-source picker, or to confirm a provider_id you pinned on issue
is still configured. An unknown id returns 404 NOT_FOUND.
200
3. Issue an incentive
POST /api/v1/incentives/issue creates one incentive — a credit, a
discount code, a gift card, or a custom payout — and records the outcome on
the tenant cdp_events ledger. The body takes reward_type (one of
credit, discount_code, gift_card, custom), optional amount /
currency, an optional provider_id (when omitted the engine picks a
default per reward type), a recipient (contact_id and/or email), and
freeform metadata. A write of this kind is gated to owner, admin, or
developer roles — the same guard the referral reward-payout surface uses.
Send an Idempotency-Key header (or idempotency_key field) to replay the
original result on retry; a replay returns 200 with replayed: true
rather than minting again.
201
4. List the issued ledger
GET /api/v1/incentives/issued returns every incentive the tenant has
issued, newest first, each row folded to its current lifecycle status
(issued, redeemed, or voided). Filter by status, source,
reward_type, or contact_id; page with limit (1–200, default 50) and
offset. Use this to drive a win-back report or the org’s own incentive
audit before you call redeem or void.
200
5. Redeem an issued incentive
POST /api/v1/incentives/{id}/redeem marks an issued incentive as
redeemed — the recipient applied the code, the gift card was spent, the
custom payout was fulfilled. The call appends an incentive.redeemed
event under a per-incentive advisory lock, so two concurrent redemptions
cannot both consume the same incentive. Only an issued incentive can be
redeemed; a terminal one (already redeemed or voided) returns 409. Pass
an optional reference (the order or ticket that consumed it) and
freeform metadata; the response returns the row in its post-redeem
state with status: "redeemed" and redeemed_at set.
200
409 CONFLICT with the
current lifecycle in error.details so your client can branch instead of
retrying:
409
6. Void an issued incentive
POST /api/v1/incentives/{id}/void cancels an issued incentive — the
code was returned unused, the recipient opted out, the payout was
reversed. The call appends an incentive.voided event under the same
per-incentive advisory lock; only an issued incentive can be voided, and
a terminal one returns 409. Pass an optional reason; the response
returns the row in its voided state with status: "voided" and
voided_at set.
200
7. Errors
Errors follow the{ error, meta } envelope. Two failures every
promotions integrator hits:
403 — role too low for a write.
403
422
redeemed or voided:
409