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.
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)
(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.
(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).
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.
(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).
;; 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.
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).
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
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |