PATCH /api/v1/offers/{offerId}
Replaces the complete editable representation. Omitted optional fields clear or default as documented, except currency, which is preserved. An open unsold offer remains editable, but its currency never changes; closed or sold offers refuse editing.
Use x-api-key with an active key bound to this school. The bearer must retain membership and current domain permissions; CASL remains authoritative for owner/admin operations.
| Role in the organization | This endpoint |
|---|---|
OWNER | Allowed |
ADMIN | Allowed |
MEMBER | Refused — 403 |
| Requirement | Rule for this endpoint |
|---|---|
| Business role | None beyond owner/admin |
| Resource scope | School bound to the key; closer reads only their own offers, without schedules |
| Participation | None |
| Resource state | Editable unsold offer; currency may change only in draft |
| Parameter | Type | Required | Description |
|---|---|---|---|
offerId | UUID | yes | Offer visible to the bearer in this school |
| Header | Required | Description |
|---|---|---|
x-api-key | yes | School API key |
Idempotency-Key | yes | Stable key for this intention and target |
X-Correlation-Id | no | Support correlation identifier |
| Field | Type | Required | Nullable | Description |
|---|---|---|---|---|
currency | EUR or USD | no | no | Creation defaults to EUR; update preserves the current currency when omitted. |
amountCents | integer | USD: yes | EUR: yes | Positive minor units, maximum 1000000000; EUR may inherit the selected program price. USD never inherits or compares against that EUR price. |
title | string | no | yes | Up to 120 characters. |
recipientName | string | no | yes | Up to 120 characters; a name or email makes the offer nominative. |
recipientEmail | no | yes | Recipient email; empty string clears it. | |
programId, cohortId | UUID | no | yes | Both must belong to this school. |
paymentMode | upfront or installments | yes | no | Instalments require a named recipient. |
installments | array | yes | no | Maximum 36 items: position non-negative integer, dueOn YYYY-MM-DD, amountCents positive integer at most 1000000000. All amounts use the offer currency. Upfront clears the schedule. |
conditions | string | no | yes | Up to 2000 characters. |
paymentMethodMode | school-default, buyer-choice or fixed | no | no | Defaults to school-default. |
preferredPaymentMethodCode | string | fixed: yes | yes | Active payment method from composer options; maximum 64 characters. |
landing | presentation or direct-payment | yes | no | Public landing behavior. |
slug | string | no | yes | Maximum 120 lowercase letters, digits and single hyphens; ignored for named offers. |
seatLimit | integer | no | yes | 1 to 100000; nominative offers have one seat. |
expiresAt | ISO 8601 instant | no | yes | Include timezone, for example 2027-01-01T00:00:00Z. |
publish | boolean | yes | no | Opens the offer and locks currency. The schedule must cover the amount; instalment fees may increase its total. |
curl -X PATCH "$COHORTE_API_BASE_URL/api/v1/offers/44444444-4444-4444-8444-444444444444" \
-H "x-api-key: $COHORTE_API_KEY" \
-H "Idempotency-Key: offer-usd-001" \
-H "Content-Type: application/json" \
-d '{"title":"Coaching","currency":"USD","amountCents":25000,"paymentMode":"upfront","installments":[],"landing":"presentation","publish":false}'200: successful read, update or replay.400: invalid payload, unsupported currency, locked currency or invalid offer state.401: invalid key, bearer or membership.403: insufficient permission.404: absent or outside the authorized school/scope.429: rate limited.500: unexpected error.| Field | Type | Nullable | Description |
|---|---|---|---|
success | boolean | no | True on success |
data | object | no | Offer editable fields above, plus id, status, soldCount, programDefaultPriceCents (null for USD), expiresAt and updatedAt ISO instants. Schedule items also carry id; installments is null for a restricted closer read. paymentPath is the private payment path for nominative offers, null for slug offers. |
{"success":true,"data":{"id":"44444444-4444-4444-8444-444444444444","title":"Coaching","amountCents":25000,"currency":"USD","status":"draft","installments":[],"programDefaultPriceCents":null}}Keep the same idempotency key for retries; a successful retry returns the existing offer without another mutation. Do not reuse a key for another target or intention. Changes are audited and emit offer events. Creation with a recipient email also resolves their contact. No payment or email is sent by this command. Currency locks at publication, even before the first sale.
Publishing a split offer requires 2–36 equal positive monthly amounts, with a total at least equal to the upfront price. A higher total is a commercial surcharge. Dates describe relative monthly periods; the actual calendar starts at purchase. A new split purchase requires a usable connected Stripe account. Stripe creates its Product and Price automatically and collects exactly N periods.
An active Checkout reservation or Stripe payment plan refuses local offer/schedule edits. Existing API-key tenant binding, CASL and business scope apply unchanged. Upfront offers retain one-time Checkout.