Make & Automation API

Connect Velocity to Make, n8n or your own scripts with the REST API behind the Zapier app.

Overview

The automation API is a small REST API made for no-code platforms. It serves the same triggers, actions and searches as the Zapier app (listed in Zapier as Velocity Quest). It's available on every plan under two base paths that behave identically:

  • https://velocity.quest/api/make/…, for Make and other HTTP-based tools
  • https://velocity.quest/api/zapier/…, which the Zapier app uses

Velocity has no packaged Make app. In Make, use the HTTP module for actions, searches and polling, and a Webhooks custom webhook for instant triggers. This page uses /api/make throughout.

Writes go through the same code as edits in the app. They send Slack notifications, fire webhooks and run your automations, and a status change syncs to a linked GitHub or Sentry issue.

Authentication

Use a personal API key (velu_ prefix) from Settings → Account → Personal API Keys. Send it as a bearer token or in X-API-Key:

Authorization: Bearer velu_your_personal_key
# or
X-API-Key: velu_your_personal_key

Workspace keys (vel_) are not accepted here. A personal key acts as you, in any workspace you belong to, so every call must name the workspace with workspaceId: in the query string for GET, in the JSON body for POST. Personal key scopes aren't checked on these endpoints.

The Zapier app signs in with OAuth instead. Its token (vel_at_) is tied to the one workspace chosen at sign-in, so it can leave workspaceId out, and naming another workspace is refused. It needs automation:read for reads and automation:write for actions and hook subscriptions.

Find your workspace IDs with GET /api/make/auth/me. It's also a good connection test:

{
  "user": { "id": "…", "email": "ada@acme.com", "display_name": "Ada" },
  "workspaces": [{ "id": "…", "slug": "acme", "name": "Acme" }]
}

Errors

StatusBodyMeaning
401invalid_api_key, invalid_key_prefix, missing_api_keyNo key, an unknown key, or not a velu_ key
401invalid_tokenOAuth token expired or revoked: refresh it
403insufficient_scopeOAuth token lacks the scope named in scope
403forbiddenNot a member of the workspace, or not the token's workspace
400missing_workspaceIdAPI key call without workspaceId
400{"error":"<field>: <message>"}Validation failure
400invalid_referenceA referenced ID (in field) isn't in the workspace, or a status or cycle belongs to another team
404issue_not_foundNo such issue in the workspace
429 Over 300 requests a minute per user, across all their keys and Zaps. Honor Retry-After.

Conventions

  • Priority is returned as a number: 0 urgent, 1 high, 2 medium, 3 low, 4 none. Actions take the names URGENT, HIGH, MEDIUM, LOW, NONE, in any case.
  • Dates are YYYY-MM-DD. An ISO timestamp is accepted, and its date part is used.
  • Issue references accept an issue's UUID or its identifier (ENG-12, any case). An identifier two issues share is treated as not found: use the UUID.
  • Lists such as labelIds are a JSON array or a comma-separated string.

Blank vs. null

In action bodies, an empty string (or only whitespace) means “not given”, and the field is left alone. No-code tools send "" for every field you didn't map. null means clear the field.

Blank entries in a list are dropped, and a list with nothing left counts as not given. An unmapped labels field therefore never removes labels. To remove every label, send "labelIds": null.

// Changes the title, clears the due date, leaves everything else alone.
{ "workspaceId": "…", "issueId": "ENG-12",
  "title": "Fix login on Safari", "dueDate": null, "assigneeId": "", "labelIds": "" }

Triggers

Each trigger can be polled, or delivered instantly through a hook. Both give the same record. Polls return newest first. limit defaults to 50, with a maximum of 100. Deduplicate on id.

TriggerPollHook event
New issueGET /triggers/issuesissue.created
Updated issueGET /triggers/issues/updatedissue.updated
Issue status changedGET /triggers/issues/status-changedissue.status_changed
New commentGET /triggers/commentscomment.created
New projectGET /triggers/projectsproject.created
Updated projectGET /triggers/projects/updatedproject.updated
Cycle startedGET /triggers/cycles/startedcycle.started
Cycle completedGET /triggers/cycles/completedcycle.completed

Issue and cycle polls also take teamId (a UUID). A poll record is the issue as it is now. The updated-issue poll gives one record per save that changed the status, priority, assignee, title, due date, estimate, team, project or cycle. The status-changed poll adds from_status, to_status, actor_id and changed_at. The updated-project poll skips projects that haven't been saved since they were created.

Hooks

To receive events as they happen, subscribe a URL, such as a Make custom webhook, to one event:

POST /api/make/hooks
{ "workspaceId": "…", "targetUrl": "https://hook.make.com/…", "event": "issue.status_changed" }

→ 201 { "id": "<hook id>" }

DELETE /api/make/hooks/<hook id>
→ 204

Events: issue.created, issue.updated, issue.status_changed, issue.deleted, comment.created, project.created, project.updated, cycle.started, cycle.completed. Deliveries use the webhook payload format, with the record under data.

  • The target must be a public http(s) URL: 400 invalid_target_url otherwise, and 503 target_url_unresolvable if its hostname doesn't resolve.
  • A hook belongs to whoever subscribed it. Only they, or a workspace admin or owner, can delete it. It is removed when they leave the workspace.
  • If the target answers 410 Gone, the hook is deleted. That's how to unsubscribe from the receiving side.
  • Admins see these hooks in Settings → Webhooks, alongside the ones created there.
  • Hooks subscribed here have no signing secret. Their deliveries carry an empty X-Velocity-Signature header. Keep the target URL private.

Actions

All actions are POST with a JSON body. Every referenced ID is checked against the workspace, and a status or cycle against the issue's team.

POST /actions/create-issue

FieldTypeNotes
teamIduuidRequired
titlestringRequired, up to 500 characters
descriptionstringPlain text. Blank lines separate paragraphs.
priorityURGENT…NONEDefault NONE
statusIduuidA status of the team. Defaults to the team's default status.
assigneeIduuidA workspace member
projectId, cycleIduuidThe cycle must be the team's
parentIdissue ref 
labelIdsuuid listWorkspace labels
dueDatedate 
estimateinteger 0–100 

POST /actions/update-issue

issueId (an issue ref, required) plus any of title, description, priority, statusId, assigneeId, projectId, cycleId, parentId, dueDate and estimate. Send null to clear one. For labels, send either labelIds to replace them all, or addLabelIds and removeLabelIds. Only the fields you give change. A request that gives no fields is 400.

Both issue actions return the issue with its description, a url to it in Velocity, and label_ids.

POST /actions/create-comment

{ "workspaceId": "…", "issueId": "ENG-12", "body": "Deployed to staging",
  "parentId": "<comment uuid, optional: a reply to a comment on the same issue>" }

Returns the comment with its issue and an issue_url.

POST /actions/create-project

name (required), identifier (up to 10 letters and digits; derived from the name when omitted), description, status (backlog, planned, in_progress, paused, completed, cancelled; default backlog), leadId, startDate, targetDate. An identifier that's already used is 409 identifier_taken.

Searches

All GET. Each returns an array, which is empty when nothing matches.

EndpointParametersReturns
/searches/find-issueidentifier or id (exact), or query (title, identifier, description)Issues, up to 25, most recently updated first
/searches/find-useremail (case-insensitive)Workspace members
/searches/find-projectname (name or identifier contains)Projects, exact matches first, up to 25

Options

GET endpoints for dropdowns, each returning [{ id, name, … }]: /options/teams, /options/statuses, /options/members, /options/labels, /options/projects and /options/cycles. Statuses, labels and cycles take an optional teamId.