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.com → Directory → 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
- Product access, the role you just picked
- Project permission, the project's own permission scheme
- 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:
It answers with one line:
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.
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¶
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¶
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 theapi.atlassian.comgateway, 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.