Skip to content

CONNECT AN API · JIRA · 10 MINUTES

Connect Jira

Jira needs a service account, not an API key, and the two sit on neighboring pages in the same admin console. Getting that right is most of this page. The token goes straight into your Enclave's vault and never reaches EKKA.

You need to be an organization admin at admin.atlassian.com.

What you'll have at the end

A service account that belongs to your company rather than to a person, its token in your Enclave's vault, a connection pinned to one Jira site, and a grant that says which agent may read it.


Step 1 · Create a service account, not an API key

Go to admin.atlassian.comDirectory → Service accounts.

There is a page next door that looks right and is not

Settings → API keys offers scopes like delete:accounts:admin and read:directories:admin. That key manages your organization: users, groups, domains and policies. It cannot read a single Jira issue, and it is far more dangerous than anything on this page.

You want Directory → Service accounts. Different page, different thing.

A service account is a machine identity your company owns. Nobody logs in as it, it has no mailbox, and it does not disappear when somebody leaves. That last part is the whole reason to use one: a token made from a personal Atlassian account dies the day that person's account is deactivated, usually at the worst moment.

Atlassian includes five service accounts at no extra charge, so this adds nothing to your Atlassian bill.

Click Create service account and give it a name you will recognize later, like EKKA Enclave.


Step 2 · Give it access to Jira

The next screen is Select app role for service account. It lists every app on your site with the role set to None.

App Role to pick
Jira User
everything else (Studio, Goals, Jira Service Management, Jira Product Discovery, Jira Administration, and the rest) leave at None

User access admin is the trap

The dropdown offers two roles and the admin-sounding one is wrong. Read Atlassian's own descriptions:

  • User access admin: "No app access. Can administer users and groups"
  • User: "Can access the app, with no app admin permissions"

Pick User. User access admin can manage people and read nothing.

Leave the Groups box empty to start. It restricts the account to specific projects, which is worth doing once your first call works, and it is one more thing to get wrong before then.

Click Create.

Three things must line up, and only one of them is the token

  1. Product access, the role you just picked
  2. Project permission, the project's own permission scheme
  3. Token scope, Step 3

All three, every time. A service account with a valid token and product access but no project role gets an empty search instead of a refusal, which looks like no results rather than a permission problem. An empty result is a permission answer, not an empty Jira.


Step 3 · Create the API token

Open the service account and choose Create credentials → API token.

One scope covers everything EKKA calls:

Scope What it buys
read:jira-work searchIssues, getIssue, listComments, listProjects, listTransitions, which is every operation in this entry

Scopes are fixed the moment the token is created

You cannot add a scope to a token afterwards. Changing your mind means creating a new token and connecting again. A service account can hold several tokens, and one token's scopes never change, so tick every scope you expect to need while you are on this screen.

Set the expiry. Atlassian allows 1 to 365 days and defaults to a year. Put the date in a calendar the day you create it, because when it lapses Jira starts refusing every call for authentication and nothing else explains why.

The token is shown once. Copy it now.

Never put a token on a command line

ekka api connect prompts for it with the input hidden. Anything typed as an argument lands in your shell history and in the process list, where every other user on the machine can read it.


Step 4 · Find your cloud id

Every Atlassian site has an id, and the API is addressed by that id rather than by your site name. While signed in, open:

https://<your-site>.atlassian.net/_edge/tenant_info

It answers with one line:

{"cloudId":"e5367508-6411-4351-b33a-043f1bce10b8"}

Step 5 · Connect

site is your site name, the part before .atlassian.net. It exists so the grant command has something a person can type.

ekka api connect jira \
  --input site=acme \
  --input cloudId=e5367508-6411-4351-b33a-043f1bce10b8

It asks for the credential on a hidden prompt. Paste the token on its own.

The credential is the token alone, with no email and no colon

A service account has no password login, so there is no email to pair the token with. Atlassian's gateway takes it as an Authorization: Bearer header, and that is what EKKA sends.

A personal API token from id.atlassian.com is the other shape: it needs basic auth as email:token. It works, and it is what a single developer testing something would use, but it dies with that person's account and is not what this entry is built for.

The connection is named after your site:

