> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.lettbestilt.no/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.lettbestilt.no/_mcp/server.

# List the menus

GET https://www.lettbestilt.no/api/v1/partner/restaurants/{slug}/menus

**Partner key + restaurant key.** Every menu, active or not, in `sortOrder`, with its schedule, counts and `isLiveNow` (whether customers see it right now). Among the active menus inside their date period: when one or more have a schedule window right now, only those are shown (the highest `priority`; equal priority shows all), also instead of the default menu. Otherwise the default menu is shown, also when it has a schedule of its own and is outside it. A menu with a schedule that is not the default is therefore shown only inside its windows, and the main menu must be the default to be shown the rest of the time. With no active default menu, all active menus are shown outside the windows. `isLiveNow` says whether the menu is shown right now. The list comes in one page (`nextCursor` is always `null`).

Reference: https://docs.lettbestilt.no/api-reference/partner/list-partner-menus

## Authentication

- `Authorization` header (bearer token, required) — The partner key (`lbp_test_…` or `lbp_live_…`), sent as `Authorization: Bearer lbp_…` and created in LettBestilt Partner under **Partner key**. It belongs to the whole partner, not to a restaurant, and only works against `/api/v1/partner`. Server to server only: a request with an `Origin` header is rejected with `403 FORBIDDEN`, and the key must never be in a browser. A TEST key sees and creates only sandboxes; LIVE keys work once LettBestilt Partner is open. A suspended or archived partner, a revoked key and a restaurant key (`lbs_…`) here give `401 UNAUTHORIZED`; an expired key gives `401 KEY_EXPIRED`.
- `X-LettBestilt-Restaurant-Key` header (required) — The restaurant's own key (`lbs_test_…` or `lbs_live_…`) for calls about one restaurant in the partner API, sent together with the partner key. The key must have full access (scope `*`, otherwise `403 INSUFFICIENT_SCOPE`), belong to a restaurant the partner owns and be in the same environment as the partner key. A restaurant that does not exist, belongs to another partner or does not match the key always gives the same `404 RESTAURANT_NOT_FOUND`. Older keys without an environment in the prefix (`lbs_<random>`) are not accepted here; rotate them in LettBestilt Partner.

## Request

### Path parameters

- `slug` (string, required) — The restaurant's slug. It must match the restaurant key in `X-LettBestilt-Restaurant-Key`.

## Response

### 200

The menus.

- `data` (list of PartnerMenu, required)
- `nextCursor` (any, required)

## Errors

### 401 Unauthorized Error

`UNAUTHORIZED`: a key is missing, invalid or revoked, the two keys are from different environments (TEST and LIVE), or the partner is suspended or archived. `KEY_EXPIRED`: a key is past its expiry date.

- `error` (ErrorBody, required)

### 403 Forbidden Error

`FORBIDDEN`: the request has an `Origin` header (server to server only). `PARTNER_AGREEMENT_REQUIRED`: a LIVE key, but the partner agreements are not signed. `PARTNER_NOT_APPROVED`: LettBestilt has not approved the partner yet (all keys). `INSUFFICIENT_SCOPE`: the restaurant key does not have full access (`error.details.requiredScope` is `"*"`).

- `error` (ErrorBody, required)

### 404 Not Found Error

`RESTAURANT_NOT_FOUND`: the same response whether the slug does not exist, belongs to another partner or does not match the restaurant key.

- `error` (ErrorBody, required)

### 429 Too Many Requests Error

`RATE_LIMITED`: more than 120 calls per minute for this partner key, or more than 600 per minute for the partner across all its keys (reads and writes). Writes also share a bucket of 30 changes per minute per partner and environment. See `Retry-After`.

- `error` (ErrorBody, required)

## Types

### PartnerMenu

A menu in list form. Among the active menus inside their date period: when one or more have a schedule window right now, only those are shown (the highest `priority`; equal priority shows all), also instead of the default menu. Otherwise the default menu is shown, also when it has a schedule of its own and is outside it. A menu with a schedule that is not the default is therefore shown only inside its windows, and the main menu must be the default to be shown the rest of the time. With no active default menu, all active menus are shown outside the windows. `isLiveNow` says whether the menu is shown right now. `Menu` has no `updatedAt`: a change that collides with a concurrent change gives 409 `CHANGED`.

- `id` (string, required)
- `name` (string, required)
- `isActive` (boolean, required) — Off = never shown.
- `isDefault` (boolean, required) — The default menu is shown when no menu has a schedule window right now. The partner API keeps at most one without a date period.
- `isLiveNow` (boolean, required) — Among the menus `/api/v1/menu` shows right now (without the CDN delay).
- `priority` (integer, required) — The menu's priority (0–100). When several menus have a schedule window at the same time, the one with the highest priority is shown; with equal priority all are shown. Reordering the menus in the restaurant dashboard rewrites the priority (top = highest).
- `sortOrder` (integer, required)
- `startsOn` (date, required, nullable) — YYYY-MM-DD (Europe/Oslo), inclusive. null = no limit.
- `endsOn` (date, required, nullable) — YYYY-MM-DD (Europe/Oslo), inclusive. null = no limit.
- `schedules` (list of PartnerMenuSchedule, required)
- `categoryCount` (integer, required)
- `productCount` (integer, required) — Active products.

### ErrorBody

