Jira
The Jira integration lets a deployment read and write a user's Jira, and subscribe to Jira events. Every endpoint acts as the connected user against their own Jira site, so what you can see and change is exactly what your Jira account allows.
Jira supports two connection styles. Basic authentication stores a site URL, username, and API token and works for both Atlassian Cloud and Server/Data Center; connect it with Connect Jira. OAuth 2.0 connects Atlassian Cloud in the browser; start it with Start a Jira OAuth connection. Once connected, the browsing and issue endpoints work the same regardless of connection style.
To index Jira content for agent search, see Vectorise Jira data on the knowledge index page.
- POST /jira/connect Connect Jira
- GET /integrations/jira/oauth/auth-url Start a Jira OAuth connection
- GET /jira/status Check Jira status
- DEL /jira/connection Disconnect Jira
- GET /jira/projects List projects
- GET /jira/boards List boards
- GET /jira/boards/{project_key} List boards for a project
- GET /jira/boards/{board_id}/backlog List board backlog
- POST /jira/issue Create an issue
- GET /jira/issue/{issue_key} Retrieve an issue
- PUT /jira/issue/{issue_key} Update an issue
- POST /jira/issue/{issue_key}/comments Add a comment
- POST /jira/issue/{issue_key}/attachments Add attachments
- POST /integrations/jira/webhooks/register Register a webhook
- GET /integrations/jira/webhooks/list List webhooks
- DEL /integrations/jira/webhooks/{webhook_id} Delete a webhook
- PUT /integrations/jira/webhooks/{webhook_id}/extend Extend a webhook
- GET /integrations/jira/webhooks/stats Retrieve webhook statistics
- GET /integrations/jira/oauth/callback Jira OAuth callback
- POST /jira/save-credentials Save Jira credentials
Paths are relative to /api
Connect Jira
POST /api/jira/connect
Connects Jira with a site URL, username, and API token.
Connects Jira using basic authentication. The deployment probes the site to confirm the credentials work and to detect whether it is an Atlassian Cloud or Server/Data Center instance, then stores the connection against the caller. Set authType to basic; OAuth 2.0 connections are started with Start a Jira OAuth connection instead.
- Authentication
- Bearer token How it works
Body parameters application/json
-
authTypestring RequiredConnection type. Must be
basic.Possible values-
basic
-
-
urlstring RequiredJira site URL or host. A bare subdomain is treated as an Atlassian Cloud site.
-
usernamestring RequiredAccount username, usually the account email address.
-
passwordstring RequiredAPI token for the account.
Returns
Returns confirmation and the detected deployment type.
Errors
- 400 A required field is missing,
authTypeis notbasic, or the credentials were rejected.
curl -X POST "$VDF_BASE_URL/api/jira/connect" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"authType": "basic",
"url": "https://acme.example.com",
"username": "ada@example.com",
"password": "<API_TOKEN>"
}' const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/connect`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
authType: 'basic',
url: 'https://acme.example.com',
username: 'ada@example.com',
password: '<API_TOKEN>',
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/api/jira/connect",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"authType": "basic",
"url": "https://acme.example.com",
"username": "ada@example.com",
"password": "<API_TOKEN>",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"message": "Successfully connected to Jira",
"deploymentType": "cloud"
} Start a Jira OAuth connection
GET /api/integrations/jira/oauth/auth-url
Returns the Atlassian URL a user opens to connect Jira Cloud by OAuth.
Begins the Atlassian OAuth 2.0 flow for Jira Cloud. Open the returned auth_url in the browser; the user grants consent to Atlassian, which then redirects back to the deployment. The deployment exchanges the authorisation code and stores the tokens against the user who started the flow, so a completed flow can only connect that user's account. When it finishes, confirm with Check Jira status.
- Authentication
- Bearer token How it works
Query parameters
-
returnUrlstringAbsolute URL to return the browser to after the flow completes. Defaults to the deployment's integrations page.
Returns
Returns the Atlassian authorisation URL to open in the browser.
curl "$VDF_BASE_URL/api/integrations/jira/oauth/auth-url?returnUrl=https%3A%2F%2Fexample.com%2Fconsultant%2Fapp%2Fintegrations" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/integrations/jira/oauth/auth-url?returnUrl=https%3A%2F%2Fexample.com%2Fconsultant%2Fapp%2Fintegrations`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/integrations/jira/oauth/auth-url",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
params={
"returnUrl": "https://example.com/consultant/app/integrations",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"auth_url": "https://auth.atlassian.com/authorize?audience=api.atlassian.com&client_id=..."
} Check Jira status
GET /api/jira/status
Returns whether the caller's Jira connection is present and working.
Tests the caller's stored Jira connection and reports whether it is connected. The site URL is returned as cloudId and url; the stored credentials are never returned.
- Authentication
- Bearer token How it works
Parameters
No parameters.
Returns
Returns the connection status.
curl "$VDF_BASE_URL/api/jira/status" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/status`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/jira/status",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"isConnected": true,
"message": "Successfully connected to Jira",
"cloudId": "https://acme.example.com",
"url": "https://acme.example.com"
} Disconnect Jira
DEL /api/jira/connection
Disconnects the caller's Jira connection.
Deactivates the caller's Jira credentials. Agents can no longer read or write Jira as this user until Jira is connected again.
- Authentication
- Bearer token How it works
Parameters
No parameters.
Returns
Returns success: true, a confirmation message, and isConnected: false.
curl -X DELETE "$VDF_BASE_URL/api/jira/connection" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/connection`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.delete(
f"{os.environ['VDF_BASE_URL']}/api/jira/connection",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"message": "Successfully disconnected from Jira",
"isConnected": false
} List projects
GET /api/jira/projects
Returns the Jira projects the caller can see.
Returns the projects visible to the connected Jira account.
- Authentication
- Bearer token How it works
Parameters
No parameters.
Returns
Returns the projects the connected account can see, in projects.
curl "$VDF_BASE_URL/api/jira/projects" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/projects`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/jira/projects",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"projects": [
{
"id": "10001",
"key": "ENG",
"name": "Engineering",
"projectTypeKey": "software"
}
]
} List boards
GET /api/jira/boards
Returns the agile boards the caller can see.
Returns the agile boards visible to the connected Jira account.
- Authentication
- Bearer token How it works
Parameters
No parameters.
Returns
Returns the boards the connected account can see, in boards.
curl "$VDF_BASE_URL/api/jira/boards" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/boards`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/jira/boards",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"boards": [
{
"id": "42",
"name": "ENG board",
"type": "scrum",
"projectKey": "ENG"
}
]
} List boards for a project
GET /api/jira/boards/{project_key}
Returns the agile boards belonging to one project.
Returns the agile boards for a single project.
- Authentication
- Bearer token How it works
Path parameters
-
project_keystring RequiredKey of the project whose boards to return.
Returns
Returns the project's boards in boards.
curl "$VDF_BASE_URL/api/jira/boards/ENG" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/boards/ENG`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/jira/boards/ENG",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"boards": [
{
"id": "42",
"name": "ENG board",
"type": "scrum"
}
]
} List board backlog
GET /api/jira/boards/{board_id}/backlog
Returns the backlog issues for a board, with paging and an optional JQL filter.
- Authentication
- Bearer token How it works
Path parameters
-
board_idstring RequiredID of the board whose backlog to return.
Query parameters
-
startAtintegerIndex of the first issue to return.
-
maxResultsintegerMaximum number of issues to return.
-
jqlstringAdditional JQL to filter the backlog.
-
fieldsstringComma-separated list of issue fields to include.
Returns
Returns the board's backlog issues in backlogItems.
curl "$VDF_BASE_URL/api/jira/boards/42/backlog?maxResults=50" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/boards/42/backlog?maxResults=50`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/jira/boards/42/backlog",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
params={
"maxResults": 50,
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"backlogItems": []
} Create an issue
POST /api/jira/issue
Creates a Jira issue in the given project.
Creates an issue in the connected Jira account. issueType defaults to Story when omitted.
On failure the response is 200 with success: false and an error describing why the issue could not be created.
- Authentication
- Bearer token How it works
Body parameters application/json
-
projectKeystring RequiredKey of the project to create the issue in.
-
summarystring RequiredIssue summary.
-
descriptionstringIssue description.
-
issueTypestringIssue type name.
Returns
Returns the created issue as Jira represents it.
curl -X POST "$VDF_BASE_URL/api/jira/issue" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"projectKey": "ENG",
"summary": "Add rate limiting to the API",
"description": "Protect the public endpoints.",
"issueType": "Story"
}' const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/issue`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
projectKey: 'ENG',
summary: 'Add rate limiting to the API',
description: 'Protect the public endpoints.',
issueType: 'Story',
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/api/jira/issue",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"projectKey": "ENG",
"summary": "Add rate limiting to the API",
"description": "Protect the public endpoints.",
"issueType": "Story",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"issue": {
"id": "10456",
"key": "ENG-123",
"self": "https://acme.example.com/rest/api/3/issue/10456"
}
} Retrieve an issue
GET /api/jira/issue/{issue_key}
Returns the details of one Jira issue.
Returns a single issue as Jira represents it.
- Authentication
- Bearer token How it works
Path parameters
-
issue_keystring RequiredKey of the issue, for example
ENG-123.
Query parameters
-
fieldsstringComma-separated list of fields to include.
-
expandstringComma-separated list of Jira expand options.
Returns
Returns the issue in issue.
Errors
- 404 No such issue is visible to the caller.
curl "$VDF_BASE_URL/api/jira/issue/ENG-123" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/issue/ENG-123`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/jira/issue/ENG-123",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"issue": {
"id": "10456",
"key": "ENG-123",
"fields": {
"summary": "Add rate limiting to the API"
}
}
} Update an issue
PUT /api/jira/issue/{issue_key}
Updates one or more fields on an issue. Fields are applied individually, so the response reports which succeeded and which failed. acceptanceCriteria is appended to the issue description rather than written to a dedicated field.
- Authentication
- Bearer token How it works
Path parameters
-
issue_keystring RequiredKey of the issue to update.
Body parameters application/json
-
summarystringNew issue summary.
-
descriptionstringNew issue description.
-
acceptanceCriteriaarray of stringsAcceptance-criteria lines, appended to the description.
-
assigneestringAssignee name.
-
prioritystringPriority name.
-
storyPointsnumberStory-point estimate.
Returns
Returns the updated issue and which fields changed.
Errors
- 400 No update data was provided.
- 500 None of the requested fields could be updated.
curl -X PUT "$VDF_BASE_URL/api/jira/issue/ENG-123" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"summary": "Add rate limiting and quotas",
"priority": "High"
}' const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/issue/ENG-123`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
summary: 'Add rate limiting and quotas',
priority: 'High',
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.put(
f"{os.environ['VDF_BASE_URL']}/api/jira/issue/ENG-123",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"summary": "Add rate limiting and quotas",
"priority": "High",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"issue": {
"id": "10456",
"key": "ENG-123"
},
"message": "Issue ENG-123 updated successfully",
"updated_fields": [
"summary",
"priority"
],
"failed_fields": null
} Add a comment
POST /api/jira/issue/{issue_key}/comments
Adds a comment to one Jira issue.
Adds a comment to an issue as the connected user.
- Authentication
- Bearer token How it works
Path parameters
-
issue_keystring RequiredKey of the issue to comment on.
Body parameters application/json
-
commentstring RequiredComment text.
Returns
Returns the created comment as Jira represents it.
Errors
- 400 No comment text was provided.
curl -X POST "$VDF_BASE_URL/api/jira/issue/ENG-123/comments" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"comment": "Picked this up, targeting this sprint."
}' const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/issue/ENG-123/comments`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
comment: 'Picked this up, targeting this sprint.',
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/api/jira/issue/ENG-123/comments",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"comment": "Picked this up, targeting this sprint.",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"result": {
"id": "10500",
"created": "2026-09-01T09:30:00Z"
}
} Add attachments
POST /api/jira/issue/{issue_key}/attachments
Attaches one or more files to a Jira issue.
Attaches files to an issue. Each attachment carries its bytes as base64 in the request body; a data: URL prefix is accepted and stripped.
- Authentication
- Bearer token How it works
Path parameters
-
issue_keystring RequiredKey of the issue to attach files to.
Body parameters application/json
-
attachmentsarray of objects RequiredFiles to attach. Must contain at least one item.
Show child parameters Hide child parameters
-
filenamestringFile name to store the attachment under.
-
contentstring RequiredFile contents, base64-encoded. A
data:URL prefix is accepted.
-
Returns
Returns the attachments as Jira represents them.
Errors
- 400 No attachments were provided, or an attachment's content was not valid base64.
curl -X POST "$VDF_BASE_URL/api/jira/issue/ENG-123/attachments" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"attachments": [
{
"filename": "mockup.png",
"content": "data:image/png;base64,iVBORw0KGgo..."
}
]
}' const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/issue/ENG-123/attachments`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
attachments: [
{
filename: 'mockup.png',
content: 'data:image/png;base64,iVBORw0KGgo...',
},
],
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/api/jira/issue/ENG-123/attachments",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"attachments": [
{
"filename": "mockup.png",
"content": "data:image/png;base64,iVBORw0KGgo...",
},
],
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"result": [
{
"id": "10600",
"filename": "mockup.png"
}
]
} Register a webhook
POST /api/integrations/jira/webhooks/register
Registers a dynamic webhook with Jira so that matching issue and comment events are delivered to the deployment for the caller. Requires an OAuth 2.0 Jira connection. A user may hold up to five active webhooks per Jira site. The webhook expires after 30 days unless extended; see Extend a webhook.
- Authentication
- Bearer token How it works
Body parameters application/json
-
jql_filterstring RequiredJQL selecting which issues generate events, for example
project IN (ENG). -
eventsarray of strings RequiredEvent types to subscribe to.
Possible values-
jira:issue_created -
jira:issue_updated -
comment_created
-
-
scope_typestringHow the webhook is scoped.
Possible values-
project -
board -
global
-
-
scope_valuestringThe project key, board ID, or other value the scope applies to.
Returns
Returns the registered webhook.
Errors
- 400
jql_filteroreventsis missing, an event type is invalid, the maximum number of webhooks is reached, or Jira is not connected by OAuth 2.0.
curl -X POST "$VDF_BASE_URL/api/integrations/jira/webhooks/register" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jql_filter": "project IN (ENG)",
"events": [
"jira:issue_created",
"jira:issue_updated"
],
"scope_type": "project",
"scope_value": "ENG"
}' const response = await fetch(`${process.env.VDF_BASE_URL}/api/integrations/jira/webhooks/register`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
jql_filter: 'project IN (ENG)',
events: ['jira:issue_created', 'jira:issue_updated'],
scope_type: 'project',
scope_value: 'ENG',
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/api/integrations/jira/webhooks/register",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"jql_filter": "project IN (ENG)",
"events": ["jira:issue_created", "jira:issue_updated"],
"scope_type": "project",
"scope_value": "ENG",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"webhook_id": "12345",
"db_id": 7,
"expires_at": "2026-10-01T09:30:00Z",
"webhook_url": "<WEBHOOK_URL>",
"jql_filter": "project IN (ENG)",
"events": [
"jira:issue_created",
"jira:issue_updated"
]
} List webhooks
GET /api/integrations/jira/webhooks/list
Returns the webhooks the caller has registered, optionally filtered to one Jira site.
- Authentication
- Bearer token How it works
Query parameters
-
cloud_idstringReturn only webhooks for this Jira site URL.
Returns
Returns your registered webhooks in webhooks, with their count.
curl "$VDF_BASE_URL/api/integrations/jira/webhooks/list" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/integrations/jira/webhooks/list`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/integrations/jira/webhooks/list",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"count": 1,
"webhooks": [
{
"id": 7,
"webhook_id": "12345",
"cloud_id": "https://acme.example.com",
"jql_filter": "project IN (ENG)",
"events": [
"jira:issue_created",
"jira:issue_updated"
],
"webhook_url": "<WEBHOOK_URL>",
"scope_type": "project",
"scope_value": "ENG",
"is_active": true,
"created_at": "2026-09-01T09:30:00Z",
"expires_at": "2026-10-01T09:30:00Z",
"last_extended_at": null,
"last_event_received_at": "2026-09-02T14:12:00Z",
"error_count": 0,
"last_error": null
}
]
} Delete a webhook
DEL /api/integrations/jira/webhooks/{webhook_id}
Deletes one of the caller's Jira webhooks.
Removes a webhook from Jira and deactivates it locally. Only the caller's own webhooks can be deleted.
- Authentication
- Bearer token How it works
Path parameters
-
webhook_idstring RequiredThe webhook's Jira ID, as returned by Register a webhook.
Returns
Returns success: true and the deleted webhook_id.
Errors
- 400 No such webhook belongs to the caller.
curl -X DELETE "$VDF_BASE_URL/api/integrations/jira/webhooks/12345" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/integrations/jira/webhooks/12345`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.delete(
f"{os.environ['VDF_BASE_URL']}/api/integrations/jira/webhooks/12345",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"webhook_id": "12345"
} Extend a webhook
PUT /api/integrations/jira/webhooks/{webhook_id}/extend
Extends one of the caller's Jira webhooks by 30 days.
Refreshes a webhook's expiry so Jira keeps delivering events. Only the caller's own active webhooks can be extended.
- Authentication
- Bearer token How it works
Path parameters
-
webhook_idstring RequiredThe webhook's Jira ID.
Returns
Returns the webhook_id and its new expires_at.
Errors
- 400 No active webhook with this ID belongs to the caller, or Jira is not connected by OAuth 2.0.
curl -X PUT "$VDF_BASE_URL/api/integrations/jira/webhooks/12345/extend" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/integrations/jira/webhooks/12345/extend`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.put(
f"{os.environ['VDF_BASE_URL']}/api/integrations/jira/webhooks/12345/extend",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"webhook_id": "12345",
"expires_at": "2026-10-31T09:30:00Z"
} Retrieve webhook statistics
GET /api/integrations/jira/webhooks/stats
Returns per-day delivery statistics for the caller's webhooks.
Returns daily counts of events received, processed, and failed for the caller's webhooks over a recent window.
- Authentication
- Bearer token How it works
Query parameters
-
webhook_idstringReturn statistics for a single webhook only.
-
daysintegerNumber of days to look back.
Returns
Returns webhook statistics in stats.
curl "$VDF_BASE_URL/api/integrations/jira/webhooks/stats?days=7" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" const response = await fetch(`${process.env.VDF_BASE_URL}/api/integrations/jira/webhooks/stats?days=7`, {
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
},
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/integrations/jira/webhooks/stats",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
params={
"days": 7,
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"stats": [
{
"webhook_id": "12345",
"stat_date": "2026-09-01",
"events_received": 145,
"events_processed": 143,
"events_failed": 2,
"avg_processing_time_ms": 234
}
]
} Jira OAuth callback
GET /api/integrations/jira/oauth/callback
Completes an Atlassian OAuth connection after the user returns from Atlassian.
The browser is sent here by Atlassian once the user has granted consent; clients never call it directly. The request is identified by the signed state created when the flow began with Start a Jira OAuth connection. On success the deployment exchanges the authorisation code, stores the tokens against the user who started the flow, and redirects to the integrations page; on failure it redirects there with an error code. This is the shared Atlassian callback, so it also completes the Confluence OAuth flow started from Start a Confluence OAuth connection.
- Authentication
- None
Query parameters
-
codestringAuthorisation code returned by Atlassian on success.
-
statestringOpaque value that ties the callback to the connection that began it.
-
errorstringError code returned by Atlassian when the user declines or consent fails.
-
error_descriptionstringHuman-readable detail accompanying
error.
Returns
Redirects the browser back to the integrations page.
The response is an HTTP redirect; there is no JSON body.
curl -i "$VDF_BASE_URL/api/integrations/jira/oauth/callback?code=%3CAUTHORIZATION_CODE%3E&state=%3CSTATE%3E" const response = await fetch(`${process.env.VDF_BASE_URL}/api/integrations/jira/oauth/callback?code=%3CAUTHORIZATION_CODE%3E&state=%3CSTATE%3E`);
if (!response.ok) throw new Error(`Request failed with status ${response.status}`); import os
import requests
response = requests.get(
f"{os.environ['VDF_BASE_URL']}/api/integrations/jira/oauth/callback",
params={
"code": "<AUTHORIZATION_CODE>",
"state": "<STATE>",
},
allow_redirects=False,
timeout=30,
)
response.raise_for_status()
print(response.headers["Location"]) Save Jira credentials Deprecated
POST /api/jira/save-credentials
Confirms the caller has a stored Jira connection.
Retained for backwards compatibility. It checks that the caller already has a stored Jira connection and returns success; it does not create or change a connection, and any cloudId in the body is ignored. Connect Jira with Connect Jira or Start a Jira OAuth connection.
- Authentication
- Bearer token How it works
Body parameters application/json
-
cloudIdstringAccepted but ignored.
Returns
Returns a confirmation that a connection exists.
Errors
- 400 The caller has no stored Jira connection to confirm.
curl -X POST "$VDF_BASE_URL/api/jira/save-credentials" \
-H "Authorization: Bearer $VDF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"cloudId": "https://acme.example.com"
}' const response = await fetch(`${process.env.VDF_BASE_URL}/api/jira/save-credentials`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VDF_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
cloudId: 'https://acme.example.com',
}),
});
if (!response.ok) throw new Error(`Request failed with status ${response.status}`);
const data = await response.json(); import os
import requests
response = requests.post(
f"{os.environ['VDF_BASE_URL']}/api/jira/save-credentials",
headers={"Authorization": f"Bearer {os.environ['VDF_ACCESS_TOKEN']}"},
json={
"cloudId": "https://acme.example.com",
},
timeout=30,
)
response.raise_for_status()
data = response.json() {
"success": true,
"message": "Jira connection saved"
}