Skip to content

API reference

Site and content

What a shop's website shows besides its products: the shop's contact details, the home page in one call, the category menu, the blog, the FAQs, the careers page and the shop's own pages, and the forms a visitor sends: contact, newsletter, blog comments and job applications. Every call needs a key with the content scope. Answers come as {"data": ...}, except the blog list, which is Laravel's paginator; an error comes as {"message": ...}, with "errors" by field when the input fails its checks (422). Only what staff publish shows: published posts and pages, FAQs and jobs set to show. The forms keep what they receive on the shop's Website pages in the back office, and the contact, newsletter and job application forms answer a filled website field, a trap only bots fill, with the usual success while keeping nothing.

Every call sends the shop's key in the X-Storefront-Key header and asks for JSON. Calls marked with a customer token also send the signed-in customer's token. The integration guide explains keys, answers, errors and limits.

Store

GET

/api/v1/storefront/config

Scope
content
Customer token
none

Everything a storefront needs to look and work as the shop's: its name, tagline, address, logo and share image; its colours with readable text colours for each, and its heading font from Google Fonts; its contacts, map pin, opening hours and social links; the delivery choices it offers with their fees and its free-delivery amount; how it takes payment, with Lipa Pole Pole's terms when it offers them; which sections it has; and the analytics ids to load. Set in Website > Store settings and Settings. Cache it for a few minutes.

methods lists only the choices the shop offers; a fee of null means staff set any charge when they confirm the order. A null section (map, lipa_pole_pole, mpesa) means the shop has not set it up. share_image_url falls back to the logo.

Example answer
{
  "data": {
    "shop": {
      "name": "Bike Yard",
      "tagline": "Gear for every ride",
      "url": "https://www.bikeyard.co.ke",
      "logo_url": "https://media.debianpos.com/tenants/9d2c/logo/bike-yard.png",
      "share_image_url": "https://media.debianpos.com/tenants/9d2c/store/bike-yard-share.jpg",
      "currency": "KES",
      "locale": "en-KE",
      "timezone": "Africa/Nairobi"
    },
    "theme": {
      "primary": "#bdd73e",
      "primary_foreground": "#111827",
      "accent": "#f9bb3b",
      "accent_foreground": "#111827",
      "heading_font": "Barlow Condensed"
    },
    "contact": {
      "phone": "0742 784 311",
      "email": "hello@bikeyard.co.ke",
      "whatsapp": "0742 784 311",
      "address": "Donholm, Nairobi",
      "map": { "latitude": -1.2948925, "longitude": 36.887969 },
      "hours": { "text": "Monday to Saturday, 08:00 to 18:00", "days": [1, 2, 3, 4, 5, 6], "opens": "08:00", "closes": "18:00" },
      "social": { "facebook": null, "instagram": "https://instagram.com/bike_yard_kenya", "x": null, "tiktok": "https://tiktok.com/@bikeyardkenya", "youtube": null, "linkedin": null }
    },
    "delivery": {
      "methods": [
        { "key": "shop_pickup", "label": "Shop pickup", "fee": null, "note": "Collect from our shop once we tell you it is ready." },
        { "key": "nairobi", "label": "Nairobi", "fee": 500, "note": "Delivered to your door in Nairobi by rider." }
      ],
      "free_over": 50000
    },
    "payments": {
      "pay_online": true,
      "pay_later": true,
      "lipa_pole_pole": { "min_total": 5000, "deposit_percent": 20, "deposit_days": 7, "term_days": 60 },
      "mpesa": { "paybill": "880100", "account": "006982" }
    },
    "features": { "blog": true, "faqs": true, "careers": true, "newsletter": true, "contact_form": true, "pre_orders": true, "loyalty": true, "referrals": true, "wishlist": true, "reviews": true },
    "integrations": { "google_analytics_id": "G-AB12CD34EF", "meta_pixel_id": null, "google_site_verification": null }
  }
}

Site

GET

/api/v1/site/settings

Scope
content
Customer token
none

The shop's own details for the header and footer: its phone, email and address, its WhatsApp number and its trading hours as one line. The WhatsApp number is the shop's own WhatsApp number when it has connected one, otherwise its phone. Its social links are those set in Website > Store settings; twitter is the X link.

Example answer
{
  "data": {
    "phone": "0712 000 000",
    "email": "hello@bikeyard.co.ke",
    "whatsapp": "0712 000 000",
    "address": "Nairobi",
    "business_hours": "Monday to Saturday, 08:00 to 18:00",
    "social": {
      "facebook": null,
      "instagram": null,
      "twitter": null,
      "tiktok": null
    }
  }
}
GET

