atlassian-rest-ops
Overview
This skill lets an agent perform any Confluence Cloud (v2) or Jira Cloud (v3) operation by calling the REST API directly with curl — no SDK, no pip dependency. The REST API is the complete API surface, including writes such as creating a Confluence page. The agent consumes the credentials the caller injected, looks up an endpoint in a bundled OpenAPI spec via an endpoint index + a $ref-resolver, constructs the curl, and parses the JSON. Confluence and Jira differ on several axes (base URL, pagination, errors, rich-text), so this skill carries per-API patterns rather than one generic shape.
When to activate
- ✅ Performing a Confluence Cloud v2 operation (create/get/update/search pages, spaces, attachments, …).
- ✅ Performing a Jira Cloud v3 operation (create/search/transition issues, comments, …).
- ✅ A write or operation you need done programmatically against the Atlassian REST API (e.g. creating a Confluence page).
Do NOT activate when:
- The target is Atlassian Server / Data Center (this skill is Cloud + API-token only).
- You only need credential setup — credentials are provided by the caller; this skill does not provision or resolve them (see
references/credentials.md for the contract it consumes).
Workflow
Step 1 — Receive the injected credentials
The caller has already resolved the account and injected what this operation needs — consume it; do not look for a record yourself. You receive from context:
base_url and email (and the capability the account acts under) — as context values, not read from a file.
- The token, by an ordered load rule the context carries the variable NAME for. Resolve that name as: the project-level
.env value if that file exists and defines the var, else the environment variable of that name — project .env is tried first (that is how a project .env overrides a global env var). The token value is never in the context prose — only its variable name; curl reads the value from the environment. The project root is supplied by the context; perform no scope resolution or directory walk to find the .env, and it is project .env, not .envrc.
Then bridge these into the example scripts' fixed vars ATLASSIAN_EMAIL / ATLASSIAN_BASE_URL / ATLASSIAN_API_TOKEN (same bridge as before; only the inputs now come from context). Full contract + the bridge: references/credentials.md.
Step 2 — Find the endpoint
Scan assets/endpoint-index.md (one line per operation: METHOD path — summary (operationId)) for the operation you need. Never load a full spec into context — they are multi-MB.
Step 3 — Resolve the call shape
Run the resolver: python3 scripts/endpoint.py <confluence|jira> <operationId>. It dereferences that one operation from the bundled OpenAPI spec ($ref-resolved) and prints the method, path, params, request/response schema, and a curl skeleton. (scripts/endpoint.py + its validation are added during augmentation; see Progressive disclosure.)
Step 4 — Construct + run the curl
Apply the per-API patterns from references/patterns.md:
- Auth (both):
curl -u "$email:$<token_env>" (HTTP Basic).
- Base URL: Confluence →
<base_url>/wiki/api/v2/<path>; Jira → <base_url>/<path> (paths already include /rest/api/3/…).
- Rich-text bodies (the easy-to-get-wrong part) — see
references/rich-text.md: Jira uses ADF as a raw JSON object; Confluence uses {representation, value} where atlas_doc_format means the ADF JSON stringified and storage means an XHTML string. If your source is Markdown (e.g. a formatted comment), convert it to ADF with python3 scripts/md_to_adf.py < body.md rather than sending the raw Markdown (which renders ##/**/| literally).
- Send/accept
application/json.
Step 5 — Handle the response
- Pagination: Confluence → cursor + follow
_links.next; Jira → startAt/maxResults (+ total/isLast), or nextPageToken on newer endpoints. See references/patterns.md.
- Errors: Jira returns
ErrorCollection {errorMessages, errors, status}; Confluence returns inline error JSON. On HTTP 429 (Jira) back off and retry.
Rules
Hard rules (never violate):
- No SDK, no
pip. API calls use curl; the only helper is scripts/endpoint.py (python3 stdlib only).
- Never read or print the token value. Reference it only as
$<token_env> in a curl -u argument; the subprocess reads it from the environment. The token lives only in .env (gitignored).
- Resolve before constructing. Build a request body/params from the
$ref-resolved schema (Step 3) or the verified rich-text formats — never from a guessed/remembered field set.
- Per-API, not generic. Apply Confluence vs Jira patterns correctly (base URL, pagination, rich-text, errors) — they differ.
- Rich-text: Jira ADF = raw object; Confluence
atlas_doc_format = stringified ADF in value. Do not mix them.
- This skill never writes credentials. Credentials are provided by the caller; this skill never provisions or resolves them.
Preferences (override-able):
- Prefer the newer Jira issue search
GET /rest/api/3/search/jql (nextPageToken) over the legacy /search (startAt).
- Request only needed fields (
fields= on Jira, body-format= on Confluence) to keep responses small.
Gotchas
- Rich-text mismatch. Sending a raw ADF object to Confluence
atlas_doc_format (instead of a stringified one), or a stringified blob to Jira (instead of a raw object), is the most common failure. Confluence value is always a string.
- Base-URL difference. Confluence paths are relative to
…/wiki/api/v2; Jira paths already include /rest/api/3. Don't double-prefix or drop /wiki.
$ref chains. The request body is rarely inline in the spec — it $refs a schema (which may $ref further). Use the resolver (Step 3); a raw grep of the spec won't give you the full shape.
- Legacy vs new Jira search.
GET /rest/api/3/search is being deprecated for GET /rest/api/3/search/jql; pagination differs (startAt vs nextPageToken).
- Rate limits. Jira returns HTTP 429 with a
Retry-After; honor it. Confluence may return 413 on oversized bodies.
- Confluence delete is two-stage (verified live, applies to pages, blogposts, and other content).
DELETE /pages/{id} (or /blogposts/{id}) only trashes it (returns 204, but GET still returns it with status: trashed). To remove permanently, call DELETE /<type>/{id}?purge=true — which only works once trashed (purge-without-trash is a no-op). A plain delete is not full removal.
- Jira
/search/jql rejects unbounded JQL (verified live). Ordering-only or empty JQL → 400 ErrorCollection ("Unbounded JQL queries are not allowed here"). Always include a search restriction (e.g. project = X, created >= -30d).
Anti-patterns
- Guessing the body. Don't hand-write a request body from memory of "what Jira issues look like" — resolve the schema; field requirements change per project/screen.
- Echoing the token. Never
echo $TOKEN, never paste it into a command you print, never write its value into any file. Reference the env var inside curl only.
- Loading the whole spec. Don't
cat/read the multi-MB OpenAPI JSON into context — scan the index, resolve one op.
- One-size-fits-all. Don't apply Jira's pagination/errors/ADF to Confluence or vice-versa.
- SDK creep. Don't reach for
atlassian-python-api / pip install — curl + the resolver cover every endpoint.
Output
This skill produces API side effects (the requested Confluence/Jira operation) and returns the parsed JSON response to the calling agent. It writes no files of its own (it only consumes the caller-injected credentials). For write operations it reports the created/updated resource (id, key, URL); for reads it returns the result set, following pagination as needed. The abstract consumer is the calling agent (or a sub-agent) that needs the operation performed; secrets never enter that output.
Related
references/credentials.md — the credential contract this skill consumes (caller-injected fields + the ordered token-load rule + the bridge into the fixed vars).
- The REST-direct + bundled-OpenAPI-spec pattern generalizes to other API providers.
Progressive disclosure
Heavy content lives in subfolders, loaded only on demand:
references/patterns.md — per-API patterns: auth, base URL, pagination (Confluence cursor vs Jira offset/token), response envelopes, errors, rate limits, expand. Load in Steps 4–5.
references/rich-text.md — ADF (Jira, raw object) and Confluence body representations (storage / atlas_doc_format, value-as-string) with worked examples + the cross-API gotcha. Load when building a request body.
references/credentials.md — the credential contract this skill consumes: caller-injected fields, the ordered token-load rule, the bridge into the fixed vars, honest-secret handling. Load in Step 1.
references/sources.md — provenance (Atlassian official docs + the bundled spec versions).
Added during augmentation (Phase 2.C), referenced above:
assets/confluence-v2.json, assets/jira-v3.json — the bundled OpenAPI specs (authoritative; queried, never loaded wholesale).
assets/endpoint-index.md — one line per operation, for discovery (Step 2).
scripts/endpoint.py + scripts/endpoint.py.validation.md — the python3 $ref-resolver (Step 3).
scripts/<op>.sh + .validation.md — the four validated example curls (create Confluence page, Confluence list, Jira create issue, Jira search).
scripts/md_to_adf.py + .validation.md — a stdlib Markdown -> ADF converter. Pipe a Markdown comment/description through it to post it as native ADF (headings, bold, code, links, lists, GFM tables render instead of showing raw ##/|): python3 scripts/md_to_adf.py < comment.md -> the ADF object for the Jira {"body": ...}.
Standalone usage (optional, not required)
This is a convenience for a human running the skill by hand outside agent-flow — it is not a dependency of the skill. The skill's normative contract is caller-injection (Step 1); this appendix is only the manual-operator bridge.
To run by hand, populate the three fixed vars yourself from a .service-accounts.yaml record + its .env token, then run the scripts. An example record:
accounts:
- name: atlassian-work
provider: atlassian
base_url: https://workco.atlassian.net
email: me@workco.com
token_env: ATLASSIAN_WORK_API_TOKEN # the var holding the token value; value lives in .env (gitignored)
set -a; source .env; set +a # loads $ATLASSIAN_WORK_API_TOKEN, never prints it
export ATLASSIAN_EMAIL="me@workco.com"
export ATLASSIAN_BASE_URL="https://workco.atlassian.net"
export ATLASSIAN_API_TOKEN="$ATLASSIAN_WORK_API_TOKEN"
bash scripts/create-confluence-page.sh <space-id> "<title>"
Get an API token at id.atlassian.com → Security → "Create API token". The value is referenced by name only, never printed.
Body budget
description ≤ 1,024 chars (agentskills.io cap).
- Body ≤ ~500 lines / 5,000 tokens — kept in context every turn; per-API detail lives in
references/.
assets/*.json are large (queried on disk, never loaded into context).
1---2name: atlassian-rest-ops3description: Use when calling the Atlassian Cloud REST API directly — Confluence Cloud v2 (pages, spaces, search) or Jira Cloud v3 (issues, JQL search, comments) — to perform operations programmatically, including writes such as creating a Confluence page. Calls REST with curl (no SDK, no pip), authenticating with a Cloud email + API token. Constructs any of the 800+ endpoints from a bundled OpenAPI spec via an endpoint index + a $ref-resolver, with per-API patterns (base URL, pagination, errors, rate limits) and the ADF / storage rich-text formats handled explicitly. Consumes caller-injected credentials (base_url, email, and a token resolved by variable name) — it does not provision or resolve them; the token value is read only by curl, never printed.4---56# atlassian-rest-ops78## Overview910This skill lets an agent perform **any** Confluence Cloud (v2) or Jira Cloud (v3) operation by calling the **REST API directly with `curl`** — no SDK, no `pip` dependency. The REST API is the complete API surface, including writes such as creating a Confluence page. The agent consumes the credentials the caller injected, looks up an endpoint in a bundled OpenAPI spec via an **endpoint index + a `$ref`-resolver**, constructs the `curl`, and parses the JSON. Confluence and Jira differ on several axes (base URL, pagination, errors, rich-text), so this skill carries **per-API** patterns rather than one generic shape.1112## When to activate1314- ✅ Performing a Confluence Cloud v2 operation (create/get/update/search pages, spaces, attachments, …).15- ✅ Performing a Jira Cloud v3 operation (create/search/transition issues, comments, …).16- ✅ A write or operation you need done programmatically against the Atlassian REST API (e.g. creating a Confluence page).1718**Do NOT activate when:**1920- The target is Atlassian **Server / Data Center** (this skill is Cloud + API-token only).21- You only need credential setup — credentials are provided by the caller; this skill does not provision or resolve them (see `references/credentials.md` for the contract it consumes).2223## Workflow2425### Step 1 — Receive the injected credentials2627The caller has already resolved the account and injected what this operation needs — **consume** it; do **not** look for a record yourself. You receive from context:2829- **`base_url`** and **`email`** (and the **capability** the account acts under) — as context values, not read from a file.30- **The token, by an ordered load rule** the context carries the **variable NAME** for. Resolve that name as: the project-level **`.env` value if that file exists** and defines the var, **else** the **environment variable** of that name — project `.env` is tried first (that is how a project `.env` overrides a global env var). The token **value** is never in the context prose — only its variable name; `curl` reads the value from the environment. The project root is supplied by the context; perform **no** scope resolution or directory walk to find the `.env`, and it is project `.env`, not `.envrc`.3132Then bridge these into the example scripts' fixed vars `ATLASSIAN_EMAIL` / `ATLASSIAN_BASE_URL` / `ATLASSIAN_API_TOKEN` (same bridge as before; only the inputs now come from context). Full contract + the bridge: [`references/credentials.md`](references/credentials.md).3334### Step 2 — Find the endpoint3536Scan [`assets/endpoint-index.md`](assets/) (one line per operation: `METHOD path — summary (operationId)`) for the operation you need. Never load a full spec into context — they are multi-MB.3738### Step 3 — Resolve the call shape3940Run the resolver: `python3 scripts/endpoint.py <confluence|jira> <operationId>`. It dereferences that one operation from the bundled OpenAPI spec (`$ref`-resolved) and prints the method, path, params, request/response schema, and a `curl` skeleton. (`scripts/endpoint.py` + its validation are added during augmentation; see Progressive disclosure.)4142### Step 4 — Construct + run the `curl`4344Apply the **per-API** patterns from [`references/patterns.md`](references/patterns.md):4546- **Auth** (both): `curl -u "$email:$<token_env>"` (HTTP Basic).47- **Base URL:** Confluence → `<base_url>/wiki/api/v2/<path>`; Jira → `<base_url>/<path>` (paths already include `/rest/api/3/…`).48- **Rich-text bodies** (the easy-to-get-wrong part) — see [`references/rich-text.md`](references/rich-text.md): Jira uses **ADF as a raw JSON object**; Confluence uses `{representation, value}` where `atlas_doc_format` means the ADF JSON **stringified** and `storage` means an XHTML string. If your source is **Markdown** (e.g. a formatted comment), convert it to ADF with `python3 scripts/md_to_adf.py < body.md` rather than sending the raw Markdown (which renders `##`/`**`/`|` literally).49- Send/accept `application/json`.5051### Step 5 — Handle the response5253- **Pagination:** Confluence → cursor + follow `_links.next`; Jira → `startAt`/`maxResults` (+ `total`/`isLast`), or `nextPageToken` on newer endpoints. See `references/patterns.md`.54- **Errors:** Jira returns `ErrorCollection` `{errorMessages, errors, status}`; Confluence returns inline error JSON. On HTTP 429 (Jira) back off and retry.5556## Rules5758**Hard rules (never violate):**5960- **No SDK, no `pip`.** API calls use `curl`; the only helper is `scripts/endpoint.py` (`python3` **stdlib** only).61- **Never read or print the token value.** Reference it only as `$<token_env>` in a `curl -u` argument; the subprocess reads it from the environment. The token lives only in `.env` (gitignored).62- **Resolve before constructing.** Build a request body/params from the `$ref`-resolved schema (Step 3) or the verified rich-text formats — never from a guessed/remembered field set.63- **Per-API, not generic.** Apply Confluence vs Jira patterns correctly (base URL, pagination, rich-text, errors) — they differ.64- **Rich-text:** Jira ADF = raw object; Confluence `atlas_doc_format` = stringified ADF in `value`. Do not mix them.65- **This skill never writes credentials.** Credentials are provided by the caller; this skill never provisions or resolves them.6667**Preferences (override-able):**6869- Prefer the newer Jira issue search `GET /rest/api/3/search/jql` (`nextPageToken`) over the legacy `/search` (`startAt`).70- Request only needed fields (`fields=` on Jira, `body-format=` on Confluence) to keep responses small.7172## Gotchas7374- **Rich-text mismatch.** Sending a raw ADF object to Confluence `atlas_doc_format` (instead of a stringified one), or a stringified blob to Jira (instead of a raw object), is the most common failure. Confluence `value` is **always a string**.75- **Base-URL difference.** Confluence paths are relative to `…/wiki/api/v2`; Jira paths already include `/rest/api/3`. Don't double-prefix or drop `/wiki`.76- **`$ref` chains.** The request body is rarely inline in the spec — it `$ref`s a schema (which may `$ref` further). Use the resolver (Step 3); a raw `grep` of the spec won't give you the full shape.77- **Legacy vs new Jira search.** `GET /rest/api/3/search` is being deprecated for `GET /rest/api/3/search/jql`; pagination differs (`startAt` vs `nextPageToken`).78- **Rate limits.** Jira returns HTTP 429 with a `Retry-After`; honor it. Confluence may return 413 on oversized bodies.79- **Confluence delete is two-stage** (verified live, applies to **pages, blogposts, and other content**). `DELETE /pages/{id}` (or `/blogposts/{id}`) only **trashes** it (returns 204, but `GET` still returns it with `status: trashed`). To remove permanently, call `DELETE /<type>/{id}?purge=true` — which only works **once trashed** (purge-without-trash is a no-op). A plain delete is not full removal.80- **Jira `/search/jql` rejects unbounded JQL** (verified live). Ordering-only or empty JQL → `400 ErrorCollection` ("Unbounded JQL queries are not allowed here"). Always include a search restriction (e.g. `project = X`, `created >= -30d`).8182## Anti-patterns8384- **Guessing the body.** Don't hand-write a request body from memory of "what Jira issues look like" — resolve the schema; field requirements change per project/screen.85- **Echoing the token.** Never `echo $TOKEN`, never paste it into a command you print, never write its value into any file. Reference the env var inside `curl` only.86- **Loading the whole spec.** Don't `cat`/read the multi-MB OpenAPI JSON into context — scan the index, resolve one op.87- **One-size-fits-all.** Don't apply Jira's pagination/errors/ADF to Confluence or vice-versa.88- **SDK creep.** Don't reach for `atlassian-python-api` / `pip install` — `curl` + the resolver cover every endpoint.8990## Output9192This skill produces **API side effects** (the requested Confluence/Jira operation) and returns the parsed JSON response to the calling agent. It writes no files of its own (it only *consumes* the caller-injected credentials). For write operations it reports the created/updated resource (id, key, URL); for reads it returns the result set, following pagination as needed. The abstract consumer is the calling agent (or a sub-agent) that needs the operation performed; secrets never enter that output.9394## Related9596- [`references/credentials.md`](references/credentials.md) — the credential contract this skill consumes (caller-injected fields + the ordered token-load rule + the bridge into the fixed vars).97- The REST-direct + bundled-OpenAPI-spec pattern generalizes to other API providers.9899## Progressive disclosure100101Heavy content lives in subfolders, loaded only on demand:102103- [`references/patterns.md`](references/patterns.md) — per-API patterns: auth, base URL, pagination (Confluence cursor vs Jira offset/token), response envelopes, errors, rate limits, `expand`. Load in Steps 4–5.104- [`references/rich-text.md`](references/rich-text.md) — ADF (Jira, raw object) and Confluence body representations (`storage` / `atlas_doc_format`, value-as-string) with worked examples + the cross-API gotcha. Load when building a request body.105- [`references/credentials.md`](references/credentials.md) — the credential contract this skill consumes: caller-injected fields, the ordered token-load rule, the bridge into the fixed vars, honest-secret handling. Load in Step 1.106- [`references/sources.md`](references/sources.md) — provenance (Atlassian official docs + the bundled spec versions).107108**Added during augmentation (Phase 2.C), referenced above:**109110- `assets/confluence-v2.json`, `assets/jira-v3.json` — the bundled OpenAPI specs (authoritative; queried, never loaded wholesale).111- `assets/endpoint-index.md` — one line per operation, for discovery (Step 2).112- `scripts/endpoint.py` + `scripts/endpoint.py.validation.md` — the `python3` `$ref`-resolver (Step 3).113- `scripts/<op>.sh` + `.validation.md` — the four validated example `curl`s (create Confluence page, Confluence list, Jira create issue, Jira search).114- `scripts/md_to_adf.py` + `.validation.md` — a stdlib Markdown -> ADF converter. Pipe a Markdown comment/description through it to post it as native ADF (headings, bold, code, links, lists, GFM tables render instead of showing raw `##`/`|`): `python3 scripts/md_to_adf.py < comment.md` -> the ADF object for the Jira `{"body": ...}`.115116## Standalone usage (optional, not required)117118This is a convenience for a **human running the skill by hand** outside agent-flow — it is **not a dependency of the skill**. The skill's normative contract is caller-injection (Step 1); this appendix is only the manual-operator bridge.119120To run by hand, populate the three fixed vars yourself from a `.service-accounts.yaml` record + its `.env` token, then run the scripts. An example record:121122```yaml123accounts:124 - name: atlassian-work125 provider: atlassian126 base_url: https://workco.atlassian.net127 email: me@workco.com128 token_env: ATLASSIAN_WORK_API_TOKEN # the var holding the token value; value lives in .env (gitignored)129```130131```bash132set -a; source .env; set +a # loads $ATLASSIAN_WORK_API_TOKEN, never prints it133export ATLASSIAN_EMAIL="me@workco.com"134export ATLASSIAN_BASE_URL="https://workco.atlassian.net"135export ATLASSIAN_API_TOKEN="$ATLASSIAN_WORK_API_TOKEN"136bash scripts/create-confluence-page.sh <space-id> "<title>"137```138139Get an API token at id.atlassian.com → Security → "Create API token". The value is referenced by name only, never printed.140141## Body budget142143- `description` ≤ 1,024 chars (agentskills.io cap).144- Body ≤ ~500 lines / 5,000 tokens — kept in context every turn; per-API detail lives in `references/`.145- `assets/*.json` are large (queried on disk, never loaded into context).