# Content and publishing

Posts, pages, pictures and the AI auto-posting campaigns, on every site type.

## What a post on this site can have

`GET /sites/{site_id}/content/options` · scope `content:read`

Before writing anything, ask this: it says how a post appears on this site type, which fields it supports (featured picture, categories, tags, author, drafts) and the real categories and authors the site has. Site types with no drafts publish straight away - the answer says so.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/sites/123/content/options" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/content/options", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/content/options", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "site_id": 123,
    "domain": "example.com",
    "type": "Wordpress",
    "how": "Published as a WordPress post with its featured image, categories, tags and author.",
    "supports": {
      "featured": true,
      "categories": true,
      "tags": true,
      "authors": true,
      "drafts": true
    },
    "statuses": [
      "publish",
      "draft"
    ],
    "categories": [
      {
        "id": "1",
        "name": "Uncategorised"
      }
    ],
    "authors": [
      {
        "id": "1",
        "name": "Ann Example"
      }
    ],
    "media_uploads": true,
    "options_error": null
  }
}
```

Errors: `conflict`, `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`

## Posts on this site

`GET /sites/{site_id}/content` · scope `content:read`

The latest posts the site itself reports, and every post this account has written for it through PBN.LTD (including ones still being written or waiting to go out).

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `limit` | query | integer | no | How many to return. Default: `20`. |
| `state` | query | string (one of: queued, writing, images, review, ready, publishing, done, failed, cancelled) | no | Only posts in this state. |
| `on_site` | query | boolean | no | Also ask the site itself for its latest posts (slower). Default: `True`. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/sites/123/content" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/content", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/content", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "site_id": 123,
    "on_site": [
      {
        "title": "Welcome",
        "url": "https://example.com/welcome/"
      }
    ],
    "on_site_error": null,
    "ours": [
      {
        "id": 90210,
        "site_id": 123,
        "title": "Five ways to speed up your shop",
        "slug": "five-ways",
        "state": "done",
        "state_text": "Published",
        "step": "Published",
        "source": "manual",
        "status": "publish",
        "tags": [
          "speed",
          "shop"
        ],
        "excerpt": "A short summary of the post.",
        "url": "https://example.com/blog/five-ways/",
        "remote_id": "412",
        "publish_at": "2026-09-20T10:00:00Z",
        "published_at": "2026-09-20T10:00:14Z",
        "created_at": "2026-09-20T09:59:02Z",
        "error": null,
        "error_code": null,
        "campaign_id": null,
        "images": 1
      }
    ]
  }
}
```

Errors: `conflict`, `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`

## Everything being written or published

`GET /content/queue` · scope `content:read`

Every post on the account that is queued, being written, waiting for approval, scheduled, publishing, published, failed or cancelled - newest first, across all sites.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `limit` | query | integer | no | How many to return. Default: `20`. |
| `cursor` | query | string | no | The next_cursor value of the previous page. |
| `state` | query | string (one of: queued, writing, images, review, ready, publishing, done, failed, cancelled) | no | Only posts in this state. |
| `site_id` | query | integer | no | Only this site. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/content/queue" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/content/queue", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/content/queue", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": [
    {
      "id": 90210,
      "site_id": 123,
      "title": "Five ways to speed up your shop",
      "slug": "five-ways",
      "state": "done",
      "state_text": "Published",
      "step": "Published",
      "source": "manual",
      "status": "publish",
      "tags": [
        "speed",
        "shop"
      ],
      "excerpt": "A short summary of the post.",
      "url": "https://example.com/blog/five-ways/",
      "remote_id": "412",
      "publish_at": "2026-09-20T10:00:00Z",
      "published_at": "2026-09-20T10:00:14Z",
      "created_at": "2026-09-20T09:59:02Z",
      "error": null,
      "error_code": null,
      "campaign_id": null,
      "images": 1
    }
  ],
  "next_cursor": null,
  "has_more": false
}
```

Errors: `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`

## Read one post

`GET /sites/{site_id}/content/{post_id}` · scope `content:read`

One post in full, with its text and the history of what happened to it.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `post_id` | path | integer | yes | The post id this API gave you when it was created. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/sites/123/content/90210" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/sites/123/content/90210", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/content/90210", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "id": 90210,
    "site_id": 123,
    "title": "Five ways to speed up your shop",
    "slug": "five-ways",
    "state": "done",
    "state_text": "Published",
    "step": "Published",
    "source": "manual",
    "status": "publish",
    "tags": [
      "speed",
      "shop"
    ],
    "excerpt": "A short summary of the post.",
    "url": "https://example.com/blog/five-ways/",
    "remote_id": "412",
    "publish_at": "2026-09-20T10:00:00Z",
    "published_at": "2026-09-20T10:00:14Z",
    "created_at": "2026-09-20T09:59:02Z",
    "error": null,
    "error_code": null,
    "campaign_id": null,
    "images": 1,
    "body_html": "<p>The text of the post.</p>",
    "events": [
      {
        "at": "2026-09-20T10:00:14Z",
        "kind": "step",
        "message": "Published: https://example.com/blog/five-ways/"
      }
    ]
  }
}
```

