Promo Codes¶
Promo codes discount an order after it has been created but before it is paid. There are three operations — preview, apply, and remove — available for a single order and for a whole batch.
Unpaid orders only
Every promo operation requires the order to still be pending and unpaid. Once payment succeeds, the discount can no longer be changed. Apply codes between creating the order and starting payment.
How codes work¶
Two slots per order¶
An order holds at most one sales-company code and one sales-agent code — two independent slots.
| Slot | Holds |
|---|---|
| Sales company | One company promo code |
| Sales agent | One agent promo code |
Applying a code replaces whichever slot its type maps to and leaves the other untouched. Using both types on one order therefore means two requests.
You never send the code's type¶
Promo codes are globally unique, so HGN resolves the type — company or agent — from the code itself. There is no type parameter, and one request carries exactly one code.
Binding¶
Applying a code to an empty slot binds the order to that code's owner, which constrains what the order will accept afterwards:
| Order state | Company code accepted | Agent code accepted |
|---|---|---|
| No binding | Any company's | Any agent's |
| Bound to company A | A's only | Any agent's |
| Bound to agent X | Any company's | X's only |
Removing a code rolls its binding back, freeing the slot again.
Preview a code¶
Shows what a code would do, without changing the order. Use it to display a discount to the customer before they commit.
POST /api/external/v1/orders/{orderRef}/promo/preview
Authorization: Bearer <accessToken>
Content-Type: application/json
Response¶
200 OK — the codes the order would carry, the projected discount, and the totals before and after.
curl -X POST \
https://api.himalayanguardian.com/api/external/v1/orders/ORD-2026-000058/promo/preview \
-H "Authorization: Bearer $HGN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"promoCode": "HGN-SAVE10"}'
Preview never mutates the order, so it is safe to call on every keystroke of a promo-code field — though debouncing is still polite.
Apply a code¶
Applies one code to an unpaid order.
POST /api/external/v1/orders/{orderRef}/promo
Authorization: Bearer <accessToken>
Content-Type: application/json
Response¶
200 OK — the same payload shape as preview, reflecting the now-applied state.
A rejected code returns 400 and leaves the order completely untouched — there is no partial application to clean up.
async function applyPromo(orderRef, promoCode, token) {
const res = await fetch(`${BASE_URL}/orders/${orderRef}/promo`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ promoCode }),
});
if (res.status === 400) {
// Every rejection reason returns this same status and message.
return { applied: false, reason: "Invalid promo code" };
}
if (!res.ok) throw new Error(`Promo failed: ${res.status}`);
const { data } = await res.json();
return { applied: true, data };
}
def apply_promo(order_ref, promo_code, access_token):
res = requests.post(
f"{BASE_URL}/orders/{order_ref}/promo",
headers={"Authorization": f"Bearer {access_token}"},
json={"promoCode": promo_code},
timeout=10,
)
if res.status_code == 400:
return {"applied": False, "reason": "Invalid promo code"}
res.raise_for_status()
return {"applied": True, "data": res.json()["data"]}
Applying both code types¶
// Two separate requests — one per slot.
await applyPromo(orderRef, "COMPANY-CODE", token);
await applyPromo(orderRef, "AGENT-CODE", token);
Remove a code¶
| Query parameter | Type | Required | Description |
|---|---|---|---|
code |
String | No | Clears the slot this code currently occupies. Omit to clear both slots |
# Remove one specific code
curl -X DELETE \
"https://api.himalayanguardian.com/api/external/v1/orders/ORD-2026-000058/promo?code=HGN-SAVE10" \
-H "Authorization: Bearer $HGN_TOKEN"
# Remove every applied code
curl -X DELETE \
"https://api.himalayanguardian.com/api/external/v1/orders/ORD-2026-000058/promo" \
-H "Authorization: Bearer $HGN_TOKEN"
Removal is safe to repeat: a code that is not applied to the order is a no-op that returns the current state rather than an error.
Clearing a code reverses its usage reservation — returning it to the pool — and rolls back any binding it created.
Batch orders¶
A batch create returns a group reference. The same three operations exist at the group level:
| Method | Endpoint |
|---|---|
POST |
/orders/groups/{groupRef}/promo/preview |
POST |
/orders/groups/{groupRef}/promo |
DELETE |
/orders/groups/{groupRef}/promo |
They take the same body and query parameters as their single-order counterparts, apply one code to every eligible order in the batch, and return one entry per order plus group totals.
curl -X POST \
https://api.himalayanguardian.com/api/external/v1/orders/groups/GRP-2026-000012/promo \
-H "Authorization: Bearer $HGN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"promoCode": "HGN-SAVE10"}'
Read the per-order entries
Eligibility is evaluated per order, so a group response can be mixed — some orders discounted, others not. Do not treat a 200 as "applied to everything"; walk the per-order entries.
Promo codes at order creation¶
You can also pass promoCode directly on POST /orders. This is applied non-fatally:
- A rejected code does not fail the order — it is still created.
- The outcome is reported on
promoApplyResultin the create response.
These endpoints are how you recover from that: apply the code afterwards, or replace one the customer changed their mind about. Both require the order to still be pending and unpaid.
Errors¶
| Status | Cause |
|---|---|
400 |
Unknown promo code, blank promoCode, or an order that is not pending and unpaid |
401 |
Missing or invalid token |
403 |
Your company does not own this order |
404 |
No order matches the reference |
Every rejection returns the same message¶
Do not branch on the rejection message
Whatever the cause — expired, inactive, usage limit reached, or owned by another party — a rejected code returns the same message: Invalid promo code.
This is deliberate. Distinct messages would let a caller probe the endpoint to discover which codes exist. Treat every 400 from a promo endpoint as a single "code not usable" outcome and surface a generic message to the customer.
Ownership¶
Promo operations resolve against the same company that created the order. You can only reach orders your own company placed — anything else returns 403.