Skip to main content

cr-create

Drive an end-to-end GitBook docs review flow from Claude Code by calling the GitBook REST API directly with curl (no CLI) — create a change request, push content (update an existing page AND create a new page), request reviewers, notify Slack, then pull review comments back in, fix them, re-push, and resolve. This is the authoring-side companion to cr-review (the reviewer side over the same API). Use this whenever someone wants to run a "docs review in GitBook" loop from the terminal/agent against the raw API (curl/HTTP), mentions creating a change request via the API, pushing content into a CR, "pull in the latest comments and fix them," requesting review on docs, or showing engineers how to collaborate on GitBook docs from Claude + Slack without a CLI.

ソース情報

リポジトリ
GitbookIO/gitbook-skills
ソースの最終更新活動
2026年9月24日 13:21
検出された SKILL.md の言語
英語
スター
17
フォーク
4

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

ファイルエクスプローラー
3 ファイル

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
cr-create
metadata
{"version":"1.0"}
description
Drive an end-to-end GitBook docs review flow from Claude Code by calling the GitBook REST API directly with curl (no CLI) — create a change request, push content (update an existing page AND create a new page), request reviewers, notify Slack, then pull review comments back in, fix them, re-push, and resolve. This is the authoring-side companion to cr-review (the reviewer side over the same API). Use this whenever someone wants to run a "docs review in GitBook" loop from the terminal/agent against the raw API (curl/HTTP), mentions creating a change request via the API, pushing content into a CR, "pull in the latest comments and fix them," requesting review on docs, or showing engineers how to collaborate on GitBook docs from Claude + Slack without a CLI.
# GitBook Review Flow (direct API) Run a documentation review loop against a GitBook space entirely through the **GitBook REST API** (`https://api.gitbook.com/v1`, hit with `curl`), so an engineer never has to leave Claude Code (plus Slack) to propose docs changes and get them reviewed. This is the authoring-side companion to `cr-review` (the reviewer side over the same API). Every action here is a plain HTTP call — there is no CLI and no helper script. The same actions serve three purposes with no separate code paths: - **CR-creation demo** — create a change request and push content (one existing page updated, one new page created). - **Notify/review demo** — request reviewers, drop a Slack link, pull comments, fix, re-push, resolve. - **Real use** — the identical actions against the user's own content. Because the demo is just a scripted sequence of the real actions, it cannot show something that doesn't actually work. Keep it that way: never fake an output. ## Auth and the `gbapi` helper Every call is a Bearer-authenticated request to `https://api.gitbook.com/v1`. The token lives in **`GITBOOK_TOKEN`** in the repo-root `.env` (create one at https://app.gitbook.com/account/developer). **Never print the token; never write it to a tracked file.** If it's missing, prompt the user for it and write it to `.env`; don't invent one. Define this shell helper once per session and use it for every call below. It loads the token from `.env`, sets the base URL and headers, and — critically for the "never fake output" rule — **fails loudly on any non-2xx, printing the API's error body** (`curl --fail-with-body`, curl ≥ 7.76 / stock on current macOS): ```bash set -a; [ -f .env ] && . ./.env; set +a # load GITBOOK_TOKEN (and SLACK_WEBHOOK_URL) gbapi() { # gbapi METHOD /path [extra curl args…] local method="$1" apipath="$2"; shift 2 # NB: not `path` — in zsh that is tied to $PATH curl -sS --fail-with-body -X "$method" \ "https://api.gitbook.com/v1${apipath}" \ -H "Authorization: Bearer ${GITBOOK_TOKEN}" \ -H "Content-Type: application/json" "$@" } ``` Every response is **JSON** — pipe it through `jq` and read whole objects. **Never hand-parse by grepping/line-pairing fields** (bind the wrong title↔id and you act on the wrong space/CR). If `gbapi` exits non-zero, surface the printed error — do not report success. ## Endpoint map (verified against api.gitbook.com/openapi.json) `<space>`, `<cr>`, `<pageId>`, `<commentId>` are the relevant IDs. Base URL is `https://api.gitbook.com/v1`; all paths below are relative to it. | Step | Method + path | Notes | |------|---------------|-------| | Who am I | `GET /user` | returns `{id, displayName, email}` — your own user ID is `.id` | | List pages | `GET /spaces/<space>/content/pages` | flat-ish tree with `id`, `title`, `type` | | Get a page (base) | `GET /spaces/<space>/content/page/<pageId>?format=markdown` | current markdown of a page on the live space | | Create CR *(GATE)* | `POST /spaces/<space>/change-requests` body `{"subject":"…"}` | returns the CR object with `id` and `urls.app` (also a `Location` header) — **`urls.app` is only the editor/diff link, not a rendered preview**; see "Surfacing the preview link" | | Get CR | `GET /spaces/<space>/change-requests/<cr>` | `subject`, `status`, `createdBy`, `comments`, `urls.app` | | Push content | `POST /spaces/<space>/change-requests/<cr>/content` body `{"changes":[…]}` | 1–50 ops, applied sequentially in one new revision; all-or-nothing | | Find the site behind a space | `GET /spaces/<space>` → `.organization`; `GET /orgs/<org>/sites`; `GET /orgs/<org>/sites/<site>/site-spaces` → match `.items[].space.id` | needed only to resolve the site preview link (see below); a space isn't required to belong to a site | | Get a site (for its preview link) | `GET /orgs/<org>/sites/<site>` | `urls.preview` (draft/CR content), `urls.published` (only once live) — **not part of the change-request response at all** | | Get a page (CR side) | `GET /spaces/<space>/change-requests/<cr>/content/page/<pageId>?format=markdown` | verify what actually landed in the CR | | Request reviewers *(GATE)* | `POST /spaces/<space>/change-requests/<cr>/requested-reviewers` body `{"users":["…"]}` | array of user IDs; optional `subject`/`description` | | List comments | `GET /spaces/<space>/change-requests/<cr>/comments?format=markdown&status=all` | bodies at `body.markdown`; location under `target.page`/`target.node`; poster at `postedBy.id` | | Reply to a comment | `POST /spaces/<space>/change-requests/<cr>/comments/<commentId>/replies` body `{"body":{"markdown":"…"}}` | | | Resolve a comment *(GATE)* | `PUT /spaces/<space>/change-requests/<cr>/comments/<commentId>` body `{"resolved":true}` | resolves unconditionally — no reply-first guard (enforce it yourself) | | Reply list (verify) | `GET /spaces/<space>/change-requests/<cr>/comments/<commentId>/replies` | confirm a reply exists before resolving | Not a GitBook API operation: any Slack/Channels action. Slack is sent **separately** (see "Slack is a stopgap"). ### Content-change ops (the `changes` array) Each item in `changes` is discriminated by `operation` (OpenAPI `ChangeRequestContentChange`): - **`update_page`** — update document and/or metadata on an existing page. - Body document: `{"operation":"update_page","page":"<pageId>","document":{"markdown":"…"}}`. `document` accepts **only** `{"markdown":"…"}` — **not** the node tree that `GET …/page` returns with `format=document` (pushing that 422s). When `document` is provided it replaces the whole page body — fetch, edit, push, or you drop blocks. - **Rename / metadata** (same op, no document required): set `title` and/or `slug`, plus optional `emoji`, `icon`, `description`, `hidden`, `noIndex`, `noRobotsIndex`, `condition` (`RevisionPageUpdate`). Title-only rename: set `title`, omit `slug`. URL-only: set `slug`, omit `title`. Example: `{"operation":"update_page","page":"<pageId>","title":"New title","slug":"new-slug"}`. - **Move**: `into` (parent page id, or `null` for root) and/or `at` (zero-based sibling index). - For Git-synced spaces, also update `SUMMARY.md` when the nav title/path should change. - **`insert_page`** — `{"operation":"insert_page","title":"…","document":{"markdown":"…"}}`. `into` (parent page ID) is **optional** — omit it to insert at the space root; `at` (index) is also optional. Title can come from `title`, markdown frontmatter, or the leading heading. - **`delete_page`** — `{"operation":"delete_page","page":"<pageId>"}`. This flow never deletes; documented for completeness. - **`insert_files`** — upload images/attachments into the CR: `{"operation":"insert_files","files":[{"name":"diagram.png","url":"https://…","ref":"diagram"}]}` (or `base64` + `contentType`, max 1MB). Prefer `url`. Reference as `![Alt](/files/<fileId>)` or via batch `ref`. Do **not** rely on inline `data:` URIs in markdown. - **`revert_page`** — `{"operation":"revert_page","page":"<pageId>"}` (optional `revision`, optional `block` array). Omitting `revision` reverts to the CR base revision. **`compat` query param** on `POST …/content`: pass `?compat=false` so the response includes the affected-page list (`ChangeRequestContentUpdate`). Default is still `true` today but will flip; prefer `compat=false` now. The markdown round-trip is **LOSSY** — see "Editing an existing page safely" before you re-push an edited page. ### API behaviors to watch - **Every endpoint returns JSON.** `GET /user` is just JSON with an `.id` — pipe every response through `jq`. - **The `authors` comment filter works server-side.** `GET …/comments?authors=<id>` is a real array query param (repeat `authors=` for several). You still pull **all** comments and split human vs agent on `postedBy.id` (see "Two operations") — a filter narrows, it doesn't classify — but the server-side filter is available if you want it. - **Nothing normalizes content for you.** The API does not strip the duplicated leading H1 or collapse multi-line `{% … %}` blocks before sending. **You must do those transforms yourself** before every push (see "Editing an existing page safely"). This is the easiest thing to get wrong — don't skip it. ## Surfacing the preview link (do this every time) **This rule is transport-agnostic — it applies whether the change request was pushed via this skill's `curl` calls, or via `configure-site`/`write-docs` over the GitBook MCP server.** The underlying gap is the same in both cases: nothing in the change-request response points at the rendered preview, so it's easy to file this under "REST-only demo detail" and skip it when the push actually happened over MCP. It isn't optional in either case. If you reach this skill's docs while working from `configure-site` or `write-docs`, translate the REST calls below to their MCP equivalents (`getSpaceById`, `list_sites`/`get_site_structure`, `getSiteById` via `invoke_operation`) rather than skipping the step because the transport doesn't match. A change request's own response only ever gives you `urls.app` — the link to the **editor / diff view** in the GitBook app. It is easy to stop there and assume that's "the link" for the CR. It isn't the link most people actually want: someone who isn't going to comment or edit just wants to **see the docs rendered with this change applied**, and that's a different URL that GitBook calls the **site preview**. The site preview link is **not exposed anywhere on the change-request object** — verified against the `ChangeRequest` schema, whose `urls` only has `app` and `location`. It lives on the **`Site`** object instead, nested under `urls.preview`, which you only ever see if you separately resolve the site behind the space. Nothing in the CR-creation or content-push flow points you at it, so it's easy to never discover it exists at all. Resolve it once per space (cache the result for the session) and mention it **alongside** `urls.app` every time you create a CR or push content to one: ```bash ORG=$(gbapi GET "/spaces/<space>" | jq -r .organization) SITE=$(gbapi GET "/orgs/$ORG/sites" | jq -r '.items[].id' | while read -r s; do gbapi GET "/orgs/$ORG/sites/$s/site-spaces" \ | jq -e --arg space "<space>" '.items[] | select(.space.id == $space)' >/dev/null \ && echo "$s" && break done) if [ -n "$SITE" ]; then # A public site's published URL needs no sign-in and never expires; anything else # (unlisted, visitor-auth, not yet published) has to go through the preview host. BASE=$(gbapi GET "/orgs/$ORG/sites/$SITE" \ | jq -r 'if .visibility == "public" and .urls.published then .urls.published else .urls.preview end') NUM=$(gbapi GET "/spaces/<space>/change-requests/<cr>" | jq -r .number) echo "${BASE%/}/~/changes/${NUM}/" # ← the preview link for THIS change request fi ``` - **The `~/changes/<number>/` segment is what scopes the link to your change request.** A bare site URL — `urls.published` or `urls.preview` — renders whatever the site currently holds, so it loads fine and shows the wrong thing. Both come back from the API with a trailing slash, so strip it before appending or you emit a double slash. - **`urls.published`** — the live site URL; only present once the site has been published. Prefer it when the site is public: no sign-in, no expiry, safe to paste anywhere. - **`urls.preview`** — the site preview host. Viewers still need access to the site and are asked to sign in, so it's a worse link to hand to someone — use it only when there's no public published URL. - **Preview only exists when the space is attached to a published docs site** — not for a bare space with no site, and GitBook itself disables the preview UI for share-link / visitor-auth sites. If the site-spaces search above finds nothing, say so plainly (*"this space isn't on a published site, so there's no rendered preview link — here's the editor link"*) rather than silently only giving `urls.app`. - If a space is unexpectedly attached to more than one site, resolve and mention all of them rather than picking one. Use the CR's `number`; its `id` works too but is longer. A **draft** change request previews fine — you don't have to open it first. **Check the link before you send it.** `curl -sL -o /dev/null -w '%{http_code}\n' "<url>"`. A 404 means the wrong number or the wrong site. A 200 is necessary but *not* sufficient — an archived CR returns 200 as well — so when it matters, fetch a page the CR touched and confirm it differs from the same path on the live site. Report both links together, e.g.: *"Change request #42 created — [review the diff](…urls.app) · [preview the rendered docs](https://docs.example.com/~/changes/42/)."* Note the preview link carries the `~/changes/42/` segment; a bare site URL is not a preview of this change request. ## Prerequisites - **`curl` and `jq`** on your `PATH`, and network access to `api.gitbook.com`. - **`GITBOOK_TOKEN`** in the repo-root `.env` (see "Auth"). Confirm with `gbapi GET /user` before running actions. - The **space ID** of the target space (and, for the demo, the page ID to update and a
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る