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

1

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.

2

Handle callback

Atlassian redirects to GET /api/integrations/jira/callback?code=...&state=.... The backend:

  1. Validates and deletes the OAuth state row.
  2. Exchanges the code for an access token via JiraService.exchange_code_for_token.
  3. Calls JiraService._get_cloud_id() to resolve the accessible Atlassian site's cloud_id (Jira requires this in every subsequent API URL).
  4. Encrypts the access token.
  5. Upserts the Integration with integration_type="jira" and config: { cloud_id, scope }.

If the OAuth exchange fails, the callback rolls back and redirects with ?jira=error&reason=exchange_failed.

3

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:

1

Generate tickets

POST /api/execution/generate

Scopes a document or document block into a batch of ExecutionTicket rows. Each ticket carries title, description, priority (P0P3), and platform-neutral fields.

2

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 (P0P3) maps to Jira's native names:

PraxiomJira
P0Highest
P1High
P2Medium
P3Low
(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

EndpointPurpose
POST /api/integrations/jira/connectInitiate OAuth 2.0 (3LO)
GET /api/integrations/jira/callbackOAuth 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/disconnectSoft-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 in config so every downstream call has it.
  • Refresh tokens, soft disconnect — the integration stores a refresh token encrypted at rest, and /disconnect preserves 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

Was this helpful?