Jira Integration
OAuth 2.0 (3LO) connection, project selection, and execution ticket push to Jira with priority mapping.
Overview
Jira is the execution-only surface today. There are no agent tools for Jira — the agent does not query backlogs, read issues, or push status. Instead, Jira lives at the end of the execution-ticket pipeline: generate tickets from a document, select Jira as the target, push. Atlassian OAuth 2.0 (3LO) with refresh tokens and cloud_id resolution underpins the connection.
Jira does not yet expose agent tools in app/core/sdk/tools/integration.py. If you need interactive "what's in this Jira project?" queries from chat, use the GitHub or Linear integrations instead, or request the capability.
Connecting
Initiate OAuth
POST /api/integrations/jira/connect{ "workspace_id": "your-workspace-uuid" }Returns { auth_url, state }. State rows expire after 10 minutes. Gated on the has_integrations feature flag.
Handle callback
Atlassian redirects to GET /api/integrations/jira/callback?code=...&state=.... The backend:
- Validates and deletes the OAuth state row.
- Exchanges the code for an access token via
JiraService.exchange_code_for_token. - Calls
JiraService._get_cloud_id()to resolve the accessible Atlassian site'scloud_id(Jira requires this in every subsequent API URL). - Encrypts the access token.
- Upserts the Integration with
integration_type="jira"andconfig:{ cloud_id, scope }.
If the OAuth exchange fails, the callback rolls back and redirects with ?jira=error&reason=exchange_failed.
Select projects
GET /api/integrations/jira/projects?workspace_id={id}Returns the list of accessible projects from JiraService.list_projects(). The user picks which ones to push to — selection state lives in the frontend execution flow rather than a server-side selected_projects config.
The Jira integration stores cloud_id — Atlassian's per-site identifier — in config. The frontend displays it on the status endpoint so users can verify they connected the right Atlassian site.
Creating Issues via Execution Tickets
Jira is not a target for individual recommendation pushes. Use the execution-ticket pipeline:
Generate tickets
POST /api/execution/generateScopes a document or document block into a batch of ExecutionTicket rows. Each ticket carries title, description, priority (P0–P3), and platform-neutral fields.
Push to Jira
POST /api/execution/push{
"workspace_id": "your-workspace-uuid",
"document_id": "doc-uuid",
"platform": "jira",
"ticket_ids": ["ticket-uuid-1", "ticket-uuid-2"]
}The route resolves the workspace's active jira Integration (404 NO_ACTIVE_INTEGRATION otherwise), instantiates JiraService with the decrypted token and stored cloud_url, and calls jira.create_issue(...) per ticket. On success, the ticket's external_id, external_url, and platform="jira" are persisted.
Priority mapping
Praxiom's platform-neutral priority (P0–P3) maps to Jira's native names:
| Praxiom | Jira |
|---|---|
P0 | Highest |
P1 | High |
P2 | Medium |
P3 | Low |
| (unset) | Medium (fallback) |
The mapping lives in JiraService.create_issue:
priority_map = {"P0": "Highest", "P1": "High", "P2": "Medium", "P3": "Low"}
jira_priority = priority_map.get(priority, "Medium") if priority else None
When a priority is mapped, the create-issue payload includes fields.priority = {"name": jira_priority}.
Status Sync
The execution-ticket pipeline includes status sync for Jira alongside GitHub and Linear:
POST /api/execution/sync
For each ticket with platform="jira", the route loads the workspace's Jira integration, calls jira.get_issue(ticket.external_id) (requesting summary,status,priority,labels), and updates the ticket's status locally. Closed/done statuses flip the ticket's synced_status accordingly.
Management Endpoints
| Endpoint | Purpose |
|---|---|
POST /api/integrations/jira/connect | Initiate OAuth 2.0 (3LO) |
GET /api/integrations/jira/callback | OAuth callback (redirect endpoint) |
GET /api/integrations/jira/status?workspace_id={id} | connected, connected_at, cloud_id |
GET /api/integrations/jira/projects?workspace_id={id} | List accessible projects |
POST /api/integrations/jira/disconnect | Soft-disconnect: flips is_active=false and nulls access_token_encrypted (row preserved for audit) |
Unlike GitHub and Linear, /disconnect for Jira is a POST (not DELETE) and is a soft-delete — the Integration row is preserved for audit, with is_active=false and the access token cleared.
Key Concepts
- Execution-only surface — no agent tools. Jira is the terminal stage of the execution pipeline, not an interactive context the agent pulls from.
- Cloud ID resolution — every Jira API call needs a
cloud_id; the backend resolves it once at connect time and stores it inconfigso every downstream call has it. - Refresh tokens, soft disconnect — the integration stores a refresh token encrypted at rest, and
/disconnectpreserves the integration row so you can audit prior connections after revoke. - P0→Highest, P1→High, P2→Medium, P3→Low — priority is mapped once at push time; Jira's native priority names are what end up on the issue.
What's Next
- Execution Tickets — the pipeline Jira plugs into.
- GitHub Integration / Linear Integration — for interactive agent surfaces.
- Billing and Plans —
has_integrationsfeature flag.
Was this helpful?