API reference

Couverty public API

Access your data and integrate Couverty into your applications.

Guide

Quick start

Create an API key and start using the API in 3 steps.

  1. 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. 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. 3

    Make requests

    Send the key in the X-API-Key header to authenticate your requests.
Security

Authentication

Three ways to authenticate your requests.

Method 1: X-API-Key header (recommended)

The simplest and most secure method.

bash
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.

bash
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.

bash
curl https://api.couverty.ch/api/integrations/3cx/lookup?apikey=qr_your_api_key_here&phone=%2B41...

Security

Your API keys are secret. Never expose them on the client side (in the browser). Store them in server-side environment variables. If a key has been compromised, regenerate it immediately in your settings.

Permissions

Each API key has configurable permissions: restaurant:read, menu:read, etc. Requests will fail if your key does not have the required permission.
Authenticated

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).

GET
/api/public/v1/restaurant
Permission:restaurant:read
Authentication:X-API-Key required
Example response
json
{
  "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.

GET
/api/public/v1/menu
Permission:menu:read
Query parameters:
  • type : all | plats | boissons (default: all)
  • includeHidden : true | false (default: false)
Example response
json
{
  "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

Public endpoints by slug

Access availability, configuration and menu without authentication. Used by the widget.

Generally available

These endpoints do not require authentication. The restaurant's slug is visible in the URL of your booking page: https://restaurant.qr-menu.ch/book/[slug]

Configuration & Settings

GET
/api/public/[slug]/config

Restaurant 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)

GET
/api/public/[slug]/settings

The restaurant's public settings (name, contact details, colours, social networks). Cache: 5 min CDN, 1 min browser.

Availability

GET
/api/public/[slug]/availability

Checks whether a date, service or time is available. Cache: 2 min.

Parameters:
  • date : YYYY-MM-DD (required)
  • service : midi | soir (required)
  • heure : HH:mm (optional)
  • personnes : number (default: 2)
GET
/api/public/[slug]/creneaux

Lists available time slots with capacity information. Cache: 1 min.

Parameters:
  • date : YYYY-MM-DD (required)
  • service : midi | soir (required)
  • personnes or nombrePersonnes : number (default: 2)

Menu & Drinks

GET
/api/public/[slug]/menu

Full menu with categories and visible dishes. Cache: 10 min.

GET
/api/public/[slug]/boissons

Drinks menu with categories. Cache: 10 min.

GET
/api/public/[slug]/fermetures

Upcoming 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

POST
/api/public/[slug]/reservations

Creates a new booking. Rate limit: 20 req/min per IP.

Request body:
  • 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
json
{
  "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"
  }
}
Telephony

3CX integration

Fetch guest data and report calls.

Required setup

An API key for the venue is all you need: these two endpoints do not require any specific permission. In practice, you won't call this API yourself: the CRM template provided does it for you. See the 3CX installation guide.
GET
/api/integrations/3cx/lookup

Looks up a guest by phone number. Returns their information and enriched data (visits, no-shows).

Parameters:
  • phone : string in E.164 format (required), e.g. +41791234567
Authentication:X-API-Key or Authorization: Bearer
POST
/api/integrations/3cx/report-call

Reports a call (inbound, outbound, missed). Creates a notification for a missed call.

Request body:
  • phoneNumber : string E.164 (required)
  • callType : Inbound | Outbound | Missed | Notanswered (required)
  • durationSeconds : number (optional)
  • callDateTime : ISO 8601 (optional)
  • agent : string (optional)
Structures

Response format

All API responses follow a standard format.

Successful response (2xx)

json
{
  "success": true,
  "data": {
    // The requested data
  },
  "meta": {
    "tenantId": "tenant_123",
    "fetchedAt": "2026-03-13T10:30:00Z"
  }
}

Error response

json
{
  "success": false,
  "error": "Descriptive error message"
}
Handling

Errors & Limits

Error codes, rate limits and troubleshooting.

HTTP error codes

CodeMeaningExample
200OK - Request successfulData returned
400Bad Request - Invalid parametersMissing date
401Unauthorized - Missing or invalid API keyExpired API key
403Forbidden - Insufficient permissionmenu:read missing
404Not Found - Resource does not existRestaurant not found
429Too Many Requests - Rate limit reachedToo many requests per minute
500Internal Server ErrorServer 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.

http
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 the X-API-Key header or via Authorization: Bearer
  • Invalid API key: the key does not exist or was copied incorrectly. It always starts with qr_
  • This API key has been revoked: the key has been deactivated. Create a new one in your settings
  • This 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:read and restaurant: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
Implementation

Code examples

Complete examples in several languages to get started quickly.

Get the restaurant's information

javascript
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();
})();

Need help?

Read our documentation or contact support.

Manage API keys