Errors: `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`

## Publish a post or page

`POST /sites/{site_id}/content` · scope `content:write` · returns a job

Writes the post and puts it on the site. Works for every site type: WordPress, Joomla, Drupal, PrestaShop, OpenCart, Grav and MediaWiki get a native post/article/page, and Static HTML and PHP hosting sites get a page built in the look of their own pages, with a blog index and sitemap.xml kept up to date. The answer carries a job - wait for it and then read the post to get its address.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `title` | body | string | yes | The title of the post. |
| `body_html` | body | string | no | The text as HTML. Or send body_markdown. |
| `body_markdown` | body | string | no | The text as Markdown (headings, lists, links, bold, code, quotes). |
| `status` | body | string (one of: publish, draft) | no | "draft" only on site types that have drafts (see content.options). Default: `publish`. |
| `category` | body | string | no | Category id or name, on site types that have them. |
| `author` | body | string | no | Author id, on site types that have authors. |
| `tags` | body | array | no | Tags for the post. |
| `slug` | body | string | no | The address of the post; one is made from the title when you leave it out. |
| `publish_at` | body | string | no | ISO date and time to publish it (default: now). |
| `featured_image_base64` | body | string | no | The main picture, base64. It leads the post and becomes the featured image on site types that have one. |
| `images_base64` | body | array | no | More pictures, base64; they are placed in the text. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/content" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Five ways to speed up your shop", "body_markdown": "## Why speed matters\n\nA faster shop sells more.", "status": "publish", "tags": ["speed", "shop"]}'
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/content", headers=headers, json={"title": "Five ways to speed up your shop", "body_markdown": "## Why speed matters\n\nA faster shop sells more.", "status": "publish", "tags": ["speed", "shop"]}, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/content", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"title": "Five ways to speed up your shop", "body_markdown": "## Why speed matters\n\nA faster shop sells more.", "status": "publish", "tags": ["speed", "shop"]})
});
console.log(res.status, await res.json());
```

### Response

`202`

```json
{
  "data": {
    "id": 90210,
    "site_id": 123,
    "title": "Five ways to speed up your shop",
    "slug": "five-ways",
    "state": "done",
    "state_text": "Published",
    "step": "Published",
    "source": "manual",
    "status": "publish",
    "tags": [
      "speed",
      "shop"
    ],
    "excerpt": "A short summary of the post.",
    "url": "https://example.com/blog/five-ways/",
    "remote_id": "412",
    "publish_at": "2026-09-20T10:00:00Z",
    "published_at": "2026-09-20T10:00:14Z",
    "created_at": "2026-09-20T09:59:02Z",
    "error": null,
    "error_code": null,
    "campaign_id": null,
    "images": 1
  },
  "job": {
    "id": "job_4f1c0a9e2b7d6c5a3e10",
    "kind": "...",
    "status": "running",
    "url": "https://app.pbn.ltd/api/v1/jobs/job_4f1c0a9e2b7d6c5a3e10"
  }
}
```

Errors: `conflict`, `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `too_large`, `unauthorized`, `validation_failed`

## Change a post

`PATCH /sites/{site_id}/content/{post_id}` · scope `content:write`

Changes a post that has not gone out yet. A post that is already on the site cannot be edited from here - remove it and write a new one.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `post_id` | path | integer | yes | The post id this API gave you when it was created. |
| `title` | body | string | no | A new title. |
| `body_html` | body | string | no | New text as HTML. |
| `body_markdown` | body | string | no | New text as Markdown. |
| `tags` | body | array | no | Replace the tags. |
| `status` | body | string (one of: publish, draft) | no |  |
| `publish_at` | body | string | no | Move when it goes out. |
| `approve` | body | boolean | no | Approve a post that is waiting for approval. |

### Example