/api/v1/site/homepage

Scope
content
Customer token
none

The home page in one call. featured_products is the twelve products featured last, new_arrivals the twelve added last, both with those in stock at the website branch first; on_sale is up to twelve products on a sale running today that the branch holds, the biggest discount first. All three are product cards, as the product list answers them. categories is every active category marked to show on the home page, the most products first, counting those in its active sub-categories. brands is up to twelve active brands marked for the home page, the best selling first (voided sales do not count), then by name. blogs is the four newest published posts. hero is the shop's home page hero from Website > Store settings (a headline, a line of text, a wide and a phone image, the phone one falling back to the wide one, and a button with a link on the store), or null until it has a headline, when a store shows the shop's name and tagline instead. reviews is the six newest approved reviews of four or five stars with words, for testimonials. sliders is always an empty list, as Debian POS has no sliders; it stays so a frontend built for Bike Yard still reads it.

Example answer
{
  "data": {
    "hero": {
      "headline": "Gear for every ride",
      "text": "Bikes, helmets and parts, delivered across Kenya.",
      "image_url": "https://media.debianpos.com/tenants/9d2c/store/bike-yard-hero.jpg",
      "mobile_image_url": "https://media.debianpos.com/tenants/9d2c/store/bike-yard-hero-phone.jpg",
      "button": { "label": "Shop helmets", "link": "/category/helmets" }
    },
    "sliders": [],
    "featured_products": [],
    "on_sale": [],
    "new_arrivals": [
      {
        "_id": "01a16356-8a39-73c4-be32-af0ba275c8f5",
        "name": "Muc-Off tubeless sealant 140 ml",
        "slug": "muc-off-tubeless-sealant-140-ml",
        "sku": "MUC-SEAL-140",
        "description": null,
        "short_description": null,
        "price": 1800,
        "sale_price": null,
        "discount_percentage": null,
        "cost_price": null,
        "stock_quantity": 20,
        "manage_stock": true,
        "in_stock": true,
        "is_featured": false,
        "is_new": true,
        "has_variants": false,
        "is_active": true,
        "weight": null,
        "weight_unit": null,
        "images": [],
        "primary_image": null,
        "category": {
          "_id": "01a16356-869c-7039-bd72-25da5ed9fb14",
          "id": "01a16356-869c-7039-bd72-25da5ed9fb14",
          "name": "Tyres and tubes",
          "slug": "tyres-and-tubes",
          "description": null,
          "image_url": null,
          "parent_id": "01a16356-8693-72a0-882a-401f785a9eb9"
        },
        "parent_category": {
          "_id": "01a16356-8693-72a0-882a-401f785a9eb9",
          "id": "01a16356-8693-72a0-882a-401f785a9eb9",
          "name": "Parts",
          "slug": "parts"
        },
        "brand": {
          "_id": "01a16356-8a34-73b5-ab46-854e2ca956df",
          "id": "01a16356-8a34-73b5-ab46-854e2ca956df",
          "name": "Muc-Off",
          "slug": "muc-off",
          "logo_url": null,
          "description": null
        },
        "attributes": [],
        "tags": [],
        "meta_title": null,
        "meta_description": null,
        "created_at": "2026-10-22T11:19:38.000000Z",
        "updated_at": "2026-10-22T11:19:38.000000Z"
      }
    ],
    "categories": [
      {
        "id": "01a16356-8681-735e-9814-a766a09b45ec",
        "name": "Bikes",
        "slug": "bikes",
        "image": "https://media.debianpos.com/categories/bikes.jpg",
        "product_count": 24
      }
    ],
    "brands": [
      {
        "id": "01a16356-8711-7365-bc56-f1bd9f9a311c",
        "name": "Fox Racing",
        "slug": "fox-racing",
        "logo_url": "https://media.debianpos.com/brands/fox-racing.png"
      }
    ],
    "blogs": [
      {
        "title": "Five safe commuting routes through Nairobi",
        "slug": "five-safe-commuting-routes-through-nairobi",
        "excerpt": "More of our customers ride to work every month. These routes keep you off the busiest stretches of Mombasa Road and Thika Road. Ride with a front and...",
        "image": null,
        "published_at": "2026-10-22T11:19:39.000000Z"
      }
    ],
    "reviews": [
      {
        "id": "01a15f39-cb03-7238-8673-e0962fe17431",
        "rating": 5,
        "comment": "Light, well vented and the visor stays put at speed.",
        "author": "Grace Achieng",
        "date": "2026-10-21T16:09:45.000000Z",
        "product": { "name": "AGV K1 S helmet", "slug": "agv-k1-s-helmet" }
      }
    ]
  }
}
GET

