API

How integrations call Quevell: the address, signing in with a token, the error envelope, dates, cursors, QQL, versions and conflicts, limits and the method reference.

Updated 17 Sep 2026

The Quevell API is the same set of methods the product's own interface uses, opened up for integrations. It speaks JSON both ways and acts only as a service account: people do not get personal tokens. This page covers how to call it and what it returns, and ends with the methods you may call.

Address

https://<key>.quevell.com/api

<key> is your company's key, the one in the product's address bar. Call your own company's address. A token belongs to one company, and at another company's address the request is rejected with auth.tenant_mismatch (403). There is no version number in the path.

Signing in

Every request carries a service account token:

Authorization: Bearer qvl_...

Service accounts explains how to mint a token, how to narrow its rights, and how to get a short-lived token through an OAuth client. A request with no token, or with one that is unknown, expired or revoked, gets 401 auth.required. A token that lacks the right for an action gets 403 authorization.forbidden.

Responses and errors

A successful response is the object, list or page itself, with no wrapper around it. An error always arrives in the same envelope:

{
  "code": "concurrency.version_conflict",
  "message": "Issue was modified concurrently",
  "status": 409,
  "path": "/api/issues/6f1c.../transitions/...",
  "correlationId": "9b2e...",
  "timestamp": "2026-09-17T08:00:00Z",
  "details": {}
}
  • code is stable. Branch on it.
  • message is a sentence for a person, in English. It may be reworded any day, so never branch on message.
  • details holds specifics when there are any: field errors in details.fieldErrors, how long to wait in details.retryAfterSeconds.
  • correlationId identifies the request. Every response also carries it in the X-Correlation-Id header; quote it when you contact support.

The most common codes:

CodeStatusMeaning
request.malformed400the body or a parameter does not parse: bad JSON, UUID or number
request.validation400a field did not pass validation; see details.fieldErrors
request.unknown_parameter400a query parameter the method does not take; the message lists the ones it does
query.syntax400the QQL query has an error
auth.required401no token, or a token that no longer works
authorization.forbidden403not enough rights
auth.tenant_mismatch403the token belongs to another company
*.not_found404the object does not exist or is not visible to you
concurrency.version_conflict409a stale version; see Versions and conflicts
files.storage_quota_exceeded409the company's attachments have used all the space allowed
tenant.rate_limited429calling too often; see Limits
tenant.record_quota_exceeded429the daily allowance of records is used up
internal.error500something failed on our side; send us the correlationId

Modules add codes in their own namespaces, such as note.*, sprint.* and identity.*. Handle an unfamiliar code by its status.

Dates and times

Moments are sent and accepted in UTC, as ISO-8601 strings ending in Z. Calendar days, such as an issue's due date or the day of a worklog, travel as year-month-day with no time. Neither the server's time zone nor the person's affects a response; converting to local time is up to your program.

Pages and cursors

Long lists come in pages. A page has items and nextCursor, and some methods add total. To get the next page, repeat the request with cursor=<nextCursor>; an empty nextCursor means you have them all. limit sets the page size, and each method's maximum is in the reference. There are no page numbers or offsets.

A cursor is tied to the sort order it was issued under. Used with a different order, it is rejected.

Search and QQL

GET /api/issues/search searches every project visible to you. text takes either plain words or a QQL query:

curl -G https://<key>.quevell.com/api/issues/search \
  -H "Authorization: Bearer qvl_..." \
  --data-urlencode 'text=project = OFFICE and status != Done order by updated desc' \
  --data-urlencode 'mode=QUERY'

Without mode, the server guesses whether text is words or a query and reports its guess in the response's mode. An integration should set mode=QUERY outright. Order comes from ORDER BY inside the query, most recently updated first by default. filterId runs a saved filter.

Issues the token may not see are left out entirely: they appear in neither items nor total.

Versions and conflicts

Objects that change carry a version. When you change one, send the version you read as expectedVersion. If somebody changed the object first, you get 409 concurrency.version_conflict, or a module's code ending in version_conflict. Read the object again and decide whether your change still applies.

Sending the same change to an issue twice does nothing the second time and writes no new event. Creating is different: a repeated POST creates a second object. If a create request was cut off, check whether the object exists before you send it again.