```bash
curl -s -X PATCH "https://app.pbn.ltd/api/v1/sites/123/content/90210" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Five ways to speed up your shop (updated)"}'
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.patch("https://app.pbn.ltd/api/v1/sites/123/content/90210", headers=headers, json={"title": "Five ways to speed up your shop (updated)"}, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/content/90210", {
  method: "PATCH",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"title": "Five ways to speed up your shop (updated)"})
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "id": 90210,
    "site_id": 123,
    "title": "Five ways to speed up your shop",
    "slug": "five-ways",
    "state": "ready",
    "state_text": "Scheduled",
    "step": "Published",
    "source": "manual",
    "status": "publish",
    "tags": [
      "speed",
      "shop"
    ],
    "excerpt": "A short summary of the post.",
    "url": "https://example.com/blog/five-ways/",
    "remote_id": "412",
    "publish_at": "2026-09-20T10:00:00Z",
    "published_at": "2026-09-20T10:00:14Z",
    "created_at": "2026-09-20T09:59:02Z",
    "error": null,
    "error_code": null,
    "campaign_id": null,
    "images": 1,
    "body_html": "<p>The new text.</p>"
  }
}
```

Errors: `conflict`, `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`

## Take a post off the site

`DELETE /sites/{site_id}/content/{post_id}` · scope `content:write` · **destructive**

Removes a published post from the site, or cancels one that has not gone out yet. There is no undo.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `post_id` | path | integer | yes | The post id this API gave you when it was created. |

### Example

```bash
curl -s -X DELETE "https://app.pbn.ltd/api/v1/sites/123/content/90210" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.delete("https://app.pbn.ltd/api/v1/sites/123/content/90210", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/content/90210", {
  method: "DELETE",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "id": 90210,
    "removed": true,
    "was": "published"
  }
}
```

Errors: `conflict`, `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`

## Upload a picture or file

`POST /sites/{site_id}/content/media` · scope `content:write`

Puts a picture or file on the site and gives back the address to use in a post. On WordPress it goes into the media library; on every other site type it goes into the site's own files.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `site_id` | path | integer | yes | The site id (see GET /sites). |
| `name` | body | string | yes | The file name, e.g. "hero.jpg". |
| `content_base64` | body | string | yes | The file itself, base64. |
| `alt` | body | string | no | Alt text (WordPress media library). |
| `folder` | body | string | no | Where to put it on site types with no media library. Default: `assets`. |
| `overwrite` | body | boolean | no | Replace a file of the same name. Default: `False`. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/sites/123/content/media" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "hero.jpg", "content_base64": "iVBORw0KGgo=", "alt": "The shop front"}'
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/sites/123/content/media", headers=headers, json={"name": "hero.jpg", "content_base64": "iVBORw0KGgo=", "alt": "The shop front"}, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/sites/123/content/media", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"name": "hero.jpg", "content_base64": "iVBORw0KGgo=", "alt": "The shop front"})
});
console.log(res.status, await res.json());
```

### Response

`201`

```json
{
  "data": {
    "name": "hero.jpg",
    "url": "https://example.com/wp-content/uploads/2026/09/hero.jpg",
    "media_id": "881",
    "where": "the WordPress media library",
    "bytes": 145322
  }
}
```

Errors: `conflict`, `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `too_large`, `unauthorized`, `validation_failed`

## List auto-posting campaigns

`GET /campaigns` · scope `content:read`

The AI auto-posting campaigns on the account: what they write, when they run next and how many articles they have made.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `limit` | query | integer | no | How many to return. Default: `20`. |
| `cursor` | query | string | no | The next_cursor value of the previous page. |
| `status` | query | string (one of: active, paused, problem, finished) | no |  |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/campaigns" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/campaigns", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/campaigns", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": [
    {
      "id": 44,
      "name": "Weekly shop tips",
      "status": "active",
      "status_reason": null,
      "schedule": "weekly",
      "times": "09:00",
      "weekdays": "0,3",
      "timezone": "Europe/London",
      "per_site": 1,
      "review": "auto",
      "total_limit": 0,
      "articles_created": 12,
      "next_run_at": "2026-09-23T08:00:00Z",
      "last_run_at": "2026-09-19T08:00:00Z",
      "site_ids": [
        123,
        124
      ],
      "created_at": "2026-07-01T10:00:00Z"
    }
  ],
  "next_cursor": null,
  "has_more": false
}
```

Errors: `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`

## Read one campaign

`GET /campaigns/{campaign_id}` · scope `content:read`

One campaign with its last ten runs.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaign_id` | path | integer | yes | The campaign. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/campaigns/44" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/campaigns/44", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/campaigns/44", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "id": 44,
    "name": "Weekly shop tips",
    "status": "active",
    "status_reason": null,
    "schedule": "weekly",
    "times": "09:00",
    "weekdays": "0,3",
    "timezone": "Europe/London",
    "per_site": 1,
    "review": "auto",
    "total_limit": 0,
    "articles_created": 12,
    "next_run_at": "2026-09-23T08:00:00Z",
    "last_run_at": "2026-09-19T08:00:00Z",
    "site_ids": [
      123,
      124
    ],
    "created_at": "2026-07-01T10:00:00Z",
    "runs": [
      {
        "id": 7781,
        "trigger": "schedule",
        "slot_at": "2026-09-19T08:00:00Z",
        "total": 2,
        "done": 2,
        "failed": 0,
        "cancelled": 0,
        "finished_at": "2026-09-19T08:04:11Z",
        "state": "finished"
      }
    ]
  }
}
```

Errors: `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`

## Pause or resume

`PUT /campaigns/{campaign_id}/status` · scope `content:write`

Pauses a campaign or starts it again. A campaign paused by our staff or by a problem with an AI key cannot be resumed from here - fix the cause first.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaign_id` | path | integer | yes |  |
| `status` | body | string (one of: active, paused) | yes | "paused" stops it writing; "active" starts it again. |

