Liking cljdoc? Tell your friends :D

clojure-atlassian-sdk

A standalone Clojure SDK for the Atlassian Cloud REST APIs, built on hato (an idiomatic wrapper over the JDK 11 java.net.http client — no Apache HttpClient on the classpath) and cheshire for JSON.

Jira (575 fns), Confluence (334 fns — both the modern v2 and the legacy v1 surfaces), and Bitbucket (316 fns) are implemented comprehensively: every user-token-accessible endpoint, generated from the official OpenAPI specs and verified against them.

Layout

src/atlassian/
  config.clj            env/.env resolution, auth header, URL bases (no network)
  client.clj            the ONLY namespace that touches the network (hato)
  jira/
    client.clj          Jira client handle + the `request` seam
    adf.clj             Atlassian Document Format <-> plain text helpers
    issues.clj          one namespace per endpoint group...
    search.clj          ...search/JQL
    comments.clj
    attachments.clj
    ...                 (24 endpoint namespaces in total)
  confluence/
    client.clj          Confluence client handle + `request` (:api :v2 default | :v1)
    v2/                 12 namespaces over the modern /wiki/api/v2 surface
    v1/                 10 namespaces over the legacy /wiki/rest/api surface (CQL search, etc.)
  bitbucket/
    client.clj          Bitbucket client handle + `request` (single api.bitbucket.org/2.0 root)
    repositories.clj    one namespace per endpoint group...
    pullrequests.clj    ...pull requests, commits, issues, pipelines...
    ...                 (14 endpoint namespaces in total)

Confluence

(require '[atlassian.confluence.client :as cf]
         '[atlassian.confluence.v2.pages :as pages]
         '[atlassian.confluence.v1.search :as search])

(def c (cf/client))                        ; same site + API token as Jira

;; v2 (modern, cursor-paginated)
(pages/get-pages c {:limit 25})
(pages/create-page c {:spaceId "123456" :title "From Clojure"
                      :body {:representation "storage" :value "<p>hi</p>"}})

;; page through cursors
(let [r (pages/get-pages c {:limit 25})]
  (cf/next-cursor r))                       ; -> cursor string for the next page

;; v1 CQL search (cql is a positional required arg)
(search/search-by-cql c "type=page and space=DEMO" {:limit 10})

The Confluence client routes on :api:v2 (default, /wiki/api/v2) or :v1 (/wiki/rest/api). Namespaces are split by version to avoid collisions (atlassian.confluence.v2.* vs .v1.*).

The endpoint namespaces were generated from the official Jira OpenAPI spec (resources/openapi/jira-platform.v3.json, sliced per-group under resources/openapi/groups/). Each function is a thin, typed call over atlassian.jira.client/request.

Bitbucket

(require '[atlassian.bitbucket.client :as bb]
         '[atlassian.bitbucket.repositories :as repos]
         '[atlassian.bitbucket.pullrequests :as prs])

(def c (bb/client))                        ; resolves Bitbucket creds from env/.env

;; Most resources are workspace- and repo-scoped; those are positional args.
(repos/get-repositories-by-workspace c "your-workspace" {:pagelen 25 :sort "-updated_on"})
(prs/list-pullrequests c "your-workspace" "your-repo" {:state "OPEN"})
(prs/get-pullrequests-by-workspace-repo-slug-pull-request-id c "your-workspace" "your-repo" 42)

;; Pagination: list responses are {:values [...] :next "<full-url>" ...}.
;; The :next link is a complete URL, not a cursor — follow it directly:
(let [page1 (repos/get-repositories-by-workspace c "your-workspace" {:pagelen 25})]
  (when-let [nxt (bb/next-url page1)]
    (bb/request-url c nxt)))                ; -> the next page

Bitbucket has a single API surface (api.bitbucket.org/2.0), so there is no :api routing key. Every GET takes an optional trailing query map (:fields, :page, :pagelen, :q, :sort).

Credentials

Set these in a local, gitignored .env (or the process environment):

JIRA_BASE_URL=https://your-site.atlassian.net
JIRA_USER_EMAIL=you@example.com
JIRA_API_TOKEN=your-api-token          # https://id.atlassian.com/manage-profile/security/api-tokens

Cloud uses Basic auth (base64(email:token)); Data Center PATs use Bearer (JIRA_INSTANCE_TYPE=datacenter). Confluence reuses the same site + token.

Bitbucket is a separate service with its own credentials (it does not reuse the Jira vars):

BITBUCKET_USERNAME=you@example.com     # Atlassian account email (or Bitbucket username for an app password)
BITBUCKET_API_TOKEN=your-api-token     # https://id.atlassian.com/manage-profile/security/api-tokens
BITBUCKET_WORKSPACE=your-workspace     # optional default workspace slug

It authenticates with Basic base64(username:token) — an Atlassian API token or a classic app password. For OAuth/Forge access tokens set BITBUCKET_AUTH_SCHEME=bearer and supply only the token.

Usage

(require '[atlassian.jira.client :as jira]
         '[atlassian.jira.issues :as issues]
         '[atlassian.jira.search :as search]
         '[atlassian.jira.adf :as adf])

(def c (jira/client))                      ; resolves creds from env/.env

;; Read an issue
(issues/get-issue c "PROJ-123" {:fields "summary,status,assignee"})

;; Search (bounded JQL required by the enhanced /search/jql endpoint)
(search/search-jql c {:jql "project = PROJ order by created DESC" :maxResults 10})

;; Create an issue (description is ADF)
(issues/create-issue c
  {:fields {:project {:key "PROJ"}
            :issuetype {:name "Task"}
            :summary "Created from the Clojure SDK"
            :description (adf/text->adf "Hello from clojure-atlassian-sdk.")}})

Every function delegates to atlassian.jira.client/request, which returns parsed, keywordized JSON on 2xx and throws ex-info {:status :method :url :body} on errors (pass :throw? false in the request opts to get {:error ...} instead).

Installing

;; deps.edn
net.clojars.deadmeme5441/atlassian-sdk {:mvn/version "0.3.0"}

hato and cheshire come transitively. Build/publish locally with tools.build:

clojure -T:build jar        # -> target/atlassian-sdk-<version>.jar (src only)
clojure -T:build install    # install into local ~/.m2
clojure -T:build deploy     # push to Clojars (needs CLOJARS_USERNAME / _PASSWORD)

The jar ships src only — the OpenAPI spec and per-group EDN under resources/ are build-time tooling for the generators in dev/, never bundled.

Testing & verification

clojure -M:test                            # pure unit + live read-only sweeps
                                           #   (live sweeps self-skip when creds are unset)
clojure -M:dev -m verify-spec              # static: Jira — every fn's method+path matches the spec
clojure -M:dev -m verify-spec-confluence   # static: Confluence v2+v1 — same check, keyed by :api
clojure -M:dev -m verify-spec-bitbucket    # static: Bitbucket — same check, single API surface
clojure -M:dev -m find-recursion           # static: no infinite same-arity self-recursion

The verify-spec* tasks are the conformance gate: they reconstruct each function's {:method :path} and confirm a matching operation exists in the OpenAPI spec. Current status: Jira 575/575, Confluence 334/334, and Bitbucket 316/316 matched, 1:1, zero mismatches. They check method + path structure (not every query-param/body-field name, which the functions pass through and document from the schema).

Coverage notes

Connect/Forge app-only endpoints (app properties, dynamic modules, app migration, app-scoped custom-field options, etc.) are intentionally omitted — they require app authentication, not user API tokens. For Bitbucket the same applies to the addon and hosted-properties surfaces. See the dev/extract* namespaces for the exact partition and the skipped tag list per product.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close