Limits

What is countedLimitResponse past it
calls by one service account120 a minute429 tenant.rate_limited
calls by all of a company's service accounts600 a minute429 tenant.rate_limited
records by one service account10 000 a day429 tenant.record_quota_exceeded
records by all of a company's service accounts50 000 a day429 tenant.record_quota_exceeded
tokens handed out to an OAuth client10 a minute per client, 30 per network address429 oauth.rate_limited
the company's attachment storageset by the company's plan409 files.storage_quota_exceeded

How the counting works:

  • A minute is a calendar minute in UTC, not a sliding window. Every request with a working token counts, including one rejected for lack of rights.
  • A day is a calendar day in UTC. A record is every event the account leaves in the audit trail: creating or changing an issue, a comment, a worklog, and also downloading an attachment or exporting CSV. Reading that leaves no event uses no records.
  • All of an account's tokens, minted by hand or handed out by an OAuth client, share one allowance.

Every 429 says how long to wait, in the Retry-After header and as the same number in details.retryAfterSeconds. For the per-minute limits that is the seconds left in the minute; for the daily ones, the time until midnight UTC.

HTTP/1.1 429 Too Many Requests
Retry-After: 17

{"code":"tenant.rate_limited","status":429,"details":{"retryAfterSeconds":17},...}

Wait that long, then retry. Retrying sooner is pointless, because those requests count too. A 409 files.storage_quota_exceeded carries usedBytes and limitBytes in details.

Examples

Create an issue:

curl -X POST https://<key>.quevell.com/api/projects/<projectId>/issues \
  -H "Authorization: Bearer qvl_..." \
  -H "Content-Type: application/json" \
  -d '{"summary": "Replace the toner", "description": "The printer on the third floor"}'

Move an issue to another status: first ask which transitions are available, then perform the one you want, passing the issue's version.

curl https://<key>.quevell.com/api/issues/<issueId>/transitions \
  -H "Authorization: Bearer qvl_..."

curl -X POST https://<key>.quevell.com/api/issues/<issueId>/transitions/<transitionId> \
  -H "Authorization: Bearer qvl_..." \
  -H "Content-Type: application/json" \
  -d '{"expectedVersion": 3}'

Add a comment:

curl -X POST https://<key>.quevell.com/api/issues/<issueId>/comments \
  -H "Authorization: Bearer qvl_..." \
  -H "Content-Type: application/json" \
  -d '{"body": "Toner replaced"}'

Method reference

This list is read from the product's code and checked against it on every build, so it never names a method that is gone. Sign-in, company administration and the helpers behind individual screens are left out, because their shape changes with the interface. A right named in a description is required of both the account's role and the token; seeing the project is required everywhere and is not repeated.

