FreshStore Store API: endpoint reference for building your own tools
This is your store's own API, not the Amazon Creators API. Use it to build your own tools, dashboards, scripts and integrations on top of your FreshStore store. The Amazon Creators API is Amazon's separate product data service, which FreshStore already handles for you. If you have not created a key yet, see the guide on creating an API key for your store.
Every store has an API that can be used to get data from or update your store. That includes products, offers, categories, articles, pages, settings, Auto Populators and analytics. Using an API needs some technical knowledge.
Base URL: https://{your-store-domain}/api
Each store has its own API, its own keys and its own rate limit. A key works on one store only.
Last verified against routes/api.php: 2026-10-15.
Changelog
- 2026-10-15 (retire and CDN).
POST /api/store/clear-cachenow also purges the store's CDN copy, unless you sendedge: false; the reply says whether the purge was queued. New:POST /api/redirects/bulk(up to 500 redirects in one call, one CDN purge),POST /api/store/retire-catalogue(switch off a whole catalogue and redirect every old URL in one call, dry run first) andGET /api/store/retire-catalogue/{id}(its result). - 2026-10-15 (sections, texts and design). New:
GETandPATCH /api/pages/{id}/sectionsand/api/zones/{page_type}/{zone}/sections(the Sections Design > Editor arranges: read them with the blocks you can add, then update, show, hide, move, add and remove in one call, published at once),GETandPATCH /api/store/texts(every template text, Custom CSS included),GETandPATCH /api/store/design(colour scheme, custom palette and fonts),POST /api/store/design/palette(a palette from five colours, or from the AI, not saved) andPOST /api/store/featured-products(choose the homepage Featured Products). - 2026-10-15 (images and template). New:
POST /api/products/{id}/images(a gallery picture from a URL),POST /api/categories/{id}/images(the listing image or banner),POST /api/store/images(the logo, store icon, link preview picture, homepage hero, About page picture and the person photo, stored where the Design pages store them),GET /api/store/template(the current template, every template with its readiness for this store, and a dry run) andPOST /api/store/template(the template switch, with the same guards as Design > Templates).template.frontendin settings stays read only; switch with the new endpoint. - 2026-10-15 (articles).
POST /api/articleswith a futurepublished_atschedules the article: saved disabled, written at once whengenerate_ai_contentis true, live on its date with that date kept. A scheduled article now goes live through the full save path, so it reaches the sitemap, the CDN purge and IndexNow on its date. - 2026-10-15.
GET /api/productstakesown_touch=with|without: products with or without something of the member's own (a verdict, pros or cons text, or an uploaded photo). The same rule as the "Your own touch" filter on the Products list and the thin pages count. Any other value returns422. - 2026-10-13. Author ratings: products take
author_rating(1 to 5 in steps of 0.5),author_verdict,author_prosandauthor_cons, and returnauthor_reviewed_at; articles take the same plusreviewed_product_id, and returnformula.GET /api/productssorts byauthor_rating. New settings groupschema(what the store sends to Google as structured data). A store that has not finished its upgrade answers409to a rating field; retry in a minute. - 2026-10-08.
POSTandPATCH /api/offers:merchantmust be one ofamazon,ebay,etsy,walmart,aliexpressorcustom(case-insensitive), otherwise422. Before, any string was stored and the offer could never refresh. - 2026-10-07 (later). New:
POST /api/store/rebuild-sitemap,POST /api/articles/{id}/imagesandGET /api/images/stock, and Auto Populator endpoints (GET /api/populators,GETandPATCH /api/populators/{id},POST /api/populators/bulk). Writingxml-sitemapsettings now queues a sitemap rebuild within about ten minutes. Tightened:/toolsnow needs thestorescope,/redirectsthesettingsscope;sort_byonGET /api/productsmust be one of the listed columns (422, not500); summarylengthis capped per period;POST /api/member-factsis platform only; a member key cannot changetemplate.frontend. - 2026-10-07. Full audit against
routes/api.phpand every action class. Documented 24 live endpoints that were missing (23 absent, pluscategories/fill, which had only a table row):store/instant-store-readiness,store/indexnow,store/messages/summary,store/clear-cache,store/footer-credit,tools/offer-health, the threeredirectsroutes,users/invite,users/{id}/invite,DELETE users/{id}, the threelaunch-planroutes,recommendations,member-facts,recompute,products/import-links,categories/fill,brands/clean,analytics/sources/summary,analytics/clicks/by-merchant,analytics/clicks/top-products. Fixed: error bodies (auth failures are403 {"error":"Forbidden"}, not 401), token ability rules, the User-Agent block,POST /api/offers/{id}/products(adds, never removes),POST /api/store/cleandefaults,generate-topicscost and itsnichefield, store stats fields, user fields, settings write responses. Added the settings group field lists, the "What the API cannot do yet" section, and the categories note onPATCH /api/products/{id}.
Getting Started
1. Create an API key
A store admin creates keys in that store's admin area: Settings > API Keys > Create API Key. Give the key a name, tick the permissions it needs, and optionally set an expiry date. The two "AI assistant" presets tick a sensible set and expire after 90 days.
You see the key once. Copy it and store it safely. It cannot be shown again.
Keys are per store. There is no account-level key that works across all your stores, and keys cannot be created through the API.
2. Authenticate requests
Send the key as a Bearer token on every request:
Authorization: Bearer YOUR_API_KEY
Responses are always JSON. You do not need to send an Accept header.
3. User-Agent and the CDN
Stores sit behind a CDN that blocks common HTTP-library User-Agents (python-requests, python-httpx, curl/, axios/, node-fetch/, Go-http-client/) on store pages. From 2026-10-07, requests to /api/... are exempt from that block, so the defaults of Python requests and httpx, curl, axios, node-fetch and Go reach your store. A blocked request gets 403 with an HTML "403 Forbidden" page, not JSON. If you see that page on an API call, set your own User-Agent, for example MyStoreScript/1.0.
User-Agent: MyStoreScript/1.0
The store itself rejects an empty or library User-Agent on a request that carries no API key (for example the public status endpoint). Requests that carry a key are not User-Agent filtered by the store.
4. Permissions (token abilities)
A key holds one or more abilities. The checks run in this order:
*(Full Access) skips every check below.- Read or write.
GETneedsreadorwrite.POST,PATCH,PUTandDELETEneedwrite. A key with only per-resource abilities (for example onlyproducts:write) fails this step. Tick the broadReadorWritebox as well. - Per-resource scope. If the key holds any
resource:verbability, every request must also match a resource ability for its path:{resource}:readforGET,{resource}:writefor the others, or{resource}:*. If the key holds noresource:verbabilities, this step is skipped and the broadread/writecovers every resource.
The first path segment after /api/ decides the resource:
| Path starts with | Resource ability needed (scoped keys) |
|---|---|
/products |
products |
/offers |
offers |
/categories |
categories |
/brands |
brands |
/articles |
articles |
/article-categories |
article-categories |
/pages, /zones |
pages |
/analytics |
analytics |
/settings, /launch-plan |
settings |
/store, /recommendations, /member-facts, /tools |
store |
/redirects |
settings |
/populators |
categories |
/images |
articles |
/users |
users |
/data |
data |
/recompute |
both settings:write and store:read |
Notes:
- The admin screen offers
store:readbut nostore:write, and nodata:writeoranalytics:write. So a scoped key cannot call thePOST /api/store/...endpoints. Use a key with Full Access, or with the broadWritebox and no per-resource boxes. - Some user endpoints also check who owns the key, not only its abilities. See Users.
- A request that fails these checks gets
403with amessagenaming the missing access, for example{"message": "This token does not have access to products."}.
5. Rate limiting
60 requests per minute, per store, per key. Each key on each store has its own bucket. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining headers. Over the limit you get 429 with {"error": "Too Many Requests"}. Wait for the minute to roll over and retry.
Response Format
Success
Most responses wrap the result in a data key:
{
"data": { ... }
}
Exceptions are called out on each endpoint (for example GET /api/store/instant-store-readiness returns the object without a data wrapper, and the settings writes return a message).
Errors
| Status | When | Body |
|---|---|---|
| 401 | The key's user lacks API access | "Unauthorized." (a JSON string) |
| 402 | An AI endpoint and the store has no AI chips left | {"error": "not_enough_chips", "message": "..."} |
| 403 | Missing, invalid or expired key | {"error": "Forbidden"} |
| 403 | Key lacks the ability (see Permissions) | {"message": "This token does not have write access."} |
| 403 | The key's owner may not manage that user, or a platform-only route | {"error": "Forbidden"} |
| 403 | Blocked User-Agent at the CDN (store pages, not /api) |
HTML "403 Forbidden" page |
| 403 | Library User-Agent on an API call with no key | Empty body |
| 404 | Unknown route, unknown id, unknown launch plan key | {"error": "Not Found"} |
| 422 | Validation failed | {"message": "...", "errors": {"field": ["..."]}} |
| 422 | A business rule refused the request | {"message": "..."} |
| 422 | Some settings could not be saved | {"message": "Some settings could not be saved.", "failed": ["key"]} |
| 429 | Rate limit | {"error": "Too Many Requests"} |
| 500 | Server error | {"error": "Server Error"} |
| 503 | The store is still being built (install window) | {"error": "Service Unavailable", "reason": "installing"} with Retry-After: 30 |
| 503 | An AI call failed | {"error": "ai_generation_failed", "message": "..."} |
A bad or missing key returns 403, not 401.
Pagination
List endpoints are paginated and accept:
| Parameter | Type | Default | Max | Description |
|---|---|---|---|---|
page |
integer | 1 | Page number | |
per_page |
integer | 15 | 100 | Items per page |
They return Laravel's standard envelope:
{
"data": [ ... ],
"links": { "first": "...?page=1", "last": "...?page=10", "prev": null, "next": "...?page=2" },
"meta": { "current_page": 1, "from": 1, "last_page": 10, "per_page": 15, "to": 15, "total": 150, "path": "...", "links": [ ... ] }
}
GET /api/redirects is different: default per_page is 50, and it returns only meta.total, meta.per_page and meta.current_page.
Booleans
Write endpoints for products, offers, categories, articles and pages accept booleans as JSON true / false or as the strings "true", "false", "1", "0".
What the API cannot do yet
Checked against the code on 2026-10-07. None of these have an endpoint:
- Take an uploaded file. Every image endpoint takes the URL of an image: Set Article Image, Set Product Image, Set Category Image and Set Store Image. Find free stock photos with Search Stock Images. Images for other pages and for the offer box background are made with
generate_images(see Image source). - Create or delete an Auto Populator, or run one now. You can list, switch on or off and edit existing ones (see Auto Populators).
POST /api/categories/fillis a one-off fill, not a populator. - Create API keys. Keys are created by a store admin in the admin area, one store at a time.
- Account-level keys. There is no key that works across all the stores on an account.
- Set a video in a section, or a picture by upload. Section images take a URL; videos are set in Design > Editor.
- Edit a page in a Country path's language. Section and text writes change the main store. Translations are edited in the admin.
Store
Get Status (Public)
GET /api/store/status
No key needed. Send a non-library User-Agent (see Getting Started). Answers during an install too.
{ "data": { "up": true } }
When the store cannot serve pages: {"data": {"up": false, "reason": "installing"}}. Reasons: no_tenant, not_installed, deleted, suspended, installing, no_template.
Get Store Info
GET /api/store/info
Ability: store:read.
{
"data": {
"name": "My Store",
"url": "https://store.example.com",
"niche": "tech",
"live": true,
"default_country": "US",
"enabled_countries": ["US", "GB", "DE"],
"version": "3.2.0",
"cart_enabled": false,
"advanced_mode": true
}
}
Get Store Stats
GET /api/store/stats
Ability: store:read. Also at GET /api/data/stats (ability data:read).
| Parameter | Type | Description |
|---|---|---|
images |
boolean | 1 adds offers_images_pending and articles_images_pending (slower) |
{
"data": {
"products": 150,
"categories": 23,
"articles": 45,
"articles_generated": 30,
"brands": 12,
"offers": 320,
"messages": 4,
"offers_expired": 3,
"offers_out_of_stock": 7,
"products_without_offers": 2
}
}
Keys come back sorted by value, smallest first.
Get Instant Store Readiness
GET /api/store/instant-store-readiness
Ability: store:read. Content counts used to judge whether a store is ready to be copied. Not wrapped in data.
{
"product_count": 150,
"category_count": 23,
"article_count": 45,
"empty_leaf_category_count": 0,
"homepage_customised_fields": 12,
"homepage_hero_is_custom": true,
"locked_content_count": 0,
"unfinished_article_generation_count": 0,
"last_content_change_at": "2026-10-06 14:02:11"
}
Get Store Warnings
GET /api/store/warnings
Ability: store:read. The same warnings as the admin's Store Health page.
{
"data": [
{
"message": "To get paid for your Amazon sales you need to add an Amazon Affiliate Tag",
"action": "Find Out More",
"url": "https://store.example.com/admin/store-health"
}
]
}
Get Store Locales
GET /api/store/locales
Ability: store:read. The enabled country configurations, keyed by lowercase country code.
{
"data": {
"us": { "name": "United States", "iso_3166_2": "US", "...": "..." },
"gb": { "name": "United Kingdom", "iso_3166_2": "GB", "...": "..." }
}
}
Get IndexNow Status
GET /api/store/indexnow
Ability: store:read. Whether automatic IndexNow submission is working. Never returns the IndexNow key.
| Parameter | Type | Default | Description |
|---|---|---|---|
recent |
integer | 10 | Recent attempts to return, 1 to 50 |
{
"data": {
"summary": {
"last_submitted_at": "2026-10-07T09:12:00+00:00",
"success_24h": 4,
"success_30d": 61,
"error_30d": 1,
"total_30d": 62
},
"recent": [
{
"url": "https://store.example.com/products/example",
"submitted_at": "2026-10-07T09:12:00+00:00",
"status": "success",
"response_code": 200,
"error_message": null
}
]
}
}
status is one of success, error, spam, exception.
Contact Messages Summary
GET /api/store/messages/summary
Ability: store:read. Contact-form messages over time. Same shape as the clicks and checkouts summaries.
| Parameter | Type | Default | Description |
|---|---|---|---|
period |
string | day |
day, week, or month |
length |
integer | 30 | Number of periods to return. At most 366 days, 260 weeks or 120 months; more returns 422 |
{
"data": {
"today": { "messages": 1 },
"this_month": { "messages": 6 },
"total": { "messages": 9 },
"periods": {
"2026-10-06": { "messages": 0 },
"2026-10-07": { "messages": 1 }
}
}
}
total covers the requested window only. Week keys look like 2026-W41, month keys like 2026-10. Empty periods are included as zeros.
Clean Store
POST /api/store/clean
Ability: store:write (Full Access or broad write only, see Permissions). Deletes catalogue clutter. Counts of what it would delete are at GET /api/tools/offer-health.
| Field | Type | Required | Description |
|---|---|---|---|
operations |
string[] | No | Any of expired_offers, out_of_stock_offers, offerless_products, empty_brands, empty_categories. Default: all except out_of_stock_offers. They always run in that order, whatever order you send. |
{
"data": {
"deleted": { "expired_offers": 3, "offerless_products": 2, "empty_brands": 1, "empty_categories": 0 }
}
}
Clear Store Cache
POST /api/store/clear-cache
Ability: store:write. Clears the store's own query cache, settings caches and global view data, so the next page view reads fresh data, and queues a purge of the store's whole CDN copy.
| Field | Type | Required | Description |
|---|---|---|---|
edge |
boolean | No | Default true: also purge the CDN. Send false to clear only the store's own caches. |
The CDN purge goes into the store's purge queue and usually reaches the CDN within a few minutes. It is skipped when the store has no CDN zone of its own. edge in the reply is queued or skipped, and edge_reason says why.
{
"data": {
"cleared": { "query_cache": true, "settings": true, "view_data": true },
"edge": "queued",
"edge_reason": "The whole CDN zone is queued for a purge, usually done within a few minutes."
}
}
Rebuild XML Sitemap
POST /api/store/rebuild-sitemap
Ability: store:write. No body. The admin's XML Sitemap "Recreate" button: queues a fresh sitemap build and answers 202 at once. Calls while a build is waiting add nothing. The sitemap also rebuilds daily on its own, and within about ten minutes of an xml-sitemap settings change made through the API.
{ "data": { "queued": true, "last_generated_at": "2026-10-07T09:12:00+00:00" } }
Retire Catalogue
POST /api/store/retire-catalogue
Ability: store:write, products:write, categories:write, articles:write, brands:write and settings:write (it writes redirects). For a re-niche: switches off an old catalogue and redirects every old URL in one call. Nothing is deleted; every product, category, article and brand can be switched back on. A dry run unless dry_run is false.
| Field | Type | Required | Description |
|---|---|---|---|
category_ids |
array | One of three | Those categories and every category under them. A product is in scope when every live category it sits in is in scope, so a product also in a kept category stays. Up to 5000 ids. |
product_ids |
array | One of three | Those products. A category is in scope when all of its live products and live child categories are in scope. Up to 5000 ids. |
created_before |
datetime | One of three | Everything made before this date: products, and categories whose live products and child categories are all in scope (a category that now holds a newer product stays). |
include |
array | No | Any of products, categories, articles, brands. Default: all four. |
redirect_to |
string | No | One path on this store, e.g. /categories/new-niche, where every retired URL goes. |
redirect_map |
object | No | {category id or slug: path}. A retired URL goes to the entry for its category, or the nearest parent category with one, else to redirect_to. Up to 500 entries. |
dry_run |
boolean | No | Default true. |
Send exactly one of category_ids, product_ids or created_before.
Articles and brands follow what is retired. A brand is retired when it has at least one live product and every one of them is retired. An article is retired when it links to a live category (its product category) or a live product (the product it reviews) and every one of those is retired. With created_before, a brand with no live products and an article with no links are also retired when they were made before the date.
Redirects. Every retired URL gets a redirect to its target. A URL that already has a redirect keeps it (already_redirected). A URL with no target (no map entry and no redirect_to) gets none (no_target). After a live run the store clears its caches, purges its CDN copy once, rebuilds the sitemap and tells IndexNow about the retired URLs.
Refused (422): no scope or more than one; an unknown category or product id; a target that is not a path on this store, that does not answer, or that this same call retires; and a scope that would retire every product or every category with no redirect_to or redirect_map.
Dry run response: the counts and the first 50 URLs per type.
{
"data": {
"dry_run": true,
"scope": "category_ids",
"include": ["products", "categories", "articles", "brands"],
"counts": { "products": 105, "categories": 6, "articles": 12, "brands": 9 },
"redirects": { "to_write": 130, "already_redirected": 2, "no_target": 0 },
"urls": {
"products": [ { "from": "/products/old-swimsuit", "to": "/categories/new-niche", "redirect": "write" } ],
"categories": [ { "from": "/categories/swimwear", "to": "/categories/beach-gear", "redirect": "write" } ],
"articles": [],
"brands": []
}
}
}
Live run response (200): the same counts and URLs, each with its result (written, already_redirected, no_target or failed), plus the redirect totals with every failure and its reason, and an id to read the result again later.
{
"data": {
"id": "3f0c2a8e-1b7d-4c55-9a51-0e7d2b9f4c11",
"status": "done",
"dry_run": false,
"queued": false,
"counts": { "products": 105, "categories": 6, "articles": 12, "brands": 9 },
"redirects": { "written": 130, "already_redirected": 2, "no_target": 0, "failed": 0, "errors": [] },
"urls": { "products": [ { "from": "/products/old-swimsuit", "to": "/categories/new-niche", "redirect": "written" } ] },
"edge": "zone",
"indexnow_queued": 128,
"sitemap": "queued"
}
}
A live run over 300 items is queued: the reply is 202 with status: queued, the planned counts and the id. Read the result with Get Catalogue Retire.
Get Catalogue Retire
GET /api/store/retire-catalogue/{id}
Same ability as Retire Catalogue. The result of a live retire, kept for a week. status is queued, running, done or failed (with error; a failed run changes nothing). An unknown or expired id returns 404.
Regenerate Stock Texts (AI)
POST /api/store/regenerate-stock-texts
Ability: store:write. Rewrites the template's generic stock copy (about page, contact box and similar) for your niche. Only texts that still hold the template default are rewritten; texts you have edited are left alone. Uses AI chips.
| Field | Type | Required | Description |
|---|---|---|---|
niche |
string | Yes | Max 255 chars |
brand_name |
string | No | Max 255 chars |
keys |
string[] | No | Template text keys to rewrite. Default: all supported keys. Unknown keys are ignored. |
{
"data": {
"regenerated": [{ "key": "ABOUT_PAGE_CONTENT_TITLE", "old": "...", "new": "..." }],
"skipped": 3,
"total_eligible": 5
}
}
402 when out of chips, 503 when the AI call fails.
Pick Logo Icon (AI)
POST /api/store/pick-logo-icon
Ability: store:write. AI picks an icon for the template's text logo. Leaves an icon the store already chose alone.
| Field | Type | Required | Description |
|---|---|---|---|
niche |
string | Yes | Max 255 chars |
{ "data": { "status": "picked", "picked": "fa-camera", "label": "Camera" } }
status is one of picked, skipped (already set), no_option, no_choices, invalid_response.
Generate Store Logo (AI)
POST /api/store/generate-logo
Ability: store:write. Queues an AI logo for the store. Skips a store that already has its own logo.
| Field | Type | Required | Description |
|---|---|---|---|
niche |
string | Yes | Max 255 chars |
image_quality |
string | No | standard (default) or high. See Image quality. |
{ "data": { "status": "queued" } }
Other answers: {"status": "skipped", "reason": "no_store_name"}, {"status": "skipped", "reason": "logo_already_set"}, {"status": "failed", "error": "..."}.
Set Store Image
POST /api/store/images
Ability: store:write. Sets one of the store's own pictures from an image URL. The store downloads the image itself and stores it where the Design pages store it, so the storefront shows it with no other step.
| Field | Type | Required | Description |
|---|---|---|---|
collection |
string | Yes | Which picture. See the table below. |
url |
string | Yes | A public http or https address of a JPEG, PNG, WebP or GIF, at most 10 MB. Redirects are not followed, so send the final address. |
replace |
boolean | No | Default true: the new picture replaces the one there. false fills an empty slot and leaves a full one as it is. |
collection |
What it is | Same as |
|---|---|---|
logo |
The header logo of the active template. Also sets the logo type to "image", because the header shows the picture only then. | Design > Logo |
icon |
The store icon (favicon), PNG only. The 180, 192 and 512 pixel sizes are made from it. | Design > Store Icon |
social |
The default link preview picture for pages without their own. | Design > Global Images |
hero |
The homepage hero picture, on every template. | Pages > Homepage > Images |
about |
The About page picture. | Pages > About > Images |
person |
The photo of the person behind the store. A square photo works best; it is shown as a circle. | Design > About You |
{ "data": { "collection": "logo", "url": "https://.../my-store-logo.png", "replaced": true, "added": true, "logo_type": "image" } }
replaced is true when a picture was there before. added is false when replace: false found the slot full; url is then the picture that stays. Extra fields: logo_type for logo; hero_section_image_cleared for hero, true when the homepage hero section named its own picture (or played a video) and that was cleared so the new picture shows; person_draft: false for person, which marks the person as yours, as saving About You does.
The logo, icon and link preview picture belong to the active template. A template switch carries them to the new template. The hero, About and person pictures belong to the store and stay through a switch.
A URL the store cannot use returns 422 with the reason in message. So does a store without the page the picture goes on.
Get Store Template
GET /api/store/template
Ability: store:read. The store's template, every template it can switch to with its readiness for this store, and with key a dry run of the switch. Reads only.
| Parameter | Type | Required | Description |
|---|---|---|---|
key |
string | No | A template key. Adds target: what Switch Store Template would do. An unknown key returns 422. |
{
"data": {
"current": { "key": "crystal", "name": "Crystal", "readiness": { "checks": [], "warnings": 0, "unmeasured": 0, "summary": { "ok": true, "text": "Ready for your store." } } },
"templates": [
{ "key": "emporium", "name": "Emporium", "current": false, "early_access": false, "can_switch": true, "readiness": { "summary": { "ok": false, "text": "You have 40 products with a price. Emporium looks best with 100 or more." }, "warnings": 1, "unmeasured": 0 } }
],
"switch_back": null,
"target": {
"key": "emporium",
"name": "Emporium",
"readiness": { "checks": [ { "ok": false, "text": "You have 40 products with a price. Emporium looks best with 100 or more.", "fix": null, "note": null, "count": 40, "min": 100, "missing": 60 } ], "warnings": 1, "unmeasured": 0, "summary": { "ok": false, "text": "You have 40 products with a price. Emporium looks best with 100 or more." } },
"plan": { "status": "dry_run", "from": "crystal", "to": "emporium", "dry_run": true, "returning": false, "target_rows_ready": true, "custom_css_lines": 0, "pick_cards": 0, "content": { "options": 4, "texts": 12, "images": 1, "links": 3, "media": 0 }, "sections": { "created": 0, "mapped": 3, "unmatched": 1, "unmatched_blocks": { "testimonials": 1 } }, "stays": ["..."], "changes": ["..."], "next": [], "undo": "You can switch back at any time. Nothing is deleted." }
}
}
}
Readiness is the template's own checks held against this store, as the Templates gallery shows them: each check has ok, the sentence in text, and where the store can fill the gap a fix (write_guides, category_images, about_you). It warns and never blocks a switch. unmeasured counts checks the store could not measure; the summary then says to preview.
The examples are shortened. The plan is the switch's own dry run, the same answer the switch applies. stays, changes, next and undo are the lines the Design page's confirmation shows. A refusal comes back in its place, with the status listed under Switch Store Template. target_rows_ready: false means the template's rows are not on the store yet; the real switch adds them first. switch_back names the template the store came from, for a few days after a switch.
Switch Store Template
POST /api/store/template
Ability: store:write. Switches the storefront to another template and carries your content across. The same switch as Design > Templates, with the same rules.
| Field | Type | Required | Description |
|---|---|---|---|
key |
string | Yes | The template key, one of the templates in Get Store Template. Anything else returns 422. |
dry_run |
boolean | No | true answers what the switch would do, as target.plan in Get Store Template, and changes nothing. |
bring_custom_css |
boolean | No | Default true. false leaves your Custom CSS on the old template. |
{ "data": { "template": { "key": "emporium", "name": "Emporium" }, "readiness": { "checks": [], "warnings": 1, "unmeasured": 0, "summary": { "ok": false, "text": "..." } }, "status": "switched", "from": "crystal", "to": "emporium", "dry_run": false, "returning": false, "content": { "options": 4, "texts": 12, "images": 1, "links": 3, "media": 2 }, "sections": { "created": 7, "mapped": 3 }, "preset": "crystal", "preset_changed": false, "picks_seeded": 0, "message": "Your content came with you, and your visitors will see the new layout shortly. Switch back from this page at any time to get Crystal back exactly as it was." } }
readiness is measured before the switch. message is the sentence the Design page shows after a switch, naming any homepage band that stayed behind.
status |
HTTP | Meaning |
|---|---|---|
switched |
200 |
Done. |
dry_run |
200 |
dry_run: true; nothing changed. |
already_active |
200 |
The store is on that template already. Nothing changed. |
early_access_required |
403 |
The template is in early access, and this store's account does not have it. |
blocked_draft |
409 |
The Store Editor holds unpublished changes. Publish or discard them first. |
error |
409 |
The store is finishing an update. Try again in a few minutes. |
draft_template |
422 |
The template is still being built. |
What a switch does. Your products, categories, articles, pages and page addresses do not change. Texts, links, options and images you edited come across by key, and so do the logo and uploaded template pictures. Fonts stay with their template. Your Custom CSS comes across unless bring_custom_css is false. A theme the new template does not support falls back to its standard colours (preset_changed).
The homepage. The first time a store uses a template, the switch builds that template's homepage lineup and fills it with your homepage content, band by band (sections.mapped). Bands the new template has no place for stay on the old template, waiting (sections.unmatched_blocks). The lineup is built once: a store that comes back to a template it used before gets that homepage back as it left it (returning: true), and it does not gain bands a later release added to the template. Podium's pick cards are filled from your best rated products (picks_seeded).
Switching back is the same call with the old key. It restores the old template exactly, because a switch never writes the template it leaves. Nothing is deleted.
Get Template Texts
GET /api/store/texts
Ability: store:read. Every template text of the active template: the words the admin's Text pages edit (header bar, homepage boxes, About and Contact page texts, buttons and labels) and CUSTOM_CSS.
| Parameter | Type | Description |
|---|---|---|
keys |
string | Comma-separated keys, e.g. ?keys=HEADER_BAR_TEXT,CUSTOM_CSS. Default: all. |
{
"data": {
"template": "crystal",
"custom_css_enabled": true,
"new_design_enabled": true,
"texts": [
{ "key": "HEADER_BAR_TEXT", "name": "Top Bar Text", "description": "...", "group": "Header", "page_key": null, "model": null, "type": "text", "text": "Free guides every week", "default": "...", "customised": true }
]
}
}
text is what the store shows: your own text, or the default when you have not set one. customised is true when you have. text can be "" with customised: true: you chose to show nothing there.
Update Template Texts
PATCH /api/store/texts
Ability: store:write. Sets texts by key. The same save as the admin's Text pages and Design > Custom CSS.
| Field | Type | Required | Description |
|---|---|---|---|
texts |
object | Yes | Key to text, up to 200 keys. null puts a text back to its default. "" shows nothing there. |
{ "texts": { "HEADER_BAR_TEXT": "Free guides every week", "CUSTOM_CSS": "h1 { letter-spacing: -0.01em; }" } }
The answer is the Get Template Texts body for the keys you sent, plus changed (the keys whose stored value changed) and notes. A key whose value does not change is left alone, so sending the current text back changes nothing.
- A key the template does not have refuses the whole request with
422and an error per key. Nothing is written. - A text equal to its default is stored as "use the default", so a later change to the default reaches you.
CUSTOM_CSSis cleaned before it is saved: anything that would close the style element is removed, and it is cut at 50,000 characters. It is saved even while support has Custom CSS switched off on the store (custom_css_enabled: false); a note says it will show once it is back on. On Crystal it also needs the new design (new_design_enabled).- To rewrite the stock texts for your niche with AI, use Regenerate Stock Texts.
Get Design
GET /api/store/design
Ability: store:read. The colour scheme, the custom palette and the fonts of the active template, with every allowed value.
{
"data": {
"template": "crystal",
"scheme": "skin-custom",
"custom_palette": true,
"new_design_enabled": true,
"roles": { "accent": "#E8692C", "background": "#FFFFFF", "text": "#1F2328", "titles": "#2B3036", "buttons": "#B4451A" },
"palette": { "COLOUR_ACCENT": "#E8692C", "COLOUR_LOGO": "#E8692C", "COLOUR_BG_DARK": "#E8692C", "COLOUR_BG_LIGHT": "#FFFFFF", "COLOUR_TITLE": "#2B3036", "COLOUR_SUBTITLE": "#2B3036", "COLOUR_TEXT": "#1F2328", "COLOUR_LIGHT": "#1F2328", "COLOUR_LINK": "#E8692C", "COLOUR_BUTTON_ACCENT": "#B4451A", "COLOUR_BUTTON_ACCENT_TEXT": "#FFFFFF", "COLOUR_BUTTON": "#FFFFFF", "COLOUR_BUTTON_TEXT": "#B4451A" },
"fonts": { "TITLE_FONT": "nunito", "TITLE_FONT_WEIGHT": "bold", "TITLE_SIZE": "medium", "TITLE_ALIGNMENT": "center", "SUBTITLE_FONT": "rubik", "SUBTITLE_FONT_WEIGHT": "normal", "SUBTITLE_SIZE": "medium", "SUBTITLE_ALIGNMENT": "center", "BODY_FONT": "source-serif-4", "BODY_FONT_WEIGHT": "normal", "BODY_SIZE": "medium" },
"choices": {
"schemes": { "skin-indigo-go": "Indigo Go", "skin-blue-dream": "Blue Dream" },
"fonts": { "TITLE_FONT": { "inter": "Inter", "outfit": "Outfit" }, "TITLE_FONT_WEIGHT": { "normal": "Normal", "bold": "Bold" } },
"font_pairings": { "classic-editorial": { "name": "Classic Editorial", "title": "playfair-display", "subtitle": "lora", "body": "open-sans" } }
}
}
}
scheme is skin-custom while the custom palette is on. roles are the five colours the Colour Scheme page shows; palette is the thirteen colours the store uses. The example lists are shortened.
Update Design
PATCH /api/store/design
Ability: store:write. The same saves as Design > Colour Scheme and Design > Typography. Send any of these:
| Field | Type | Description |
|---|---|---|
scheme |
string | One of choices.schemes. Turns the custom palette off. |
custom_palette |
object | Your own colours: any of accent, background, text, titles, buttons (hex), and overrides, an object of any palette key to a hex colour. Roles you leave out keep their current colour. Turns the custom palette on. |
fonts |
object | Any fonts key to one of its choices.fonts values. |
font_pairing |
string | One of choices.font_pairings. Sets the three font families; a font you also send in fonts wins. |
{ "custom_palette": { "accent": "#E8692C", "background": "#FFFFFF", "text": "#1F2328", "titles": "#2B3036", "buttons": "#B4451A" }, "fonts": { "TITLE_FONT": "nunito", "TITLE_FONT_WEIGHT": "bold" } }
The answer is the Get Design body plus adjustments: colours the store changed so text stays readable (each pair is checked for contrast, overrides too).
schemeandcustom_palettetogether, an unknown scheme, colour, font or value:422with an error per field. Nothing is written.custom_paletteneeds the new design on the store, like the page:409witherror: new_design_offotherwise.
Preview a Palette
POST /api/store/design/palette
Ability: store:write. A palette to look at. Nothing is saved.
| Field | Type | Required | Description |
|---|---|---|---|
roles |
object | No | Any of accent, background, text, titles, buttons as hex. The store derives the full palette from them. Free. |
Without roles, the AI designs five role colours for your niche and store name, the Colour Scheme page's "Generate with AI" button. Uses a few hundred AI chips. 402 when out of chips, 503 when the AI call fails.
{ "data": { "source": "ai", "roles": { "accent": "#1D6F42", "background": "#F7FAF7", "text": "#1E2B24", "titles": "#10241A", "buttons": "#1D6F42" }, "palette": { "COLOUR_ACCENT": "#1D6F42" }, "adjustments": {} } }
To apply it, send roles back as custom_palette to Update Design.
Seed Featured Products
POST /api/store/featured-products
Ability: store:write. Chooses the products in the homepage Featured Products section: a varied set spread across your top categories, main items before accessories, priced products first, only products with a live, in-stock offer.
| Field | Type | Required | Description |
|---|---|---|---|
quantity |
integer | No | 4 to 12. Default 8. The section never shrinks: a larger quantity you set stays. |
force |
boolean | No | true chooses again over products already chosen. Default false. |
dry_run |
boolean | No | true answers what it would choose and changes nothing. |
{ "data": { "status": "seeded", "section_id": 25, "before": [], "after": [5, 116, 7, 107], "quantity": 4, "dry_run": false, "message": "Featured products chosen.", "products": [{ "id": 5, "title": "..." }] } }
Always 200. status says what happened:
status |
Meaning |
|---|---|
seeded |
Chosen (or would be, with dry_run). |
already_chosen |
Products are already chosen. Nothing changed. Send force: true to choose again. |
blocked_draft |
The Store Editor holds unpublished changes. Publish or discard them first. |
no_homepage |
The store has no homepage. |
no_band |
The homepage has no Featured Products section switched on. |
too_few_products |
Fewer than four products with a live offer. Nothing changed. |
To set the products yourself, update the section's custom_ids with Update Page Sections.
Set Footer Credit
POST /api/store/footer-credit
Ability: store:write. Sets the wording of the "Powered by FreshStore" footer credit on every template. You send plain text; the store builds the link. The link always points at the FreshStore homepage and becomes your referral link automatically if you are in the referral programme.
| Field | Type | Required | Description |
|---|---|---|---|
before |
string | No | Text before the link, max 200 chars |
link |
string | Yes | Link text, max 100 chars |
after |
string | No | Text after the link, max 200 chars |
show |
boolean | No | true turns the credit back on where it was hidden. Omit to keep each template's own choice. |
{ "data": { "templates": 6, "shown": false } }
Recompute Store State
POST /api/recompute
Ability: both settings:write and store:read on a scoped key. No body. Optional query ?limit= (recommendation cards, 1 to 50).
Re-checks the launch plan against the store's real data, saves the result, and returns the launch plan and the recommendations in one response. Use it after changing settings when you want the launch plan to reflect the change straight away. It never returns 500 for a failed half: a half that could not be computed is null.
{
"data": {
"launch_plan": { "percent": 60, "launch_complete": false, "next_step": { "...": "..." }, "sections": [ ] },
"recommendations": [ { "key": "...", "title": "...", "...": "..." } ]
}
}
Tools
Get Offer Health
GET /api/tools/offer-health
Ability: store:read. Counts of what POST /api/store/clean would remove, using the same rules.
{
"data": {
"expired_offers": 3,
"out_of_stock_offers": 7,
"products_without_offers": 2,
"empty_brands": 1,
"empty_categories": 0,
"checked_at": "2026-10-07T12:00:00+00:00"
}
}
Redirects
Ability: settings:read to list, settings:write to create or delete. The AI key presets leave settings out, so they cannot change redirects.
Redirect Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
from |
string | Old path, e.g. /products/old-thing |
from_url |
string | Old address as a full URL |
to |
string | Where it goes, as a label |
to_url |
string | Where it goes, as a full URL |
status |
string | active, waiting (old page still live), or broken (new address not working) |
status_label |
string | Human label for status |
source |
string | Who made it. Automatic redirects from slug changes are marked. |
last_used_at |
datetime | |
created_at |
datetime | |
type, old_slug, new_slug, source_still_live |
Older fields, kept for older callers |
List Redirects
GET /api/redirects
Newest first.
| Parameter | Type | Default | Description |
|---|---|---|---|
per_page |
integer | 50 | 1 to 100 |
hide_automatic |
boolean | false | Hide redirects the store made itself when a slug changed |
{
"data": [ { "id": 12, "from": "/products/old-thing", "to_url": "https://store.example.com/products/new-thing", "status": "active", "...": "..." } ],
"meta": { "total": 1, "per_page": 50, "current_page": 1 }
}
Create Redirect
POST /api/redirects
| Field | Type | Required | Description |
|---|---|---|---|
from |
string | Yes* | Old address. Any form: full URL, path or slug. Max 255 chars. |
to |
string | Yes* | New address. Any form. Max 255 chars. |
switch_off_source |
boolean | No | Also disable the old page, so the redirect takes effect |
type, old_slug, new_slug |
string | No | Older request shape. type is page, product, category, article or brand. Required only when from / to are not sent. |
A second redirect from the same address replaces the first. An address the store cannot use returns 422 with a message.
{ "data": { "id": 13, "from": "/old", "to_url": "https://store.example.com/new", "status": "active", "...": "..." } }
Create Redirects in Bulk
POST /api/redirects/bulk
| Field | Type | Required | Description |
|---|---|---|---|
redirects |
array | Yes | 1 to 500 rows, each with the fields of Create Redirect (from, to, switch_off_source) |
Each row is checked exactly like Create Redirect. A row that fails is reported with its position (index, from 0) and message, and the other rows are still saved. The rows are saved together, and the CDN is purged once for the lot. More than 500 rows returns 422 and saves nothing.
{
"data": {
"total": 2,
"created": 1,
"failed": 1,
"results": [
{ "index": 0, "ok": true, "redirect": { "id": 14, "from": "/products/old-thing", "status": "active", "...": "..." } },
{ "index": 1, "ok": false, "error": "Your homepage always works, so it cannot be redirected. Enter the old address that has stopped working." }
]
}
}
Delete Redirect
DELETE /api/redirects/{id}
Response 204 (no content).
Users
Ability: users:read / users:write.
The user endpoints also check who made the key. A key made by an Admin cannot see, change, delete or sign in as users above it (for example the store owner). The platform's own machine account is exempt. Breaking this rule returns 403 {"error": "Forbidden"}.
User Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
firstName |
string | |
lastName |
string | |
email |
string | |
roles |
string[] | Role names |
hidden |
boolean | Hidden from the admin's user list |
lastLoginAt |
datetime | Null if they have never signed in |
List Users
GET /api/users
Paginated. Only the users the key's owner may manage.
Create User
POST /api/users
If the email already exists in this store (including a deleted user), that user is restored and returned instead of an error.
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Valid email, max 255 chars |
password |
string | No | A random password is set if omitted |
first_name |
string | No | Max 255 chars |
last_name |
string | No | Max 255 chars |
roles |
string[] | No | Existing role names. Super Admin, Admin and Owner need a Full Access (*) key, otherwise 403. |
newsletter |
boolean | No | Default true |
hidden |
boolean | No | Needs a Full Access (*) key, otherwise 403 |
Response 201 for a new user, 200 for an existing one, with the user in data.
Invite User (platform only)
POST /api/users/invite
Only the platform's machine account may call this; any other key gets 403. Invites someone as an Admin of this store and emails them a 7-day link to set their own password. Limit: 10 invited people per store.
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | |
first_name |
string | Yes | |
last_name |
string | No | |
invited_by |
string | No | Name shown in the email, max 100 chars |
{ "data": { "user": { "id": 7, "email": "helper@example.com", "...": "..." }, "created": true } }
Resend Invite (platform only)
POST /api/users/{id}/invite
Platform machine account only. Sends a fresh 7-day link to an invited Admin. Optional body {"invited_by": "Name"}. Returns the user in data. 422 for the owner, hidden accounts or anyone who is not an invited Admin.
Get Login Link
GET /api/users/login-link?email=jane@example.com
Returns a magic link that signs the holder in as that user. Treat it like a password. Needs a Full Access key: any other key gets 403 (since 2026-10-07).
| Parameter | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Must belong to a user in this store |
admin |
boolean | No | Default true: the link opens the admin area |
target |
string | No | Admin page to land on: products, categories, articles, pages, design, store-editor, homepage, media, settings, api-keys, launch-plan, amazon-settings, ebay-settings, colour-scheme, social-settings, keyword-research, product-finder, article-writer, tools, store-health, offer-settings. Anything else lands on the dashboard. |
{ "data": { "loginLink": "https://store.example.com/magiclink/..." } }
Get User
GET /api/users/{id}
Update User
PATCH /api/users/{id}
| Field | Type | Required | Description |
|---|---|---|---|
first_name |
string | No | Max 255 chars |
last_name |
string | No | Max 255 chars |
email |
string | No | Valid email, max 255 chars |
Delete User
DELETE /api/users/{id}
Removes the user's access: their API keys and any pending invite are deleted, then the user. The store owner, hidden and FreshStore accounts, and the key's own user cannot be removed (422). Response 204.
Settings
Ability: settings:read / settings:write.
Get All Settings
GET /api/settings
Every setting as flat {group}_{setting} keys. Credential-shaped keys are removed: any key ending in _password, _secret, _key, _keystring, _key_base64, _token or _cert_id. Also includes design_new_design_enabled (read only).
{
"data": {
"store_name": "My Fresh Store",
"store_niche": "tech",
"product_show_reviews": true,
"offers_amazon_enabled": true
}
}
Update Settings (flat)
PATCH /api/settings
Send one or more {group}_{setting} keys.
{ "store_name": "My Updated Store", "product_show_reviews": false }
- A key that names no settings group is ignored.
- A key that names a real group but cannot be saved makes the request fail with
422and lists the key:{"message": "Some settings could not be saved.", "failed": ["store_skin"]}. The other keys in the same request are still saved. store_launch_checklistcannot be written here. Use the Launch Plan endpoints.template_frontendcannot be changed with a member key. Sending the current value is accepted. Use Switch Store Template.- A saved
xml-sitemap_*key queues a sitemap rebuild within about ten minutes.
Success: {"message": "Settings saved successfully."} (no data wrapper).
Get Settings by Group
GET /api/settings/{group}
{group} is one of store, offers, product, traffic, article, social, template, xml-sitemap, schema. Keys come back without the group prefix.
The store group hides its internal keys. Every group drops credential-shaped keys (since 2026-10-07), with the same rule as GET /api/settings: any key ending in _password, _secret, _key, _keystring, _key_base64, _token or _cert_id. So GET /api/settings/offers no longer returns merchant API keys and secrets, and GET /api/settings/traffic no longer returns the IndexNow key. You can still write them with PATCH.
{ "data": { "name": "My Fresh Store", "niche": "tech", "live": true, "default_country": "US" } }
Update Settings by Group
PATCH /api/settings/{group}
Send keys without the group prefix. Same rules and responses as the flat update; failed lists your own key names. In the social group, pasted full profile URLs are reduced to handles. Changing store.ai_language also purges the store's cached pages.
{ "name": "My Updated Store", "niche": "tech" }
Settings groups and fields
Field names are as returned by GET /api/settings/{group}. Prefix with {group}_ for the flat endpoints (store_name, xml-sitemap_products_quantity).
store
| Field | Type | Notes |
|---|---|---|
name |
string | Store name |
niche |
string | |
live |
boolean | Store open to the public |
ai_language |
string | Language for AI text and the page lang |
default_country |
string | |
enabled_countries |
string[] | |
cart_enabled |
boolean | |
cart_show_buy_now |
boolean | |
advanced_mode |
boolean | |
header_scripts |
string | HTML added to every page head |
footer_scripts |
string | HTML added before the closing body tag |
ads_txt_content |
string | |
enable_message_email_notifications |
boolean | |
matomo_site_id |
string | |
launch_checklist |
object | Read only here; see Launch Plan |
Hidden from this group (not returned, managed by the platform): url, url_private, version, user_id, website_id, the stock image provider keys (pixabay_api_key, pexels_api_key, unsplash_api_key), stream_*, api_cacher_*, aws_ses_smtp_*, openai_api_key, matomo_api_url, matomo_api_key, helpscout_beacon_key. The platform manages them; do not write them.
offers
| Field pattern | Type | Notes |
|---|---|---|
{merchant}_enabled |
boolean | amazon, ebay, etsy, walmart, aliexpress |
{merchant}_search_button_enabled |
boolean | Same merchants |
amazon_{cc}_tag |
string | Your Amazon tracking ID per marketplace. cc: au be br ca de eg es fr gb ie in it jp mx nl pl sa se sg tr ae us za |
amazon_{cc}_tag_default |
string | Platform default tag per marketplace |
amazon_{cc}_api_key, amazon_{cc}_api_secret, amazon_{cc}_oauth_client_id, amazon_{cc}_oauth_client_secret, amazon_{cc}_credential_version |
string | Amazon API credentials per marketplace |
amazon_tag_overridden_locales |
string[] | Marketplaces whose tag was set in this store, so an account-level tag does not replace it |
amazon_link_type |
string | product_page or add_to_cart |
amazon_direct_link_fallback |
boolean | |
aliexpress_tracking_id, aliexpress_api_key, aliexpress_api_secret |
string | Also per country: aliexpress_{cc}_... for us gb it es de in |
aliexpress_tracking_id_overridden |
boolean | |
ebay_tracking_id, ebay_app_id, ebay_cert_id |
string | Also per country: ebay_{cc}_... for us gb at au be ca ch de es fr hk ie in it my nl ph pl sg th tw vn |
ebay_tracking_id_overridden |
boolean | |
etsy_tracking_id, etsy_keystring, etsy_shared_secret |
string | Also per country: etsy_{cc}_... for us ca au gb in de fr nl it es ru pt gr ie jp nz pl |
walmart_affiliate_id, walmart_consumer_id, walmart_api_key_base64 |
string | Also walmart_us_... |
preferred_network |
string or null | Merchant whose offers sort first on the product page |
auto_refresh_offers |
boolean | Refresh stale offers in the background |
auto_refresh_offers_minutes |
integer | Default 4320 |
show_estimated_price |
boolean | |
hide_estimated_price |
boolean | Unused |
product
| Field | Type | Notes |
|---|---|---|
auto_review_quantity_minimum, auto_review_quantity_maximum |
integer | Range for automatic review counts |
auto_review_score_minimum, auto_review_score_maximum |
float | Range for automatic review scores |
show_reviews |
boolean | |
show_ebay_related_products |
boolean | |
auto_generate_content_with_ai |
boolean | |
auto_generate_product_titles_with_ai |
boolean | |
show_button_on_listings |
boolean | |
listing_default_sort |
string | default, newest, price-asc, price-desc, rating, title |
traffic
| Field | Type | Notes |
|---|---|---|
indexnow_api_key |
string | |
indexnow_submit_pages, indexnow_submit_products, indexnow_submit_product_categories, indexnow_submit_brands, indexnow_submit_articles, indexnow_submit_article_categories |
boolean | |
robots_txt_content |
string | |
gsc_verification_content |
string | Google Search Console verification |
bing_verification_content |
string | Bing Webmaster verification |
ai_treasure_map_include_products, ..._product_categories, ..._articles, ..._article_categories, ..._pages |
boolean | What llms.txt lists |
article
| Field | Type | Notes |
|---|---|---|
default_author |
string | |
show_article_meta |
boolean | |
show_table_of_contents |
boolean | |
auto_refresh_modified_date_enabled |
boolean | |
auto_refresh_modified_date_after_days |
integer | Default 90 |
auto_refresh_modified_date_spread_days |
integer | Default 14 |
social
All strings: twitter_handle, facebook_page_handle, facebook_group_handle, youtube_channel_handle, instagram_handle, tiktok_handle, pinterest_handle, linkedin_page_handle, tumblr_handle, discord_invite, whatsapp_url, custom_social_url, custom_social_label.
template
| Field | Type | Notes |
|---|---|---|
frontend |
string | The active template key. Read only for member keys: sending a different value is refused. Switch with Switch Store Template, which carries your content across. |
xml-sitemap
| Field | Type | Notes |
|---|---|---|
products_quantity, categories_quantity, pages_quantity, articles_quantity, brands_quantity |
integer | Cap per type. 0 means include everything. |
product_method |
string | recent, popular, oldest, random |
schema
What the store sends to Google as structured data. All true by default. None of them changes the stars visitors see on the store.
| Field | Type | Notes |
|---|---|---|
price_and_availability |
boolean | The product price and whether it is in stock |
shipping_and_returns |
boolean | The standard shipping and returns details on each product offer |
article_author |
boolean | The author name on articles |
generated_stars_in_google |
boolean | The star rating on products that have no rating of your own |
your_ratings_in_google |
boolean | Your own ratings (author_rating) on products and review articles |
Launch Plan
Ability: settings:read / settings:write.
Get Launch Plan
GET /api/launch-plan
Re-checks the plan against the store's data, then returns it.
{
"data": {
"percent": 60,
"launch_complete": false,
"next_step": { "key": "store_logo_set", "title": "...", "section": "...", "...": "..." },
"sections": [
{
"title": "Set up your store",
"description": "...",
"counted": true,
"ongoing": false,
"steps": [
{
"key": "store_niche_set",
"title": "...",
"description": "...",
"done": true,
"manual": false,
"target": "settings",
"button_label": "Open",
"repeats": null
}
]
}
]
}
}
manual: true means the store cannot detect the step itself.
Mark Step Done
POST /api/launch-plan/{key}
No body. {key} is a step key from GET /api/launch-plan. Unknown key: 404.
{ "data": { "key": "bing_webmaster_set", "done": true, "percent": 65 } }
Mark Step Not Done
DELETE /api/launch-plan/{key}
{ "data": { "key": "bing_webmaster_set", "done": false, "percent": 60 } }
Recommendations
Get Recommendations
GET /api/recommendations
Ability: store:read. What the store suggests doing next, built live on every call. Read only.
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
integer | 10 | Cards to return, 1 to 50 |
{
"data": [
{
"key": "thin_category",
"subject_type": "category",
"subject_id": 12,
"title": "...",
"body": "...",
"icon": "...",
"color": "...",
"severity": "...",
"priority": 50,
"band": "...",
"dismissible": true,
"snooze_hours": 168,
"cta": { "type": "...", "label": "...", "url": "...", "external": false }
}
]
}
Member Facts (platform)
Push Member Facts
POST /api/member-facts
Platform only: the store's own system account. A member key gets 403, whatever its abilities. Used by the FreshStore platform to tell a store facts about its owner's account. It replaces the stored row: a field you leave out becomes null.
| Field | Type | Required | Description |
|---|---|---|---|
plan_tier |
string | No | Max 64 chars |
store_count |
integer | No | 0 to 100000 |
account_last_login_at |
date | No | |
estimated_earnings_rate |
number | No | 0 to 1 |
estimated_conversion_rate |
number | No | 0 to 1 |
estimated_commission_rate |
number | No | 0 to 1 |
video_standing |
object | No | allowance_bytes, used_bytes, over (required with the object), optional limits.videos, limits.stored_minutes, limits.file_mb |
Returns the stored facts in data, plus synced_at.
Products
Product Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
slug |
string | |
title |
string | |
subtitle |
string | |
link_title |
string | |
content |
string | HTML content |
content_bottom |
string | HTML content below main |
highlights |
string[] | Key features / bullet points |
summary |
string | |
meta_title |
string | |
meta_keywords |
string | |
meta_description |
string | |
review_quantity |
integer | |
review_score |
float | |
author_rating |
float | Your own rating, 1 to 5 in steps of 0.5. Shown as "Our rating" in place of the stars above |
author_verdict |
string | Your verdict, max 600 chars |
author_pros |
string[] | What you like, max 8 items of 160 chars |
author_cons |
string[] | What is worth knowing, max 8 items of 160 chars |
author_reviewed_at |
datetime | When the rating last changed (read only, set by the store) |
enabled |
boolean | |
in_sitemap |
boolean | |
brand_id |
integer | |
analytics_visits |
integer | Total page views |
upc |
string | |
ean |
string | |
isbn |
string | |
mpn |
string | |
main_image_url |
string | Primary product image URL (read only) |
created_at |
datetime | |
updated_at |
datetime | |
brand |
object | With ?include=brand |
offers |
array | With ?include=offers |
categories |
array | With ?include=categories |
Categories are not a product field you can write. See Set Product Categories.
List Products
GET /api/products
| Parameter | Type | Description |
|---|---|---|
search |
string | Search by title |
enabled |
boolean | Filter by enabled status |
brand_id |
integer | Filter by brand |
category_id |
integer | Filter by category |
sort_by |
string | One of id, title, slug, enabled, brand_id, analytics_visits, review_score, review_quantity, author_rating, created_at, updated_at (default created_at). Anything else returns 422. Products with no author_rating sort last when descending. |
sort_dir |
string | asc or desc (default desc) |
own_touch |
string | with or without: products with or without something of your own (a verdict, pros or cons text, or a photo you uploaded). Anything else returns 422. |
Create Product
POST /api/products
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Max 255 chars |
slug |
string | No | Max 255 chars |
subtitle |
string | No | Max 255 chars |
link_title |
string | No | Max 255 chars |
content |
string | No | HTML |
content_bottom |
string | No | HTML |
highlights |
string[] | No | |
summary |
string | No | |
meta_title |
string | No | Max 255 chars |
meta_keywords |
string | No | Max 255 chars |
meta_description |
string | No | |
review_quantity |
integer | No | |
review_score |
number | No | |
author_rating |
number | No | 1 to 5 in steps of 0.5 (4.5, not 4.3). null clears it |
author_verdict |
string | No | Max 600 chars |
author_pros |
string[] | No | Max 8 items, 160 chars each |
author_cons |
string[] | No | Max 8 items, 160 chars each |
enabled |
boolean | No | |
in_sitemap |
boolean | No | |
brand_id |
integer | No | Must exist in brands |
upc |
string | No | Max 255 chars |
ean |
string | No | Max 255 chars |
isbn |
string | No | Max 255 chars |
mpn |
string | No | Max 255 chars |
Response 201 with the product in data.
Rating fields on a store mid-upgrade. A store that has not finished its latest upgrade answers 409 with "Store is upgrading, retry in a minute." when the request carries an author_* field. Retry after a minute. To rate many products, send one PATCH per product.
A product made this way has no offer, so no price and no image. Add one with POST /api/offers and POST /api/offers/{id}/products, or import Amazon products with POST /api/products/import-links. Then set its categories with POST /api/products/{id}/categories.
Import Amazon Links
POST /api/products/import-links
Ability: products:write. Imports Amazon products from links, like the admin's Link Importer. Imports run in the background.
| Field | Type | Required | Description |
|---|---|---|---|
links |
string[] | Yes | 1 to 100 Amazon links, ASINs or pasted text |
categories |
integer[] | No | Category ids to put the new products in |
locale |
string | No | Marketplace for bare ASINs. Default us. One of au be br ca cn de eg es fr gb ie in it jp mx nl pl sa se sg tr ae us za. |
Response 202
{
"data": {
"total": 3,
"importing": 1,
"duplicates": 1,
"failed": 1,
"links": [
{ "link": "https://www.amazon.com/dp/B0EXAMPLE1", "asin": "B0EXAMPLE1", "locale": "us", "status": "importing" },
{ "link": "...", "asin": "B0EXAMPLE2", "locale": "us", "status": "duplicate", "reason": "Product already exists", "product_id": 41 },
{ "link": "not a link", "status": "failed", "reason": "...", "reason_code": "..." }
]
}
}
Get Product
GET /api/products/{id}
Use ?include=brand,offers,categories to load related data.
{
"data": {
"id": 1,
"title": "Example Product",
"brand": { "id": 5, "title": "Acme" },
"offers": [{ "id": 10, "title": "..." }],
"categories": [{ "id": 3, "title": "..." }]
}
}
Update Product
PATCH /api/products/{id}
Same fields as Create, all optional. Only the fields you send change. Returns the product in data.
PATCH does not set categories. Sending categories or category_ids returns 422:
{
"message": "Set product categories with POST /api/products/{id}/categories and {\"category_ids\": [...]}.",
"errors": { "category_ids": ["Set product categories with POST /api/products/{id}/categories and {\"category_ids\": [...]}."] }
}
Stores on a release from before 2026-10-07 drop those fields without an error and still answer 200, so do not rely on the 422. Always use Set Product Categories.
Delete Product
DELETE /api/products/{id}
Soft-deletes the product. Response 204 (no content).
Get Product Offers
GET /api/products/{id}/offers
Paginated list of offers linked to this product.
Get Product Categories
GET /api/products/{id}/categories
{ "data": [{ "id": 1, "title": "Electronics", "...": "..." }] }
Set Product Categories
POST /api/products/{id}/categories
Ability: products:write. Replaces the product's categories with exactly the list you send. Send [] to remove the product from every category.
| Field | Type | Required | Description |
|---|---|---|---|
category_ids |
integer[] | Yes | Every id must exist in categories |
{ "category_ids": [1, 2, 3] }
Response:
{ "data": { "category_ids": [1, 2, 3] } }
Get Product Analytics
GET /api/products/{id}/analytics
{ "data": { "analytics_visits": 1250, "total_clicks": 89, "affiliate_clicks": 45 } }
Set Product Image
POST /api/products/{id}/images
Ability: products:write. Adds a picture to the product's own gallery from an image URL, as the Images tab on the product form does. The product shows its own pictures before any offer picture.
| Field | Type | Required | Description |
|---|---|---|---|
collection |
string | Yes | gallery |
url |
string | Yes | A public http or https address of a JPEG, PNG, WebP or GIF, at most 10 MB. Redirects are not followed, so send the final address. |
replace |
boolean | No | Default false: the picture goes last in the gallery. true removes the gallery's pictures first, which makes this one the main picture. Offer pictures are not touched. |
{ "data": { "collection": "gallery", "url": "https://.../copper-kettle.jpg", "replaced": false, "added": true, "count": 2 } }
count is the number of pictures in the gallery now. A URL already in the gallery is not stored twice: added is then false. A URL the store cannot use returns 422 with the reason in message.
Offers
Offer Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
uuid |
string | For merchant offers: {merchant}_{locale}_{merchant_offer_id} |
merchant |
string | amazon, ebay, etsy, walmart, aliexpress or custom |
merchant_offer_id |
string | The merchant's item id (ASIN for Amazon) |
merchant_product_id |
string | Older name for merchant_offer_id, same value |
title |
string | |
summary |
string | |
features |
array | |
currency |
string | e.g. USD, GBP |
price |
float | |
price_full |
float | Original / list price |
price_hidden |
boolean | The store does not show this price to visitors. Anything that publishes prices must respect it. |
price_source |
string | Where the price came from |
price_checked_at |
datetime | When the price was last checked |
min_price |
float | |
max_price |
float | |
condition |
string | e.g. new, used |
stock_available |
boolean | |
enabled |
boolean | |
locale |
string | e.g. us, gb |
images |
array | |
url_detail |
string | |
created_at |
datetime | |
updated_at |
datetime |
Get Enabled Merchants
GET /api/offers/merchants
{ "data": { "amazon": "Amazon", "ebay": "eBay", "custom": "Custom" } }
List Offers
GET /api/offers
Newest first.
| Parameter | Type | Description |
|---|---|---|
search |
string | Search by title |
merchant |
string | Filter by merchant |
enabled |
boolean | Filter by enabled status |
product_id |
integer | Filter by product |
Create Offer
POST /api/offers
| Field | Type | Required | Description |
|---|---|---|---|
merchant |
string | Yes | amazon, ebay, etsy, walmart, aliexpress or custom (case-insensitive). Anything else is 422. Use custom for your own stock or any other shop. |
title |
string | Yes | Max 255 chars |
url_detail |
string | Yes | Offer URL |
merchant_offer_id |
string | Conditional | Required unless merchant_product_id is sent. A bare eBay item number is accepted. |
merchant_product_id |
string | No | Older name for merchant_offer_id |
uuid |
string | No | Custom offers only; ignored for merchant offers |
summary |
string | No | |
features |
array | No | |
currency |
string | No | Max 10 chars |
price |
number | No | |
price_full |
number | No | |
min_price |
number | No | |
max_price |
number | No | |
condition |
string | No | Max 50 chars |
stock_available |
boolean | No | |
enabled |
boolean | No | Default true |
locale |
string | No | Max 10 chars, default us |
images |
array | No |
For a merchant offer (not custom) the store keeps one offer per merchant, locale and item. Creating one that already exists returns the existing offer (restored and enabled if needed). The store then fetches price, stock and images from the merchant in the background. Link the offer to a product with POST /api/offers/{id}/products.
Response 201 with the offer in data.
Get Offer
GET /api/offers/{id}
Update Offer
PATCH /api/offers/{id}
Same fields as Create except uuid, all optional.
Delete Offer
DELETE /api/offers/{id}
Removes the offer from every product that uses it. Response 204 (no content).
Link Offer to Products
POST /api/offers/{id}/products
Ability: offers:write. Adds the offer to the products you list. Products already linked stay linked; nothing is removed. To take an offer off everything, delete the offer.
| Field | Type | Required | Description |
|---|---|---|---|
product_ids |
integer[] | Yes | Every id must exist in products |
{ "product_ids": [1, 2, 3] }
Response: every product the offer is now linked to.
{ "data": { "product_ids": [1, 2, 3, 9] } }
Categories
Category Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
parent_id |
integer | Parent category (null = top level) |
slug |
string | |
title |
string | |
subtitle |
string | |
link_title |
string | |
content_top |
string | HTML shown above products |
content_bottom |
string | HTML shown below products |
meta_title |
string | |
meta_keywords |
string | |
meta_description |
string | |
enabled |
boolean | |
sort_order |
integer | |
products_count |
integer | On list, get and tree |
children_count |
integer | On list, get and tree |
children |
array | On the tree only |
created_at |
datetime | |
updated_at |
datetime |
Get Category Tree
GET /api/categories/tree
All categories nested under their parents, ordered by sort_order.
{
"data": [
{ "id": 1, "title": "Electronics", "children": [ { "id": 5, "title": "Phones", "children": [] } ] }
]
}
List Categories
GET /api/categories
Ordered by sort_order.
| Parameter | Type | Description |
|---|---|---|
search |
string | Search by title |
enabled |
boolean | Filter by enabled status |
parent_id |
integer | Filter by parent |
Create Category
POST /api/categories
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Max 255 chars |
slug |
string | No | Max 255 chars |
parent_id |
integer | No | Must exist in categories |
subtitle |
string | No | Max 255 chars |
link_title |
string | No | Max 255 chars |
content_top |
string | No | |
content_bottom |
string | No | |
meta_title |
string | No | Max 255 chars |
meta_keywords |
string | No | Max 255 chars |
meta_description |
string | No | |
enabled |
boolean | No | |
sort_order |
integer | No | |
generate_ai_content |
boolean | No | Write the subtitle, link title, top and bottom content and meta with AI in the background. Uses AI chips. |
generate_images |
boolean | No | Make the category banner and listing image in the background. See image_source. |
image_source |
string | No | auto (default), stock, or ai. See Image source. |
image_quality |
string | No | standard (default) or high. AI images only. See Image quality. |
Response 201 with the category in data. To put products in it, use POST /api/products/{id}/categories, POST /api/products/import-links with categories, or POST /api/categories/fill.
Generate Category List (AI)
POST /api/categories/generate-list
Ability: categories:write. Suggests a category tree for a niche. Creates nothing; call POST /api/categories for each one you want. One AI call.
| Field | Type | Required | Description |
|---|---|---|---|
niche |
string | Yes | Max 255 chars |
max_categories |
integer | No | 1 to 20, default 5 |
max_subcategories |
integer | No | 0 to 10. Accepted but not used: the AI returns 3 to 5 subcategories each. |
{
"data": {
"niche": "vintage cameras",
"categories": [
{ "title": "Film SLRs", "subcategories": ["35mm", "Medium Format", "Manual Focus"] }
]
}
}
402 when out of chips, 503 when the AI call fails.
Fill Categories with Products
POST /api/categories/fill
Ability: categories:write. Finds products from a merchant for each category and adds them, in the background. A one-off run, not an Auto Populator.
| Field | Type | Required | Description |
|---|---|---|---|
api_name |
string | Yes | amazon, ebay, etsy, walmart or aliexpress |
locale |
string | Yes | A locale that merchant supports, e.g. us. Otherwise 422. |
categories |
integer[] | No | Up to 50 category ids. Default: every category. |
quantity |
integer | No | Products per category, 1 to 50, default 5 |
generate_content |
boolean | No | Write product content with AI. Default true. |
ai_relevance_check |
boolean | No | Let AI reject products that do not fit the category. Default true. |
Response 202
{ "data": { "categories_queued": 8, "quantity_per_category": 5, "api_name": "amazon", "locale": "us" } }
Get Category
GET /api/categories/{id}
Update Category
PATCH /api/categories/{id}
Same fields as Create, all optional. generate_ai_content and generate_images regenerate on update too.
Delete Category
DELETE /api/categories/{id}
Response 204 (no content).
Get Category Products
GET /api/categories/{id}/products
Paginated list of products in this category.
Set Category Image
POST /api/categories/{id}/images
Ability: categories:write. Sets a category picture from an image URL.
| Field | Type | Required | Description |
|---|---|---|---|
collection |
string | Yes | listing: the picture on category tiles, the All Categories page and the homepage, the category form's Listing Image. banner: the wide category picture that generate_images makes beside it. |
url |
string | Yes | A public http or https address of a JPEG, PNG, WebP or GIF, at most 10 MB. Redirects are not followed, so send the final address. |
replace |
boolean | No | Default true. false fills an empty slot and leaves a full one as it is. |
{ "data": { "collection": "listing", "url": "https://.../copper-kettles.jpg", "replaced": true, "added": true } }
A URL the store cannot use returns 422 with the reason in message.
Auto Populators
An Auto Populator adds products to one category from one merchant search, a few a day. Ability: categories:read to read, categories:write to change. Create, delete and run-now stay in the admin.
Populator Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
category_id |
integer | The category it fills |
category_title |
string | |
enabled |
boolean | Writable |
search_term |
string | Writable, max 255 chars |
merchant |
string | |
search_options |
object | Locale, merchant category, sort and price filters |
quantity_per_day |
integer | Writable: 1, 3, 5, 10, 25 or 50 |
max_quantity |
integer | Writable. Maximum Products in total; 0 is unlimited |
max_products_in_category |
integer | Writable. Adds nothing while the category holds this many; 0 is no limit |
ai_relevance_check |
boolean | Writable |
total_products_added |
integer | |
reached_max_products |
boolean | true when total_products_added has reached max_quantity |
stopped_reason |
string | Why it is off, when it switched itself off |
enabled_at, last_run_at, last_completed_at, created_at, updated_at |
datetime |
List Populators
GET /api/populators
Paginated. Filters: enabled (boolean), category_id, search (on the search term).
Get Populator
GET /api/populators/{id}
Update Populator
PATCH /api/populators/{id}
Any of the writable fields. Switching on a populator that has reached its Maximum Products returns 422 with the reason, because it would switch itself off again on its next run. Raise max_quantity (or set it to 0) in the same call.
{ "enabled": true, "max_quantity": 200 }
Switch Many On or Off
POST /api/populators/bulk
| Field | Type | Required | Description |
|---|---|---|---|
ids |
integer[] | Yes | 1 to 500 populator ids |
enabled |
boolean | Yes |
{ "data": { "updated": [4, 9], "unchanged": [12], "reached_max_products": [15], "not_found": [] } }
Populators in reached_max_products stay off; raise their max_quantity with PATCH first.
Brands
Brand Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
slug |
string | |
title |
string | |
subtitle |
string | |
link_title |
string | |
content |
string | |
content_bottom |
string | |
meta_title |
string | |
meta_keywords |
string | |
meta_description |
string | |
enabled |
boolean | |
products_count |
integer | On list and get |
created_at |
datetime | |
updated_at |
datetime |
List Brands
GET /api/brands
Ordered by title.
| Parameter | Type | Description |
|---|---|---|
search |
string | Search by title |
enabled |
boolean | Filter by enabled status |
Create Brand
POST /api/brands
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Max 255 chars |
slug |
string | No | Max 255 chars |
subtitle |
string | No | Max 255 chars |
link_title |
string | No | Max 255 chars |
content |
string | No | |
content_bottom |
string | No | |
meta_title |
string | No | Max 255 chars |
meta_keywords |
string | No | Max 255 chars |
meta_description |
string | No | |
enabled |
boolean | No | |
generate_ai_content |
boolean | No | Write the subtitle, link title, content and meta with AI in the background. On update this overwrites that copy. |
Response 201 with the brand in data. Link products to a brand with brand_id on the product.
Clean Brands (AI)
POST /api/brands/clean
Ability: brands:write. Tidies brand titles, then AI sorts up to 60 brands (most products first) into enable, keep disabled, or delete. Enabled brands get AI content. Deleted brands are removed from their products first. Uses AI chips.
| Field | Type | Required | Description |
|---|---|---|---|
niche |
string | No | Helps the AI judge fit, max 255 chars |
keep_top |
integer | No | 1 to 50. Enable at most this many; the rest stay disabled. |
{ "data": { "classified": 24, "enabled": 10, "capped": 2, "deleted": 5, "kept_disabled": 9, "title_fixes": 3 } }
Get Brand
GET /api/brands/{id}
Update Brand
PATCH /api/brands/{id}
Same fields as Create, all optional.
Delete Brand
DELETE /api/brands/{id}
Response 204 (no content).
Get Brand Products
GET /api/brands/{id}/products
Paginated list of products for this brand.
Articles
Article Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
slug |
string | |
title |
string | |
subtitle |
string | |
link_title |
string | |
content |
string | HTML |
meta_title |
string | |
meta_keywords |
string | |
meta_description |
string | |
enabled |
boolean | |
generated |
integer | 1 if AI-generated |
product_category_id |
integer | Product category the article is tied to |
fallback_random_products |
boolean | |
formula |
string | Layout: standard, review or fact-sheet. Empty uses the store default (read only here) |
reviewed_product_id |
integer | The product a review article rates |
author_rating |
float | Your rating of that product, 1 to 5 in steps of 0.5 |
author_verdict |
string | |
author_pros |
string[] | |
author_cons |
string[] | |
author_reviewed_at |
datetime | When the rating last changed (read only) |
published_at |
datetime | |
created_at |
datetime | |
updated_at |
datetime | |
article_categories |
array | On get only |
List Articles
GET /api/articles
Newest first.
| Parameter | Type | Description |
|---|---|---|
search |
string | Search by title |
enabled |
boolean | Filter by enabled status |
generated |
boolean | Filter by AI-generated status |
article_category_id |
integer | Only articles in this article category |
Create Article
POST /api/articles
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Max 255 chars |
slug |
string | No | Max 255 chars |
subtitle |
string | No | Max 255 chars |
link_title |
string | No | Max 255 chars |
content |
string | No | HTML |
meta_title |
string | No | Max 255 chars |
meta_keywords |
string | No | Max 255 chars |
meta_description |
string | No | |
enabled |
boolean | No | |
published_at |
date | No | ISO 8601. A future date schedules the article: it is saved disabled, AI generation still runs at once, and it goes live on that date with its date kept |
product_category_id |
integer | No | A product category, for the sidebar and related products |
article_categories |
integer[] | No | Article category ids |
generate_ai_content |
boolean | No | Write the body, subtitle and meta with AI in the background. Uses AI chips. |
generate_seo_keyword |
string | No | Seed keyword for generate_ai_content, max 255 chars |
generate_images |
boolean or string[] | No | true for all three (listing, hero, social) or a subset, e.g. ["hero","social"] |
image_source |
string | No | auto (default), stock, or ai. See Image source. |
image_quality |
string | No | standard (default) or high. AI images only. See Image quality. |
reviewed_product_id |
integer | No | The product this article reviews. Must exist |
author_rating |
number | No | 1 to 5 in steps of 0.5. null clears it |
author_verdict |
string | No | Max 600 chars |
author_pros |
string[] | No | Max 8 items, 160 chars each |
author_cons |
string[] | No | Max 8 items, 160 chars each |
Response 201 with the article in data. A rating field on a store mid-upgrade answers 409, as for products.
Generate Article Topics (AI)
POST /api/articles/generate-topics
Ability: articles:write. Suggests article topics for the store. Creates nothing; pass a topic to POST /api/articles with generate_ai_content: true to write it. One AI call for the whole batch.
| Field | Type | Required | Description |
|---|---|---|---|
count |
integer | No | 1 to 10, default 3 |
niche |
string | No | Max 255 chars. Default: the store's niche. |
{
"data": {
"topics": ["Beginners Guide to Buying Your First Film Camera", "Caring for Vintage Camera Leather and Bellows"],
"requested": 3,
"returned": 2
}
}
returned can be lower than requested when the AI returns duplicates or blanks.
Get Article
GET /api/articles/{id}
Includes article_categories.
Update Article
PATCH /api/articles/{id}
Same fields as Create, all optional. article_categories replaces the attached set. generate_ai_content: true clears the body and writes it again. generate_images without generate_ai_content remakes the images only.
Delete Article
DELETE /api/articles/{id}
Response 204 (no content).
Fill Empty Images
POST /api/articles/{id}/fill-empty-images
Ability: articles:write. Copies the article's hero image into the listing and social image slots when they are empty. Never overwrites. Does nothing if there is no hero image.
{ "data": { "filled": ["listingImage", "socialImage"] } }
Set Article Image
POST /api/articles/{id}/images
Ability: articles:write. Sets the article's hero, listing or social image from an image URL, replacing the one there. The store downloads the image itself.
| Field | Type | Required | Description |
|---|---|---|---|
collection |
string | Yes | hero, listing or social |
url |
string | Yes | A public http or https address of a JPEG, PNG, WebP or GIF, at most 10 MB. Redirects are not followed, so send the final address. |
{ "data": { "collection": "hero", "url": "https://.../my-article-hero.jpg" } }
A URL the store cannot use returns 422 with the reason in message.
Search Stock Images
GET /api/images/stock
Ability: articles:read. The editor's Find Stock Image search over the free stock photo libraries the store uses (Pexels, Unsplash, Pixabay). No AI chips. Pass a result's url to any image endpoint: Set Article Image, Set Product Image, Set Category Image or Set Store Image.
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | 2 to 100 chars |
orientation |
string | No | horizontal (default), vertical or all |
quantity |
integer | No | 1 to 12 (default 6) |
{ "data": [ { "url": "https://images.pexels.com/...", "thumbnail": "https://...", "width": 4000, "height": 2667, "provider": "pexels", "description": "A shark underwater", "attribution": { "photographer": "...", "photographer_url": "...", "source_url": "..." } } ] }
Article Categories
Article Category Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
parent_id |
integer | Parent category |
slug |
string | |
title |
string | |
subtitle |
string | |
link_title |
string | |
content_top |
string | |
content_bottom |
string | |
meta_title |
string | |
meta_keywords |
string | |
meta_description |
string | |
enabled |
boolean | |
sort_order |
integer | |
articles_count |
integer | On list and get |
created_at |
datetime | |
updated_at |
datetime |
List Article Categories
GET /api/article-categories
Ordered by sort_order.
| Parameter | Type | Description |
|---|---|---|
search |
string | Search by title |
enabled |
boolean | Filter by enabled status |
Create Article Category
POST /api/article-categories
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Max 255 chars |
slug |
string | No | Max 255 chars |
parent_id |
integer | No | Must exist in article categories |
subtitle |
string | No | Max 255 chars |
link_title |
string | No | Max 255 chars |
content_top |
string | No | |
content_bottom |
string | No | |
meta_title |
string | No | Max 255 chars |
meta_keywords |
string | No | Max 255 chars |
meta_description |
string | No | |
enabled |
boolean | No | |
sort_order |
integer | No |
Response 201
Get Article Category
GET /api/article-categories/{id}
Update Article Category
PATCH /api/article-categories/{id}
Same fields as Create, all optional.
Delete Article Category
DELETE /api/article-categories/{id}
Response 204 (no content).
Pages
Page Fields
| Field | Type | Description |
|---|---|---|
id |
integer | |
key |
string | Stable id for system pages (e.g. homepage, about, contact). Null for your own pages. |
slug |
string | |
title |
string | |
subtitle |
string | |
link_title |
string | |
content |
string | HTML |
meta_title |
string | |
meta_keywords |
string | |
meta_description |
string | |
enabled |
boolean | |
created_at |
datetime | |
updated_at |
datetime |
List Pages
GET /api/pages
Newest first.
| Parameter | Type | Description |
|---|---|---|
search |
string | Search by title |
key |
string | Exact match on key, e.g. ?key=homepage |
enabled |
boolean | Filter by enabled status |
Create Page
POST /api/pages
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Max 255 chars |
key |
string | No | System page id, max 255 chars. Decides which template image slots generate_images fills on update. Set with care. |
slug |
string | No | Max 255 chars |
subtitle |
string | No | Max 255 chars |
link_title |
string | No | Max 255 chars |
content |
string | No | |
meta_title |
string | No | Max 255 chars |
meta_keywords |
string | No | Max 255 chars |
meta_description |
string | No | |
enabled |
boolean | No | |
generate_ai_content |
boolean | No | Write the title, subtitle, meta and (for the homepage) the hero text with AI in the background. Uses AI chips. |
Response 201
Get Page
GET /api/pages/{id}
Update Page
PATCH /api/pages/{id}
Same fields as Create, all optional, plus:
| Field | Type | Required | Description |
|---|---|---|---|
generate_images |
boolean | No | Make the page's template images in the background (homepage hero, offer box and social image, or the about page image), chosen by key |
image_source |
string | No | auto (default), stock, or ai. See Image source. |
image_quality |
string | No | standard (default) or high. AI images only. See Image quality. |
generate_ai_content: true works here too, for example on the homepage page of a new store.
Delete Page
DELETE /api/pages/{id}
Response 204 (no content).
Get Page Sections
GET /api/pages/{id}/sections
Ability: pages:read. The page's Sections for the active template, in order, enabled or not, and the blocks you can add, each with its settings. The same composition Design > Editor shows. Sections belong to the homepage, your own pages and any page already built from sections.
{
"data": {
"target": { "type": "page", "id": 1, "key": "homepage", "title": "Home" },
"template": "crystal",
"editable": true,
"editor_draft": false,
"sections": [
{ "id": 25, "block": "products", "name": "Products", "position": 10, "enabled": true, "placeable": true, "settings": { "title": "Vans at our pick-up cities", "mode": "custom", "custom_ids": [5, 116], "quantity": 8 } },
{ "id": 39, "block": "faq", "name": "FAQ", "position": 20, "enabled": true, "placeable": true, "settings": { "title": "Questions", "items": [{ "question": "...", "answer": "<p>...</p>" }] } }
],
"blocks": [
{ "key": "faq", "name": "FAQ", "description": "...", "settings": [
{ "key": "title", "type": "text", "label": "Title", "default": "" },
{ "key": "items", "type": "repeater", "label": "Questions", "count": 6, "default": [], "fields": [{ "key": "question", "type": "text", "label": "Question", "default": "" }, { "key": "answer", "type": "wysiwyg", "label": "Answer", "default": "" }] }
] }
]
}
}
editableisfalsewhile the store has the new design off, or for a stock page that keeps its own fixed layout.editor_draftistruewhile Design > Editor holds unpublished changes for this page. Writes are refused until they are published or discarded.placeableisfalsefor a Section this page cannot draw. It shows nothing; remove it.blocks[].settingsis each block's settings schema.typeis one oftext,textarea,wysiwyg,code,number,boolean,select(withoptions),colour,date,link,image,product,products,select_multiple,repeater(withcountandfields) andvideo.visible_whensays which other setting a field depends on.
Update Page Sections
PATCH /api/pages/{id}/sections
Ability: pages:write. Applies a list of operations in order, all or nothing, and publishes the result at once (no draft). The answer is the Get Page Sections body plus result, the counts of Sections updated, created and deleted.
| Field | Type | Required | Description |
|---|---|---|---|
operations |
array | Yes | 1 to 50 operations, below. |
op |
Fields | What it does |
|---|---|---|
update |
id, settings, optional enabled |
Changes the settings you send; the others keep their value. |
enable |
id |
Shows the Section. |
disable |
id |
Hides the Section and keeps its settings. |
move |
id, position |
Moves it to that place in the list, 1 being the top. |
add |
block, optional settings, position, enabled |
Adds a Section from blocks, starting from its defaults. Without position it goes last; a hero goes first. |
remove |
id |
Deletes the Section. |
{
"operations": [
{ "op": "update", "id": 39, "settings": { "title": "Questions people ask", "items": [{ "question": "Do I need a special licence?", "answer": "<p>Not for most campervans.</p>" }] } },
{ "op": "add", "block": "newsletter-signup", "position": 2 },
{ "op": "move", "id": 25, "position": 1 },
{ "op": "remove", "id": 31 }
]
}
How settings are written:
- Each value is checked against the block's settings. An unknown setting, a
selectvalue not in itsoptions, a bad colour or date, or a non-URL imagesrcrefuses the request. - A
linkis{"link": "https://...", "link_text": "", "new_tab": false, "no_follow": false}.linkalso takes an internal target such asinternal:page:categoriesorinternal:article:{slug}. Sub-keys you leave out keep their value. - An
imageis{"src": "https://...", "alt": "", "title": ""}. A newsrcdrops the old picture's sized copies. - A
repeater(FAQ questions, testimonials, cards) takes the whole list: send every item you want to keep, up to itscount. Each item you send is merged onto the item at the same place, so a field you leave out keeps its value. Items past the end of your list are emptied. productsandselect_multipletake a list of ids. Videos are set in Design > Editor; avideosetting is refused.
Positions are renumbered to 10, 20, 30 and on whenever the order changes.
| Status | When |
|---|---|
422 |
An operation is invalid: an unknown id, a block this page does not take, a refused setting. The errors keys name the operation, e.g. operations.1.settings.alignment. Nothing is written. |
409 |
error: editor_draft (unpublished Editor changes for this page), new_design_off (the store does not show sections yet) or not_composable (a stock page with its own fixed layout). |
Zones
Zones are the Sections above, below or beside the content of every category page, brand page or article, as arranged in Design > Editor. A zone belongs to the page type, not to one category, so there is no /api/categories/{id}/sections. Scoped keys need pages.
page_type |
Zones |
|---|---|
category |
above_core, below_core, sidebar |
brand |
above_core, below_core, sidebar |
article |
above_core, below_core |
The sidebar takes only blocks built for a narrow column; the others take what a page takes, except the hero. An unknown page type or zone returns 404.
Get Zone Sections
GET /api/zones/{page_type}/{zone}/sections
Ability: pages:read. Same body as Get Page Sections, with "target": {"type": "zone", "page_type": "category", "zone": "sidebar", "label": "Category pages: Sidebar"}.
Update Zone Sections
PATCH /api/zones/{page_type}/{zone}/sections
Ability: pages:write. Same operations and answers as Update Page Sections.
Image quality
Every endpoint that accepts generate_images (and POST /api/store/generate-logo) also accepts image_quality. It applies to images made with AI; stock images are unaffected.
| Value | Chips per AI image | Time | Notes |
|---|---|---|---|
standard |
20,000 | ~15 seconds | Default. Suits most images. |
high |
80,000 | ~25 seconds | Finer detail and more natural light. |
Any other value returns 422. Leaving it out is the same as standard.
Image source (stock vs AI)
Endpoints that make images (generate_images on categories, articles and pages) accept image_source:
| Mode | Behaviour | Chip cost |
|---|---|---|
auto |
Default. Try Pexels, then Unsplash, then Pixabay. Use AI only if all three miss for that image. | A few hundred chips per stock image (for the search keyword); about 20,000 per AI fallback (80,000 at high). |
stock |
Stock only. Skip the image if nothing matches. | A few hundred chips per attempt. |
ai |
AI for every image. | About 20,000 chips per image (80,000 at high). |
How auto works for each image (banner, listing, hero, social):
- A short AI call builds a 2 to 5 word search term from the title, the store's niche and the image shape.
- Pexels, then Unsplash, then Pixabay are searched, stopping at the first match. A provider without an API key is skipped.
- A match is downloaded and attached like any other image.
- If nothing matches, an AI image is made.
Stock provider keys are set per store in the store settings group (pixabay_api_key, pexels_api_key, unsplash_api_key). With no keys set, auto behaves like ai.
Stock images record the provider and photographer in the media record's custom_properties.attribution.
You cannot choose a specific stock image through the API.
Analytics
Ability: analytics:read. Every analytics call queues a refresh from the analytics service when the store's copy is more than 10 minutes old, so the newest visits can lag by one refresh.
Summary parameters
The /summary and /by-merchant endpoints take:
| Parameter | Type | Default | Description |
|---|---|---|---|
period |
string | day |
day, week, or month |
length |
integer | 30 | Number of periods to return. At most 366 days, 260 weeks or 120 months; more returns 422 |
Period keys look like 2026-10-07 (day), 2026-W41 (week) or 2026-10 (month). Periods with no activity are included as zeros.
Get Visits Summary
GET /api/analytics/visits/summary
{
"data": {
"2026-10-07": { "nb_uniq_visitors": 45, "nb_visits": 52, "nb_actions": 120 }
}
}
List Visits
GET /api/analytics/visits
Newest first.
| Parameter | Type | Description |
|---|---|---|
country_code |
string | Filter by country (e.g. US) |
device_type |
string | Filter by device (e.g. Desktop) |
from |
date | Start date |
to |
date | End date |
Fields: id, ip_address, country_code, region, city, visits, actions, events, visit_duration_seconds, referrer_type_name, device_type, browser, first_action_at, last_action_at.
List Visitors
GET /api/analytics/visitors
Most recently seen first. Fields: id, matomo_visitor_id, first_seen, last_seen, visits_count, clicks_count.
Get Visitor
GET /api/analytics/visitors/{id}
Get Pages Analytics
GET /api/analytics/pages
Not paginated.
{
"data": [
{ "id": 1, "label": "/products/example", "visitors": 120, "hits": 145, "average_time_on_page": 42, "bounce_rate": "35%", "exit_rate": "20%" }
]
}
Get Countries Analytics
GET /api/analytics/countries
Not paginated.
{
"data": [
{ "id": 1, "locale": "us", "country_name": "United States", "visitors": 500, "visits": 620, "actions": 1800, "visit_duration_seconds": 45000 }
]
}
Get Devices Analytics
GET /api/analytics/devices
Not paginated.
{
"data": [
{ "id": 1, "device": "Desktop", "visitors": 300, "visits": 400, "actions": 1200, "visit_duration_seconds": 30000 }
]
}
Get Sources Analytics
GET /api/analytics/sources
Not paginated.
{
"data": [
{ "id": 1, "source": "Google", "visitors": 200, "visits": 250, "actions": 600, "visit_duration_seconds": 18000 }
]
}
Sources Summary
GET /api/analytics/sources/summary
Unique visitors over time, optionally for one traffic source. Takes the summary parameters plus:
| Parameter | Type | Description |
|---|---|---|
source |
string | Part of a source label, e.g. google or Search Engines. Case-insensitive. |
{
"data": {
"total": { "visitors": 340 },
"periods": { "2026-10-06": { "visitors": 12 }, "2026-10-07": { "visitors": 9 } }
}
}
Affiliate Clicks Summary
GET /api/analytics/clicks/summary
All clicks and affiliate clicks over time. Takes the summary parameters.
{
"data": {
"total_clicks": 1500,
"affiliate_clicks": 890,
"affiliate_revenue": 3245.50,
"by_merchant": {
"amazon": { "clicks": 450, "revenue": 1820.00 },
"ebay": { "clicks": 200, "revenue": 875.50 }
},
"periods": {
"2026-10-06": { "clicks": 12, "affiliate_clicks": 5, "revenue": 125.00 },
"2026-10-07": { "clicks": 0, "affiliate_clicks": 0, "revenue": 0.0 }
}
}
}
total_clicks counts every click. affiliate_clicks counts outbound clicks to merchants. revenue is the total price of the products clicked, at the time of the click. It is not commission or confirmed sales; the store cannot see what the merchant paid.
Clicks by Merchant
GET /api/analytics/clicks/by-merchant
Affiliate clicks only, over time. Takes the summary parameters plus:
| Parameter | Type | Description |
|---|---|---|
merchant |
string | Part of a merchant name, e.g. amazon. Case-insensitive. Omit for all. |
{
"data": {
"total": { "clicks": 890, "revenue": 3245.50 },
"by_merchant": { "amazon": { "clicks": 450, "revenue": 1820.00 } },
"periods": { "2026-10-07": { "clicks": 5, "revenue": 125.00 } }
}
}
by_merchant is ordered by revenue, highest first. revenue has the same meaning as above.
Top Clicked Products
GET /api/analytics/clicks/top-products
Products with the most affiliate clicks.
| Parameter | Type | Default | Description |
|---|---|---|---|
days |
integer | 30 | 1 to 365 |
limit |
integer | 10 | 1 to 50 |
{
"data": {
"days": 30,
"products": [
{ "id": 41, "title": "Example Product", "path": "/products/example-product", "clicks": 37, "value": 1480.00 }
]
}
}
value is the total price of the clicked product at click time. A deleted product shows as "Deleted product" with path: null.
List Clicks
GET /api/analytics/clicks
Every tracked click, newest first. Use ?affiliate=true for outbound merchant clicks only.
| Parameter | Type | Description |
|---|---|---|
affiliate |
boolean | true for affiliate clicks only, false for internal only |
product_id |
integer | Filter by product |
offer_id |
integer | Filter by offer |
from |
date | Start date |
to |
date | End date |
| Field | Type | Description |
|---|---|---|
id |
integer | |
product_id |
integer | |
offer_id |
integer | |
affiliate |
integer | 1 = affiliate click, 0 = internal click |
source |
string | Page the click came from |
destination |
string | Merchant name (e.g. amazon) |
url |
string | Full destination URL |
price |
float | Product price at the time of the click |
clicked_at |
datetime |
Cart Checkouts Summary
GET /api/analytics/checkouts/summary
For stores with the cart turned on. Takes the summary parameters.
{
"data": {
"today": { "checkouts": 3, "revenue": 245.97 },
"this_month": { "checkouts": 42, "revenue": 3891.50 },
"total": { "checkouts": 580, "revenue": 52340.00 },
"periods": { "2026-10-07": { "checkouts": 2, "revenue": 89.99 } }
}
}
List Cart Checkouts
GET /api/analytics/checkouts
Newest first.
| Parameter | Type | Description |
|---|---|---|
from |
date | Start date |
to |
date | End date |
| Field | Type | Description |
|---|---|---|
id |
integer | |
action_type |
string | Always cart_checkout |
price |
float | Total checkout value |
properties |
object | Cart details including cart_items |
product_id |
integer | |
offer_id |
integer | |
performed_at |
datetime |
List Actions
GET /api/analytics/actions
Every tracked visitor action, newest first.
| Parameter | Type | Description |
|---|---|---|
action_type |
string | Filter by type (see below) |
product_id |
integer | Filter by product |
from |
date | Start date |
to |
date | End date |
Action types: page_view, action, event, outlink, form, ecommerceOrder, ecommerceAbandonedCart, goal, crash, media, search, affiliate_click, cart_view, cart_add, cart_remove, cart_update, cart_checkout
Fields: id, action_type, product_id, offer_id, click_type, click_destination, click_url, search_keyword, price, properties, performed_at.
Data
Ability: data:read.
Get AI Token Usage
GET /api/data/openai-tokens
AI tokens used per month, newest first.
{ "data": { "2026-10": 4500, "2026-09": 3200 } }
Get Store Stats (alias)
GET /api/data/stats
Same as GET /api/store/stats.
All Endpoints
"Scoped key" is the resource ability a key needs when it holds any resource:verb ability. Every key also needs the broad read (for GET) or write (for the rest), unless it is Full Access.
| Method | Path | Scoped key | Description |
|---|---|---|---|
GET |
/api/store/status |
none (public) | Health check |
GET |
/api/store/info |
store:read |
Store info |
GET |
/api/store/instant-store-readiness |
store:read |
Content counts for copying a store |
GET |
/api/store/stats |
store:read |
Content counts |
GET |
/api/store/warnings |
store:read |
Store Health warnings |
GET |
/api/store/locales |
store:read |
Enabled countries |
GET |
/api/store/indexnow |
store:read |
IndexNow status |
GET |
/api/store/messages/summary |
store:read |
Contact messages over time |
POST |
/api/store/clean |
store:write |
Delete catalogue clutter |
POST |
/api/store/clear-cache |
store:write |
Clear the store's caches and its CDN copy |
POST |
/api/store/rebuild-sitemap |
store:write |
Rebuild the XML sitemap |
POST |
/api/store/retire-catalogue |
store, products, categories, articles, brands, settings write |
Retire a catalogue and redirect its URLs |
GET |
/api/store/retire-catalogue/{id} |
store, products, categories, articles, brands, settings read |
A retire's result |
POST |
/api/store/regenerate-stock-texts |
store:write |
AI-rewrite template stock copy |
POST |
/api/store/pick-logo-icon |
store:write |
AI-pick a logo icon |
POST |
/api/store/footer-credit |
store:write |
Set the footer credit wording |
POST |
/api/store/generate-logo |
store:write |
AI-generate a store logo |
GET |
/api/tools/offer-health |
store:read |
Counts of what clean would delete |
GET |
/api/redirects |
settings:read |
List redirects |
POST |
/api/redirects |
settings:write |
Create redirect |
POST |
/api/redirects/bulk |
settings:write |
Create up to 500 redirects |
DELETE |
/api/redirects/{id} |
settings:write |
Delete redirect |
GET |
/api/users |
users:read |
List users |
POST |
/api/users |
users:write |
Create user |
POST |
/api/users/invite |
platform only | Invite an Admin |
GET |
/api/users/login-link |
Full Access (*) only |
Magic login link |
GET |
/api/users/{id} |
users:read |
Get user |
PATCH |
/api/users/{id} |
users:write |
Update user |
POST |
/api/users/{id}/invite |
platform only | Resend invite |
DELETE |
/api/users/{id} |
users:write |
Remove user |
GET |
/api/settings |
settings:read |
All settings (credentials removed) |
PATCH |
/api/settings |
settings:write |
Update settings (flat keys) |
GET |
/api/settings/{group} |
settings:read |
Settings for one group (credentials removed) |
PATCH |
/api/settings/{group} |
settings:write |
Update one group |
GET |
/api/launch-plan |
settings:read |
Launch plan |
POST |
/api/launch-plan/{key} |
settings:write |
Mark step done |
DELETE |
/api/launch-plan/{key} |
settings:write |
Mark step not done |
GET |
/api/recommendations |
store:read |
Recommendations |
POST |
/api/member-facts |
platform only | Push member facts (platform) |
POST |
/api/recompute |
settings:write + store:read |
Re-check launch plan, return both |
GET |
/api/products |
products:read |
List products |
POST |
/api/products |
products:write |
Create product |
GET |
/api/products/{id} |
products:read |
Get product |
PATCH |
/api/products/{id} |
products:write |
Update product (not categories) |
DELETE |
/api/products/{id} |
products:write |
Delete product |
GET |
/api/products/{id}/offers |
products:read |
Product offers |
GET |
/api/products/{id}/categories |
products:read |
Product categories |
POST |
/api/products/{id}/categories |
products:write |
Set product categories (replace) |
GET |
/api/products/{id}/analytics |
products:read |
Product analytics |
POST |
/api/products/import-links |
products:write |
Import Amazon links |
GET |
/api/offers/merchants |
offers:read |
Enabled merchants |
GET |
/api/offers |
offers:read |
List offers |
POST |
/api/offers |
offers:write |
Create offer |
GET |
/api/offers/{id} |
offers:read |
Get offer |
PATCH |
/api/offers/{id} |
offers:write |
Update offer |
DELETE |
/api/offers/{id} |
offers:write |
Delete offer |
POST |
/api/offers/{id}/products |
offers:write |
Link offer to products (add) |
GET |
/api/categories/tree |
categories:read |
Category tree |
GET |
/api/categories |
categories:read |
List categories |
POST |
/api/categories |
categories:write |
Create category |
GET |
/api/categories/{id} |
categories:read |
Get category |
PATCH |
/api/categories/{id} |
categories:write |
Update category |
DELETE |
/api/categories/{id} |
categories:write |
Delete category |
GET |
/api/categories/{id}/products |
categories:read |
Category products |
POST |
/api/categories/fill |
categories:write |
Fill categories with products |
POST |
/api/categories/generate-list |
categories:write |
AI-suggest a category tree |
POST |
/api/articles/{id}/images |
articles:write |
Set article image from a URL |
GET |
/api/images/stock |
articles:read |
Search stock photos |
GET |
/api/populators |
categories:read |
List Auto Populators |
POST |
/api/populators/bulk |
categories:write |
Switch many on or off |
GET |
/api/populators/{id} |
categories:read |
Get Auto Populator |
PATCH |
/api/populators/{id} |
categories:write |
Update Auto Populator |
GET |
/api/brands |
brands:read |
List brands |
POST |
/api/brands |
brands:write |
Create brand |
POST |
/api/brands/clean |
brands:write |
AI-clean brands |
GET |
/api/brands/{id} |
brands:read |
Get brand |
PATCH |
/api/brands/{id} |
brands:write |
Update brand |
DELETE |
/api/brands/{id} |
brands:write |
Delete brand |
GET |
/api/brands/{id}/products |
brands:read |
Brand products |
GET |
/api/articles |
articles:read |
List articles |
POST |
/api/articles |
articles:write |
Create article |
POST |
/api/articles/generate-topics |
articles:write |
AI-suggest article topics |
GET |
/api/articles/{id} |
articles:read |
Get article |
PATCH |
/api/articles/{id} |
articles:write |
Update article |
DELETE |
/api/articles/{id} |
articles:write |
Delete article |
POST |
/api/articles/{id}/fill-empty-images |
articles:write |
Fill empty article images |
GET |
/api/article-categories |
article-categories:read |
List article categories |
POST |
/api/article-categories |
article-categories:write |
Create article category |
GET |
/api/article-categories/{id} |
article-categories:read |
Get article category |
PATCH |
/api/article-categories/{id} |
article-categories:write |
Update article category |
DELETE |
/api/article-categories/{id} |
article-categories:write |
Delete article category |
GET |
/api/pages |
pages:read |
List pages |
POST |
/api/pages |
pages:write |
Create page |
GET |
/api/pages/{id} |
pages:read |
Get page |
PATCH |
/api/pages/{id} |
pages:write |
Update page |
DELETE |
/api/pages/{id} |
pages:write |
Delete page |
GET |
/api/analytics/visits/summary |
analytics:read |
Visits summary |
GET |
/api/analytics/visits |
analytics:read |
List visits |
GET |
/api/analytics/visitors |
analytics:read |
List visitors |
GET |
/api/analytics/visitors/{id} |
analytics:read |
Get visitor |
GET |
/api/analytics/pages |
analytics:read |
Page analytics |
GET |
/api/analytics/countries |
analytics:read |
Country analytics |
GET |
/api/analytics/devices |
analytics:read |
Device analytics |
GET |
/api/analytics/sources |
analytics:read |
Source analytics |
GET |
/api/analytics/sources/summary |
analytics:read |
Visitors over time by source |
GET |
/api/analytics/clicks/summary |
analytics:read |
Clicks summary |
GET |
/api/analytics/clicks/by-merchant |
analytics:read |
Affiliate clicks by merchant |
GET |
/api/analytics/clicks/top-products |
analytics:read |
Most clicked products |
GET |
/api/analytics/clicks |
analytics:read |
List clicks |
GET |
/api/analytics/checkouts/summary |
analytics:read |
Cart checkouts summary |
GET |
/api/analytics/checkouts |
analytics:read |
List cart checkouts |
GET |
/api/analytics/actions |
analytics:read |
List actions |
GET |
/api/data/openai-tokens |
data:read |
AI token usage |
GET |
/api/data/stats |
data:read |
Content counts (alias) |