API reference
Promo Codes and Manual Grants API
Manage product promo codes and grant customer access without confusing a grant with a paid checkout.
Choose a discount or an access grant
A promo code changes the offer used at checkout. A manual grant creates product access for a named customer without collecting a checkout payment. These are different operations: a grant is not a refund, paid sale or recurring card mandate.
Use an account session for the effective developer workspace. Promo listing requires marketing.view; promo creation and changes require marketing.manage. Manual grants require entitlements.manage. Product ownership and team product assignments still apply.
Create a code and inspect its sync state
POST /api/developer/products/{productId}/promo-codes with code, discountType (percent or amount), discountValue and the intended purchaseType (one_time or subscription). Amount discounts are in dollars. New codes have a fixed $9.99 minimum spend; the paid product must also remain at least $9.99 after discounts, before tax.
List returns both product-specific codes and workspace-wide codes. global:true creates a workspace-wide code and requires all-products workspace access, even through this product route. Assigned-product teammates can still manage codes for their assigned products. Optional dates, usage limits, customerIds/customerEmails, sale-item discounts and automatic-offer stacking affect eligibility. A saved code can have a Stripe sync error; check stripeSyncError and the correct test/live provider IDs. Do not treat a successful save as proof that every provider offer is active.
SoundSync
Illustrative promo creation
curl --fail-with-body -sS -X POST "$SOUNDSYNC_API_BASE/api/developer/products/123/promo-codes" \
-H "Authorization: Bearer $DEVELOPER_TOKEN" \
-H "Content-Type: application/json" \
--data '{"code":"EXAMPLE10","discountType":"percent","discountValue":10,"purchaseType":"one_time"}'
Edit eligibility without rewriting the offer
PUT /api/developer/products/{productId}/promo-codes/{promoCodeId} is a sparse update. It changes supported limits, dates, active state, sale-item/stacking controls, customer eligibility and metadata. Omitted fields keep their values. code, discountType and discountValue are not changed by this handler; minimum spend and purchase type cannot be changed after creation.
PROMO_MINIMUM_IMMUTABLE and PROMOTION_TYPE_IMMUTABLE require a new or duplicated offer, not repeated retries. PROMOTION_LIMIT_BELOW_COMMITTED means the requested limit is below completed plus reserved uses. maxRedemptions:null removes the limit. Updating a saved workspace-wide code requires all-products access; setting global:false or supplying a productId does not make it product-only. A restricted write returns 403 developer_team_all_products_scope_required without updating the code or syncing Stripe.
Grant access with a reason
POST /api/developer/manual-grants requires productId, email and reason. email remains required when customerId is supplied. Choose access, new_serial or existing_serial through grantType; serial-only non-subscription products can use generated serial fulfillment. licenseType labels the grant, and internalNote records private context. Use sendEmail:false to suppress the normal customer email attempt.
Inspect created, skipped, reason, entitlements, serials and email. An existing active entitlement can return already_granted without a new purchase. Partial workflow errors need reconciliation before a retry; this endpoint does not promise whole-workflow atomicity. Keep returned customer identity, serials and account links private. A grant does not import payment methods or override an unsubscribe.
Subscription grants have an additional lifecycle
Subscription access grants require subscriptionGrantDuration: week, month, three_months, custom or lifetime. custom also needs subscriptionGrantEndDate as YYYY-MM-DD. A timed first-billing date must be at least two days and no more than two years away. Read the returned subscription-specific result and customer continuation rather than treating it as a normal one-time serial.
Keep-products subscription ownership begins only after successful payment. An unpaid manual grant cannot create that ownership. Existing grant conflict, bundle readiness, provider setup and serial-fulfillment checks can reject the request. Consult the subscription and customer-management references before integrating this path.
SoundSync
Related workflows
SoundSync
Requests and responses
/api/developer/products/{productId}/promo-codes
List product and workspace-wide promo codes
- Auth
- Account Bearer JWT
- Scope
- marketing.view and assigned-product access; effective workspace product ownership.
/api/developer/products/{productId}/promo-codes
Create a product promo code
- Auth
- Account Bearer JWT
- Scope
- marketing.manage and assigned-product access; workspace-wide creation additionally requires all-products scope.
/api/developer/products/{productId}/promo-codes/{promoCodeId}
Update limits, dates, eligibility and active state
- Auth
- Account Bearer JWT
- Scope
- marketing.manage and assigned-product access; editing an existing workspace-wide code additionally requires all-products scope.
/api/developer/manual-grants
Grant access to an owned product with an audit reason
- Auth
- Account Bearer JWT
- Scope
- entitlements.manage and assigned-product access; product ownership and customer matching remain enforced.
