Core concepts
Read this once and the rest of the API stops surprising you.
The domain
Section titled “The domain”Identifiers
Section titled “Identifiers”| Id | Looks like | Notes |
|---|---|---|
| Store id | Ab3xK_9pQrLm | Read it from GET /stores; it matches your client id. Case-sensitive. |
| Member id | m_nsr714RRBEHX | Opaque. Case-sensitive. Store it against your customer record. |
| Department id | 101 | A string in JSON even when it looks numeric |
| Coupon code | WELCOME15 | Member coupon endpoints address coupons by code, never by numeric id |
| Transaction id | ORDER-1001 | Yours. You choose it; we store it as external_id |
Treat store and member ids as opaque strings. Never lowercase, trim, or numerically parse them.
Departments
Section titled “Departments”A department is where an action came from. Discover them once and cache the result:
GET /api/v2.0/stores/{storeId}/departments[ { "id": "101", "name": "Web" }, { "id": "102", "name": "Oslo" }]Most webshops use a single department for all online orders. If the merchant wants online and in-store reported separately — and they usually do — this is the mechanism.
department_id appears in four places, and they are not redundant:
| Where | Meaning |
|---|---|
origin.department_id | Where this API call came from. On a signup call, that is the department the member was created in |
department_id on enroll | Required. The department an enrolled member is associated with |
Transaction department_id | Which department the sale belongs to |
Member department_ids[] | Which departments the member is associated with |
Only department_ids[] comes back on the member. The origin department is
recorded against the call, not echoed on the member, so store it yourself if
you need to report on where a member signed up.
Segments — the store’s own profile questions
Section titled “Segments — the store’s own profile questions”Segments are custom questions a merchant defines: interests, clothing size, whether the customer is a student. They are how a merchant targets campaigns, so getting them right matters commercially.
GET /api/v2.0/stores/{storeId}/segments[ { "id": 633, "text": "What are you interested in?", "type": "Checkbox", "is_visible_to_follower": true, "is_required": false, "options": [ { "id": 4101, "text": "Repairs" }, { "id": 4102, "text": "Consulting" }, { "id": 4103, "text": "Project management" } ] }, { "id": 2222, "text": "Student", "type": "Radio", "is_visible_to_follower": true, "is_required": false, "options": [ { "id": 5001, "text": "Yes" }, { "id": 5002, "text": "No" } ] }]Five types: Text, DateTime, Radio, Checkbox, Dropdown.
Render your signup form from this response. Do not hardcode the questions — the merchant can change them in Diller at any time, and a hardcoded form silently stops collecting the new ones.
Sending answers back
Section titled “Sending answers back”Answers go in the segments array on member create and update. Which field
you fill depends on the type:
| Segment type | Fill | Leave out |
|---|---|---|
Text, DateTime | value | selected_options |
Radio, Dropdown | selected_options (exactly one) | value |
Checkbox | selected_options (zero or more) | value |
"segments": [ { "segment_id": 633, "selected_options": [4101, 4103] }, { "segment_id": 2222, "selected_options": [5001] }, { "segment_id": 900, "value": "Regular size M" }]selected_options holds option ids, not option labels. Map the label
the customer clicked back to its id from the segments response.
Consent — four flags, one of them mandatory
Section titled “Consent — four flags, one of them mandatory”"consent": { "gdpr_accepted": true, "save_order_history": true, "receive_sms": true, "receive_email": false}| Flag | Meaning | Required? |
|---|---|---|
gdpr_accepted | Member joined the programme and accepted the terms | Must be true |
save_order_history | May link purchases to their profile | Optional |
receive_sms | May send marketing SMS | Always send it — see below |
receive_email | May send marketing email | Always send it — see below |
If you are porting a signup form from a Diller plugin, the mapping is:
| Plugin form field | API field |
|---|---|
membership_consent_accepted | consent.gdpr_accepted |
purchase_history_consent_accepted | consent.save_order_history |
gdpr_accepted: false is rejected, not stored. Both create and update
return 400:
The 'gdpr_accepted' property on the 'Consent' object must be set to true.On update the message continues: “If your goal is to delete the member, please use the DELETE endpoint.” That is the intended path for a withdrawal of consent — see Members reference.
Your signup form must show the merchant’s own terms and link their privacy
policy, available as privacy_policy_url on the store object.
Coupons
Section titled “Coupons”A coupon is a discount the member holds, addressed by code.
{ "id": 0, "external_ids": [ { "platform": "Shopify", "external_id": "gid://shopify/DiscountCodeNode/1" } ], "code": "WELCOME15", "title": "15% off your first order", "description": "Welcome to the club", "valid_from": "2026-01-01T00:00:00", "valid_to": "2026-12-31T23:59:59", "type": "Welcome", "discount_type": "Percentage", "discount_value": 15.0, "max_redemptions": 1, "times_redeemed": 0, "product_ids": ["SKU-123"], "image_url": null}| Field | Values |
|---|---|
type | Standard, Welcome, Campaign, Public, Recurring, Birthday, ChildBirthday, Automation |
discount_type | NotDefined, Percentage, Fixed, FreeShipping |
external_ids[] maps a Diller coupon to its counterpart in your platform —
useful if you mirror Diller coupons as native discount codes.
max_redemptions is never null. A coupon with no limit carries a large
sentinel instead: 1000 on the store’s coupon list, 10000000 on a
member’s. Do not show max_redemptions - times_redeemed as “uses left”
without allowing for it — an unlimited coupon would read “9,999,999 uses
left”. Treat either value as unlimited and show no count.
We do not apply discounts to your cart. We tell you what the member holds and track redemption; your checkout does the arithmetic. See Checkout and coupons.
Stamp cards
Section titled “Stamp cards”Collect n stamps, get a reward.
{ "id": 12, "title": "Coffee card", "required_stamps": 10, "stamps_collected": 3, "card_type": "standard", "threshold_amount": 0.0, "is_restartable": true, "valid_from": "2026-01-01T00:00:00", "valid_to": "2026-12-31T23:59:59"}card_type: "standard"— one stamp per qualifying product.card_type: "threshold"— one stamp per order abovethreshold_amount.
Stamps are awarded through the transaction you post. See Transactions.
Points
Section titled “Points”Points live on the member:
"membership_detail": { "points": 250.0, "qualification_points": 340.0, "pending_points": 0.0, "remaining_points": 160.0, "level_title": "Gold", "next_level_title": "Platinum", "next_level_points": 500.0, "level_expire_date": "2027-06-16T00:00:00"}| Field | Meaning |
|---|---|
points | The customer-facing spendable balance |
qualification_points | Points earned inside the current qualification window, which ends at level_expire_date. Tier renewal is assessed on this |
pending_points | Points earned but not yet released for spending |
level_title | Current tier |
next_level_title | The tier above, or empty at the top tier |
next_level_points | The qualification_points threshold for the next tier |
remaining_points | Qualification points still needed for the next tier: next_level_points − qualification_points |
remaining_points is not a balance, and showing it as one is a visible
bug. It can also go negative once the member has passed the threshold —
treat anything at or below zero as “reached”.
At the top tier there is no next level: next_level_title is empty and
next_level_points is 0. Hide the progress display rather than dividing
by zero.
We award points when you post a transaction. You do not add them yourself.
JSON conventions
Section titled “JSON conventions”- All properties are
snake_case. - Enums are strings:
"OnlineStore","Percentage","NotShared". - Dates are ISO 8601:
2026-09-10T12:30:00Z. See the timezone note. Content-Type: application/jsonon every request with a body.- Money is a JSON number, not a string:
499.00, not"499.00". country_codeon an address is ISO alpha-2 (NO,SE).country_codeon a phone is a dial code (+47). Same name, different meaning, and they sit next to each other in the payload.
