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

# Invite the restaurant's owner

POST https://www.lettbestilt.no/api/v1/partner/restaurants/{slug}/owner-invite
Content-Type: application/json

**Partner key + restaurant key.** Emails the owner an invitation. The owner creates their own login, signs the customer agreement and the data processing agreement, sets up online payment and sets the restaurant live themselves once LettBestilt has approved it (see [go-live approval](/partner-api#go-live-approval)). You cannot do those steps for the owner.

Real restaurants without an owner only, so it needs a live partner key (available once LettBestilt Partner is open). A sandbox gives `409 SANDBOX_NOT_SUPPORTED`. The invitation expires after 7 days. There is no `Idempotency-Key`: calling again is "send again" and replaces the open invitation (also to another email address) with 7 new days. If the owner already has a LettBestilt account they get a notice with a link instead of a sign-up link, and must accept the invitation themselves (`owner.status` stays `INVITED` until then); the response is the same in both cases.

Limits: 5 sent invitations per hour per restaurant and 30 per partner, plus 20 attempts per hour per restaurant and 60 per partner (rejected ones included; shared with the partner portal). A rejected email (409/422) does not count as sent.

Reference: https://docs.lettbestilt.no/api-reference/partner/invite-restaurant-owner

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

### Body (application/json)

This endpoint expects an object.

- `name` (string, required) — Eierens navn.
- `email` (string, required)

## Response

### 201

The invitation is sent (or, for an existing account, the notice). `inviteId` is what `DELETE` takes as `inviteId`.

- `data` (PartnerOwnerInvite, required) — En sendt eierinvitasjon. Utløper etter 7 dager.

## 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`: the same response whether the slug does not exist, belongs to another partner or does not match the restaurant key.

- `error` (ErrorBody, required)

### 409 Conflict Error

`OWNER_EXISTS`: the restaurant already has an owner. `SANDBOX_NOT_SUPPORTED`: sandboxes have no owner. `RESTAURANT_ARCHIVED`: the restaurant is archived. `CHANGED`: changed at the same time; try again.

- `error` (ErrorBody, required)

### 422 Unprocessable Entity Error

`VALIDATION_FAILED`: `name` or `email` is missing or invalid (`error.details.issues`). `OWNER_EMAIL_NOT_ALLOWED`: the email belongs to a partner user, an open invitation to a partner team or a member of the restaurant.

- `error` (ErrorBody, required)

### 429 Too Many Requests Error

`RATE_LIMITED`: more than 120 calls per minute for this partner key, or the invitation limits above.

- `error` (ErrorBody, required)

### 502 Bad Gateway Error

`INVITE_SEND_FAILED`: the invitation is stored and valid for 7 days, but the email did not go out. Send it again in a while (same call).

- `error` (ErrorBody, required)

## Types

### PartnerOwnerInvite

En sendt eierinvitasjon. Utløper etter 7 dager.

- `id` (string, required) — Invitasjonens id. Send den som `inviteId` til DELETE …/owner-invite.
- `status` (any, required)
- `email` (string, required)
- `name` (string, required)
- `expiresAt` (datetime, 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`.

## Examples

**Request**

```json
{
  "name": "Kari Nordmann",
  "email": "kari@pizzaroma.no"
}
```

**Response**

```json
{
  "data": {
    "id": "string",
    "status": null,
    "email": "string",
    "name": "string",
    "expiresAt": "2024-01-15T09:30:00Z"
  }
}
```

**SDK Code**

```python Partner_inviteRestaurantOwner_example
import requests

url = "https://www.lettbestilt.no/api/v1/partner/restaurants/slug/owner-invite"

payload = {
    "name": "Kari Nordmann",
    "email": "kari@pizzaroma.no"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

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

print(response.json())
```

```javascript Partner_inviteRestaurantOwner_example
const url = 'https://www.lettbestilt.no/api/v1/partner/restaurants/slug/owner-invite';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"name":"Kari Nordmann","email":"kari@pizzaroma.no"}'
};

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

```go Partner_inviteRestaurantOwner_example
package main

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

func main() {

	url := "https://www.lettbestilt.no/api/v1/partner/restaurants/slug/owner-invite"

	payload := strings.NewReader("{\n  \"name\": \"Kari Nordmann\",\n  \"email\": \"kari@pizzaroma.no\"\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_inviteRestaurantOwner_example
require 'uri'
require 'net/http'

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

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  \"name\": \"Kari Nordmann\",\n  \"email\": \"kari@pizzaroma.no\"\n}"

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

```java Partner_inviteRestaurantOwner_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/owner-invite")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"name\": \"Kari Nordmann\",\n  \"email\": \"kari@pizzaroma.no\"\n}")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://www.lettbestilt.no/api/v1/partner/restaurants/slug/owner-invite', [
  'body' => '{
  "name": "Kari Nordmann",
  "email": "kari@pizzaroma.no"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp Partner_inviteRestaurantOwner_example
using RestSharp;

var client = new RestClient("https://www.lettbestilt.no/api/v1/partner/restaurants/slug/owner-invite");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"name\": \"Kari Nordmann\",\n  \"email\": \"kari@pizzaroma.no\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Partner_inviteRestaurantOwner_example
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "name": "Kari Nordmann",
  "email": "kari@pizzaroma.no"
] 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/owner-invite")! 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()
```