Interface Control Document (ICD): Salesforce - EPC
DRAFT FOR DISCUSSION. This document is not an agreed interface. It exposes the shapes of the current EPC backoffice API requests for creating and updating partners, offers and vouchers, so that the Salesforce team can tell us, field by field, what Salesforce holds and can send. Endpoints, behaviour, classifications and the activation model described here are proposals and will change once the field list is agreed.
| Version | Status | Update Comment | By | Date |
|---|---|---|---|---|
| 1.0 | Draft | Initial draft for field discussion with the Salesforce team. Inventory of the current EPC create and update request fields for partners, offers and vouchers, with what is mandatory today and what would need to hold before activation. Proposes the "create as draft, enrich in the backoffice, guard at activation" model | Retter | 19 Oct 2026 |
| Abbreviation | Description |
|---|---|
| EPC | Emirates Platinum Card — the Platinum programme and its API |
| Salesforce | The CRM in which partner accounts, contracts and the commercial terms of offers and vouchers are agreed. The source of truth for every field marked SF in this document |
| Backoffice | The EPC administration portal where programme staff enrich and activate records |
| Partner | A merchant organisation contracted to offer benefits to Platinum members |
| Venue | A physical location of a partner at which offers are redeemed. Called "location" in the EPC API |
| Offer | A benefit a partner publishes to members: a percentage discount, a special price, a special deal or a lifestyle-hub access |
| Voucher | A partner-supplied benefit sold or allocated to members as a redeemable code, with stock and a sale window |
| Pending | The EPC status of a partner, offer or voucher that has been created but not activated. Members never see a Pending record. This document uses "draft" and "Pending" interchangeably |
| Activation | The EPC control point after which a record is live for members. Always performed in the backoffice, never by this interface |
| Activation blocker | A field or condition that must be satisfied before a record may be activated. Shown in the backoffice so operators can see what is still missing |
| API key | A static secret issued to the Salesforce integration and sent in the X-API-Key header on every call |
| AED, fils | UAE Dirham and its minor unit (1 AED = 100 fils). Money in this interface is sent in AED; EPC stores fils |
| UAT | User Acceptance Testing environment |
Partners, their contracts and the commercial terms of offers and vouchers are agreed in Salesforce. Today the same data is typed a second time into the EPC backoffice. This draft describes an interface through which Salesforce would create and update the EPC partner, offer and voucher records directly, so that Salesforce becomes the source of truth for the fields it holds and the backoffice is used for what Salesforce does not hold: images, categories, eligibility rules, finance coding, voucher codes and the activation decision itself. Which fields those are is the question this draft is meant to settle: section 4.4 lists every field of the current EPC requests for the Salesforce team to mark as available or not.
In scope: creation and update of partners (including venues), offers and vouchers, in the direction Salesforce to EPC.
Out of scope: activation, deactivation and deletion through this interface (see open item 4 for the one proposed exception); images, documents and voucher codes (binary content stays in the backoffice); member data; redemption, sales or usage data flowing back to Salesforce (open item 8).
? in section 4.4 is a candidate to be confirmed. Fields marked — under "Salesforce" are set in EPC only| Risk | Mitigation |
|---|---|
| Salesforce creates an offer or voucher for a partner EPC has not received | EPC returns 404; Salesforce sends the partner first, then retries |
| A Salesforce push overwrites a value an operator corrected in the backoffice | Salesforce-owned fields are read-only in the backoffice, so there is nothing to overwrite. The audit log records both values |
| A record stays Pending because nobody notices the missing enrichment | The backoffice lists what is still missing for activation; a daily digest of Pending Salesforce-created records is recommended (open item 6) |
| Dates, prices or quantities change in Salesforce after a record is live | EPC applies the update to the live record exactly as a backoffice edit would, including the existing approval rules (open item 5) |
| Network timeouts | Client timeout of 30 s; retry with back-off; the upsert makes a duplicate retry safe |
| Sr No. | Non-Functional Requirement | Value |
|---|---|---|
| 1 | PHI or Credit Card data? | No credit card data and no patient health information. Only partner business data and partner contact details |
| 2 | Authentication Pattern | Static API key sent in the X-API-Key header, one key per environment, issued by the EPC team at onboarding |
| 3 | Authorisation | The key identifies the Salesforce integration. It may create and update partners, offers and vouchers and nothing else. It cannot activate, deactivate or delete |
| 4 | Secure Channel / Encryption in Transit | TLS 1.2 or higher (HTTPS only) |
| 5 | Input validation | Yes. Every field is validated against the JSON schema in section 4.4. Unknown and EPC-only fields are dropped, never stored |
| 6 | Internet exposed? IP whitelisting? | Yes, exposed via CloudFront. An IP allow-list (WAF) for the Salesforce egress ranges is recommended |
| 7 | Logging and audit | Every call is recorded with its outcome, without the API key or body (integration-call log). Every created or changed record writes an EPC audit entry with actor salesforce and the before and after values |
| 8 | Data exposure | Responses return EPC identifiers and status only. No member data is ever returned |
| Component | Value |
|---|---|
| Source | Salesforce (CRM) |
| Source Protocol | HTTPS REST API |
| Source Data Format | JSON, UTF-8 |
| Target | EPC (Emirates Platinum Card) API |
| Target Protocol | HTTPS |
| Target Data Format | JSON |
| Direction | Inbound to EPC. Every call is initiated by Salesforce; EPC does not call Salesforce in this ICD |
| Operation | Upsert: one PUT per record, keyed by the Salesforce identifier. Create when it is unknown, update when it is known |
┌──────────────┐ PUT /partners/salesforce/{sfId} ┌──────────────┐ enrich + activate ┌──────────────┐
│ │ PUT /offers/salesforce/{sfId} │ │ ◀─────────────────── │ │
│ Salesforce │ PUT /vouchers/salesforce/{sfId} │ EPC API │ │ Backoffice │
│ (Source) │ ─────────────────────────────────────▶ │ (Target) │ ───────────────────▶ │ (Operator) │
│ │ ◀───────────────────────────────────── │ │ missing for │ │
└──────────────┘ created / updated + what is missing └──────────────┘ activation └──────────────┘
│
▼
Pending ──(blockers cleared,
operator activates)──▶ Active
Flow:
X-API-Key headerStandard EPC error envelope and HTTP statuses (400 validation, 401 invalid key, 404 unknown partner, 409 locked by a pending approval, 500 retry with back-off). The error catalogue is specified once the field list is agreed.
partnerId from it. Its name and shape are Salesforce's to decide. Whether a Salesforce lifecycle status should also be sent is open item 4. The EPC id (UUID) and the human-readable offerId and voucherId numbers are returned for referencenull, never omitted and never as an empty string. The same convention as the HRMS employee interface. Fields EPC owns are never in the payload, so a full overwrite never touches themYYYY-MM-DD. An offer end date includes the day it names. Voucher dates carry a time of day and are ISO 8601 with a UTC offset, for example 2026-11-01T10:00:00+04:00; they are shown in Asia/Dubai25 or 25.5). EPC stores fils. No currency field is sent| Column | Values |
|---|---|
| API | Which current EPC backoffice request carries the field. C create only, U update only, C, U both. — not in either request today: it has its own endpoint (image upload, venue, eligibility rule, code import) or is set by EPC |
| Today | Whether the EPC create request requires it today. Req required, Opt optional, Cond conditional (stated in the description), — not in the create request |
| Salesforce | Whether Salesforce holds and sends the field. ? not yet known: to be confirmed with the Salesforce team field by field (open item 3). Once confirmed each becomes M (mandatory on create, 400 without it) or O (optional). — EPC-owned, never part of this interface. M is already fixed only for the owning partner reference |
| Activation | Whether it must hold before the record may be activated. Req required, Cond conditional (stated in the description), — not required |
| Field | Type | API | Today | Salesforce | Activation | Description |
|---|---|---|---|---|---|---|
| brandName | String (1–200) | C, U | Req | ? | Req | Trading name shown to members, e.g. "Acme Hotels" |
| legalName | String (≤200) | C, U | Opt | ? | — | Registered entity name, e.g. "Acme Hospitality LLC" |
| contractId | String | C, U | Req | ? | Req | Contract reference, e.g. "SF-00123". Today exported under the heading "Salesforce ID"; see open item 1 for whether it is the Contract Id or a contract number |
| contractStartDate | Date | C, U | Req | ? | Req | YYYY-MM-DD |
| contractEndDate | Date | C, U | Opt | ? | — | YYYY-MM-DD. null = open-ended |
| businessAddress | String | C, U | Req | ? | Req | Head-office address |
| city | String | C, U | Req | ? | Req | City |
| country | String | C, U | Req | ? | Req | Country |
| website | URL | C, U | Opt | ? | — | Full URL including https:// |
| primaryContact.name | String | C, U | Opt | ? | — | Contact person |
| primaryContact.email | C, U | Opt | ? | — | Must be a valid e-mail address | |
| primaryContact.phone | String | C, U | Opt | ? | — | E.164 recommended, e.g. "+971501234567" |
| partnerTier | String | C, U | Opt | ? | — | Free-text tier label |
| partnerGroup | String (1–100) | C, U | Opt | ? | — | Free-text grouping |
| accountAgent | String | C, U | Opt | ? | — | Owning account manager |
| tradeLicenseNumber | String | C, U | Opt | ? | — | Trade licence number |
| socialMediaAccounts | Array | C, U | Opt | ? | — | Items of { platform, url }. platform is one of facebook, instagram, linkedin, x, youtube, tiktok |
| locations | Array | — | — | ? | — | Venues, one item per venue; see 4.4.2. null leaves venues untouched |
| brandLogo | Image | C | Opt | — | Req (new) | Uploaded in the backoffice. The backoffice create form already requires it; the API does not, so this is a new activation check |
| heroImage, additionalImages | Image | — | — | — | — | Uploaded in the backoffice |
| documents | File | — | — | — | — | Contract documents uploaded in the backoffice |
| status, statusReason | Enum | — | — | — | — | pending, active, inactive. Changed only by backoffice activation and deactivation |
Venues have their own add and update requests in the current API; this draft nests them in the partner payload. Each venue would need a stable Salesforce identifier to be matched by (see Data Conventions). A venue EPC has not seen is added active, as the backoffice does. A venue present in EPC but absent from the array is left untouched (no deletion through this interface).
| Field | Type | API | Today | Salesforce | Activation | Description |
|---|---|---|---|---|---|---|
| name | String (1–200) | C, U | Req | ? | — | Venue name, e.g. "Acme Downtown" |
| address | String | C, U | Req | ? | — | Street address |
| coordinates.latitude | Number | C, U | Req | ? | — | Decimal degrees, -90 to 90. Required by EPC for the member map; if Salesforce cannot supply coordinates, venues are EPC-owned and entered in the backoffice (open item 2) |
| coordinates.longitude | Number | C, U | Req | ? | — | Decimal degrees, -180 to 180 |
| contact.name, contact.email, contact.phone | String | C, U | Opt | ? | — | Venue contact |
| status | Enum | — | — | — | — | active, inactive. Deactivated in the backoffice |
| Field | Type | API | Today | Salesforce | Activation | Description |
|---|---|---|---|---|---|---|
| partnerId | String (UUID) | C | Req | M | Req | EPC id of the owning partner; 404 if unknown. Immutable after create. Salesforce would send its own partner identifier here, which EPC resolves (see Data Conventions) |
| name | String (1–200) | C, U | Req | ? | Req | Offer name, e.g. "Two-for-one brunch" |
| offerType | Enum | C, U | Req | ? | Req | discount, special_price, special_deal, lifestyle_hub. Salesforce picklist mapping in open item 3 |
| percentage | Number (0–100) | C, U | Cond | ? | Cond | Required when offerType is discount, forbidden otherwise |
| price | Number (AED) | C, U | Cond | ? | Cond | Required when offerType is special_price, forbidden otherwise |
| startDate | Date | C, U | Req | ? | Req | YYYY-MM-DD. First day the offer may be redeemed |
| endDate | Date | C, U | Opt | ? | — | YYYY-MM-DD, on or after startDate. null = open-ended. Cannot be activated once past |
| viewStartDate | Date | C, U | Req | ? | Req | YYYY-MM-DD. When null, EPC sets it to startDate |
| viewEndDate | Date | C, U | Cond | ? | Cond | YYYY-MM-DD. Required once endDate is set and may not be later than it; when null EPC sets it to endDate |
| quota | Integer (≥0) | C, U | Opt | ? | — | Total redemptions allowed. null = unlimited |
| shortDescription | String (1–500) | C, U | Req | ? | Req | Member-facing summary |
| longDescription | String | C, U | Req | ? | — | Member-facing rich text |
| description.termsAndConditions | String | C, U | Opt | ? | — | Rich text |
| description.discountInfo, includes, guests, howToBook, howToRedeem, disclaimer | String | C, U | Opt | ? | — | Rich text sections of the member detail page |
| categories | Array of ids | C, U | Req | — | Req | At least one EPC category serving offers. Existing activation check (MISSING_CATEGORY) |
| subcategories | Array of ids | C, U | Opt | — | — | Sub-category ids |
| keywords | Array (≤100 each) | C, U | Opt | — | — | Search keywords |
| partnerLocations | Array of venue ids | C, U | Opt | — | — | Venues the offer is valid at. Empty = every active venue |
| isFeatured | Boolean | C, U | Opt | — | — | Default false |
| isVisibleToAll | Boolean | C, U | Opt | — | Cond | Default false. See eligibilityRules |
| label | String (≤50) | C, U | Opt | — | — | Editable only for special_deal; derived by EPC otherwise |
| badges | Array (≤10 of ≤30) | C, U | Opt | — | — | Display badges |
| lifestyleHubDetails | Object | C, U | Cond | — | Cond | Required when offerType is lifestyle_hub: accessOptions (at least one of individual, couple, family with an optional daily quota), day-pass prices, access text |
| eligibilityRules | Array | — | — | — | Cond (new) | Who may see and redeem. Proposed check: at least one active rule, or isVisibleToAll set to true, so that "everyone" is an explicit decision and not a default |
| heroImage | Image | — | — | — | Req (new) | Uploaded in the backoffice. No activation check today |
| additionalImages | Image | — | — | — | — | Uploaded in the backoffice |
| offerId | String (8 digits) | — | — | — | — | Human-readable offer number, allocated on create and returned |
| status, statusReason | Enum | — | — | — | — | pending, active, inactive, expired |
| Field | Type | API | Today | Salesforce | Activation | Description |
|---|---|---|---|---|---|---|
| partnerId | String (UUID) | C, U | Req | M | Req | EPC id of the owning partner; 404 if unknown. The current update request accepts a partner change; this draft proposes to make it immutable as on offers. Salesforce would send its own partner identifier here (see Data Conventions) |
| name | String (1–200) | C, U | Req | ? | Req | Voucher name |
| memberPrice | Number (AED) | C, U | Req | ? | Req | What the member pays. 0 for a free-of-charge voucher. Must be 0 when distributionModel is free_of_charge or manual_allocation |
| denomination | Number (AED) | C, U | Req | ? | Req | Face value the member redeems at the outlet |
| supplierCost | Number (AED) | C, U | Opt | ? | — | What the programme pays the partner per voucher. Never shown to members or partners |
| releaseQuantity | Integer (≥0) | C, U | Req | ? | Req | Agreed number of vouchers released. In a later phase code uploads are capped against it |
| saleReleaseAt | DateTime | C, U | Req | ? | Req | Sale opens. Must be before saleClosingAt |
| saleClosingAt | DateTime | C, U | Req | ? | Req | Sale closes. Must be no later than validUntil |
| validFrom | DateTime | C, U | Req | ? | Req | Instrument becomes redeemable. Must be before validUntil |
| validUntil | DateTime | C, U | Req | ? | Req | Expiry carried onto every issued code |
| discountType | String (≤100) | C, U | Opt | ? | — | Pricing mechanic as described in Salesforce. Already planned as read-only from Salesforce |
| vendorReference | String (≤200) | C, U | Opt | ? | — | Vendor invoice or batch reference |
| vatCode | String (≤50) | C, U | Opt | ? | — | VAT code for the finance posting |
| taxable | Boolean | C, U | Opt | ? | — | Whether the member price is subject to VAT. Default false; a wrong default under-reports VAT, so Salesforce should send it for every priced voucher |
| limitPerMember | Integer (≥1) | C, U | Req | ? | Req | How many one member may buy. Proposed default 1 when null |
| distributionModel | Enum | C, U | Req | ? | Req | first_come_first_serve, manual_allocation, free_of_charge, raffle. Picklist mapping in open item 3 |
| shortDescription | String (1–500) | C, U | Req | ? | Req | Member-facing summary |
| longDescription | String | C, U | Opt | ? | — | Member-facing rich text |
| description.inclusions, howToRedeem, termsAndConditions, disclaimer | String | C, U | Opt | ? | — | Rich text sections |
| customerServiceContact.email, phone | String | C, U | Opt | ? | — | Who the member contacts when a code does not work. Editable after release |
| categories | Array of ids | C, U | Req | — | Req | At least one EPC category serving vouchers |
| subcategories | Array of ids | C, U | Opt | — | — | Sub-category ids |
| keywords | Array | C, U | Opt | — | — | Search keywords |
| isFeatured | Boolean | C, U | Opt | — | — | Default false |
| isVisibleToAll | Boolean | C, U | Opt | — | — | Preview to a wider audience; eligibility rules still decide who may buy |
| waitlistEnabled | Boolean | C, U | Opt | — | — | Waitlist once sold out. A raffle always uses the waitlist |
| redemptionType | Enum | C, U | Opt | — | — | electronic (default), physical |
| presentationFormat | Enum | C, U | Opt | — | — | text (default), barcode_1d, qr_2d |
| defaultPaymentTypes | Array | C, U | Req | — | Cond | credit_card, salary_deduction. A priced voucher must offer at least one rail through this list or a rule. Existing activation check (NO_PAYMENT_TYPE_AVAILABLE) |
| eligibilityRules | Array | C | Opt | — | — | Rules with optional per-rule payment types. No rule = everyone eligible |
| financeConfig | Object | C, U | Opt | — | Cond | GL coding: cost centre, nominal, sub-nominal, analysis key, ERP comment prefix. Required when memberPrice is greater than 0. Existing activation check (FINANCE_CONFIG_REQUIRED) |
| trackCoding | Object | C, U | Opt | — | Cond | TRACK receivable coding. Required whenever financeConfig is set. Existing check (TRACK_CODING_REQUIRED) |
| codes | File | — | — | — | Req | At least one code list loaded. Existing activation check (NO_CODES_LOADED) |
| heroImage | Image | — | — | — | Req (new) | Uploaded in the backoffice. No activation check today |
| additionalImages | Image | — | — | — | — | Uploaded in the backoffice |
| voucherId | String (8 digits) | — | — | — | — | Human-readable voucher number, allocated on create and returned |
| status, statusReason | Enum | — | — | — | — | pending, active, deactivated |
These are the requests the EPC backoffice sends today, shown with real eu-uat records (the Costa Coffee partner, one of its venues, its offer 89786458 and its voucher 39320053, as stored on 19 Oct 2026). They show the exact field names and value formats of the current API, which is what the Salesforce payloads would be mapped onto. Contact names, e-mail addresses and phone numbers are replaced with placeholders. Images, documents and voucher codes are uploaded through separate endpoints and are not shown. The update requests are partial today: only the fields sent are changed.
All requests carry an administrator token:
Authorization: Bearer <admin access token>
Content-Type: application/json
POST /partners HTTP/1.1
Host: uat.api.platinum.retter.io
{
"brandName": "Costa Coffee",
"legalName": null,
"partnerTier": "Platinum",
"partnerGroup": "ELR",
"businessAddress": "EMIRATES LEISURE RETAIL CORPORATE HEAD OFFICE Level 5, Emirates Holidays Building P.O.Box 122199, Sheikh Zayed Road",
"city": "Dubai",
"country": "AE",
"website": "https://www.costacoffee.ae/",
"primaryContact": {
"name": "<contact name>",
"email": "<contact e-mail>",
"phone": "<contact phone>"
},
"tradeLicenseNumber": null,
"contractId": "575876543",
"contractStartDate": "2026-03-01",
"contractEndDate": null,
"accountAgent": null,
"socialMediaAccounts": [
{ "platform": "facebook", "url": "https://www.facebook.com/CostaCoffeeUAE" },
{ "platform": "instagram", "url": "https://www.instagram.com/costacoffeeuae/" }
],
"brandLogoFileId": "<id of a pending logo upload, optional>"
}
PUT /partners/da63c652-316c-4b57-ba94-3e1176665086 HTTP/1.1
Host: uat.api.platinum.retter.io
{
"contractEndDate": "2027-02-28",
"accountAgent": "<account manager name>"
}
POST /partners/da63c652-316c-4b57-ba94-3e1176665086/locations HTTP/1.1
Host: uat.api.platinum.retter.io
{
"name": "Emirates Group HQ",
"address": "2ND FLOOR, EMIRATES GROUP HEADQUARTERS",
"coordinates": { "latitude": 25.241316, "longitude": 55.366476 },
"contact": {
"name": "<venue contact name>",
"email": "<venue contact e-mail>",
"phone": "<venue contact phone>"
}
}
PUT /partners/da63c652-316c-4b57-ba94-3e1176665086/locations/f0b6bd30-08ee-445a-b1f5-57bf4049e92c HTTP/1.1
Host: uat.api.platinum.retter.io
{
"address": "2nd Floor, Emirates Group Headquarters, Airport Road, Dubai"
}
Special-price offer. A discount offer sends percentage instead of price; special_deal and lifestyle_hub send neither. The list of venues is shortened here; the stored offer names 30 of the partner's venues.
POST /offers HTTP/1.1
Host: uat.api.platinum.retter.io
{
"name": "Flat Rate AED 15 Any Coffee",
"partnerId": "da63c652-316c-4b57-ba94-3e1176665086",
"partnerLocations": [
"ca3a5f4b-7a32-47e7-bebf-0c410a69077e",
"5f93a8cd-093e-4c9b-85e1-858385b759ba",
"da584b5f-8f2b-4eab-88bf-be4b389148fd"
],
"offerType": "special_price",
"price": 15,
"isFeatured": false,
"isVisibleToAll": true,
"label": "AED 15",
"categories": ["c391f62e-d854-478e-9085-7890217f0a33"],
"subcategories": [
"522a5613-c514-4a90-bc8d-70dec417667a",
"54fbc405-b1d3-40ca-8e3c-a0c2c3ac70d8"
],
"startDate": "2026-01-01",
"endDate": "2027-12-01",
"viewStartDate": "2026-01-01",
"viewEndDate": "2027-12-01",
"shortDescription": "Any regular coffee for just AED 15 at selected Costa Coffee branches. Choose from Americano, Latte, Cappuccino, and more.",
"longDescription": "Keep it simple with our flat rate coffee deal. Any regular-sized coffee from our core menu is yours for just AED 15. Whether you prefer a bold Americano, a creamy Latte, or a classic Cappuccino, the choice is yours.",
"description": {
"includes": "Any regular-sized coffee from the core menu",
"howToRedeem": "Show your membership card or app at the counter before ordering",
"termsAndConditions": "Valid on regular size only. Excludes specialty and seasonal drinks. Valid at selected locations only.",
"disclaimer": "Subject to availability. Management reserves the right to modify or cancel this offer."
}
}
PUT /offers/e348af64-5068-4770-8eda-3803bcc063f3 HTTP/1.1
Host: uat.api.platinum.retter.io
{
"price": 16,
"label": "AED 16",
"endDate": "2028-03-31",
"viewEndDate": "2028-03-31"
}
Money is sent in AED. The four dates carry a time of day and are shown in Asia/Dubai.
POST /vouchers HTTP/1.1
Host: uat.api.platinum.retter.io
{
"name": "Costa Coffee AED 50 Gift Voucher",
"partnerId": "da63c652-316c-4b57-ba94-3e1176665086",
"categories": ["c391f62e-d854-478e-9085-7890217f0a33"],
"subcategories": [],
"isFeatured": true,
"discountType": "20% off face value",
"memberPrice": 40,
"denomination": 50,
"supplierCost": 35,
"taxable": false,
"financeConfig": {
"costCentre": "53050",
"nominal": "48001",
"subnominal": "P943",
"analysisKey": "00000000",
"erpCommentPrefix": "Voucher"
},
"trackCoding": {
"lob": "99",
"responsibilityCentre": "50000",
"nominalCode": "37012",
"subnominalCode": "G274",
"analysisKey": "00000000"
},
"vendorReference": "COSTA-GV50-UAT-2026Q4",
"limitPerMember": 2,
"distributionModel": "first_come_first_serve",
"redemptionType": "electronic",
"presentationFormat": "qr_2d",
"defaultPaymentTypes": ["credit_card", "salary_deduction"],
"isVisibleToAll": true,
"saleReleaseAt": "2026-09-01T09:00:00+04:00",
"saleClosingAt": "2026-10-31T23:00:00+04:00",
"validFrom": "2026-09-01T09:00:00+04:00",
"validUntil": "2027-03-31T23:00:00+04:00",
"releaseQuantity": 100,
"shortDescription": "Pay AED 40 for an AED 50 Costa Coffee gift voucher",
"longDescription": "Enjoy your favourite Costa Coffee for less. Purchase this AED 50 digital gift voucher for AED 40, then show the QR code from your wallet at the till in any participating Costa Coffee store across the UAE. Valid until 31 March 2027.",
"description": {
"howToRedeem": "<p>Redeemable on drinks, food and merchandise at participating Costa Coffee stores in the UAE.</p>",
"termsAndConditions": "<p>Valid on drinks, food and merchandise. One voucher per transaction. Not exchangeable for cash.</p>",
"disclaimer": "<p>This voucher is for UAT testing only.</p>"
},
"keywords": ["costa", "coffee", "gift voucher", "cafe", "drinks"],
"customerServiceContact": {
"email": "<partner customer service e-mail>",
"phone": "<partner customer service phone>"
}
}
PUT /vouchers/616fee21-6317-41b0-88c8-966e4d2f3796 HTTP/1.1
Host: uat.api.platinum.retter.io
{
"saleClosingAt": "2026-12-31T23:00:00+04:00",
"releaseQuantity": 150,
"supplierCost": 34
}
| Environment | Base URL |
|---|---|
| Staging | https://staging.api.platinum.retter.io |
| UAT | https://uat.api.platinum.retter.io |
| Production | Issued at go-live |
| Method | Path | Purpose |
|---|---|---|
| PUT | /partners/salesforce/{id} |
Create or update a partner and, optionally, its venues |
| PUT | /offers/salesforce/{id} |
Create or update an offer |
| PUT | /vouchers/salesforce/{id} |
Create or update a voucher |
Paths are provisional until the interface is built; {id} is the Salesforce identifier of the record. All three require the X-API-Key header.
These rules run today on the backoffice create and update paths and will run unchanged on this interface. They are evaluated on the merged record (stored values plus the payload).
| Entity | Rule |
|---|---|
| Offer | endDate on or after startDate; viewEndDate on or after viewStartDate; viewEndDate required once endDate is set and not later than it |
| Offer | discount requires percentage and forbids price; special_price requires price and forbids percentage; special_deal and lifestyle_hub take neither |
| Offer | lifestyle_hub offers must carry the lifestyle-hub category and no other type may (EPC-owned category, so this surfaces as an activation blocker) |
| Offer | Named venues must belong to the offer's partner (EPC-owned field) |
| Voucher | saleClosingAt after saleReleaseAt; validUntil after validFrom; saleClosingAt not later than validUntil |
| Voucher | No voucher date may be in the past on create |
| Voucher | memberPrice must be 0 for free_of_charge and manual_allocation |
| Voucher | financeConfig implies trackCoding |
What must hold before an operator can activate a Salesforce-created record. Which checks are needed depends on the outcome of the field discussion, so this section states the rule rather than the final list.
Checks that exist today and apply unchanged
| Entity | Check |
|---|---|
| Offer | Partner is active |
| Offer | Partner has at least one active venue |
| Offer | At least one category |
| Offer | endDate not in the past |
| Voucher | financeConfig present when the voucher is priced |
| Voucher | trackCoding present when financeConfig is set |
| Voucher | At least one payment rail when the voucher is priced |
| Voucher | At least one code list loaded |
Rule for fields the create request requires today. Every field marked Req or Cond in the Today column of section 4.4 is either sent by Salesforce or completed in the backoffice. If Salesforce sends it, it is mandatory in the payload and nothing changes. If Salesforce does not hold it, the create-time requirement is lifted for this interface and the same condition becomes an activation check instead, so the record is created as Pending and cannot go live until the field is filled. The list of such fields is known only once every ? in section 4.4 is resolved.
Checks that would be new whatever Salesforce sends, because they concern content that is always EPC-only, or a precondition that moves from create time to activation time:
| Entity | Check |
|---|---|
| Partner | Brand logo uploaded |
| Offer | Hero image uploaded |
| Offer | At least one active eligibility rule, or isVisibleToAll set to true, so that "everyone" is an explicit decision |
| Offer | Partner is active and has an active venue: enforced at create today, checked at activation for records created by Salesforce against a Pending partner |
| Voucher | Hero image uploaded |
| Voucher | Partner is active: enforced at create today, checked at activation for records created by Salesforce against a Pending partner |
| No. | Item | Owner |
|---|---|---|
| 1 | Which Salesforce object is a Partner and what contractId carries: a Salesforce record identifier or a contract number. The export heading already reads "Salesforce ID" |
Emirates / Salesforce team |
| 2 | Whether Salesforce holds venues or outlets with coordinates. If not, section 4.4.2 is dropped and venues stay EPC-owned | Emirates / Salesforce team |
| 3 | Field-by-field confirmation of what Salesforce holds: every ? in section 4.4 becomes M or O. Includes the picklist mappings (Salesforce offer type to offerType, sales model to distributionModel) and whether a shared category code list exists so Salesforce can send categories |
Both |
| 4 | Whether Salesforce sends its lifecycle status and which values, if any, should deactivate a live record in EPC, and whether a reactivation in Salesforce should ever reactivate in EPC (this draft says no) | Emirates |
| 5 | Whether a Salesforce update to a live offer or voucher bypasses the EPC approval policy or is held for approval like a backoffice edit. This draft holds it for approval and returns 202 |
Emirates |
| 6 | Notification of Pending Salesforce-created records awaiting enrichment: in-app only, or a daily e-mail digest to the partnerships team | Emirates |
| 7 | Whether Salesforce is also the source of marketing copy (shortDescription, longDescription, description sections) or only of the commercial terms |
Emirates |
| 8 | Reverse direction (EPC to Salesforce): activation status, offer and voucher numbers, redemption and sales figures | Both |