API ReferenceEndpointsCoupons

Coupons

Coupons are discount codes redeemed at checkout — percent off, fixed amount, free shipping, or BOGO. They carry usage limits (total + per-customer), date windows, eligibility rules (products, customer segments), and platform sync state. Use these endpoints to create, list, validate, and redeem coupons.

Every operation below is on the public API surface and takes Authorization: Bearer brainerce_*. The 8 /v1/coupons/* operations are the stable, versioned surface for external integrators (list, create, get, update, delete, sync, publish, platform capabilities).

The merchant dashboard uses its own internal coupon routes (signed-in session: conflicts, bulk operations, validate-for-order). They are not part of the public contract and are not listed here.

The newsletter welcome offer (/v1/newsletter-benefit/*) is on this page for the same reason it carries the coupons:* scopes: it mints a Coupon, and the coupon machinery enforces it. It issues nothing on its own. A coupon appears when a shopper confirms their own newsletter signup, one per mailbox per store, and resend re-sends that same code rather than minting a second. To hand a code to a named customer, create a coupon.

List coupons with pagination

Lists coupons, newest first by default, 20 per page.

GET
/v1/coupons
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Query Parameters

storeIdstring

ID of the store whose coupons are listed. The validator accepts a missing value, but the route answers 400 STORE_ID_REQUIRED without it.

page?integer

1-based page number.

Default1
Range1 <= value
limit?integer

Items per page. Default 20. Maximum 100. Larger values are capped to 100.

Default20
Range1 <= value <= 100
search?string

Text search on the coupon code.

status?string

Return only coupons with this status.

Value in"ACTIVE" | "SCHEDULED" | "EXPIRED" | "DISABLED"
type?string

Return only coupons of this discount type.

Value in"PERCENTAGE" | "FIXED_AMOUNT"
platform?string

Only coupons that have a mapping to this platform code (case-insensitive). Legacy filter: coupons are no longer synced to other platforms.

sortBy?string

Field to sort by.

Default"createdAt"
Value in"code" | "createdAt" | "updatedAt" | "startsAt" | "endsAt" | "usageCount"
sortOrder?string

Sort direction. Default desc.

Default"desc"
Value in"asc" | "desc"
curl -X GET "https://api.brainerce.com/api/v1/coupons?storeId=clx1a2b3c4d5e6f7g8h9&page=1&limit=20&search=string&status=ACTIVE&type=PERCENTAGE&platform=string&sortBy=code&sortOrder=asc"
{
  "data": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "code": "SUMMER20",
      "title": "Welcome offer",
      "description": "First order discount",
      "type": "PERCENTAGE",
      "value": "10.00",
      "startsAt": "2019-08-24T14:15:22Z",
      "endsAt": "2019-08-24T14:15:22Z",
      "status": "ACTIVE",
      "usageLimit": 100,
      "usageLimitPerCustomer": 1,
      "usageCount": 12,
      "minimumOrderAmount": "200.00",
      "maximumDiscount": "50.00",
      "conditions": {
        "loyaltySystem": true,
        "customerId": "string",
        "newsletterBenefit": true,
        "boundEmail": "string",
        "firstOrderOnly": true,
        "emailSequenceStep": true,
        "enrollmentId": "string",
        "stepId": "string"
      },
      "applicableProducts": [
        {
          "id": "clx1a2b3c4d5e6f7g8h9",
          "name": "Summer Collection"
        }
      ],
      "excludedProducts": [
        {
          "id": "clx1a2b3c4d5e6f7g8h9",
          "name": "Summer Collection"
        }
      ],
      "applicableCategories": [
        {
          "id": "clx1a2b3c4d5e6f7g8h9",
          "name": "Summer Collection"
        }
      ],
      "excludedCategories": [
        {
          "id": "clx1a2b3c4d5e6f7g8h9",
          "name": "Summer Collection"
        }
      ],
      "regionIds": [
        "string"
      ],
      "combinesWithOther": true,
      "needsSync": true,
      "lastSyncedAt": "2019-08-24T14:15:22Z",
      "channels": {},
      "channelPublishes": [
        {
          "salesChannel": {
            "id": "clx1a2b3c4d5e6f7g8h9",
            "name": "My storefront",
            "connectionId": "vc_9f3a2b1c8d7e6f5a"
          },
          "connection": {
            "id": "clx1a2b3c4d5e6f7g8h9",
            "name": "My storefront",
            "connectionId": "vc_9f3a2b1c8d7e6f5a"
          }
        }
      ],
      "vibeCodedPublishes": [
        {
          "salesChannel": {
            "id": "clx1a2b3c4d5e6f7g8h9",
            "name": "My storefront",
            "connectionId": "vc_9f3a2b1c8d7e6f5a"
          },
          "connection": {
            "id": "clx1a2b3c4d5e6f7g8h9",
            "name": "My storefront",
            "connectionId": "vc_9f3a2b1c8d7e6f5a"
          }
        }
      ],
      "createdAt": "2019-08-24T14:15:22Z",
      "updatedAt": "2019-08-24T14:15:22Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 137,
    "totalPages": 7
  }
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Create a new coupon

Honors Idempotency-Key header. See /docs/api/idempotency.

POST
/v1/coupons
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
codestring

Redemption code. Case-insensitive, and unique per store.

Lengthlength <= 100
title?string

Human-readable title shown in the dashboard list.

Lengthlength <= 200
description?string

Internal description (not shown to shoppers).

Lengthlength <= 2000
typestring

Discount type. PERCENTAGE = value is a percent (0-100); FIXED_AMOUNT = value is in store currency.

Value in"PERCENTAGE" | "FIXED_AMOUNT"
valuenumber

Discount amount, at most 2 decimal places. Interpretation depends on type: 25 for PERCENTAGE means 25% off (a PERCENTAGE value above 100 is refused); 25 for FIXED_AMOUNT means $25 off.

Range0 <= value
startsAt?string

ISO 8601 start date. Coupon is inactive before this timestamp. A bare YYYY-MM-DD means 00:00:00.000 of that day in the store time zone; a full timestamp is stored exactly as given.

endsAt?string

ISO 8601 end date. Coupon is inactive after this timestamp. A bare YYYY-MM-DD means 23:59:59.999 of that day in the store time zone, so the last day itself is included; a full timestamp is stored exactly as given.

status?string

Initial status. Defaults to ACTIVE. Use SCHEDULED when startsAt is in the future.

Default"ACTIVE"
Value in"ACTIVE" | "SCHEDULED" | "EXPIRED" | "DISABLED"
usageLimit?number

Total redemption cap across all customers. Omit for unlimited; 0 is stored as unlimited too.

Range0 <= value <= 2147483647
usageLimitPerCustomer?number

Per-customer redemption cap (requires customerId on the checkout). Omit for unlimited; 0 is stored as unlimited too.

Range0 <= value <= 2147483647
minimumOrderAmount?number

Minimum cart subtotal to redeem.

Range0 <= value <= 1000000000000
maximumDiscount?number

Cap on the discount value for PERCENTAGE coupons (e.g. "25% off, up to $50"). Ignored for FIXED_AMOUNT.

Range0 <= value <= 1000000000000
conditions?object

Advanced conditions (customer segment, sales channel, …). Free-form JSON.

applicableProducts?array<string>

Restrict redemption to specific product IDs.

regionIds?array<string>

Restrict the coupon to specific regions (PRD §24). Empty/omitted = applies in all regions. Holds Region IDs of this store.

excludedProducts?array<string>

Exclude specific product IDs from the coupon.

applicableCategories?array<string>

Restrict redemption to specific category IDs.

excludedCategories?array<string>

Exclude specific category IDs from the coupon.

combinesWithOther?boolean

When true (default), this coupon can stack with other discount sources (other coupons, automatic discount rules). When false, it cannot stack.

Defaulttrue
platforms?array<string>

LEGACY. Outside platform codes to push the coupon to. Brainerce no longer pushes coupons to outside platforms (Shopify and WooCommerce are retired and no installable connector carries coupons), so listing a platform only flags the coupon for sync and queues no useful job. It does NOT make the coupon redeemable anywhere. To make a coupon redeemable on one of the merchant's own sales channels, publish it to that channel with POST /v1/coupons/{id}/publish-sales-channel (an unpublished coupon is refused on a sales-channel storefront). Still accepted so existing callers keep working.

channels?object

Per-channel field overrides, keyed by sales channel ID (for example vc_abc123). Useful when one channel wants a different code or title. Keys that are legacy outside-platform codes are still accepted but no connector reads them.

curl -X POST "https://api.brainerce.com/api/v1/coupons" \  -H "Idempotency-Key: string" \  -H "Content-Type: application/json" \  -d '{    "code": "SUMMER25",    "type": "PERCENTAGE",    "value": 25  }'
{
  "id": "clx1a2b3c4d5e6f7g8h9",
  "code": "SUMMER20",
  "title": "Welcome offer",
  "description": "First order discount",
  "type": "PERCENTAGE",
  "value": "10.00",
  "startsAt": "2019-08-24T14:15:22Z",
  "endsAt": "2019-08-24T14:15:22Z",
  "status": "ACTIVE",
  "usageLimit": 100,
  "usageLimitPerCustomer": 1,
  "usageCount": 12,
  "minimumOrderAmount": "200.00",
  "maximumDiscount": "50.00",
  "conditions": {
    "loyaltySystem": true,
    "customerId": "string",
    "newsletterBenefit": true,
    "boundEmail": "string",
    "firstOrderOnly": true,
    "emailSequenceStep": true,
    "enrollmentId": "string",
    "stepId": "string"
  },
  "applicableProducts": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "excludedProducts": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "applicableCategories": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "excludedCategories": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "regionIds": [
    "string"
  ],
  "combinesWithOther": true,
  "needsSync": true,
  "lastSyncedAt": "2019-08-24T14:15:22Z",
  "channels": {},
  "channelPublishes": [
    {
      "salesChannel": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      },
      "connection": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      }
    }
  ],
  "vibeCodedPublishes": [
    {
      "salesChannel": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      },
      "connection": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      }
    }
  ],
  "createdAt": "2019-08-24T14:15:22Z",
  "updatedAt": "2019-08-24T14:15:22Z",
  "validationWarnings": [
    {
      "platform": "TIKTOK",
      "warnings": [
        "TIKTOK does not support product exclusions - will be ignored on sync"
      ]
    }
  ]
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons"
}

{
  "statusCode": 409,
  "code": "CONFLICT",
  "message": "The request conflicts with the current state of the resource",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Get platform capabilities for coupon features

Deprecated. Always an empty object: coupons are not synced to other platforms, so there are no per-platform coupon capabilities to report.

GET
/v1/coupons/platform-capabilities
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

curl -X GET "https://api.brainerce.com/api/v1/coupons/platform-capabilities"
{}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/platform-capabilities"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/platform-capabilities"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Get a single coupon by ID

Returns one coupon of this store.

GET
/v1/coupons/{id}
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

idstring

Coupon ID

curl -X GET "https://api.brainerce.com/api/v1/coupons/string"
{
  "id": "clx1a2b3c4d5e6f7g8h9",
  "code": "SUMMER20",
  "title": "Welcome offer",
  "description": "First order discount",
  "type": "PERCENTAGE",
  "value": "10.00",
  "startsAt": "2019-08-24T14:15:22Z",
  "endsAt": "2019-08-24T14:15:22Z",
  "status": "ACTIVE",
  "usageLimit": 100,
  "usageLimitPerCustomer": 1,
  "usageCount": 12,
  "minimumOrderAmount": "200.00",
  "maximumDiscount": "50.00",
  "conditions": {
    "loyaltySystem": true,
    "customerId": "string",
    "newsletterBenefit": true,
    "boundEmail": "string",
    "firstOrderOnly": true,
    "emailSequenceStep": true,
    "enrollmentId": "string",
    "stepId": "string"
  },
  "applicableProducts": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "excludedProducts": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "applicableCategories": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "excludedCategories": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "regionIds": [
    "string"
  ],
  "combinesWithOther": true,
  "needsSync": true,
  "lastSyncedAt": "2019-08-24T14:15:22Z",
  "channels": {},
  "channelPublishes": [
    {
      "salesChannel": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      },
      "connection": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      }
    }
  ],
  "vibeCodedPublishes": [
    {
      "salesChannel": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      },
      "connection": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      }
    }
  ],
  "createdAt": "2019-08-24T14:15:22Z",
  "updatedAt": "2019-08-24T14:15:22Z"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Update a coupon

Supports Idempotency-Key header for safe retries.

PATCH
/v1/coupons/{id}
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

idstring

Coupon ID

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
code?string

New redemption code. Stored in upper case, unique per store (a code another coupon already uses answers 409). Cannot be null.

Lengthlength <= 100
title?string

Human-readable title shown in the dashboard list.

Lengthlength <= 200
description?string

Internal description (not shown to shoppers).

Lengthlength <= 2000
type?string

Discount type. PERCENTAGE = value is a percent (0-100); FIXED_AMOUNT = value is in store currency.

Value in"PERCENTAGE" | "FIXED_AMOUNT"
value?number

Discount amount, at most 2 decimal places. A PERCENTAGE value above 100 is refused only when this body also sends type: PERCENTAGE.

Range0 <= value
startsAt?string

ISO 8601 start date. Coupon is inactive before this timestamp. A bare YYYY-MM-DD means 00:00:00.000 of that day in the store time zone; a full timestamp is stored exactly as given.

endsAt?string

ISO 8601 end date. Coupon is inactive after this timestamp. A bare YYYY-MM-DD means 23:59:59.999 of that day in the store time zone, so the last day itself is included; a full timestamp is stored exactly as given. Must be after startsAt when the body sends both (the same day is fine for bare dates).

status?string

New stored status.

Value in"ACTIVE" | "SCHEDULED" | "EXPIRED" | "DISABLED"
usageLimit?number

Total redemption cap across all customers.

Range0 <= value <= 2147483647
usageLimitPerCustomer?number

Per-customer redemption cap (requires customerId on the checkout).

Range0 <= value <= 2147483647
minimumOrderAmount?number

Minimum cart subtotal to redeem.

Range0 <= value <= 1000000000000
maximumDiscount?number

Cap on the discount value for PERCENTAGE coupons (e.g. "25% off, up to $50"). Ignored for FIXED_AMOUNT.

Range0 <= value <= 1000000000000
conditions?object

Advanced conditions (customer segment, sales channel, ...). Free-form JSON; replaces the stored object.

applicableProducts?array<string>

Restrict redemption to these product IDs (replaces the stored list). Every id must be a product of this store.

regionIds?array<string>

Restrict the coupon to these Region IDs of the store (replaces the stored list). An empty array = applies in every region.

excludedProducts?array<string>

Exclude these product IDs from the coupon (replaces the stored list).

applicableCategories?array<string>

Restrict redemption to these category IDs (replaces the stored list). Every id must be a category of this store.

excludedCategories?array<string>

Exclude these category IDs from the coupon (replaces the stored list).

combinesWithOther?boolean

Whether this coupon can stack with other discount sources (other coupons, automatic discount rules).

needsSync?boolean

Legacy. Every update that changes a field sets the coupon needsSync flag unless this is false. Nothing syncs coupons to other platforms, so the flag has no effect.

channels?object

Per-channel field overrides, keyed by sales channel ID (for example vc_abc123). Replaces the stored object. A key for a channel outside the caller's sales-channel scope is refused.

curl -X PATCH "https://api.brainerce.com/api/v1/coupons/string" \  -H "Idempotency-Key: string" \  -H "Content-Type: application/json" \  -d '{}'
{
  "id": "clx1a2b3c4d5e6f7g8h9",
  "code": "SUMMER20",
  "title": "Welcome offer",
  "description": "First order discount",
  "type": "PERCENTAGE",
  "value": "10.00",
  "startsAt": "2019-08-24T14:15:22Z",
  "endsAt": "2019-08-24T14:15:22Z",
  "status": "ACTIVE",
  "usageLimit": 100,
  "usageLimitPerCustomer": 1,
  "usageCount": 12,
  "minimumOrderAmount": "200.00",
  "maximumDiscount": "50.00",
  "conditions": {
    "loyaltySystem": true,
    "customerId": "string",
    "newsletterBenefit": true,
    "boundEmail": "string",
    "firstOrderOnly": true,
    "emailSequenceStep": true,
    "enrollmentId": "string",
    "stepId": "string"
  },
  "applicableProducts": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "excludedProducts": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "applicableCategories": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "excludedCategories": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "name": "Summer Collection"
    }
  ],
  "regionIds": [
    "string"
  ],
  "combinesWithOther": true,
  "needsSync": true,
  "lastSyncedAt": "2019-08-24T14:15:22Z",
  "channels": {},
  "channelPublishes": [
    {
      "salesChannel": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      },
      "connection": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      }
    }
  ],
  "vibeCodedPublishes": [
    {
      "salesChannel": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      },
      "connection": {
        "id": "clx1a2b3c4d5e6f7g8h9",
        "name": "My storefront",
        "connectionId": "vc_9f3a2b1c8d7e6f5a"
      }
    }
  ],
  "createdAt": "2019-08-24T14:15:22Z",
  "updatedAt": "2019-08-24T14:15:22Z"
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 409,
  "code": "CONFLICT",
  "message": "The request conflicts with the current state of the resource",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Delete a coupon

Supports Idempotency-Key header for safe retries.

DELETE
/v1/coupons/{id}
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

idstring

Coupon ID

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
platforms?array<string>

Optional platforms to remove the coupon from

curl -X DELETE "https://api.brainerce.com/api/v1/coupons/string" \  -H "Idempotency-Key: string" \  -H "Content-Type: application/json" \  -d '{}'
{
  "success": true,
  "jobIds": [
    "string"
  ],
  "warnings": [
    "string"
  ]
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Sync a coupon to all connected platforms

Deprecated no-op. Coupons are not synced to other platforms, so nothing is queued: the response has success: false and an empty jobIds. Returns 404 when the coupon does not exist. To limit a coupon to one of your storefronts, use POST /v1/coupons/{id}/publish-sales-channel. Supports Idempotency-Key header for safe retries.

POST
/v1/coupons/{id}/sync
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

idstring

Coupon ID

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
curl -X POST "https://api.brainerce.com/api/v1/coupons/string/sync" \  -H "Idempotency-Key: string"
{
  "success": false,
  "message": "Coupons are not synced to other platforms.",
  "jobIds": [
    "string"
  ]
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}/sync"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}/sync"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}/sync"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Publish a coupon to specific platforms

Deprecated no-op. Coupons are not published to other platforms, so no job is queued and jobIds is empty. The platforms array is still required but ignored. To limit a coupon to one of your storefronts, use POST /v1/coupons/{id}/publish-sales-channel. Supports Idempotency-Key header for safe retries.

POST
/v1/coupons/{id}/publish
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

idstring

Coupon ID

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
platformsarray<string>

Accepted and ignored: coupons are not published to other platforms.

curl -X POST "https://api.brainerce.com/api/v1/coupons/string/publish" \  -H "Idempotency-Key: string" \  -H "Content-Type: application/json" \  -d '{    "platforms": [      "string"    ]  }'
{
  "message": "Coupons are not published to other platforms.",
  "jobIds": [
    "string"
  ]
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}/publish"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}/publish"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}/publish"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/coupons/{id}/publish"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Get the newsletter signup benefit settings

Returns null when the store has never configured a benefit. enabled: false means configured but switched off — two different states, and a storefront should treat both as "offer nothing".

GET
/v1/newsletter-benefit/settings
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

curl -X GET "https://api.brainerce.com/api/v1/newsletter-benefit/settings"
{
  "id": "clx1a2b3c4d5e6f7g8h9",
  "storeId": "clx1a2b3c4d5e6f7g8h9",
  "enabled": true,
  "version": 3,
  "discountType": "PERCENTAGE",
  "discountValue": 10,
  "minimumOrderAmount": 200,
  "maximumDiscount": 50,
  "combinesWithOther": false,
  "validityDays": 30,
  "eligibilityTtlHours": 48,
  "firstOrderOnly": true,
  "applicableProducts": [
    "string"
  ],
  "excludedProducts": [
    "string"
  ],
  "applicableCategories": [
    "string"
  ],
  "excludedCategories": [
    "string"
  ],
  "salesChannelIds": [
    "string"
  ],
  "content": {},
  "createdAt": "2026-09-02T10:30:00.000Z",
  "updatedAt": "2026-09-02T10:30:00.000Z"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/settings"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/settings"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Create or replace the newsletter signup benefit settings

A FULL REPLACEMENT, not a patch: every field is written, so omitting one clears it. Bumps the stored settings version.

Changing these settings never rewrites a promise already made. Signups still waiting for a confirmation click keep the terms they were shown at signup, and coupons already issued are untouched.

Supports Idempotency-Key header for safe retries.

PUT
/v1/newsletter-benefit/settings
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Header Parameters

Idempotency-Key?string

Client-supplied key (a UUID v4 is the recommended form, max 255 characters) that makes this mutation safe to retry.

  • Replay window: 24 hours. The first response for a key is cached; a retry inside the window with the same request body returns the original status and body without re-running the handler.
  • Reusing a key with a different body returns 409 Conflict with code: "IDEMPOTENCY_KEY_REUSED". The request fingerprint (method + path + body hash) is compared against the stored one; a mismatch is refused rather than served the old response.
  • Keys are scoped to the calling credential and store — two API keys may safely use the same key value.
  • Error responses are cached too, so a retry of a request that failed validation returns the same 400 immediately.
  • Sending this header on a GET returns 400 with code: "IDEMPOTENCY_KEY_NOT_SUPPORTED" — GETs are already idempotent.
  • Support is per-route and this parameter is the authoritative signal: a key sent to a route that does not declare it is accepted and silently ignored.

See /docs/api/idempotency.

Lengthlength <= 255
enabledboolean

Turn the benefit on or off. Off stops new offers immediately; coupons already issued keep working, and signups still waiting for a confirmation click keep the terms they were shown.

discountTypeCouponType

Kind of coupon issued to a new subscriber. PERCENTAGE takes a percent off, FIXED_AMOUNT a fixed amount in the store currency.

Value in"PERCENTAGE" | "FIXED_AMOUNT"
discountValuenumber

Percent (1-100) for PERCENTAGE, or an amount in the store currency for FIXED_AMOUNT.

Range0.01 <= value <= 1000000
minimumOrderAmount?number

Minimum order subtotal required to use the coupon.

Range0 <= value
maximumDiscount?number

Cap on the discount a percentage benefit may produce. Ignored — and stored as null — for a fixed-amount benefit.

Range0 <= value
combinesWithOtherboolean

Whether the coupon may stack with other discounts.

validityDaysinteger

Days the coupon stays usable, counted from issuance.

Range1 <= value <= 365
eligibilityTtlHoursinteger

Hours a signup stays eligible, counted from the moment the form was submitted. A confirmation click after this still subscribes the address but earns no coupon — the confirmation LINK itself never expires, so this is the only deadline in the flow.

Range1 <= value <= 8760
firstOrderOnlyboolean

Restrict the coupon to buyers with no previous order in this store. Checked against the address the coupon is bound to, including guest orders.

applicableProducts?array<string>

Product IDs the coupon applies to.

Itemsitems <= 200
excludedProducts?array<string>

Product IDs the coupon never applies to. Up to 200.

Itemsitems <= 200
applicableCategories?array<string>

Category IDs the coupon applies to. Empty means all categories. Every ID must belong to the store. Up to 200.

Itemsitems <= 200
excludedCategories?array<string>

Category IDs the coupon never applies to. Up to 200.

Itemsitems <= 200
salesChannelIds?array<string>

Sales channels the issued coupon may be redeemed on — each the channel id from list_sales_channels, its vc_* connectionId, or its exact name (stored as the id). Leave empty for every enabled channel — an empty list is expanded at issuance, because a coupon with no channel rows is refused on every sales-channel storefront.

Itemsitems <= 50
content?object

Localized copy: { "en": { "headline": "...", "terms": "..." }, "he": { ... } }. Shown on the signup form and repeated in the email.

curl -X PUT "https://api.brainerce.com/api/v1/newsletter-benefit/settings" \  -H "Idempotency-Key: string" \  -H "Content-Type: application/json" \  -d '{    "enabled": true,    "discountType": "PERCENTAGE",    "discountValue": 10,    "combinesWithOther": true,    "validityDays": 30,    "eligibilityTtlHours": 72,    "firstOrderOnly": true  }'
{
  "id": "clx1a2b3c4d5e6f7g8h9",
  "storeId": "clx1a2b3c4d5e6f7g8h9",
  "enabled": true,
  "version": 3,
  "discountType": "PERCENTAGE",
  "discountValue": 10,
  "minimumOrderAmount": 200,
  "maximumDiscount": 50,
  "combinesWithOther": false,
  "validityDays": 30,
  "eligibilityTtlHours": 48,
  "firstOrderOnly": true,
  "applicableProducts": [
    "string"
  ],
  "excludedProducts": [
    "string"
  ],
  "applicableCategories": [
    "string"
  ],
  "excludedCategories": [
    "string"
  ],
  "salesChannelIds": [
    "string"
  ],
  "content": {},
  "createdAt": "2026-09-02T10:30:00.000Z",
  "updatedAt": "2026-09-02T10:30:00.000Z"
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/settings"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/settings"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/settings"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

List issued newsletter benefits

Paginated { data, meta }, newest first. redeemed is derived from the coupon having been used, so it is accurate the moment an order completes.

There is deliberately NO email filter: a lookup-by-address would turn this into a "does this person shop here" probe for any leaked key with coupons:read. Filter the page you get back.

GET
/v1/newsletter-benefit/grants
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Query Parameters

storeId?string

Dashboard only: the store the grants belong to (the dashboard route requires it). Ignored on /v1, where the store is always the one the API key belongs to, so do not send it there.

page?integer

1-based page number.

Default1
Range1 <= value
limit?integer

Items per page. Default 20. Maximum 100.

Default20
Range1 <= value <= 100
status?string

Only grants in this status.

Value in"PENDING" | "ISSUING" | "ISSUED" | "EXPIRED" | "FAILED"
from?string

Only signups created on or after this ISO-8601 date.

Formatdate-time
to?string

Only signups created on or before this ISO-8601 date.

Formatdate-time
curl -X GET "https://api.brainerce.com/api/v1/newsletter-benefit/grants?storeId=string&page=1&limit=20&status=PENDING&from=2019-08-24T14%3A15%3A22Z&to=2019-08-24T14%3A15%3A22Z"
{
  "data": [
    {
      "id": "clx1a2b3c4d5e6f7g8h9",
      "email": "[email protected]",
      "status": "PENDING",
      "source": "storefront",
      "couponCode": "WELCOME-7K2QX",
      "redeemed": false,
      "expiresAt": "2026-10-02T10:30:00.000Z",
      "eligibleUntil": "2026-09-04T10:30:00.000Z",
      "confirmedAt": "2026-09-02T11:00:00.000Z",
      "emailSentAt": "2026-09-02T11:00:05.000Z",
      "attempts": 1,
      "lastError": null,
      "settingsVersion": 3,
      "createdAt": "2026-09-02T10:30:00.000Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 137,
    "totalPages": 7
  }
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/grants"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/grants"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/grants"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}

Re-send an issued newsletter benefit

Re-queues the SAME coupon code. It never mints a second one, so a support ticket cannot turn into two discounts. For a benefit whose issuance failed before any coupon existed, this retries the issuance and mails whatever comes out.

Rejects a signup that has not been confirmed yet, and one that lapsed before any coupon was minted: in both cases there is nothing to re-send and nothing that may be created.

POST
/v1/newsletter-benefit/grants/{grantId}/resend
AuthorizationBearer <token>

Admin API key (server-to-server). Issue via the dashboard at Settings → API keys. Plain-text key is shown once — store in a secret manager. Format: Authorization: Bearer brainerce_xxxxxxxxx.

In: header

Path Parameters

grantIdstring

Grant ID from the grants list

curl -X POST "https://api.brainerce.com/api/v1/newsletter-benefit/grants/string/resend"
{
  "code": "WELCOME-7K2QX",
  "discountType": "PERCENTAGE",
  "discountValue": 10,
  "expiresAt": "2026-10-02T10:30:00.000Z",
  "minimumOrderAmount": 200,
  "firstOrderOnly": true
}

{
  "statusCode": 400,
  "code": "VALIDATION_FAILED",
  "message": "name should not be empty, price must be a positive number",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/grants/{grantId}/resend"
}

{
  "statusCode": 401,
  "code": "UNAUTHORIZED",
  "message": "Invalid or expired credential",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/grants/{grantId}/resend"
}

{
  "statusCode": 403,
  "code": "INSUFFICIENT_SCOPE",
  "message": "API key does not have the required scope: products:write",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/grants/{grantId}/resend"
}

{
  "statusCode": 404,
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource not found",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/newsletter-benefit/grants/{grantId}/resend"
}

{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Too many requests",
  "details": {
    "retryAfterSeconds": 12
  },
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/store"
}