✓ connection written, pinned to jira@1

  Grant your agent the right to use it:
    ekka gate grant add --type api --instance <your api gate> \
      --resource apis/jira-acme --capability api.read --no-fingerprint

Why the cloud id is asked twice

The cloud id sits inside the URL path, so a plan step has to supply it. Answering it here as well pins the connection: the Enclave refuses any call naming a different cloud id, before the request is sent.

This matters because one token can often see several Atlassian sites. Without the pin, a single grant would reach all of them. With it, apis/jira-acme can only ever reach that one site, and a plan that names another gets API_RULE_DENIED before a connection is opened.

Two Jira sites? Connect twice. Each gets its own name and its own grant.


Step 6 · Grant

Run the command connect printed. api.read is all this entry can use: the row is read-only, every operation is a GET, so no grant on it can change anything in Jira.


Step 7 · Check it

ekka api test jira-acme

Ask for it by the connection name, not the API name

The connection is named after your site, so it is jira-acme, not jira.


Step 8 · Use it in a plan

ekka api spec jira

prints the step for every operation. A search looks like this:

{
  "action_ref": "ekka.gate.api.v1",
  "target": "<your Enclave's api gate>",
  "op": "read",
  "call": "searchIssues",
  "inputs": {
    "resource": "apis/jira-acme",
    "cloudId": "e5367508-6411-4351-b33a-043f1bce10b8",
    "jql": "project = ENG AND status != Done ORDER BY updated DESC",
    "fields": "summary,status,assignee,updated",
    "maxResults": "25"
  }
}

resource names the connection. cloudId must match the one it is pinned to. Everything else is a filter.

What you can call

Call What it does Needs
searchIssues Issues matching a JQL query. Page with nextPageToken; the response says isLast. cloudId, jql
getIssue One issue in full, by key (ENG-1234) or numeric id. cloudId, issueIdOrKey
listComments The comments on one issue. cloudId, issueIdOrKey
listProjects The projects this credential can see. cloudId
listTransitions The workflow transitions available on one issue right now. cloudId, issueIdOrKey

jql must be bounded, or Jira refuses the search

Atlassian retired the old search endpoint in 2025, and its replacement refuses a query that would scan everything. A bare sort is not enough:

ORDER BY created DESC                      ✗ refused
created >= -365d ORDER BY created DESC     ✓
project = ENG AND status != Done           ✓

Any real clause makes it bounded. Jira names the problem in errorMessages when it refuses, so a failing plan tells you which query it disliked.

Ask for less

Responses are capped, and a capped read is marked truncated in the receipt. A tight jql plus a fields list beats fetching whole issues and throwing most of them away.


When something is wrong

A search comes back empty when you expected issues. This is the common one, and it is almost never the token. The service account has product access and no permission to the project, because the project's permission scheme does not grant Browse Projects to this account. Jira answers such a search with no issues rather than a refusal, which is why it looks like an empty Jira. Add the account to a project role or a group in the project's own settings.

Widening the token's scopes will not fix it. Scope decides what kind of thing the token may ask for; permission decides which of those things exist for this account.

Every call is refused for authentication. Two causes, both worth checking:

  • The token was sent to <your-site>.atlassian.net. A scoped token only authenticates against the api.atlassian.com gateway, and the site host refuses it forever. EKKA always uses the gateway, so if you see this from your own testing, that is why.
  • The token expired. They last 1 to 365 days.

One call is refused while the others work. A missing scope, and a token's scopes cannot be changed. Create a new token with read:jira-work, then ekka api connect jira again.

API_RULE_DENIED, "the request was refused before it was sent". The plan named a different cloud id from the one this connection is pinned to. That is the pin doing its job. Check cloudId in the step, or connect a second time for the other site.

searchIssues is refused outright. Almost always an unbounded jql. See the warning above: the query needs a real clause, not only an ORDER BY.

ekka api test jira says this machine has no connection for that API. Ask for it by the connection name, ekka api test jira-acme.

Jira Data Center and Jira Server

This entry is for Jira Cloud. A self-hosted Jira lives on your own host, so it needs its own catalog entry. That is a row you add yourself, not a change to EKKA. See Add your own API.