- `code` (enum, required) — A machine-readable error code.
  - Allowed values: `MISSING_SLUG`, `MISSING_TOKEN`, `INVALID_JSON`, `INVALID_SUCCESS_URL`, `INVALID_CANCEL_URL`, `VALIDATION_FAILED`, `RESTAURANT_NOT_FOUND`, `ORDER_NOT_FOUND`, `RATE_LIMITED`, `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `INSUFFICIENT_SCOPE`, `KEY_EXPIRED`, `POPUP_NOT_AVAILABLE`, `PRICE_CHANGED`, `PAYMENT_INCOMPLETE`, `PAYMENT_PENDING`, `PAYMENT_PROVIDER_INVALID`, `FULFILLMENT_NOT_ALLOWED`, `IDEMPOTENCY_CONFLICT`, `DINE_IN_DISABLED`, `PARTY_TOO_LARGE`, `TOO_EARLY`, `NO_TABLE`, `INVALID_PARTY_SIZE`, `INTERNAL_ERROR`, `PARTNER_AGREEMENT_REQUIRED`, `IDEMPOTENCY_KEY_REQUIRED`, `SLUG_TAKEN`, `SANDBOX_LIMIT`, `RETENTION`, `RESTAURANT_ARCHIVED`, `KEY_NOT_FOUND`, `KEY_IN_USE`, `WEBHOOK_NOT_FOUND`, `WEBHOOK_LIMIT`, `CHANGED`, `PDF_TOO_LARGE`, `PDF_INVALID`, `PDF_PASSWORD`, `PDF_TOO_MANY_PAGES`, `PDF_NO_TEXT`, `PDF_PARSE_TIMEOUT`, `MENU_IMPORT_EMPTY`, `FILE_UNSUPPORTED`, `IMAGE_HEIC`, `IMAGE_INVALID`, `IMAGE_DIMENSIONS`, `MENU_IMPORT_UNAVAILABLE`, `MENU_IMPORT_AI_LIMIT`, `MENU_IMPORT_TOO_BIG`, `MENU_IMPORT_NOT_FOUND`, `MENU_IMPORT_CLOSED`, `PARTNER_NOT_APPROVED`, `PARTNER_DATA_LOCKED`, `DOMAIN_NOT_FOUND`, `DOMAIN_TAKEN`, `DOMAIN_LIMIT`, `DOMAIN_MANAGED`, `DOMAIN_NOT_VERIFIED`, `OWNER_EXISTS`, `OWNER_EMAIL_NOT_ALLOWED`, `OWNER_INVITE_NOT_FOUND`, `SANDBOX_NOT_SUPPORTED`, `INVITE_SEND_FAILED`, `GO_LIVE_NOT_REQUIRED`, `GO_LIVE_NOT_READY`, `GO_LIVE_CHECKS_FAILED`, `GO_LIVE_ALREADY_REQUESTED`, `GO_LIVE_ALREADY_APPROVED`, `GO_LIVE_NOTHING_TO_WITHDRAW`, `DEMO_RESTAURANT`, `LEGAL_LOCKED`, `HOURS_OVERRIDE_NOT_FOUND`, `HOURS_OVERRIDE_LIMIT`, `DELIVERY_ZONE_NOT_FOUND`, `DELIVERY_ZONE_LIMIT`, `MENU_NOT_FOUND`, `MENU_IS_DEFAULT`, `CATEGORY_NOT_FOUND`, `PRODUCT_NOT_FOUND`, `ADDON_GROUP_NOT_FOUND`, `CATEGORY_NOT_EMPTY`, `CATEGORY_LIMIT`, `PRODUCT_LIMIT`, `ADDON_GROUP_LIMIT`, `KEY_ROLE_FORBIDDEN`, `WHITE_LABEL_REQUIRED`, `LOGO_NOT_ALLOWED`, `FILE_TOO_LARGE`, `UPLOAD_FAILED`, `PARTNER_ARCHIVED`, `PARTNER_NEWS_FORBIDDEN`, `NEWS_NOT_FOUND`, `NOT_DRAFT`, `NOT_PUBLISHED`, `NOT_SCHEDULED`, `NOT_CLAIMABLE`, `NO_RECIPIENTS`, `IMAGE_NOT_ALLOWED`, `SENDER_DOMAIN_ALREADY_SET`, `SENDER_DOMAIN_NOT_SET`, `DOMAIN_EXISTS`, `SENDER_DOMAIN_REJECTED`, `EMAIL_SERVICE_UNAVAILABLE`, `HUMAN_REQUIRED`, `LENGTH_REQUIRED`, `PAYLOAD_TOO_LARGE`
- `message` (string, required) — A human-readable explanation, often in Norwegian. For business rules it is usually suitable to show the customer.
- `requestId` (string, required) — The same value as the `X-Request-Id` response header. Search for it in the API log in LettBestilt Partner, and include it when you contact LettBestilt.
- `details` (any, optional) — Extra data for some codes, for example `issues` for `VALIDATION_FAILED`, the totals for `PRICE_CHANGED` or `requiredScope` for `INSUFFICIENT_SCOPE`.

### PartnerMenuSchedule

- `id` (string, required)
- `dayOfWeek` (enum, required)
  - Allowed values: `MONDAY`, `TUESDAY`, `WEDNESDAY`, `THURSDAY`, `FRIDAY`, `SATURDAY`, `SUNDAY`
- `startsAt` (string, required) — HH:MM (Europe/Oslo).
- `endsAt` (string, required) — HH:MM (Europe/Oslo).
- `priority` (integer, required) — Stored on the row, but not used. Which of two menus with overlapping schedule windows is shown is decided by the menu's `priority`.
- `isActive` (boolean, required)

## Examples

**Response**

```json
{
  "data": [
    {
      "id": "string",
      "name": "string",
      "isActive": true,
      "isDefault": true,
      "isLiveNow": true,
      "priority": 1,
      "sortOrder": 1,
      "startsOn": "2023-01-15",
      "endsOn": "2023-01-15",
      "schedules": [
        {
          "id": "string",
          "dayOfWeek": "MONDAY",
          "startsAt": "string",
          "endsAt": "string",
          "priority": 1,
          "isActive": true
        }
      ],
      "categoryCount": 1,
      "productCount": 1
    }
  ],
  "nextCursor": null
}
```

**SDK Code**

```python
import requests

url = "https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menus"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menus';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menus"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menus")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menus")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menus', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menus");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menus")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```