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

# Request a table reservation

POST https://www.lettbestilt.no/api/v1/reservations
Content-Type: application/json

Creates a table reservation **request**. The restaurant approves or rejects it from its dashboard, and the guest gets an email when that happens (if you sent `guestEmail`). A new request always has `status: "PENDING_APPROVAL"` and `tableId: null`; the table is assigned on approval.

Follow the status with `GET /orders/{token}` using `publicToken`, or listen for the `RESERVATION_APPROVED` and `RESERVATION_REJECTED` webhooks. `reservationId` is the ID that webhooks call `orderId`.

The server checks the party size, the minimum notice and whether online booking is open. It does not check the time against opening hours or existing bookings, so only offer times from `GET /dine-in/availability`.

`Idempotency-Key` is supported. A replay with the same key and the same body returns the existing request with status `200` and `idempotent: true`; the same key with a different body returns `409 IDEMPOTENCY_CONFLICT`.

Rate limit: 20 requests per 60 seconds per slug and IP. Walkthrough: [Table reservations](/reservations).

Reference: https://docs.lettbestilt.no/api-reference/reservations/create-reservation

## Authentication

- `Authorization` header (bearer token, required) — Either this or a trusted browser origin. The restaurant's API key, sent as `Authorization: Bearer lbs_…`. Use it from your server only, never in browser code. Keys are issued per restaurant by LettBestilt.
- `Origin` header (required) — Either this or an API key. No key is needed when the request's `Origin` header is trusted for this restaurant: `https://{slug}.lettbestilt.no`, a verified domain of the restaurant, or a partner origin LettBestilt has bound to the restaurant. Browsers set `Origin` themselves; it is not something you configure. Not accepted by `GET /site-gate`, which requires an API key.

## Request

### Query parameters

- `slug` (string, required) — The restaurant's slug.

### Headers

- `Idempotency-Key` (string, optional) — Optional. 16–128 URL-safe characters (`a-z`, `A-Z`, `0-9`, `_`, `-`).

### Body (application/json)

This endpoint expects an object.

- `dineInAt` (datetime, required) — Start time in UTC (ISO 8601). Use `iso` from `GET /dine-in/availability`.
- `partySize` (integer, required) — Number of guests.
- `guestName` (string, required) — The guest's name.
- `guestEmail` (string, optional) — Strongly recommended. Without it, the guest cannot be told when the request is approved or rejected.
- `guestPhone` (string, optional) — The guest's phone number.
- `note` (string, optional) — A note to the restaurant, for example about allergies or a high chair.
- `marketingOptIn` (boolean, optional) — The guest's consent to marketing. Requires `guestEmail`; the consent is stored in the restaurant's subscriber list.
- `consentGivenAt` (datetime, optional) — Optional. When the guest gave consent. Only allowed with `marketingOptIn: true`. If you leave it out, the server uses the time it received the request.
- `locale` (enum, optional, default: nb) — Language of the emails to the guest.
  - Allowed values: `nb`, `en`
- `attributedCampaignId` (string, optional) — Campaign attribution: the value of `?lb_campaign=<id>` from a LettBestilt campaign link. The reservation counts as a conversion for that campaign. Optional metadata: an invalid or unknown ID is dropped silently and never blocks the reservation. Note that orders call this field `attributionCampaignId`.

## Response

### 201

The request was created.

- `reservationId` (string, optional) — The reservation's ID. Webhooks call it `orderId`.
- `publicToken` (string, optional) — Use it with `GET /orders/{token}` to follow the status.
- `orderNumber` (integer, optional) — The number the restaurant sees in its dashboard.
- `status` (enum, optional)
  - Allowed values: `PENDING_APPROVAL`
- `dineInAt` (datetime, optional)
- `partySize` (integer, optional)
- `tableId` (string, optional, nullable) — Always `null` when the request is created. Assigned when the restaurant approves it.

## Errors

### 400 Bad Request Error

`MISSING_SLUG`, `INVALID_JSON`, or `BAD_REQUEST` for an invalid `Idempotency-Key`.

- `error` (ErrorBody, required)

### 401 Unauthorized Error

`UNAUTHORIZED`: missing or invalid API key, and the request does not come from a trusted browser origin.

- `error` (ErrorBody, required)

### 404 Not Found Error

`RESTAURANT_NOT_FOUND`: no live restaurant has this slug.

- `error` (ErrorBody, required)

### 409 Conflict Error

Either `IDEMPOTENCY_CONFLICT` in the standard error envelope (the key was already used with a different body, or by an order), or `ONLINE_BOOKING_CLOSED` in an older, flat error shape (`{ "error": "…", "code": "ONLINE_BOOKING_CLOSED" }`) when the restaurant has closed online booking for that date or time.

- `CreateReservationRequestConflictError`

### 422 Unprocessable Entity Error

