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

# Import the proposal as a new menu

POST https://www.lettbestilt.no/api/v1/partner/restaurants/{slug}/menu-imports/{importId}/commit
Content-Type: application/json

**Partner key + restaurant key.** Creates a **new** menu that is turned off (`isActive: false`) and is not the default menu, with categories, products, sizes and allergens in one transaction. No existing menu is overwritten or changed. Customers do not see it until the restaurant turns it on and makes it the default menu (or gives it a schedule); turning it on is not enough while another menu is the default.

Without `proposal` the stored proposal is used (with changes made in the dashboard). The name is "Importert DD.MM.YYYY" (Oslo) when `menuName` is left out. A second commit, also after a lost response, gives `409 MENU_IMPORT_CLOSED` with `details.menuId`, so a retry finds the menu. Imports from before the restaurant came to the partner give `404`. At most 10 per hour per restaurant and 30 changes per minute per partner.

Reference: https://docs.lettbestilt.no/api-reference/partner/commit-menu-import

## Authentication

- `Authorization` header (bearer token, required) — The partner key, sent as `Authorization: Bearer lbp_test_…`. Created in LettBestilt Partner under **Partner key**. It belongs to the 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`. A test key sees and creates only sandboxes; live partner keys are not available yet. A revoked key, a restaurant key (`lbs_…`) here, or a suspended or archived partner returns `401 UNAUTHORIZED`; an expired key returns `401 KEY_EXPIRED`.
- `X-LettBestilt-Restaurant-Key` header (required) — The restaurant's own key (`lbs_test_…` or `lbs_live_…`), sent in `X-LettBestilt-Restaurant-Key` together with the partner key for calls about one restaurant. 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 (otherwise `401 UNAUTHORIZED`). A slug 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.
- `importId` (string, required) — The import's `id`.

### Body (application/json)

This endpoint expects an object.

- `menuName` (string, optional)
- `proposal` (MenuImportCommitProposal, optional) — Det commit leser. Andre felt (confidence, reasons, warnings, stats …) ignoreres, så hele `MenuImportProposal` kan også sendes. Minst ett produkt, høyst 60 kategorier og 800 produkter.

## Response

### 201

The menu is created and turned off.

- `data` (PartnerMenuImportCommitted, required)

## Errors

### 400 Bad Request Error

`INVALID_JSON`: the body is not valid JSON.

- `error` (ErrorBody, required)

### 401 Unauthorized Error

`UNAUTHORIZED`: a key is missing, invalid or revoked, the two keys are in different environments, or the partner is suspended or archived. `KEY_EXPIRED`: one of the keys is past its expiry date.

- `error` (ErrorBody, required)

### 403 Forbidden Error

`FORBIDDEN`: the request has an `Origin` header. `PARTNER_AGREEMENT_REQUIRED`: live key, but the partner agreements are not signed. `PARTNER_NOT_APPROVED`: LettBestilt has not approved the partner yet. `INSUFFICIENT_SCOPE`: the restaurant key does not have full access.

- `error` (ErrorBody, required)

### 404 Not Found Error

`RESTAURANT_NOT_FOUND`, or `MENU_IMPORT_NOT_FOUND` when the import does not exist or belongs to another restaurant.

- `error` (ErrorBody, required)

### 409 Conflict Error

`MENU_IMPORT_CLOSED`: already imported, discarded or expired (`details: { status, menuId }`). `CHANGED`: without `proposal`, the proposal was changed in the dashboard between reading and importing; nothing is imported, try again. `RESTAURANT_ARCHIVED`: the restaurant is archived.

- `error` (ErrorBody, required)

### 422 Unprocessable Entity Error

`VALIDATION_FAILED` with `error.details.issues`: a missing name or price, fields that are too long, unknown allergen codes or too many products.

- `error` (ErrorBody, required)

### 429 Too Many Requests Error

`RATE_LIMITED`: more than 120 calls per minute for this partner key, 10 commits per hour for the restaurant or 30 changes per minute for the partner.

- `error` (ErrorBody, required)

## Types

### MenuImportCommitProposal

Det commit leser. Andre felt (confidence, reasons, warnings, stats …) ignoreres, så hele `MenuImportProposal` kan også sendes. Minst ett produkt, høyst 60 kategorier og 800 produkter.

- `version` (any, required)
- `categories` (list of MenuImportCommitCategory, required)

### PartnerMenuImportCommitted

- `importId` (string, required)
- `menuId` (string, required)
- `menuName` (string, required)
- `isActive` (any, required) — Menyen er av og er ikke standardmeny. For at kundene skal se den, må restauranten slå den på og gjøre den til standardmeny (eller gi den en tidsplan); å slå den på er ikke nok når en annen meny er standard.
- `categoryCount` (integer, required)
- `productCount` (integer, required)
- `variantCount` (integer, required)

### ErrorBody

- `code` (enum, required) — A machine-readable error code. The list comes from the platform spec, which a test keeps in step with the code. See the Errors guide for what each code means.
  - 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`