/api/v1/site/sliders

Scope
content
Customer token
none

Always an empty list. Debian POS has no sliders; the call stays so a frontend built for Bike Yard still reads it.

Example answer
{
  "data": []
}

Blog

GET

/api/v1/blogs

Scope
content
Customer token
none

A page of published posts, newest first, as cards: the excerpt is the first 150 characters of the post as plain text, tags is the tag names in one comma list and blog_tags the same tags with their slugs. reading_time counts 200 words a minute, at least one. author is the writer's name, or the shop's where the post has no writer.

Parameters

category string · query
A blog category's slug: only its posts.
tag string · query
A tag's slug: only posts with it.
q string · query
Words to find in the title or the post's text, in any case.
per_page integer · query
Posts a page, 1 to 50; 12 when left out.
page integer · query
The page, from 1.
Example answer
{
  "data": [
    {
      "id": "01a16356-90a1-72b4-ac6a-2ef326933a80",
      "title": "How to fix a puncture on the trail",
      "slug": "how-to-fix-a-puncture-on-the-trail",
      "excerpt": "A flat on the Ngong Road forest loop does not have to end your ride. Carry two tyre levers, a spare tube that matches your rim, and a mini pump or CO2...",
      "image": "https://media.debianpos.com/blogs/puncture-on-the-trail.jpg",
      "author": "Bike Yard Owner",
      "category": {
        "name": "Maintenance",
        "slug": "maintenance"
      },
      "tags": "Mountain biking",
      "blog_tags": [
        {
          "name": "Mountain biking",
          "slug": "mountain-biking"
        }
      ],
      "reading_time": "1 min read",
      "published_at": "2026-10-22T11:19:39.000000Z"
    }
  ],
  "links": {
    "first": "https://app.debianpos.com/api/v1/blogs?page=1",
    "last": "https://app.debianpos.com/api/v1/blogs?page=1",
    "prev": null,
    "next": null
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 1,
    "links": [
      {
        "url": "https://app.debianpos.com/api/v1/blogs?page=1",
        "label": "1",
        "page": 1,
        "active": true
      }
    ],
    "path": "https://app.debianpos.com/api/v1/blogs",
    "per_page": 12,
    "to": 2,
    "total": 2
  }
}
GET

/api/v1/blogs/slugs

Scope
content
Customer token
none

Every published post's slug and when it last changed, the latest change first, for a site built ahead of time and its sitemap.

Example answer
{
  "data": [
    {
      "slug": "how-to-fix-a-puncture-on-the-trail",
      "updated_at": "2026-10-22T11:19:39.000000Z"
    }
  ]
}
GET

/api/v1/blogs/{slug}

Scope
content
Customer token
none

A published post in full: its HTML in description, the card's fields, its views, its approved comments oldest first, each with its approved replies, up to four related posts (the newest published in the same category, as cards) and its search listing in meta. The meta title and description are the ones staff set, or else the post's title with the shop's name and its excerpt. Each call counts a view, once a day for each visitor's address, without changing updated_at.

Parameters

slug string · path · required
The post's slug.

Answers 404 (No query results for model [App\Models\Blog].) when no published post has that slug.