`VALIDATION_FAILED`; `DINE_IN_DISABLED` (the restaurant does not take reservations, or has no tables that can be booked online); `PARTY_TOO_LARGE`; `TOO_EARLY` (inside the restaurant's minimum notice); or `BAD_REQUEST` for an invalid `dineInAt`.

- `error` (ErrorBody, required)

### 429 Too Many Requests Error

`RATE_LIMITED`: more than 20 requests in 60 seconds for this slug and IP.

- `error` (ErrorBody, required)

## Types

### 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`, `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`, `INTERNAL_ERROR`
- `message` (string, required) — A human-readable explanation. Often in Norwegian, and for business rules usually suitable to show the customer.
- `details` (any, optional) — Extra data for some codes, for example `issues` for `VALIDATION_FAILED` or the totals for `PRICE_CHANGED`.

### Error

The error envelope used by every endpoint. Branch on `error.code`, not on `message`.

- `error` (ErrorBody, required)

### CreateReservationRequestConflictError1

- `error` (string, required)
- `code` (enum, required)
  - Allowed values: `ONLINE_BOOKING_CLOSED`

## Examples

**Request**

```json
{
  "dineInAt": "2026-10-16T15:00:00.000Z",
  "partySize": 4,
  "guestName": "Kari Nordmann",
  "guestEmail": "kari@example.com",
  "guestPhone": "+4799999999",
  "note": "Én barnestol, takk",
  "locale": "nb"
}
```

**Response**

```json
{
  "reservationId": "cm2res0001abcdefghijklmn",
  "publicToken": "cm2tok0002abcdefghijklmn",
  "orderNumber": 1043,
  "status": "PENDING_APPROVAL",
  "dineInAt": "2026-10-16T15:00:00.000Z",
  "partySize": 4,
  "tableId": null
}
```

**SDK Code**

```python Reservations_createReservation_example
import requests

url = "https://www.lettbestilt.no/api/v1/reservations"

querystring = {"slug":"slug"}

payload = {
    "dineInAt": "2026-10-16T15:00:00.000Z",
    "partySize": 4,
    "guestName": "Kari Nordmann",
    "guestEmail": "kari@example.com",
    "guestPhone": "+4799999999",
    "note": "Én barnestol, takk",
    "locale": "nb"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

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

print(response.json())
```

```javascript Reservations_createReservation_example
const url = 'https://www.lettbestilt.no/api/v1/reservations?slug=slug';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"dineInAt":"2026-10-16T15:00:00.000Z","partySize":4,"guestName":"Kari Nordmann","guestEmail":"kari@example.com","guestPhone":"+4799999999","note":"Én barnestol, takk","locale":"nb"}'
};

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

```go Reservations_createReservation_example
package main

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

func main() {

	url := "https://www.lettbestilt.no/api/v1/reservations?slug=slug"

	payload := strings.NewReader("{\n  \"dineInAt\": \"2026-10-16T15:00:00.000Z\",\n  \"partySize\": 4,\n  \"guestName\": \"Kari Nordmann\",\n  \"guestEmail\": \"kari@example.com\",\n  \"guestPhone\": \"+4799999999\",\n  \"note\": \"Én barnestol, takk\",\n  \"locale\": \"nb\"\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 Reservations_createReservation_example
require 'uri'
require 'net/http'

url = URI("https://www.lettbestilt.no/api/v1/reservations?slug=slug")

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  \"dineInAt\": \"2026-10-16T15:00:00.000Z\",\n  \"partySize\": 4,\n  \"guestName\": \"Kari Nordmann\",\n  \"guestEmail\": \"kari@example.com\",\n  \"guestPhone\": \"+4799999999\",\n  \"note\": \"Én barnestol, takk\",\n  \"locale\": \"nb\"\n}"

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

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

HttpResponse<String> response = Unirest.post("https://www.lettbestilt.no/api/v1/reservations?slug=slug")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"dineInAt\": \"2026-10-16T15:00:00.000Z\",\n  \"partySize\": 4,\n  \"guestName\": \"Kari Nordmann\",\n  \"guestEmail\": \"kari@example.com\",\n  \"guestPhone\": \"+4799999999\",\n  \"note\": \"Én barnestol, takk\",\n  \"locale\": \"nb\"\n}")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://www.lettbestilt.no/api/v1/reservations?slug=slug', [
  'body' => '{
  "dineInAt": "2026-10-16T15:00:00.000Z",
  "partySize": 4,
  "guestName": "Kari Nordmann",
  "guestEmail": "kari@example.com",
  "guestPhone": "+4799999999",
  "note": "Én barnestol, takk",
  "locale": "nb"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp Reservations_createReservation_example
using RestSharp;

var client = new RestClient("https://www.lettbestilt.no/api/v1/reservations?slug=slug");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"dineInAt\": \"2026-10-16T15:00:00.000Z\",\n  \"partySize\": 4,\n  \"guestName\": \"Kari Nordmann\",\n  \"guestEmail\": \"kari@example.com\",\n  \"guestPhone\": \"+4799999999\",\n  \"note\": \"Én barnestol, takk\",\n  \"locale\": \"nb\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Reservations_createReservation_example
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "dineInAt": "2026-10-16T15:00:00.000Z",
  "partySize": 4,
  "guestName": "Kari Nordmann",
  "guestEmail": "kari@example.com",
  "guestPhone": "+4799999999",
  "note": "Én barnestol, takk",
  "locale": "nb"
] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://www.lettbestilt.no/api/v1/reservations?slug=slug")! 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()
```