# REST API

Everything PageCycle's tools do is also an HTTPS route: check, move and find pages, read History and change a space's workflow from your own scripts. Each call acts as the person who signed in, with exactly their Confluence permissions. PageCycle runs on Atlassian's platform and answers only the integrations people on your site authorize.

> **Info:** App REST APIs are an Atlassian Preview feature. PageCycle's routes and answers follow its OpenAPI file, and answers may gain fields, so ignore the ones you don't know. How you turn the API on and sign in is Atlassian's, and may change as Atlassian finishes it.

## Turn it on

A site or org admin switches the API on once per site. It is off until then, and switching it off again stops every call at once.

1. Open **Atlassian Administration**, pick your site and open **Connected apps**.
2. Find **PageCycle** and select **View app details**.
3. On the **Details** tab, under **App REST APIs**, select **Enable app REST APIs**, then **Enable**.

The section then reads **Enabled**. Reinstalling PageCycle turns it off again.

## Create an OAuth 2.0 integration

Calls are signed in as a person with an OAuth 2.0 (3LO) integration you create in the Atlassian developer console. It acts only for a person on your site who signs in and accepts it, and only with their permissions.

1. Open the [developer console](https://developer.atlassian.com/console/myapps/), select **Create**, then **OAuth 2.0 integration**. Name it, choose **Resource-level** and create it.
2. Open **Permissions** and select **Add Marketplace or custom app**. Pick your site, then **PageCycle**, then the scopes your script needs (below).
3. Still in **Permissions**, open **Confluence API**, select **Configure**, then **Granular scopes**, and add `read:forge-app:confluence`. Every call needs it.
4. Open **Authorization** and add your callback URL.
5. Open **Settings** for the **Client ID** and **Secret**. Keep the secret out of your code and your logs.

## Get a token

Send the person to the authorization URL. They sign in, pick your site and accept:

```text
https://auth.atlassian.com/authorize
  ?audience=api.atlassian.com
  &client_id=YOUR_CLIENT_ID
  &scope=read%3Apagecycle%3Acustom%20write%3Apagecycle-page%3Acustom%20read%3Aforge-app%3Aconfluence%20offline_access
  &redirect_uri=YOUR_CALLBACK_URL
  &state=A_VALUE_YOU_CHECK
  &response_type=code
  &prompt=consent
  &sns=db702242-b6fc-4a44-bcea-e6ad97e8db84.7b249cc1-1e34-4a73-850f-d51fafab4b8c
```

Keep `sns` as written. It names PageCycle; without it, Atlassian leaves PageCycle's scopes out of the token and every call answers `403 Missing required scopes`. With it, the consent screen lists what the script may do in PageCycle.

Exchange the code your callback receives for a token:

```bash
curl -s -X POST https://auth.atlassian.com/oauth/token \
  -H 'Content-Type: application/json' \
  -d '{"grant_type":"authorization_code","client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_SECRET","code":"THE_CODE","redirect_uri":"YOUR_CALLBACK_URL"}'
```

The answer's `access_token` lasts an hour; `offline_access` adds a `refresh_token` to get a new one. Your site's cloud id is at `https://YOUR_SITE.atlassian.net/_edge/tenant_info`.

## Basics

Every route lives under one base URL:

```text
https://api.atlassian.com/svc/confluence/YOUR_CLOUD_ID/apps/db702242-b6fc-4a44-bcea-e6ad97e8db84_7b249cc1-1e34-4a73-850f-d51fafab4b8c
```

Send the token as `Authorization: Bearer YOUR_TOKEN` and `Accept: application/json`; send JSON bodies with `Content-Type: application/json`, up to 16,000 bytes. Every answer from PageCycle has the same four fields (Atlassian's own refusals, below, are plain):

| Field | Type | Meaning |
|---|---|---|
| `ok` | boolean | `true` when PageCycle did it |
| `answer` | string | What happened, in the same words Rovo and MCP get. A refused change starts "Not done." |
| `code` | string or null | `null` on success, `working` for a long change that goes on, or why it was refused (status codes, below) |
| `data` | object or null | The same answer as fields: the reference lists every route's |

A page's next review is `reviewBy` in every answer; you send it as `reviewDate` with a move and as `date` to `PUT /pages/{page}/reviewDate`. Lists check the person's view of every page, so a large space takes a few seconds a call: page through it with `after`, and don't poll in a tight loop.

> **Note:** A change to a space's settings never applies on the first call. The route answers a preview and a `confirmation`; nothing changes until the same person sends that confirmation to `POST /confirmations/{id}`, at least 8 seconds later and within 15 minutes.

## Examples

### Check a page

```bash
curl -s "$BASE/pages/8013/status" -H "Authorization: Bearer $TOKEN" -H 'Accept: application/json'
```

### The answer

```json
{ "ok": true, "answer": "Approved by Jordan Lee, Sep 30 · version 4 · next review Mar 30\nYou can move it to Archived (Archive).", "code": null,
  "data": { "page": { "id": "8013", "title": "Export to CSV spec", "version": 4, "spaceId": "7001" },
            "status": { "id": "approved", "name": "Approved", "category": "final" }, "since": "2026-09-30T10:00:00.000Z",
            "movedBy": "Jordan Lee", "approvedVersion": 4, "reviewBy": "2027-03-30", "confluenceStatusRemoved": false,
            "flagged": false, "canEdit": true, "movesPaused": false,
            "moves": [{ "to": { "id": "archived", "name": "Archived" }, "label": "Archive", "commentRequired": false }] } }
```

### Move a page

```bash
curl -s -X POST "$BASE/pages/8013/moves" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'Accept: application/json' \
  -d '{"to":"In review","comment":"Added the over-limit case"}'
```

### The answer

```json
{ "ok": true, "answer": "In review · recorded in History", "code": null,
  "data": { "page": { "id": "8013", "title": "Export to CSV spec" }, "status": { "id": "in-review", "name": "In review", "category": "review" }, "reviewBy": null, "endedApproval": false } }
```

### Change a space's workflow

Ask for the change; the answer is a preview:

```bash
curl -s -X POST "$BASE/spaces/WS/statuses" -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'Accept: application/json' \
  -d '{"name":"On hold","after":"Draft"}'
```

Show the person `data.preview`. When they agree, confirm it with `data.confirmation`:

```bash
curl -s -X POST "$BASE/confirmations/op_8f2c1d0e9a7b6c5d" -H "Authorization: Bearer $TOKEN" -H 'Accept: application/json'
```

A change that touches many pages (removing or renaming a status, turning PageCycle on) answers `202` with `data.operation`. Call `POST /operations/{id}` until it answers `200`; `GET /operations/{id}` only reads where it stands.

## Routes

Every route's fields and a real answer are in the [API reference](https://atlassian.braingrid.ai/docs/pagecycle/api). To generate a client, download the [OpenAPI file](https://atlassian.braingrid.ai/pagecycle-api.openapi.json).

`{page}` is a page id. `{space}` is a space key. A status, move or automation in a path is its name or its id; a name with `/` or `%` in it must be named by its id, because Atlassian refuses those characters in a path.

### Read

Scope `read:pagecycle:custom`. Anyone who can view the page or space.

| Method | Path | What it does |
|---|---|---|
| GET | `/pages/{page}/status` | A page's status, since when and who moved it there, the version approved, its next review and the moves you can make |
| GET | `/pages/{page}/history?limit=` | Every move with who, when and why, newest first |
| GET | `/spaces?limit=` | The spaces that use PageCycle, with their counts |
| GET | `/spaces/{space}/summary` | Pages in each status, and the pages that need attention |
| GET | `/spaces/{space}/pages?status=&overdue=&stuckDays=&limit=&after=` | Pages by status, overdue for review, or stuck for some days |
| GET | `/spaces/{space}/history?person=&status=&from=&to=&limit=` | A space's History, filtered by person, status or dates |
| GET | `/spaces/{space}/workflow` | Statuses, moves and who may make them |
| GET | `/spaces/{space}/automations` | Automations, and for space admins whether they run well |
| GET | `/operations/{id}` | Where a change stands (space admins; read only) |

### Move pages

Scope `write:pagecycle-page:custom`. People who can edit the page, within the move's groups.

| Method | Path | What it does |
|---|---|---|
| POST | `/pages/{page}/moves` | Moves a page: `{ "to", "comment", "reviewDate", "confirm" }` |
| PUT | `/pages/{page}/reviewDate` | Sets the next review, `{ "date": "2027-03-30" }` or `"in 6 months"`; `null` clears it |

### Space admin

Scope `manage:pagecycle-space:custom`. Space admins. Each answers a preview.

| Method | Path | What it does |
|---|---|---|
| POST | `/spaces/{space}/statuses` | Adds a status after an existing one |
| PATCH | `/spaces/{space}/statuses/{status}` | Renames, recolors or changes the kind of a status |
| DELETE | `/spaces/{space}/statuses/{status}?moveTo=` | Removes a status after moving its pages |
| POST | `/spaces/{space}/moves` | Adds a move between two statuses |
| PATCH | `/spaces/{space}/moves/{move}` | Changes a move's name, groups or comment rule |
| DELETE | `/spaces/{space}/moves/{move}` | Removes a move |
| PUT | `/spaces/{space}/workflowDefaults` | Sets where pages start and the status new pages get |
| POST | `/spaces/{space}/automations` | Adds an automation: `{ "when", "if", "then", "name" }` |
| PATCH | `/spaces/{space}/automations/{automation}` | Changes, switches on or off, or reorders an automation |
| DELETE | `/spaces/{space}/automations/{automation}` | Removes an automation |
| PUT | `/spaces/{space}/settings` | What happens when someone breaks the workflow, and how long History is kept |
| POST | `/spaces/{space}/pagecycle` | Turns PageCycle on in a space |
| DELETE | `/spaces/{space}/pagecycle` | Turns PageCycle off in a space; History is kept |

### Confirmations and long changes

Scope `manage:pagecycle-space:custom`.

| Method | Path | What it does |
|---|---|---|
| POST | `/confirmations/{id}` | Applies your own preview. Repeating it answers the saved result |
| POST | `/operations/{id}` | Continues a long change by its next part (its previewer or another space admin) |

Pages a person can't view never appear in a list, a count or a History line. Lists answer at most 50 rows: only the page list takes `after` (pass the previous answer's `data.next`); the others say `data.truncated` and narrow by their filters.

## Status codes

| Status | Means |
|---|---|
| 200 | Done, or a preview |
| 202 | A long change goes on: call `POST /operations/{id}` |
| 400 | The request is malformed, or names something PageCycle can't read |
| 401 | No person is signed in behind the token |
| 403 | The person may not do this, it is someone else's preview, or they have not allowed PageCycle access yet |
| 404 | Not found, or the person can't view it; nothing in the answer names it |
| 409 | PageCycle is off in the space, the workflow doesn't allow it, or the preview expired or the settings changed (a fresh preview is in `data`). When `data.confirmNeeded` is `true`, the page's Confluence status was removed, which can mean a Confluence approval is running: ask the person, then send the move again with `"confirm": true` |
| 425 | Too early: a confirmation waits 8 seconds after its preview |
| 429 | Too many unconfirmed previews in two minutes |
| 500 | PageCycle couldn't finish; the answer says so |
| 503 | The PageCycle license is inactive: moves, review dates and long changes are paused; reads and settings still answer |

### Refusals from Atlassian

Some calls never reach PageCycle. Atlassian's gateway answers them itself, in plain text or its own JSON, without the four fields:

| Status | When |
|---|---|
| 401 | No token, or one that expired |
| 403 | `Missing required scopes`: the token lacks the route's scope (or was taken without `sns`) |
| 403 | `REST APIs are not allowed`: App REST APIs are off for PageCycle on the site |
| 400 | A path value with `/` or `%` in it, even encoded: name the status, move or automation by its id |
| 404 | `No matching API route found`: the path is not one of PageCycle's routes |

## Scopes

| Scope | Shown as | Opens |
|---|---|---|
| `read:pagecycle:custom` | Read PageCycle | Every Read route |
| `write:pagecycle-page:custom` | Move PageCycle pages | Moving pages and setting review dates |
| `manage:pagecycle-space:custom` | Change PageCycle space settings | Every Space admin route, confirmations and continuing a long change |
| `read:forge-app:confluence` | Atlassian's | Every call, beside one of the above |

> **Warning:** A token acts as the person who signed in, with everything they may do in PageCycle. Ask only for the scopes your script needs, keep tokens out of code and logs, and revoke an integration you no longer use in the developer console.

### Each route and its Rovo tool

| Route | Rovo and MCP tool |
|---|---|
| `GET /pages/{page}/status` | getPagecyclePageStatus |
| `POST /pages/{page}/moves` | movePagecyclePage |
| `GET /pages/{page}/history` | getPagecyclePageHistory |
| `PUT /pages/{page}/reviewDate` | setPagecycleReviewDate |
| `GET /spaces` | listPagecycleSpaces |
| `GET /spaces/{space}/summary` | getPagecycleSpaceSummary |
| `GET /spaces/{space}/pages` | listPagecyclePages |
| `GET /spaces/{space}/history` | getPagecycleSpaceHistory |
| `GET /spaces/{space}/workflow` | getPagecycleWorkflow |
| `GET /spaces/{space}/automations` | getPagecycleAutomations |
| `POST /spaces/{space}/statuses` | addPagecycleStatus |
| `PATCH /spaces/{space}/statuses/{status}` | updatePagecycleStatus |
| `DELETE /spaces/{space}/statuses/{status}` | removePagecycleStatus |
| `POST /spaces/{space}/moves` | addPagecycleMove |
| `PATCH /spaces/{space}/moves/{move}` | updatePagecycleMove |
| `DELETE /spaces/{space}/moves/{move}` | removePagecycleMove |
| `PUT /spaces/{space}/workflowDefaults` | setPagecycleWorkflowDefaults |
| `POST /spaces/{space}/automations` | addPagecycleAutomation |
| `PATCH /spaces/{space}/automations/{automation}` | updatePagecycleAutomation |
| `DELETE /spaces/{space}/automations/{automation}` | removePagecycleAutomation |
| `PUT /spaces/{space}/settings` | updatePagecycleSpaceSettings |
| `POST /spaces/{space}/pagecycle` | enablePagecycleSpace |
| `DELETE /spaces/{space}/pagecycle` | disablePagecycleSpace |
| `POST /operations/{id}` | getPagecycleOperation |
| `POST /confirmations/{id}` | (the confirmation of any preview) |
| `GET /operations/{id}` | (read only) |
