A Leantime plugin that exposes API endpoints for retrieving tickets filtered by username (email) — with optional date range and status filtering — and for creating tickets assigned to a user. Designed for consumption by remote AI agents and external integrations.
- Place the
Databridgefolder inapp/Plugins/ - Go to Settings > Plugins in Leantime
- Find "Databridge" under new plugins and click Install
- Enable the plugin
Or via CLI:
php bin/leantime plugin:enable DatabridgeThe plugin uses its own API keys, defined in a YAML file — core Leantime API keys
(lt_...) are not accepted on these endpoints. The key is passed via the x-api-key
header.
- Copy
databridge_auth.sample.yaml(in this plugin) to<leantime>/config/databridge_auth.yaml— next to Leantime's.env, not inside the plugin folder (deploys may overwrite it). - Generate a key per consumer:
openssl rand -base64 32 - Restrict access:
chmod 600 config/databridge_auth.yaml. Never commit it. - Optionally override the file location in
config/.env:LEAN_DATABRIDGE_AUTH_FILE=/absolute/path/to/databridge_auth.yaml
Each user entry defines a name (used in logs), a plaintext key, the granted
operations, and the granted projects. Changes apply on the next request — no cache
to clear.
users:
- name: reporting-agent
key: "32+-random-chars"
operations: [read]
projects: [1, 5, 12] # Leantime project IDs this key may access
- name: sync-service
key: "another-key"
operations: [read, write]
projects: all # explicit sentinel: every project| Operation | Used by |
|---|---|
read |
GET /api/databridge/tickets |
write |
POST /api/databridge/tickets |
delete |
Reserved for future endpoints |
- Keys are stored in plaintext (same trust level as the DB password in
config/.env). - No key expiry or rotation yet.
Failed authentication attempts are throttled: more than 20 failures per minute from the
same IP returns 429 Too Many Requests. Both limits are configurable in config/.env
(a non-positive or non-numeric value falls back to the default):
LEAN_DATABRIDGE_RATELIMIT_ATTEMPTS— failed attempts allowed per window (default20)LEAN_DATABRIDGE_RATELIMIT_DECAY— window length in seconds (default60)
| Method | Path | Operation | Description |
|---|---|---|---|
GET |
/api/databridge/tickets |
read |
List tickets for a username |
POST |
/api/databridge/tickets |
write |
Create a ticket for a username |
The 401 / 403 (operation) / 429 responses in Error responses apply to both.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
username |
string | Yes | Email/username to filter tickets by | |
dateFrom |
string | No | ISO date (Y-m-d), filters dateToFinish >= |
|
dateTo |
string | No | ISO date (Y-m-d), filters dateToFinish <= |
|
status |
string | No | Status type: NEW, INPROGRESS, DONE (case-insensitive) |
|
sinceId |
int | No | 0 | Pagination cursor: minimum ticket ID (id >=), not a row offset |
limit |
int | No | 100 | Maximum number of results |
A ticket is returned if the user is either:
- The assigned editor of the ticket, or
- A collaborator on the ticket
Milestones are excluded.
Results only include tickets from projects granted to the API user (the projects
key in the auth YAML). A grant listing a nonexistent project ID is legal and simply
yields empty results, not an error. Status filtering is equally scoped: the status
type (NEW/INPROGRESS/DONE) is resolved against granted projects only.
curl -k "https://leantime.example.com/api/databridge/tickets?username=user@example.com" \
-H "x-api-key: YOUR_API_KEY"curl -k "https://leantime.example.com/api/databridge/tickets?username=user@example.com&limit=50" \
-H "x-api-key: YOUR_API_KEY"curl -k "https://leantime.example.com/api/databridge/tickets?username=user@example.com&dateFrom=2026-01-01&dateTo=2026-12-31" \
-H "x-api-key: YOUR_API_KEY"curl -k "https://leantime.example.com/api/databridge/tickets?username=user@example.com&status=inprogress" \
-H "x-api-key: YOUR_API_KEY"curl -k "https://leantime.example.com/api/databridge/tickets?username=user@example.com&status=inprogress&dateFrom=2026-01-01&dateTo=2026-12-31&limit=10" \
-H "x-api-key: YOUR_API_KEY"sinceId is a keyset cursor (minimum ticket ID), not a row offset. To fetch the next
page, pass the last returned ticket ID + 1:
# First page
curl -k "https://leantime.example.com/api/databridge/tickets?username=user@example.com&sinceId=0&limit=50" \
-H "x-api-key: YOUR_API_KEY"
# Next page (use the last ticket ID + 1 from the previous response)
curl -k "https://leantime.example.com/api/databridge/tickets?username=user@example.com&sinceId=1051&limit=50" \
-H "x-api-key: YOUR_API_KEY"{
"parameters": {
"username": "user@example.com",
"dateFrom": "2026-01-01",
"dateTo": "2026-12-31",
"status": null,
"sinceId": 0,
"limit": 100
},
"resultsCount": 3,
"results": [
{
"id": 42,
"projectId": 1,
"name": "Fix login bug",
"status": "INPROGRESS",
"milestoneId": null,
"tags": ["bug"],
"worker": "user@example.com",
"plannedHours": 4.0,
"remainingHours": 2.0,
"dueDate": "2026-03-15T00:00:00.000000Z",
"resolutionDate": null,
"modified": "2026-03-01T14:30:00.000000Z"
}
]
}Note: plannedHours/remainingHours may be 0 (UI-created tickets — core defaults empty
inputs to zero) or null (API-created tickets with no estimate).
Creates a ticket in a granted project, assigned to the given username. Requires the
write operation. Body is JSON.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
projectId |
int | Yes | Target project; must be covered by the key's projects grant |
|
username |
string | Yes | Assignee email; must have access to the target project; recorded as both the assignee and the creator | |
name |
string | Yes | Ticket headline (max 255 characters) | |
description |
string | No | "" |
Ticket description (max 65535 bytes) |
dueDate |
string | No | none | Y-m-d or Y-m-d H:i:s, interpreted as UTC; a bare date gets a 00:00:00 time |
tags |
string[] | No | [] |
Each tag non-empty and comma-free (comma is the storage delimiter); combined length max 255 |
plannedHours |
number | No | none | Planned hours, 0–100 (upper limit configurable, see below); also initializes remaining hours |
milestoneId |
int | No | none | Milestone to attach the ticket to; must be a milestone in the target project |
- Project grant —
403ifprojectIdis not covered by the key'sprojectsgrant. The grant is checked before existence, so an ungranted key cannot probe which project IDs exist. - Assignee must have project access — the
usernameuser must be able to access the target project per Leantime's access model (admins/owners always; everyone for "accessible to everyone" projects; client users for client-scoped projects; directly assigned users otherwise). Otherwise400— a ticket assigned to someone who cannot see its project would be invisible to them. - No tickets in closed projects — creating into a closed project ("Closed" in project
settings; the projects board calls the same state "archive") returns
400; Leantime hides closed projects from every listing, so the ticket would be invisible. Reading tickets from closed projects viaGETstill works (reporting/history). - No notifications, no events — unlike in-app creation, this endpoint fires no Leantime events and sends no notification emails. This is by design: API-key requests have no session user to attribute the activity to.
- Not idempotent — each call creates a new ticket; retries create duplicates.
- plannedHours cap — values above
100are rejected. Override the limit inconfig/.envviaLEAN_DATABRIDGE_MAX_PLANNED_HOURS(a non-positive or non-numeric value falls back to the default of100). - Tickets are created as type
taskwith the project's firstNEWstatus — the status is not client-settable. A fresh ticket's remaining hours equal its planned hours.
curl -k -X POST "https://leantime.example.com/api/databridge/tickets" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"projectId": 1,
"username": "user@example.com",
"name": "Fix login bug",
"description": "Users cannot log in with SSO",
"dueDate": "2026-08-01",
"tags": ["bug", "auth"],
"plannedHours": 4
}'The created ticket is returned in the same shape as a list result:
{
"parameters": {
"projectId": 1,
"username": "user@example.com",
"name": "Fix login bug",
"description": "Users cannot log in with SSO",
"dueDate": "2026-08-01",
"tags": ["bug", "auth"],
"plannedHours": 4.0,
"milestoneId": null
},
"resultsCount": 1,
"results": [
{
"id": 43,
"projectId": 1,
"name": "Fix login bug",
"status": "NEW",
"milestoneId": null,
"tags": ["bug", "auth"],
"worker": "user@example.com",
"plannedHours": 4.0,
"remainingHours": 4.0,
"dueDate": "2026-08-01T00:00:00.000000Z",
"resolutionDate": null,
"modified": "2026-07-22T09:30:00.000000Z"
}
]
}Shared across both endpoints unless noted.
Input is invalid; the error message states the problem. Examples:
The "username" parameter is required.— GET, missing usernameRequest body must be a non-empty JSON object.— POST, missing or non-JSON bodyThe "projectId" field is required and must be a positive integer.The "name" field is required./The "name" field must not exceed 255 characters.Unknown "projectId"./Unknown "username".The given project is closed.The "username" user does not have access to the given project.The "dueDate" field must be a valid date in "Y-m-d" or "Y-m-d H:i:s" format (UTC).The "tags" field must be an array of non-empty strings without commas.The "plannedHours" field must be a number between 0 and 100.The "description" field must not exceed 65535 bytes.The "milestoneId" field must be a positive integer.Unknown "milestoneId" for the given project.
{"error": "The \"username\" parameter is required."}Returned for a missing, empty, or unknown key — and, fail-closed, when the auth YAML file
is missing or malformed, or when the user's entry is invalid (e.g. a missing or invalid
projects key; check the Leantime log). Also returned by Leantime core when the plugin
is disabled.
{"error": "Invalid API Key"}The key is valid but its user lacks the operation the endpoint requires (e.g. a read-only
key on the POST endpoint).
{"error": "Operation not permitted for this API key"}For POST, a valid write key whose projects grant does not cover the target project
gets a distinct 403:
{"error": "Project not granted for this API key."}More than 20 failed authentications per minute from the same IP.
{"error": "Too many failed authentication attempts. Try again later."}