Example answer
{
  "data": {
    "id": "01a16356-90a1-72b4-ac6a-2ef326933a80",
    "title": "How to fix a puncture on the trail",
    "slug": "how-to-fix-a-puncture-on-the-trail",
    "description": "<p>A flat on the Ngong Road forest loop does not have to end your ride. Carry two tyre levers, a spare tube that matches your rim, and a mini pump or CO2 inflator.</p><p>Lever the tyre off on one side only, pull the old tube out and run your fingers inside the casing to find the thorn before it punctures the new one.</p>",
    "excerpt": "A flat on the Ngong Road forest loop does not have to end your ride. Carry two tyre levers, a spare tube that matches your rim, and a mini pump or CO2...",
    "image": "https://media.debianpos.com/blogs/puncture-on-the-trail.jpg",
    "author": "Bike Yard Owner",
    "category": {
      "id": "01a16356-9087-7036-b912-7caa114260d5",
      "name": "Maintenance",
      "slug": "maintenance"
    },
    "tags": "Mountain biking",
    "blog_tags": [
      {
        "name": "Mountain biking",
        "slug": "mountain-biking"
      }
    ],
    "reading_time": "1 min read",
    "views": 1,
    "published_at": "2026-10-22T11:19:39.000000Z",
    "updated_at": "2026-10-22T11:19:39.000000Z",
    "comments": [
      {
        "id": "01a16356-90c2-734b-8b9e-73bdac6d3316",
        "author": "Brian Otieno",
        "comment": "Saved my Sunday ride in Karura. The tip about checking the casing first is gold.",
        "date": "2026-10-22T11:19:39.000000Z",
        "replies": [
          {
            "id": "01a16357-1b20-7a41-9c3e-5d2f8e6a7b10",
            "author": "Bike Yard",
            "comment": "Glad it helped, Brian. Spare tubes for 29er rims are back in stock at Donholm.",
            "date": "2026-10-22T14:05:12.000000Z"
          }
        ]
      }
    ],
    "related": [
      {
        "id": "01a16357-0c4d-7e12-8f30-4a9b6c2d1e55",
        "title": "When to service your suspension fork",
        "slug": "when-to-service-your-suspension-fork",
        "excerpt": "A fork that feels harsh or sticky over the roots in Karura is asking for a service. Most makers ask for new seals and oil every 50 hours of riding...",
        "image": null,
        "author": "Bike Yard Owner",
        "category": {
          "name": "Maintenance",
          "slug": "maintenance"
        },
        "tags": "Mountain biking",
        "blog_tags": [
          {
            "name": "Mountain biking",
            "slug": "mountain-biking"
          }
        ],
        "reading_time": "2 min read",
        "published_at": "2026-10-15T08:30:00.000000Z"
      }
    ],
    "meta": {
      "title": "How to fix a puncture on the trail | Bike Yard",
      "description": "Fix a flat on the trail in ten minutes: what to carry, how to find the thorn, and how to seat a new tube without pinching it."
    }
  }
}
POST

/api/v1/blogs/{slug}/comment

Scope
content
Customer token
none
Limit
10 in 5 minutes

Adds a reader's comment to a published post, or a reply to one of its approved comments. Tags are stripped from the name and the comment. Every comment waits for staff to approve it before the post shows it, so status is pending. One with more than two links, or a word such as casino, bitcoin or click here, is filed as spam; the answer is the same.

Parameters

slug string · path · required
The post's slug.
author string · body · required
The reader's name, 2 to 100 characters.
email string · body · required
The reader's email, up to 150 characters. It is never shown on the website.
comment string · body · required
10 to 2,000 characters, as plain text.
parent_id string · body
To reply: the id of an approved comment on the same post.

Answers 201. A missing or unpublished post answers 404. A parent_id that is not an approved comment on the post answers 422 (The selected parent id is invalid.). One email can comment three times in five minutes; the fourth answers 422 with "Please wait a few minutes before commenting again." on comment. The route's limit counts ten comments in five minutes from one visitor. Staff who can write posts get an email and a back office alert for each comment waiting, not for spam, and approve it on the post's Comments tab under Website > Blogs.

Example answer
{
  "data": {
    "id": "01a16731-d982-7056-855f-f45e71a5ee9d",
    "status": "pending",
    "message": "Comment submitted and awaiting moderation."
  }
}
GET

/api/v1/blog-categories

Scope
content
Customer token
none

The blog categories that hold at least one published post, by name, with how many they hold.

Example answer
{
  "data": [
    {
      "id": "01a16356-9087-7036-b912-7caa114260d5",
      "name": "Maintenance",
      "slug": "maintenance",
      "description": "Keeping your bike running between services.",
      "blogs_count": 1
    }
  ]
}
GET

/api/v1/blog-tags

Scope
content
Customer token
none

The tags on at least one published post, the most used first, then by name, with how many published posts carry each.

Example answer
{
  "data": [
    {
      "id": "01a16356-908f-7326-b940-fb73731b18d0",
      "name": "Commuting",
      "slug": "commuting",
      "usage_count": 1
    }
  ]
}

FAQs

GET

/api/v1/faqs

Scope
content
Customer token
none

Every question set to show, in the order staff set on Website > FAQs: by category, then by the question's place in it; questions with no category come last. The answer is HTML. The list is not paged.

A question shows here even when its category is hidden; faq-categories leaves hidden categories out.

