The
/jira/* routes exist on the standalone orchestrator only. A library-mode mount (createOrchestrator()) does not serve them. They are internal-only: they don’t appear in the public API reference, and they answer 403 when DEMO_MODE=1 is set.How it works
The integration runs a three-stage state machine driven by Jira ticket statuses and comment events. Review — The agent reads the full ticket context (summary, description, attachments, complete comment thread) and posts one of two comments:- A plan: “Here’s what I’ll do — let me know when to proceed.”
- Questions: “I need more info on X before I can start.”
JIRA_EXECUTE_STATUS (In Progress) so the board reflects live progress. The agent then runs with full tool access, including the complete comment history so any clarifications posted as replies reach the agent. It reads the current site state, applies ops, posts a comment listing every change with clickable preview links, then parks the ticket at JIRA_PREVIEW_STATUS.
Preview — The reporter reviews the draft in the editor. An approval comment triggers publish and moves the ticket to Done. Any other comment re-runs execute with the new instruction.
Approval keywords
The dispatcher uses keyword matching (not LLM inference) to detect approval. A comment approves only when approval is all it says: at least one of these words, plus nothing but courtesy (please, thanks, it, the changes…):go only matches as a whole token)
Self-loop prevention
The agent detects its own comments using both the author account ID and the comment body. A comment is only treated as agent-authored when the JiraaccountId matches JIRA_AGENT_ACCOUNT_ID AND the comment body starts with a known agent headline (e.g. “Draft updated. Ready for your review.” or “Review complete — ready to proceed.”). The dual check prevents false positives on solo / dev Jira tenants where the operator and agent share the same account — reporter replies from that account are still treated as reporter input, not self-loops.
Prerequisites
- A Jira Cloud or Jira Server / Data Center instance
- An API token (Cloud) or Personal Access Token (Server/DC)
ANTHROPIC_API_KEYorOPENAI_API_KEYin the orchestrator environment — the Jira processor uses the same AI backend as the chat pipeline; without at least one key it will refuse every ticket with an error- Orchestrator reachable from Jira’s webhook delivery (for webhook mode) or able to reach Jira (for polling mode)
Cloud vs Server/DC auth
The client detects which to use based on whether
JIRA_USER_EMAIL is set.
Configuration
Set these variables in your.env (or deployment environment). Only JIRA_BASE_URL and JIRA_API_TOKEN are required; everything else has a sensible default.
Required
Auth
Workflow statuses
These must exactly match your Jira workflow status names (case-insensitive comparison is used).Behaviour
Polling mode
Setting up webhooks
Webhooks are the primary event delivery mechanism. If your orchestrator is not publicly reachable (local dev, firewalled environments), use polling mode instead.1
Make the orchestrator reachable
The orchestrator must be accessible from Jira’s servers. For local development, use a tunnelling tool (e.g.
ngrok http 4200). For production, deploy to a public URL and set ORCHESTRATOR_PUBLIC_ORIGIN accordingly.2
Create the webhook in Jira
Create a system webhook under Settings → System → WebHooks (Jira admin) pointing to:Subscribe to these event types:
- Issue created (
jira:issue_created) - Issue updated (
jira:issue_updated) — covers status changes - Comment created (
comment_created) - Comment updated (
comment_updated)
3
Configure the secret
The orchestrator compares the secret from the A system webhook can’t send a custom header, so put the secret in the URL as above. Its own Secret field produces an HMAC signature, which the orchestrator does not verify — leave it empty. If you send events from a Jira Automation rule (Send web request) instead, you can set the
x-jira-webhook-secret header or the secret query parameter against JIRA_WEBHOOK_SECRET:x-jira-webhook-secret header there.The comparison is timing-safe. Without a configured secret, all requests are accepted — fine for local dev, not for production.4
Verify delivery
Move a test ticket to your
JIRA_REVIEW_STATUS. You should see an HTTP 202 response in Jira’s webhook delivery log and a new comment from the agent on the ticket within seconds.If the webhook times out or returns 503, the integration isn’t configured — check that JIRA_BASE_URL and JIRA_API_TOKEN are set and the orchestrator has restarted.Polling mode
Use polling when webhooks aren’t available (firewall, local dev without ngrok).issueKey:status:commentCount so it only re-processes a ticket when its state actually changes. The dedup cache is cleared every 24 hours.
If the Jira query fails, the poller backs off: the wait doubles with each consecutive failure, up to 15 minutes, and only the first failure is logged as an error. GET /jira/status shows poller.consecutiveFailures and poller.lastError throughout, and a recovery is logged once.
Webhook routing logic
The table below summarises how the dispatcher maps incoming events to processing modes.An @mention means a real Jira mention of the agent’s account (the person picker), not typed text —
@site-editor written as plain text is ignored. Mentions only matter outside the managed statuses; in the review and preview statuses every reporter comment is already routed.
Which site a ticket edits
The agent resolves the target site from the registered sites, in this order:- Named in the ticket — a registered site’s id or name appears in the summary, description, or a reporter comment. Exactly one match wins.
- Ambiguous — no single match and more than one site is registered. The agent posts a comment listing the candidates and asks; replying with the site’s id resolves it.
- Only one site — exactly one site is registered, so that one is used.
- No sites registered —
JIRA_SITE_IDis used, defaulting toavocado-stories.
Attachment handling
Attachments on the ticket are downloaded and injected into the agent’s context:
Maximum attachment size: 25 MB.
Unsplash image URLs
When a reporter pastes an Unsplash photo-page URL (https://unsplash.com/photos/<id>), the execute agent uses the built-in unsplash_get_by_id tool to resolve it to a direct images.unsplash.com asset URL instead of searching for a substitute. The same tool accepts a bare photo ID. If the URL can’t be resolved, the agent asks rather than silently falling back to a keyword search.
What reporters see
Every agent action posts a comment to the ticket. Here’s what each one looks like and what to reply.Review comment (waiting for approval)
Execution complete (waiting for publish approval)
After publish
Session isolation
Jira runs share one session per site —scopedSessionKey(JIRA_SESSION, siteId), i.e. {siteId}::{JIRA_SESSION} — deliberately, so Jira changes show up in the editor without per-ticket URLs. There is no per-ticket isolation: two tickets targeting the same site edit the same draft and their changes interleave.
Every edit the agent makes is in page history like any other: it can be undone from the editor, and the version panel lists it under the agent as Jira SITE-42.
Endpoints
All three endpoints require thex-jira-webhook-secret header (or ?secret= query parameter) if JIRA_WEBHOOK_SECRET is configured.
POST /jira/webhook
Receives Jira webhook events. Returns 202 Accepted immediately; processing runs asynchronously.
Response (queued):
POST /jira/process
Manually trigger processing for a ticket — useful for retries and testing.
Request body:
mode is one of review, execute, or publish. Defaults to review.
Unlike /jira/webhook, this call blocks until processing completes and returns the full result.
GET /jira/status
Returns the current state of the processing queue, recent results, and poller status.
Telemetry
Every ticket run appends a line to~/.data/telemetry/jira-telemetry.jsonl (NDJSON). Each entry contains:
The orchestrator also logs a structured
JIRA: agent tool call line to stdout for each tool invocation during live runs.
Troubleshooting
503 from/jira/webhook
JIRA_BASE_URL or JIRA_API_TOKEN is missing. Check your .env and restart the orchestrator.
Ticket errors with “No AI API key configured”
Neither ANTHROPIC_API_KEY nor OPENAI_API_KEY is set in the orchestrator environment. Add one and restart the orchestrator. The Anthropic key is preferred when both are present.
401 from /jira/webhook
JIRA_WEBHOOK_SECRET is set but the request didn’t include the right value in x-jira-webhook-secret. Confirm Jira is sending the secret in the webhook header.
Agent posts questions but never edits
The reporter’s approval reply didn’t match any keyword, or said more than yes. Post one of the approval keywords above as a comment on its own.
Agent keeps re-reviewing after approval
JIRA_AGENT_ACCOUNT_ID is not set or is wrong. The dispatcher can’t detect which comments are from the agent, so it treats them as reporter comments and triggers re-review. Find your account ID via GET /rest/api/3/myself and set the env var.
Ticket stays in Execute status
The agent failed to apply edits or transition the ticket. Check GET /jira/status for the recent array — the error field shows the root cause.
Poller processes the same ticket repeatedly
The fingerprint (issueKey:status:commentCount) didn’t change between polls. This happens if the agent’s comment failed to post (so comment count didn’t increment). Check orchestrator logs for Jira API errors.
Edits go to the wrong site
Name the site’s id or name in the ticket summary or description. JIRA_SITE_ID won’t help once any site is registered — it is only the fallback when none are (see Which site a ticket edits).
Typing @site-editor does nothing
Only a real Jira @mention of the JIRA_AGENT_ACCOUNT_ID user counts. Pick the agent from the mention autocomplete.
Agent ignores or replaces an Unsplash URL I pasted
Paste the full photo-page URL (https://unsplash.com/photos/<id>) or just the bare photo ID. The agent resolves it via unsplash_get_by_id and uses it directly. If UNSPLASH_ACCESS_KEY is missing from the orchestrator environment, resolution will fail and the agent will ask for a direct image URL instead.
What to read next
MCP server
The other inbound channel — 49 tools over Model Context Protocol.
Environment reference
Every
JIRA_* variable, and everything else Avocado reads.