- `message` (string, required) — A human-readable explanation. Often in Norwegian, and for business rules 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`.

### MenuImportCommitCategory

- `name` (string, required)
- `products` (list of MenuImportCommitProduct, required) — Kategorier uten produkter tas ikke med.
- `key` (string, optional) — Valgfri. Brukes ikke av commit, men valideres (1–40 tegn).

### MenuImportCommitProduct

Prisen ligger enten på `price` eller på `variants` (den første varianten blir standard). Uten varianter må `price` være satt.

- `name` (string, required)
- `key` (string, optional) — Valgfri. Brukes ikke av commit, men valideres (1–40 tegn).
- `number` (integer, optional, nullable) — Valgfri (standard null). «12» gir navnet «12. Pepperoni».
- `description` (string, optional, nullable) — Valgfri (standard null).
- `price` (integer, optional, nullable) — Øre. Valgfri (standard null) når produktet har varianter.
- `variants` (list of MenuImportCommitVariant, optional) — Valgfri (standard tom).
- `allergens` (list of enum, optional) — Valgfri (standard tom).
  - Allowed values: `WHEAT`, `RYE`, `BARLEY`, `OATS`, `SPELT`, `KAMUT`, `CRUSTACEANS`, `EGGS`, `FISH`, `PEANUTS`, `SOYBEANS`, `MILK`, `NUTS`, `CELERY`, `MUSTARD`, `SESAME`, `SULPHITES`, `LUPIN`, `MOLLUSCS`

### MenuImportCommitVariant

- `name` (string, required)
- `price` (integer, required) — Øre.

## Examples

**Request**

```json
{
  "menuName": "Hovedmeny"
}
```

**Response**

```json
{
  "data": {
    "importId": "string",
    "menuId": "string",
    "menuName": "string",
    "isActive": null,
    "categoryCount": 1,
    "productCount": 1,
    "variantCount": 1
  }
}
```

**SDK Code**

```python Partner_commitMenuImport_example
import requests

url = "https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menu-imports/importId/commit"

payload = { "menuName": "Hovedmeny" }
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Partner_commitMenuImport_example
const url = 'https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menu-imports/importId/commit';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"menuName":"Hovedmeny"}'
};

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

```go Partner_commitMenuImport_example
package main

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

func main() {

	url := "https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menu-imports/importId/commit"

	payload := strings.NewReader("{\n  \"menuName\": \"Hovedmeny\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

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

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

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

}
```

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

url = URI("https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menu-imports/importId/commit")

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

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"menuName\": \"Hovedmeny\"\n}"

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

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

HttpResponse<String> response = Unirest.post("https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menu-imports/importId/commit")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"menuName\": \"Hovedmeny\"\n}")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menu-imports/importId/commit', [
  'body' => '{
  "menuName": "Hovedmeny"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp Partner_commitMenuImport_example
using RestSharp;

var client = new RestClient("https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menu-imports/importId/commit");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"menuName\": \"Hovedmeny\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Partner_commitMenuImport_example
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["menuName": "Hovedmeny"] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://www.lettbestilt.no/api/v1/partner/restaurants/slug/menu-imports/importId/commit")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

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()
```