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

# Publish, schedule, unschedule or archive a news item

POST https://www.lettbestilt.no/api/v1/partner/news/{newsId}/publish
Content-Type: application/json

**Partner key only.** `action` is required (an empty body never publishes): `publish` (draft or scheduled → published; the recipients are locked, and owners are emailed when `sendEmail` is true), `schedule` (draft → scheduled at `scheduledAt`, in the future with a time zone), `unschedule` (scheduled → draft) or `archive` (published → archived). The news reaches real restaurants and their owners. Publishing and scheduling need someone to send to now and count in the send bucket (10 per day per partner, shared with LettBestilt Partner). Through LettBestilt MCP with a LIVE key, `publish` and `schedule` give `403 HUMAN_REQUIRED`: a person publishes news to real restaurants. Bound to the key's environment: a TEST key only reaches news whose recipients are all sandboxes (never `audienceAll`); a LIVE key reaches all news that is not bound to TEST (including `audienceAll` news, which on publishing is locked to the sandboxes as well). Needs a key created by an owner or administrator. Changes count in the partner's bucket of 60 per 10 minutes (shared with LettBestilt Partner). Every change is logged with ids and field names, never the text.

Reference: https://docs.lettbestilt.no/api-reference/partner/publish-partner-news

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

## Request

### Path parameters

- `newsId` (string, required) — The news item's `id` from `GET /news` or `POST /news`.

### Body (application/json)

This endpoint expects a PartnerNewsAction.

- `action` (enum, required) — Required: an empty body never publishes.
  - Allowed values: `publish`, `schedule`, `unschedule`, `archive`
- `scheduledAt` (datetime, optional) — Required for `schedule`; in the future, with a time zone.

## Response

### 200

The new status.

- `data` (PartnerNewsActionResult, required)

## Errors

### 400 Bad Request Error

`INVALID_JSON`: the body is not valid JSON.

- `error` (ErrorBody, required)

### 401 Unauthorized Error

`UNAUTHORIZED`: the partner key is missing, invalid or revoked, or the partner is suspended or archived. `KEY_EXPIRED`: the key is past its expiry date.

- `error` (ErrorBody, required)

### 403 Forbidden Error

`KEY_ROLE_FORBIDDEN`: the person who created the key is no longer an owner or administrator in the partner team (`error.details.capability` is `news.manage`). `PARTNER_NEWS_FORBIDDEN`: only external partners have news. `HUMAN_REQUIRED`: `publish` or `schedule` through LettBestilt MCP with a LIVE key (news to real restaurants is published by a person; `unschedule` is allowed). Or as for reading: `FORBIDDEN` (`Origin` header), `PARTNER_AGREEMENT_REQUIRED` or `PARTNER_NOT_APPROVED`.

- `error` (ErrorBody, required)

### 404 Not Found Error

`NEWS_NOT_FOUND`: the same response for an unknown id, another partner's news and news outside the key's environment.

- `error` (ErrorBody, required)

### 409 Conflict Error

`NOT_DRAFT` (schedule), `NOT_SCHEDULED` (unschedule), `NOT_PUBLISHED` (archive), `NOT_CLAIMABLE` (already published or archived) or `PARTNER_ARCHIVED`.

- `error` (ErrorBody, required)

### 422 Unprocessable Entity Error

`NO_RECIPIENTS`: none of the recipients can be reached now. `VALIDATION_FAILED`: `action` missing or unknown, an unknown field, or `scheduledAt` missing or in the past.

- `error` (ErrorBody, required)

### 429 Too Many Requests Error

`RATE_LIMITED`: 30 changes per minute, 60 per 10 minutes, or 10 sends per day.

- `error` (ErrorBody, required)

## Types

### PartnerNewsActionResult

- `id` (string, required)
- `status` (enum, required)
  - Allowed values: `DRAFT`, `SCHEDULED`, `PUBLISHED`, `ARCHIVED`
- `scheduledAt` (datetime, optional) — Only for `schedule`.
- `restaurants` (integer, optional) — Only for `publish`: the restaurants the news was locked to.
- `recipients` (integer, optional) — Only for `publish`: emails put in the queue.
- `emailed` (boolean, optional) — Only for `publish`.

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

## Examples

**Request**

```json
{
  "action": "schedule",
  "scheduledAt": "2026-11-01T09:00:00+01:00"
}
```

**Response**

```json
{
  "data": {
    "id": "string",
    "status": "DRAFT",
    "scheduledAt": "2024-01-15T09:30:00Z",
    "restaurants": 1,
    "recipients": 1,
    "emailed": true
  }
}
```

**SDK Code**

```python Partner_publishPartnerNews_example
import requests

url = "https://www.lettbestilt.no/api/v1/partner/news/newsId/publish"

payload = {
    "action": "schedule",
    "scheduledAt": "2026-11-01T09:00:00+01:00"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

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

print(response.json())
```

```javascript Partner_publishPartnerNews_example
const url = 'https://www.lettbestilt.no/api/v1/partner/news/newsId/publish';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"action":"schedule","scheduledAt":"2026-11-01T09:00:00+01:00"}'
};

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

```go Partner_publishPartnerNews_example
package main

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

func main() {

	url := "https://www.lettbestilt.no/api/v1/partner/news/newsId/publish"

	payload := strings.NewReader("{\n  \"action\": \"schedule\",\n  \"scheduledAt\": \"2026-11-01T09:00:00+01:00\"\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_publishPartnerNews_example
require 'uri'
require 'net/http'

url = URI("https://www.lettbestilt.no/api/v1/partner/news/newsId/publish")

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  \"action\": \"schedule\",\n  \"scheduledAt\": \"2026-11-01T09:00:00+01:00\"\n}"

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

```java Partner_publishPartnerNews_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/news/newsId/publish")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"action\": \"schedule\",\n  \"scheduledAt\": \"2026-11-01T09:00:00+01:00\"\n}")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://www.lettbestilt.no/api/v1/partner/news/newsId/publish', [
  'body' => '{
  "action": "schedule",
  "scheduledAt": "2026-11-01T09:00:00+01:00"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp Partner_publishPartnerNews_example
using RestSharp;

var client = new RestClient("https://www.lettbestilt.no/api/v1/partner/news/newsId/publish");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"action\": \"schedule\",\n  \"scheduledAt\": \"2026-11-01T09:00:00+01:00\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Partner_publishPartnerNews_example
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "action": "schedule",
  "scheduledAt": "2026-11-01T09:00:00+01:00"
] as [String : Any]

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

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