API reference
Couverty public API
Access your data and integrate Couverty into your applications.
Quick start
Create an API key and start using the API in 3 steps.
- 1
Create an API key
Open your integration settings. Click "Create an API key" and select the permissions you need (restaurant:read, menu:read, etc.). - 2
Copy the key
Copy the generated API key and store it securely. You will not be able to see it again once the dialog is closed. - 3
Make requests
Send the key in theX-API-Keyheader to authenticate your requests.
Authentication
Three ways to authenticate your requests.
Method 1: X-API-Key header (recommended)
The simplest and most secure method.
curl -H "X-API-Key: qr_your_api_key_here" \ https://api.couverty.ch/api/public/v1/restaurant
Method 2: Authorization Bearer
An alternative using the standard Authorization header.
curl -H "Authorization: Bearer qr_your_api_key_here" \ https://api.couverty.ch/api/public/v1/restaurant
Method 3: query parameter (3CX only)
Only for 3CX integrations that cannot use headers.
curl https://api.couverty.ch/api/integrations/3cx/lookup?apikey=qr_your_api_key_here&phone=%2B41...
Security
Permissions
restaurant:read, menu:read, etc. Requests will fail if your key does not have the required permission.API v1 endpoints
Access your restaurant's data with API key authentication.
GET /api/public/v1/restaurant
Returns the restaurant's information (name, contact details, opening hours, branding).
/api/public/v1/restaurantExample response
{
"success": true,
"data": {
"name": "Le Restaurant",
"slug": "le-restaurant",
"contact": {
"email": "contact@example.com",
"telephone": "+41 22 123 45 67",
"address": {
"rue": "10 Rue de la Paix",
"npaVille": "1200 Genève",
"pays": "Suisse"
}
},
"hours": {
"midi": {
"enabled": true,
"ouverture": "11:30",
"fermeture": "14:30",
"jours": [1, 2, 3, 4, 5]
},
"soir": {
"enabled": true,
"ouverture": "18:30",
"fermeture": "23:00",
"jours": [0, 1, 2, 3, 4, 5, 6]
}
},
"social": {
"website": "https://example.com",
"facebook": "https://facebook.com/restaurant",
"instagram": "https://instagram.com/restaurant",
"tiktok": null,
"googleReview": null,
"tripadvisor": null
},
"branding": {
"primaryColor": "#B91C1C",
"secondaryColor": "#D4AF37",
"logoUrl": "https://example.com/logo.png",
"coverImageUrl": "https://example.com/cover.png"
}
},
"meta": {
"tenantId": "tenant_123",
"fetchedAt": "2026-03-13T10:30:00Z"
}
}GET /api/public/v1/menu
Returns the full menu (dishes and drinks) with categories and the menu of the week.
/api/public/v1/menutype: all | plats | boissons (default: all)includeHidden: true | false (default: false)
Example response
{
"success": true,
"data": {
"plats": {
"categories": [
{
"id": "cat_123",
"nom": "Entrées",
"description": null,
"plats": [
{
"id": "plat_456",
"nom": "Salade César",
"description": "Salade fraîche avec croutons",
"prix": 18.50,
"imageUrl": "https://example.com/salade.jpg",
"allergenes": ["gluten", "oeuf"],
"vegetarien": true,
"vegan": false,
"sansGluten": false
}
]
}
],
"totalPlats": 45,
"totalCategories": 5
},
"boissons": {
"categories": [
{
"id": "cat_789",
"nom": "Vins rouges",
"description": "Sélection de vins rouges",
"boissons": [
{
"id": "boi_321",
"nom": "Côtes du Rhône",
"description": "AOC 2020",
"prix": 35.00,
"volume": "75cl",
"region": "Rhône",
"annee": 2020
}
]
}
],
"totalBoissons": 30,
"totalCategories": 4
},
"menuSemaine": {
"config": { ... },
"menus": [ ... ]
}
},
"meta": {
"tenantId": "tenant_123",
"fetchedAt": "2026-03-13T10:30:00Z"
}
}Public endpoints by slug
Access availability, configuration and menu without authentication. Used by the widget.
Generally available
https://restaurant.qr-menu.ch/book/[slug]Configuration & Settings
/api/public/[slug]/configRestaurant configuration for the widget (opening hours, time slots, capacity). Cache: 5 min CDN, 1 min browser.
Returned fields
horairesActifs, serviceMidiActif, serviceSoirActif
creneauxMidiConfig, creneauxSoirConfig
modeCapacite, capaciteMidi, capaciteSoir
joursOuvertsMidi, joursOuvertsSoir
And 40+ more fields (see the full response)
/api/public/[slug]/settingsThe restaurant's public settings (name, contact details, colours, social networks). Cache: 5 min CDN, 1 min browser.
Availability
/api/public/[slug]/availabilityChecks whether a date, service or time is available. Cache: 2 min.
date: YYYY-MM-DD (required)service: midi | soir (required)heure: HH:mm (optional)personnes: number (default: 2)
/api/public/[slug]/creneauxLists available time slots with capacity information. Cache: 1 min.
date: YYYY-MM-DD (required)service: midi | soir (required)personnesornombrePersonnes: number (default: 2)
Menu & Drinks
/api/public/[slug]/menuFull menu with categories and visible dishes. Cache: 10 min.
/api/public/[slug]/boissonsDrinks menu with categories. Cache: 10 min.
/api/public/[slug]/fermeturesUpcoming closures, one entry per period: dateDebut, dateFin, type (FERMETURE, VACANCES, EVENEMENT_PRIVE, COMPLET), motif and affected services. Enough to show a "closed for holidays" banner on your website. Cache: 2 min.
Bookings
/api/public/[slug]/reservationsCreates a new booking. Rate limit: 20 req/min per IP.
prenom: string (required)nom: string (required)email: string (required)telephone: string (required)dateReservation: YYYY-MM-DD (required)service: midi | soir (required)nombrePersonnes: number (required)heureReservation: HH:mm (optional)commentaires: string (optional)
Example response
{
"success": true,
"data": {
"id": "res_123abc",
"prenom": "Jean",
"nom": "Dupont",
"email": "jean@example.com",
"telephone": "+41 79 123 45 67",
"dateReservation": "2026-03-20",
"service": "soir",
"nombrePersonnes": 4,
"heureReservation": "19:30",
"statut": "PENDING",
"createdAt": "2026-03-13T10:30:00Z"
}
}3CX integration
Fetch guest data and report calls.
Required setup
/api/integrations/3cx/lookupLooks up a guest by phone number. Returns their information and enriched data (visits, no-shows).
phone: string in E.164 format (required), e.g. +41791234567
/api/integrations/3cx/report-callReports a call (inbound, outbound, missed). Creates a notification for a missed call.
phoneNumber: string E.164 (required)callType: Inbound | Outbound | Missed | Notanswered (required)durationSeconds: number (optional)callDateTime: ISO 8601 (optional)agent: string (optional)
Response format
All API responses follow a standard format.
Successful response (2xx)
{
"success": true,
"data": {
// The requested data
},
"meta": {
"tenantId": "tenant_123",
"fetchedAt": "2026-03-13T10:30:00Z"
}
}Error response
{
"success": false,
"error": "Descriptive error message"
}Errors & Limits
Error codes, rate limits and troubleshooting.
HTTP error codes
| Code | Meaning | Example |
|---|---|---|
| 200 | OK - Request successful | Data returned |
| 400 | Bad Request - Invalid parameters | Missing date |
| 401 | Unauthorized - Missing or invalid API key | Expired API key |
| 403 | Forbidden - Insufficient permission | menu:read missing |
| 404 | Not Found - Resource does not exist | Restaurant not found |
| 429 | Too Many Requests - Rate limit reached | Too many requests per minute |
| 500 | Internal Server Error | Server error |
Rate limits
API v1
1000 requests per hour per API key
Public endpoints
No strict limit, uses the CDN cache (5-10 min)
POST Bookings
20 requests per minute per IP address
3CX
1000 requests per hour per API key
Cache headers
Public endpoints include a short browser cache. The server cache is invalidated automatically when changes are made in the dashboard.
Cache-Control: public, max-age=10 - max-age: Short browser cache (10 seconds) - The server cache is invalidated on demand via revalidateTag()
Troubleshooting
401 Unauthorized
The message states the cause, each case has its own response:
API key is required: no key received. Send it in theX-API-Keyheader or viaAuthorization: BearerInvalid API key: the key does not exist or was copied incorrectly. It always starts withqr_This API key has been revoked: the key has been deactivated. Create a new one in your settingsThis API key has expired: the expiry date has passed
403 Forbidden - Missing required permission
The key is valid but does not carry the requested permission. The message names it, for example Missing required permission: menu:read.
- Regenerate the key with the necessary permissions in your settings
- Keys used by the WordPress plugin need
menu:readandrestaurant:read
429 Too Many Requests
The key's quota has been reached. The response includes a Retry-After header giving the number of seconds until the reset: use it rather than guessing.
- API v1: 1000 requests per hour per key, counter reset every hour (not a rolling window)
- Cache the responses rather than calling the API again
- A key shared between several websites uses a common quota: create one key per website
CORS errors
Public endpoints ([slug]) support CORS for the widget. If you get CORS errors:
- Only call these endpoints from the browser, not from a private server
- For server-to-server calls, use the v1 endpoints with an API key
Code examples
Complete examples in several languages to get started quickly.
Get the restaurant's information
const API_KEY = process.env.COUVERTY_API_KEY;
const API_BASE = 'https://api.couverty.ch';
async function getRestaurantInfo() {
try {
const response = await fetch(`${API_BASE}/api/public/v1/restaurant`, {
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
if (!result.success) {
throw new Error(result.error);
}
console.log('Restaurant:', result.data);
return result.data;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}
async function getMenu() {
try {
const response = await fetch(
`${API_BASE}/api/public/v1/menu?type=plats&includeHidden=false`,
{
headers: { 'X-API-Key': API_KEY },
}
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
return result.data;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}
// Usage
(async () => {
const restaurant = await getRestaurantInfo();
const menu = await getMenu();
})();