Example answer
{
  "data": [
    {
      "id": "01a16356-90ea-7127-a61f-66dc8c31247c",
      "question": "How long does delivery take in Nairobi?",
      "answer": "<p>Orders placed before 2pm reach most of Nairobi the same day, by our own rider.</p>",
      "category": {
        "id": "01a16356-90e1-71b1-9e10-22ee5b1a3bc9",
        "name": "Delivery",
        "slug": "delivery"
      }
    }
  ]
}
GET

/api/v1/faq-categories

Scope
content
Customer token
none

The FAQ categories set to show, in the order staff set, each with its icon (a Font Awesome class) and how many of its questions show. A category with no questions showing is still listed.

Example answer
{
  "data": [
    {
      "id": "01a16356-90e1-71b1-9e10-22ee5b1a3bc9",
      "name": "Delivery",
      "slug": "delivery",
      "description": "Getting your order to you.",
      "icon": "fa-solid fa-truck",
      "faq_count": 2
    }
  ]
}
GET

/api/v1/faq-categories/{slug}

Scope
content
Customer token
none

One FAQ category set to show, with its questions that show, in the order staff set.

Parameters

slug string · path · required
The category's slug.

Answers 404 (No query results for model [App\Models\FaqCategory].) when no category set to show has that slug.

Example answer
{
  "data": {
    "category": {
      "id": "01a16356-90e1-71b1-9e10-22ee5b1a3bc9",
      "name": "Delivery",
      "slug": "delivery",
      "description": "Getting your order to you.",
      "icon": "fa-solid fa-truck"
    },
    "faqs": [
      {
        "id": "01a16356-90ea-7127-a61f-66dc8c31247c",
        "question": "How long does delivery take in Nairobi?",
        "answer": "<p>Orders placed before 2pm reach most of Nairobi the same day, by our own rider.</p>"
      }
    ]
  }
}

Careers

GET

/api/v1/careers

Scope
content
Customer token
none

The jobs set to show, featured first then newest, split into active, still taking applications, and expired, past their closing date. A job with a rolling deadline never expires; one with a fixed deadline expires the day after its deadline_date, on the shop's clock. salary_range is null when staff chose not to show it. employment_type is full_time, part_time, contract or internship. The cards carry no description.

Example answer
{
  "data": {
    "active": [
      {
        "id": "01a16356-9109-722c-9580-a453b7aa2dfe",
        "title": "Bicycle mechanic",
        "slug": "bicycle-mechanic",
        "department": "Workshop",
        "employment_type": "full_time",
        "employment_type_label": "Full time",
        "location": "Donholm, Nairobi",
        "salary_range": "KES 35,000 - 45,000",
        "show_salary": true,
        "cover_image": null,
        "deadline_type": "rolling",
        "deadline_date": null,
        "is_expired": false,
        "is_featured": true,
        "created_at": "2026-10-22T11:19:40.000000Z"
      }
    ],
    "expired": []
  }
}
GET

/api/v1/careers/{slug}

Scope
content
Customer token
none

One job set to show, as its card with its description in HTML. An expired job still answers, with is_expired true.

Parameters

slug string · path · required
The job's slug.

Answers 404 (No query results for model [App\Models\CareerJob].) when no job set to show has that slug.

Example answer
{
  "data": {
    "id": "01a16356-910e-73f7-b1b8-07effdf04dab",
    "title": "Weekend sales associate",
    "slug": "weekend-sales-associate",
    "department": "Sales",
    "employment_type": "part_time",
    "employment_type_label": "Part time",
    "location": "Westlands, Nairobi",
    "salary_range": null,
    "show_salary": false,
    "cover_image": null,
    "deadline_type": "fixed",
    "deadline_date": "2026-11-21T00:00:00.000000Z",
    "is_expired": false,
    "is_featured": false,
    "created_at": "2026-10-22T11:19:40.000000Z",
    "description": "<p>Help riders choose bikes, helmets and kit on Saturdays and Sundays.</p>"
  }
}
POST

/api/v1/careers/{slug}/apply

Scope
content
Customer token
none
Limit
3 a minute

Sends an application for a job still taking them, with a PDF CV. Send it as multipart/form-data. The email is kept in lower case and one email can apply once for each job. The CV is kept privately, for staff only.

Parameters

slug string · path · required
The job's slug.
applicant_name string · body · required
Up to 255 characters.
applicant_email string · body · required
Up to 255 characters.
applicant_phone string · body · required
Up to 20 characters.
applicant_location string · body · required
Where the applicant lives, up to 255 characters.
cv file · body · required
A PDF, up to 5 MB.
cover_letter string · body
Up to 5,000 characters.
website string · body
A trap for bots: leave the field hidden and empty. When it is filled, the call answers as a success and keeps nothing.