### Example

```bash
curl -s -X PUT "https://app.pbn.ltd/api/v1/campaigns/44/status" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "paused"}'
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.put("https://app.pbn.ltd/api/v1/campaigns/44/status", headers=headers, json={"status": "paused"}, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/campaigns/44/status", {
  method: "PUT",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"status": "paused"})
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "id": 44,
    "name": "Weekly shop tips",
    "status": "active",
    "status_reason": null,
    "schedule": "weekly",
    "times": "09:00",
    "weekdays": "0,3",
    "timezone": "Europe/London",
    "per_site": 1,
    "review": "auto",
    "total_limit": 0,
    "articles_created": 12,
    "next_run_at": "2026-09-23T08:00:00Z",
    "last_run_at": "2026-09-19T08:00:00Z",
    "site_ids": [
      123,
      124
    ],
    "created_at": "2026-07-01T10:00:00Z"
  }
}
```

Errors: `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`, `validation_failed`

## Write one now

`POST /campaigns/{campaign_id}/write-now` · scope `content:write`

Asks a campaign to write and publish now, the same as the "Write now" button. Returns the run; follow it with runs.get.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `campaign_id` | path | integer | yes |  |
| `site_ids` | body | array | no | Only these sites of the campaign (default: all of them). |
| `count` | body | integer | no | Articles per site. Default: `1`. |

### Example

```bash
curl -s -X POST "https://app.pbn.ltd/api/v1/campaigns/44/write-now" \
  -H "Authorization: Bearer $PBN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"count": 1}'
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.post("https://app.pbn.ltd/api/v1/campaigns/44/write-now", headers=headers, json={"count": 1}, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/campaigns/44/write-now", {
  method: "POST",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`, "Content-Type": "application/json"},
  body: JSON.stringify({"count": 1})
});
console.log(res.status, await res.json());
```

### Response

`202`

```json
{
  "data": {
    "run_id": 7782,
    "articles": 2,
    "problems": []
  }
}
```

Errors: `conflict`, `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`

## How a run is going

`GET /runs/{run_id}` · scope `content:read`

One run of a campaign and every article in it.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `run_id` | path | integer | yes | The run. |

### Example

```bash
curl -s "https://app.pbn.ltd/api/v1/runs/7782" \
  -H "Authorization: Bearer $PBN_API_KEY"
```

```python
import os
import requests

headers = {"Authorization": "Bearer " + os.environ["PBN_API_KEY"]}
r = requests.get("https://app.pbn.ltd/api/v1/runs/7782", headers=headers, timeout=120)
print(r.status_code, r.json())
```

```javascript
const res = await fetch("https://app.pbn.ltd/api/v1/runs/7782", {
  method: "GET",
  headers: {Authorization: `Bearer ${process.env.PBN_API_KEY}`}
});
console.log(res.status, await res.json());
```

### Response

`200`

```json
{
  "data": {
    "id": 7782,
    "campaign_id": 44,
    "trigger": "manual",
    "slot_at": "2026-09-20T10:00:00Z",
    "total": 2,
    "done": 1,
    "failed": 0,
    "cancelled": 0,
    "finished_at": null,
    "state": "running",
    "articles": [
      {
        "id": 90210,
        "site_id": 123,
        "title": "Five ways to speed up your shop",
        "slug": "five-ways",
        "state": "done",
        "state_text": "Published",
        "step": "Published",
        "source": "manual",
        "status": "publish",
        "tags": [
          "speed",
          "shop"
        ],
        "excerpt": "A short summary of the post.",
        "url": "https://example.com/blog/five-ways/",
        "remote_id": "412",
        "publish_at": "2026-09-20T10:00:00Z",
        "published_at": "2026-09-20T10:00:14Z",
        "created_at": "2026-09-20T09:59:02Z",
        "error": null,
        "error_code": null,
        "campaign_id": null,
        "images": 1
      }
    ]
  }
}
```

Errors: `not_found`, `payment_required`, `rate_limited`, `scope_missing`, `unauthorized`
