Redeem points for a reward
POST /customers/{customer_id}/redemptions| Scope | redemptions:write |
| Customer session token | Accepted with the write scope, for the token’s own customer |
| Rate limit class | write_shopify (limits) |
| Idempotency key | Required (idempotency) |
| MCP tool | charm_create_redemption |
Spends points and mints a real Shopify discount code. Rate limits on this endpoint fail closed: if usage cannot be metered, the request is refused rather than allowed. reward_id and quantity may also be sent as query parameters, for no-code clients (a Gorgias action button, say) whose only way to collect a value from a human is the query string — without it a button can only ever grant the one reward hardcoded into its body. The body wins when both are present, and the query string is part of the idempotency hash.
Parameters
Section titled “Parameters”| Name | In | Type | Description |
|---|---|---|---|
customer_id | path | string | Shopify customer GID (percent-encoded, e.g. gid%3A%2F%2Fshopify%2FCustomer%2F123) or the numeric customer id. |
reward_id (optional) | query | string | Alternative to the body field of the same name. |
quantity (optional) | query | integer | Alternative to the body field of the same name. |
Request body
Section titled “Request body”| Field | Type | Description |
|---|---|---|
reward_id | string | A redemption rule id from /v1/rewards. A reward’s name is accepted too when exactly one enabled reward has it, so a no-code client can show readable text where it must send this value. |
quantity | integer | (optional) |
variant_id | string | For a product reward added to the cart by your storefront: the gift variant (GID or numeric id). The response then carries line_attribute; add the variant with that line property and apply discount_code. (optional) |
Response 200
Section titled “Response 200”Returns Redemption.
| Field | Type | Description |
|---|---|---|
changed | true | |
variant_id | string or null | The gift variant, when variant_id was sent. (nullable) |
line_attribute | object or null | (nullable) |
id | string or null | Redemption id; pass to /redemptions/{redemption_id}/reverse. (nullable) |
reward_id | string | |
reward_name | string or null | (nullable) |
quantity | number | |
points_spent | number | |
balance_after | number or null | (nullable) |
discount_code | string or null | (nullable) |
expires_at | string or null | (nullable) |
credit_amount | number or null | (nullable) |
{ "changed": true, "variant_id": "44123456789", "line_attribute": { "key": "key", "value": "value" }, "id": "id", "reward_id": "rr_10off", "reward_name": "10% off", "quantity": 1, "points_spent": 120, "balance_after": 120, "discount_code": "CHARM-7Q2X9", "expires_at": "2026-10-01T09:30:00.000Z", "credit_amount": 1}Example
Section titled “Example”curl -X POST "https://charm.appfleece.app/api/v1/customers/7712345/redemptions" \ -H "Authorization: Bearer chrm_live_..." \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"reward_id":"rr_10off"}'Errors
Section titled “Errors”unauthorized · invalid_token · token_expired · insufficient_scope · access_required · not_found · validation_error · rate_limited · idempotency_conflict · shopify_unavailable · insufficient_points
Every error body carries code, message, request_id and a doc_url pointing at the matching entry in the error catalog.