Issues
MethodPathWhat it does
GET/api/issues/referencesLooks up issues by the keys in keys (up to 50); keys that match nothing or that you can't see are silently left out
PUT/api/issues/{issueId}Replaces the issue's fields, including summary, description and issueTypeId. Needs issue.edit; returns 409 if expectedVersion is stale
GET/api/issues/{issueId}/activityReturns the issue's history, oldest first: who changed what and when, including changes made by automation rules
GET/api/issues/{issueId}/checklistReturns the issue's checklist in order, with each item's done state, who checked it and when, plus the done count
POST/api/issues/{issueId}/checklistAdds an item to the end of the checklist from text (up to 500 characters). Needs issue.edit; returns the whole checklist
PUT/api/issues/{issueId}/checklist/orderReorders checklist items by itemIds; items you leave out keep their place after the ones you list. Needs issue.edit
DELETE/api/issues/{issueId}/checklist/{itemId}Removes an item from the issue's checklist and returns what remains. Needs issue.edit
PUT/api/issues/{issueId}/checklist/{itemId}Changes an item's text and/or sets done, recording who checked it and when. Needs issue.edit
GET/api/issues/{issueId}/childrenReturns the issues one level below this one; children hidden from you by issue security are silently left out
GET/api/issues/{issueId}/contributorsLists the teams contributing to the issue, with mine set for teams you belong to
PUT/api/issues/{issueId}/contributorsSets the issue's contributing teams from teamIds; you can only add or remove teams you are a member of
GET/api/issues/{issueId}/labelsReturns the issue's labels in alphabetical order
PUT/api/issues/{issueId}/labelsReplaces the issue's labels with the words in names (up to 30, 60 characters each); a new word becomes a label. Needs issue.edit
GET/api/issues/{issueId}/linksLists the issue's links in both directions; links to issues you can't see are silently left out
POST/api/issues/{issueId}/linksLinks the issue to another one given typeId, issueKey and direction. Needs issue.edit on both issues
DELETE/api/issues/{issueId}/links/{linkId}Removes an issue link and returns the remaining links. Needs issue.edit
GET/api/issues/{issueId}/rollupCounts the issues below this one by status category, in total and per child; only issues you can see are counted
PUT/api/issues/{issueId}/securitySets or clears (null) the issue's securityLevelId. Needs issue.security.manage; returns 409 if expectedVersion is stale
GET/api/issues/{issueId}/transitionsLists the transitions available from the issue's current status in its workflow
POST/api/issues/{issueId}/transitions/doneDeprecated: moves the issue to a status in the done category. Needs issue.transition; send expectedVersion in the body
POST/api/issues/{issueId}/transitions/{transitionId}Performs a transition available from the issue's current status. Needs issue.transition; send expectedVersion in the body
GET/api/issues/{issueId}/watchersLists the issue's watchers with display names and when each started watching
DELETE/api/issues/{issueId}/watchers/meStops watching the issue as the caller
GET/api/issues/{issueId}/watchers/meTells whether the caller is watching the issue
POST/api/issues/{issueId}/watchers/meStarts watching the issue as the caller
GET/api/issues/{issueReference}Returns an issue by UUID or by a key like PROJ-12; a former key of the issue still finds it
GET/api/projects/{projectId}/issuesLists the project's issues newest first, limit 1 to 200 (default 100); issues hidden by issue security are silently left out
POST/api/projects/{projectId}/issuesCreates an issue in the project; summary and description are required, and omitted fields take the project's defaults. Needs issue.create
Search and QQL
MethodPathWhat it does
GET/api/issues/query-fieldsLists the fields a QQL query can use, with their type, allowed operators, sortability, and whether each is custom
GET/api/issues/query-fields/{fieldName}/choicesThe same value suggestions as values, each with a note: a sprint name that several projects share comes with the project key beside it, and only the name goes into the query
GET/api/issues/query-fields/{fieldName}/valuesSuggests values for a field while writing a QQL query, drawn only from projects the caller can view
GET/api/issues/searchSearches issues across every project you can view, by text or a QQL query with mode=QUERY; limit defaults to 40, max 1000; returns items, nextCursor, total
GET/api/issues/search/export.csvExports matching issues as CSV using the same criteria as search; limit defaults to 1000 and caps at 10000
GET/api/projects/{projectId}/issues/searchSearches issues within one project with filters and a cursor; limit defaults to 40, max 100; issues you may not see are silently left out
Projects
MethodPathWhat it does
GET/api/projectsLists the projects you can open, with their last activity time and their issue counts: issueCount in all and openIssueCount not in a Done status, counting only the issues you can see; pass archived=true to list archived ones instead
POST/api/projectsCreates a project with a key, a name, and the default configuration, making you its project administrator; needs projects.create
DELETE/api/projects/{projectId}Permanently deletes a project that is already archived; company administrators only, with expectedVersion in the query string
PUT/api/projects/{projectId}Renames a project or changes its key, with expectedVersion and 409 on a stale version; needs project.access.manage, and a key change needs a company administrator
POST/api/projects/{projectId}/archiveArchives a project and sets the date it will be purged; needs project.access.manage, expectedVersion in the body, 409 on a stale version
GET/api/projects/{projectId}/configurationReturns the project configuration: fields, issue types, the default issue type, and per-type field defaults
PUT/api/projects/{projectId}/issue-security-defaultSets the issue security level new issues in the project start with, or null to clear it; needs issue.security.manage
GET/api/projects/{projectId}/issue-security-levelsLists the project's issue security levels with their grants and how many issues each guards; needs issue.security.manage
POST/api/projects/{projectId}/issue-security-levelsCreates an issue security level with a name and grants to users, project roles, or groups; needs issue.security.manage
DELETE/api/projects/{projectId}/issue-security-levels/{levelId}Deletes an issue security level, refused while any issue still sits behind it; needs issue.security.manage
PUT/api/projects/{projectId}/issue-security-levels/{levelId}Replaces an issue security level's name and grants as a whole; needs issue.security.manage, takes expectedVersion, 409 on a stale version
GET/api/projects/{projectId}/membersLists project participants with names and roles; limit defaults to 50, max 200; returns items, nextCursor, total
PUT/api/projects/{projectId}/membersAssigns, changes, or removes roles for up to 500 participants in one call; needs project.access.manage
GET/api/projects/{projectId}/members/groupsLists the groups that hold a role in the project, with their names and roles
DELETE/api/projects/{projectId}/members/groups/{groupId}Removes a group's role in the project; its people keep any roles held in their own name; needs project.access.manage
PUT/api/projects/{projectId}/members/groups/{groupId}Gives a group a role in the project: viewer, contributor, or admin; needs project.access.manage
DELETE/api/projects/{projectId}/members/{userId}Removes a participant from the project; needs project.access.manage; you cannot remove yourself or the last project administrator
PUT/api/projects/{projectId}/members/{userId}Gives a user a role in the project: viewer, contributor, or admin; needs project.access.manage, and you cannot change your own role
POST/api/projects/{projectId}/restoreRestores an archived project; needs project.access.manage, expectedVersion in the body, 409 on a stale version
GET/api/projects/{projectReference}Returns a project; {projectReference} accepts the project key, a former key, or the id
GET/api/projects/{projectReference}/status-summaryCounts the project's issues per status, only among issues you can see; {projectReference} accepts the project key or id
Boards
MethodPathWhat it does
GET/api/boardsLists the boards you can see plus each project's default board; pass archived=true for the archive
POST/api/boardsCreates a KANBAN or SCRUM board scoped by either projectIds or a query, never both
PUT/api/boards/projects/{projectId}/defaultSets a project's default board via boardId (null clears it); the board must include the project, needs board.manage (or be the board's creator)
DELETE/api/boards/{boardId}Permanently deletes an archived board with its columns, filters and sprints; issues stay, needs board.manage (or be the board's creator)
GET/api/boards/{boardId}Returns the board with columns, cards and swimlanes; filters applies quick filters by id
POST/api/boards/{boardId}/archiveArchives or restores a board based on archived; a project's default board can't be archived, needs board.manage (or be the board's creator)
GET/api/boards/{boardId}/backlogReturns the ranked backlog and the open sprints with their issues and estimate totals
PUT/api/boards/{boardId}/columnsReplaces the board's columns (columns, enforceWip); send expectedVersion, 409 if stale, needs board.manage (or be the board's creator)
PUT/api/boards/{boardId}/placementsPuts a card (issueId) into a status-less column (columnId) without changing its status; needs issue.rank
DELETE/api/boards/{boardId}/placements/{issueId}Sends a card back to the column its status maps to; needs issue.rank
PUT/api/boards/{boardId}/quick-filtersReplaces the board's quick filters (filters of name and query, up to 20); send expectedVersion, 409 if stale, needs board.manage (or be the board's creator)
POST/api/boards/{boardId}/rankRanks issueId before beforeIssueId on the board; send expectedVersion, 409 if stale, needs issue.rank
PUT/api/boards/{boardId}/swimlanesSets the swimlane mode, with lanes for QUERIES mode; send expectedVersion, 409 if stale, needs board.manage (or be the board's creator)
Sprints
MethodPathWhat it does
GET/api/boards/{boardId}/sprint-reportSprint report for a SCRUM board: closed sprints with results and velocity averaged over the last three
POST/api/boards/{boardId}/sprintsCreates a planned sprint on a SCRUM board with a name and optional goal; needs board.manage (or be the board's creator)
GET/api/issues/{issueId}/sprintReturns the issue's current sprint, active first, then planned; null when it's in no open sprint
GET/api/issues/{issueId}/sprintsLists every sprint the issue has been in, closed ones included, with board names
PUT/api/issues/{issueId}/sprintsReplaces the issue's open sprints with sprintIds; closed sprints stay put, needs issue.rank
GET/api/projects/{projectId}/sprintsLists started sprints, active and closed, on the project's boards, the ones a burndown can show
GET/api/sprints/{sprintId}/burndownSprint burndown: remaining issue count per day alongside the ideal line
POST/api/sprints/{sprintId}/completeCompletes an active sprint, optionally moving open issues to sprint moveOpenTo; send expectedVersion, 409 if stale, needs board.manage (or be the board's creator)
GET/api/sprints/{sprintId}/issuesReturns the sprint's issues you can see, with summed estimates and time spent
POST/api/sprints/{sprintId}/issuesAdds issueId to an open sprint; an issue can sit in only one open sprint per board, needs issue.rank
DELETE/api/sprints/{sprintId}/issues/{issueId}Removes an issue from an open sprint, returning it to the backlog; needs issue.rank
POST/api/sprints/{sprintId}/startStarts a planned sprint with endsAt and optional startsAt; send expectedVersion, 409 if stale, needs board.manage (or be the board's creator)
Comments
MethodPathWhat it does
GET/api/issues/{issueId}/commentsReturns the issue's comments, oldest first
POST/api/issues/{issueId}/commentsAdds a comment from body (up to 10,000 characters); mentions in the text are recorded. Needs comment.create
Attachments
MethodPathWhat it does
GET/api/issues/{issueId}/attachmentsLists the issue's attachments with file name, type, size, and who attached each one and when
POST/api/issues/{issueId}/attachmentsAttaches a file to the issue (multipart field file, up to 10 MiB) within the storage the company's plan allows. Needs attachment.create
DELETE/api/issues/{issueId}/attachments/{attachmentId}Deletes an attachment from storage; the history keeps its name. attachment.delete covers your own files; others' need a project administrator (project.access.manage)
GET/api/issues/{issueId}/attachments/{attachmentId}Downloads the attachment's file; each download is recorded as an event in the history, and repeat downloads are fine
GET/api/issues/{issueId}/attachments/{attachmentId}/thumbnailReturns the small copy of an image attachment (PNG, 256 on its long side). Only files whose bytes turned out to be an image have one; anything else answers 404. Same rights as the download
GET/api/issues/{issueId}/attachments/{attachmentId}/viewReturns the original of an image to be looked at: Content-Disposition: inline and the type the bytes turned out to be rather than the declared one. Anything that is not an image answers 404. Same rights as the download
Time tracking
MethodPathWhat it does
PUT/api/issues/{issueId}/estimateSets originalEstimate and remainingEstimate as duration strings like "1w 2d 4h 30m"; send expectedVersion, 409 if stale, needs issue.edit
GET/api/issues/{issueId}/timeTime tracking for an issue: estimate, remaining estimate, time spent and all worklogs
POST/api/issues/{issueId}/worklogsLogs work: spent as a duration like "2h 30m", optional spentOn and note; lowers the remaining estimate, needs work.log
DELETE/api/issues/{issueId}/worklogs/{worklogId}Deletes a worklog without restoring the remaining estimate; needs work.log, plus project.access.manage for someone else's
PUT/api/issues/{issueId}/worklogs/{worklogId}Edits a worklog's spent duration string and note; needs work.log, plus project.access.manage for someone else's
GET/api/reports/timeTime report: minutes per person for calendar days from through to, up to a year; without projectId needs company right reports.view
Knowledge base
MethodPathWhat it does
GET/api/issues/{issueId}/pagesKnowledge base pages linked to an issue, limited to the pages the caller can open
GET/api/notesPage tree of a project's knowledge base with projectId (needs project.view), or of your personal space plus pages others shared with you; archived pages come flagged archived
POST/api/notesCreates a page in a project's knowledge base (projectId, needs note.edit) or your personal space (personal); also title, body, parentId, draft, linkedIssueKeys
GET/api/notes/searchSearches page titles and bodies for q in a project (projectId) or your personal space; archived pages are left out
DELETE/api/notes/{pageId}Deletes a page with its revisions, page access and issue links; a page that still has child pages is refused
GET/api/notes/{pageId}Returns a page with its body, version and canEdit flag; answers 403 when you have no page access
PUT/api/notes/{pageId}Saves a page's title and body with expectedVersion, 409 on a stale version; saving the same title and body writes no revision
GET/api/notes/{pageId}/accessLists page access: the people and groups named on the page, each with READ or EDIT
PUT/api/notes/{pageId}/accessGrants a person or group page access, or changes its level; body has subjectKind, subjectId, level; you must be able to edit the page
DELETE/api/notes/{pageId}/access/{subjectKind}/{subjectId}Revokes a person's or group's page access and returns the remaining access list
POST/api/notes/{pageId}/archiveArchives or restores a page together with all its child pages; body is archived
POST/api/notes/{pageId}/cloneCopies a page into the same place with the same body and "(копия)" appended to the title
GET/api/notes/{pageId}/issuesLists a page's linked issues with each issue's key, summary and status
POST/api/notes/{pageId}/issuesLinks a page to an issue by its key; the caller must be able to see the issue
DELETE/api/notes/{pageId}/issues/{issueId}Removes a page's link to an issue and returns the remaining linked issues
POST/api/notes/{pageId}/moveMoves a page under another parent (parentId) or into another space, children included; takes expectedVersion, 409 on a stale version
POST/api/notes/{pageId}/ownerHands a personal page and its children to a new page owner (ownerId); only the current owner or an administrator may
GET/api/notes/{pageId}/revisionsLists a page's revisions newest first, with version, title, editor and edit time
GET/api/notes/{pageId}/revisions/{version}Returns one page revision by version, including its title and body
Reference data
MethodPathWhat it does
GET/api/configuration/field-typesReference data: the field types available in the company
GET/api/configuration/fieldsReference data: the company's fields, archived ones excluded, with required flags and default values
GET/api/configuration/hierarchy-levelsReference data: the company's hierarchy levels, top level first
GET/api/configuration/issue-typesReference data: the company's issue types, archived ones excluded, ordered by hierarchy level
GET/api/configuration/link-typesReference data: active link types between issues, with their outward and inward wording
GET/api/configuration/statusesReference data: the statuses defined in the company
GET/api/configuration/workflowsReference data: the company's workflows
GET/api/labelsThe company's labels in alphabetical order; pass query to match part of a name
People and teams
MethodPathWhat it does
GET/api/groupsNames of the company's groups in alphabetical order, for group pickers
GET/api/peopleLists the people in the company with name, status and account type
GET/api/people/{userId}A person's page: contact details, time away, deputy and filled-in profile fields
PUT/api/people/{userId}/profile-fieldsUpdates a person's profile field values from an object of field id to value, an empty value clears it; needs people.edit
GET/api/teamsLists every team in the company with member count and the caller's own role
POST/api/teamsCreates a team from name and description; the caller becomes its team creator
GET/api/teams/of/{userId}The teams a person belongs to, with their role in each
DELETE/api/teams/{teamId}Deletes a team and removes it wherever it is used; only the team creator or an administrator may
GET/api/teams/{teamId}Returns a team with its members and their roles
PUT/api/teams/{teamId}Renames a team or changes its description with expectedVersion, 409 on a stale version; only the team creator or an administrator may
POST/api/teams/{teamId}/membersAdds a person to a team (userId, role); the team creator, a manager or an administrator may
DELETE/api/teams/{teamId}/members/{userId}Removes a person from a team; anyone but the team creator may leave, removing others takes the creator, a manager or an administrator
PUT/api/teams/{teamId}/members/{userId}Changes a team member's role; the team creator's role cannot be changed
Filters
MethodPathWhat it does
GET/api/filtersYour personal filters plus everyone's shared filters, with query text and mine and shared flags
POST/api/filtersSaves a filter from name, query and shared to make it a shared filter; names must be unique among your filters
DELETE/api/filters/{id}Deletes a filter; only the person who saved it may
PUT/api/filters/{id}Updates a filter's name, query and shared with expectedVersion, 409 on a stale version; only its author may