Answers 201. A job not set to show answers 404; a job past its deadline answers 422 with "This job posting has expired and is no longer accepting applications." and no errors. A second application from the same email answers 422 with "You have already applied for this job." on applicant_email. The applicant gets a confirmation email, and staff who manage careers get an email and a back office alert; the application is on the job's page under Website > Careers.

Example answer
{
  "data": {
    "message": "Your application has been submitted successfully. We will review it and get back to you."
  }
}

Pages

GET

/api/v1/pages

Scope
content
Customer token
none

The shop's own pages that are published, in a fixed order, for the footer's links: About us (about-us), Delivery and returns (refund-shipping), Terms and conditions (terms-and-conditions) and Privacy policy (privacy-policy). Staff edit them in Website > Pages, where each starts from a draft filled with the shop's details, and may hide any of them.

Example answer
{
  "data": [
    { "slug": "about-us", "title": "About us", "updated_at": "2026-10-23T08:12:40.000000Z" },
    { "slug": "refund-shipping", "title": "Delivery and returns", "updated_at": "2026-10-23T08:15:02.000000Z" },
    { "slug": "terms-and-conditions", "title": "Terms and conditions", "updated_at": "2026-10-23T08:12:40.000000Z" }
  ]
}
GET

/api/v1/pages/{slug}

Scope
content
Customer token
none

One published page with its content in HTML (headings as h1 inside it, paragraphs as div, as the shop's editor writes them) and its search listing. meta_title falls back to the title and meta_description to the page's opening words when the shop left them blank.

Parameters

slug string · path · required
about-us, refund-shipping, terms-and-conditions or privacy-policy.

Answers 404 (There is no such page on this store.) for a hidden page or any other slug.

Example answer
{
  "data": {
    "slug": "refund-shipping",
    "title": "Delivery and returns",
    "updated_at": "2026-10-23T08:15:02.000000Z",
    "content": "<div>We confirm every order before it leaves us.</div><h1>Delivery</h1><ul><li><strong>Nairobi</strong>: Delivered to your door in Nairobi by rider. Fee: KES 500.</li></ul>",
    "meta_title": "Delivery and returns",
    "meta_description": "We confirm every order before it leaves us. Delivery Nairobi: Delivered to your door in Nairobi by rider. Fee: KES 500."
  }
}

Forms

POST

/api/v1/contact

Scope
content
Customer token
none
Limit
5 a minute

Sends the contact form. Tags are stripped from every field and spaces trimmed.

Parameters

name string · body · required
Up to 255 characters.
email string · body · required
Up to 255 characters.
phone string · body · required
Up to 20 characters.
subject string · body · required
Up to 255 characters.
message string · body · required
Up to 5,000 characters.
website string · body
A trap for bots: leave the field hidden and empty. When it is filled, the call answers as a success and keeps nothing.

Answers 201. The message shows on Website > Contact Requests, staff who manage contact requests get an email and a back office alert, and the sender gets a confirmation email.

Example answer
{
  "data": {
    "message": "Thank you for your message! Our team will get back to you shortly."
  }
}
POST

/api/v1/newsletter/subscribe

Scope
content
Customer token
none
Limit
5 a minute

Adds an email to the shop's newsletter list, kept in lower case. A new email answers 201; one already subscribed is left as it is and answers 200 with "You're already subscribed! We'll keep you posted."; one that unsubscribed or that staff deleted comes back and answers 201 with "Welcome back! You have been re-subscribed." No email is sent.

Parameters

email string · body · required
Up to 255 characters.
website string · body
A trap for bots: leave the field hidden and empty. When it is filled, the call answers as a success and keeps nothing.
_t integer · body
A second trap: when the form opened, as a Unix time in seconds. A form sent less than two seconds later answers as a success and keeps nothing.

Send _t in seconds, Math.floor(Date.now() / 1000), not JavaScript's milliseconds: a time in milliseconds always looks too quick, and the sign-up is dropped without a word. Subscribers are listed on Website > Newsletter.

Example answer
{
  "data": {
    "message": "Thanks for subscribing! Stay tuned for updates."
  }
}
POST

/api/v1/newsletter/unsubscribe

Scope
content
Customer token
none
Limit
5 a minute

Marks an email unsubscribed. It stays on the shop's list as unsubscribed, and signing up again brings it back. An email not on the list gets the same answer.

Parameters

email string · body · required
Up to 255 characters, in any case.
Example answer
{
  "data": {
    "message": "You have been unsubscribed."
  }
}