The Cafe API powers digital menus: a cafe has groups and categories, and
categories contain menu items. These endpoints are available on all plans.
Scopes: reads require cafe.read, creating a cafe requires cafe.create, and deleting
a cafe or a group requires cafe.delete. All other writes — profile, theme, groups,
categories, items, including deleting a category or an item — require cafe.update.
The tables below note the exact scope per endpoint.
:::note Multipart uploads
Endpoints that accept images — cafe profile (logo/cover), group images, category
images, and item images — expect multipart/form-data rather than JSON. The JSON
examples below are for the non-image endpoints.
:::
Cafes
| Method & path | Scope | Description |
|---|
GET /cafes | cafe.read | List cafes. |
GET /cafes/:id | cafe.read | Get one cafe. |
POST /cafes | cafe.create | Create a cafe. |
PUT /cafes/:id | cafe.update | Update a cafe. |
PUT /cafes/:id/profile | cafe.update | Update profile (multipart: logo, cover). |
PUT /cafes/:id/theme | cafe.update | Update theme/colors. |
DELETE /cafes/:id | cafe.delete | Delete a cafe. |
Create a cafe
| Field | Type | Required | Notes |
|---|
name | string | yes | Max 255 chars. |
description | string | no | Max 2000 chars. |
curl -X POST https://api.prolinksqr.com/cafes \
-H "Authorization: Basic key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "name": "Downtown Cafe", "description": "Specialty coffee & brunch" }'
Update a cafe
| Field | Type | Required | Notes |
|---|
name | string | no | Max 255 chars. |
description | string | no | Max 2000 chars. |
operating_hours | array | no | [{ "day", "enabled", "ranges": [{ "from", "to" }] }, ...]. |
facilities | array | no | Array of strings (e.g. "wifi", "parking"). |
social_links | array | no | [{ "platform", "url" }, ...]. |
qr_options | object | no | See QR options. Applied to the cafe's underlying link, not the cafe record itself. |
Contact fields (email, address, phone, website) and branding (name, title,
subtitle, logo/cover) are set via Update profile below, not this
endpoint.
curl -X PUT https://api.prolinksqr.com/cafes/<cafeId> \
-H "Authorization: Basic key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "facilities": ["wifi", "parking"] }'
Update profile
Multipart form data. All fields are optional:
| Field | Type | Notes |
|---|
name | string | |
title | string | |
subtitle | string | |
address | string | |
phone | string | |
email | string | |
website | string | |
description | string | |
logo | file | New logo image. |
cover | file | New cover image. |
delete_logo | boolean | Remove the existing logo (send as "true"). |
delete_cover | boolean | Remove the existing cover (send as "true"). |
Update theme
| Field | Type | Required | Notes |
|---|
themeImage | string | yes | A supported theme key (e.g. modern, coffee, brunch). |
primaryColor, secondaryColor | string | no | |
showDietarySection | boolean | no | |
Groups
| Method & path | Scope | Description |
|---|
GET /cafes/:id/groups | cafe.read | List groups. |
GET /cafes/:id/groups/:groupId | cafe.read | Get one group. |
POST /cafes/:id/groups | cafe.update | Create a group (multipart: optional image). |
PUT /cafes/:id/groups/:groupId | cafe.update | Update a group (multipart: optional image). |
DELETE /cafes/:id/groups/:groupId | cafe.delete | Delete a group. |
PATCH /cafes/:id/groups/reorder | cafe.update | Reorder groups. |
Create a group
| Field | Type | Required | Notes |
|---|
name | string | yes | Max 255 chars. |
visible | boolean | no | Defaults to true. |
order | integer | no | |
Reorder groups
{
"orders": [
{ "id": "<groupId>", "order": 0 },
{ "id": "<groupId>", "order": 1 }
]
}
Categories
| Method & path | Scope | Description |
|---|
GET /cafes/:id/categories | cafe.read | List categories. |
GET /cafes/:id/categories/:categoryId | cafe.read | Get one category. |
POST /cafes/:id/categories | cafe.update | Create a category. |
PUT /cafes/:id/categories/:categoryId | cafe.update | Update a category. |
PUT /cafes/:id/categories/:categoryId/image | cafe.update | Set category image (multipart: image). |
DELETE /cafes/:id/categories/:categoryId | cafe.update | Delete a category. |
PATCH /cafes/:id/categories/reorder | cafe.update | Reorder categories. |
Create a category
| Field | Type | Required | Notes |
|---|
name | string | yes | Max 255 chars. |
description | string | no | Max 1000 chars. |
group_id | string (UUID) | no | Assign to a group. |
type | string | no | |
order | integer | no | |
visible | boolean | no | Defaults to true. |
Category names must be unique (case-insensitive) within a cafe. Creating or renaming a
category to a name that already exists returns 400.
curl -X POST https://api.prolinksqr.com/cafes/<cafeId>/categories \
-H "Authorization: Basic key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "name": "Hot Drinks", "visible": true }'
| Method & path | Scope | Description |
|---|
GET /cafes/:id/items | cafe.read | List items. |
GET /cafes/:id/items/:itemId | cafe.read | Get one item. |
POST /cafes/:id/items | cafe.update | Create an item (multipart: optional image). |
POST /cafes/:id/items/bulk-insert | cafe.update | Bulk insert items (JSON). |
PUT /cafes/:id/items/:itemId | cafe.update | Update an item (multipart: optional image). |
DELETE /cafes/:id/items/:itemId | cafe.update | Delete an item. |
POST /cafes/:id/items/:itemId/hide | cafe.update | Hide an item. |
POST /cafes/:id/items/:itemId/show | cafe.update | Show an item. |
PATCH /cafes/:id/items/reorder | cafe.update | Reorder items. |
Create an item
Sent as multipart/form-data (so an image file can be attached). Fields:
| Field | Type | Required | Notes |
|---|
category_id | string (UUID) | yes | The category this item belongs to. |
name | string | yes | Max 255 chars. |
price | number | yes | ≥ 0. |
currency | string | no | ISO code (e.g. USD, EUR). Defaults to USD. |
description | string | no | Max 2000 chars. |
visible, is_featured, is_new, is_recommended | boolean | no | |
spice_level | string | no | One of none, mild, medium, hot, extra_hot. |
ingredients, tags, allergens | string | no | |
image | file | no | Image upload. |
curl -X POST https://api.prolinksqr.com/cafes/<cafeId>/items \
-H "Authorization: Basic key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-F "category_id=<categoryId>" \
-F "name=Cappuccino" \
-F "price=4.50" \
-F "currency=USD" \
-F "image=@cappuccino.jpg"
Reorder items
Unlike reorder groups and reorder categories, which take an orders
wrapper object, the items reorder endpoint expects the array directly as the request
body — no orders key:
[
{ "id": "<itemId>", "order": 0 },
{ "id": "<itemId>", "order": 1 }
]
Bulk insert items
:::danger Replaces the entire menu
This endpoint is not additive. It deletes every existing item, category, and group
for the cafe, then inserts the submitted items (auto-creating a "General" group if none
of the submitted items specify one). Use it for a full menu import/replace, not for
appending a handful of items to an existing menu — use
Create an item for that.
:::
JSON body with an items array (1–5000 items):
| Field | Type | Required | Notes |
|---|
name | string | yes | |
price | number | yes | ≥ 0. |
currency | string | yes | ISO code. |
category | string | yes | Category name (lowercased). |
group | string | no | Group name. |
description | string | no | |
ingredients | array | no | Array of strings. |
curl -X POST https://api.prolinksqr.com/cafes/<cafeId>/items/bulk-insert \
-H "Authorization: Basic key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "name": "Espresso", "price": 3.0, "currency": "USD", "category": "hot drinks" },
{ "name": "Latte", "price": 4.5, "currency": "USD", "category": "hot drinks" }
]
}'