Skip to main content
Documentation

Tasks

tasks.create

Create a task, anchored to a conversation, customer, job or message
WritesWrite budget

REST

Shipping
POST /api/v1/tasks/create

MCP tool

Live
tasks.create

Exposed on: REST API · Shop MCP server · Claude connector · Dash (in-app copilot) · Zapier. Part of the Tasks domain.

Operating contract

Adds one to-do to the shop's queue. The title is the only thing required; every other field is an ANCHOR, and a task with anchors is worth several times one without.

ANCHOR IT TO WHAT CAUSED IT. message_id is the strongest: the thread line the task came out of, so whoever picks it up reads the customer's own words rather than your paraphrase of them. conversation files it against the thread, customer_id against the person, job_id against the car. Pass every one you know — they are independent, and the shop's own screens cross-link all four.

ASSIGNING PINGS SOMEBODY. staff_id puts the task in a real person's queue and sends them a notification, so assign when somebody is meant to act rather than as filing. The target must be on tasks.assignees; anyone else is refused rather than silently parked, because a task assigned to someone who cannot open the Tasks tab is work that never happens. Leave it unassigned when it belongs to whoever gets to it first.

due_at IS AN ABSOLUTE INSTANT, and 'tomorrow' has to be resolved against the SHOP's timezone before you call — if you do not know it, ask. An undated task sorts to the bottom of the open block, which is the right place for something that genuinely has no deadline and the wrong place for something that does.

IT IS NOT IDEMPOTENT: calling twice makes two tasks. tasks.list with the same conversation is the cheap check before creating one from a thread you may have already read.

Who may call it

Permission
inbox.accessThe caller must hold Inbox at the ACT level. A read-only dashboard grant on the same section is refused.
Plan
Every planNo plan gate. Available on every Service VIN plan.
Retries
noneA repeat genuinely does it twice and no key can make it not. Read before you write; the operating contract says what to read.
Rate class
writeCounted against the write budget, which is tighter than a read.

Input

FieldTypeDescription
titlerequiredstringmax 200 chars, min 1 chars

What needs doing, in one line. This is what the shop sees in its queue.

notesstringmax 4000 chars

Any detail that does not fit the title.

prioritystring

How urgent. Defaults to normal.

Default: "normal"One of: low, normal, high
due_atstringmax 40 chars

ISO instant it is due. Resolve 'tomorrow' against the shop's timezone first.

conversationstringuuid

The thread it belongs to, as returned by comms.list_conversations.

customer_idstringuuid

The customer it is about, as returned by customers.list or comms.resolve_contact.

job_idstringuuid

The job it hangs off, as returned by jobs.list.

message_idstringuuid

The thread line that caused it, as returned by comms.list_messages.

staff_idstringuuid

Hand it to this teammate, as returned by tasks.assignees. They are notified.

Output

FieldTypeDescription
idstring

titlestring

notesstring | null

statusstring

prioritystring

due_atstring | null

overdueboolean

completed_atstring | null

created_atstring

conversationstring | null

conversation_labelstring | null

customer_idstring | null

job_idstring | null

message_idstring | null

assigned_tostring | null

assignee_namestring | null

Examples

Built from this capability's own schema — required fields and the ones carrying a default, and nothing invented. Paste one and it validates.

curl
export SERVICEVIN_API_KEY=svk_live_…

curl -X POST https://www.servicevin.com/api/v1/tasks/create \
  -H "Authorization: Bearer $SERVICEVIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"…","priority":"normal"}'

TypeScript (fetch)
const res = await fetch("https://www.servicevin.com/api/v1/tasks/create", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SERVICEVIN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "title": "…",
    "priority": "normal"
  }),
});

// Success and failure are both envelopes. Switch on error.code, never
// on error.message — the codes are stable, the messages are for people.
const payload = await res.json();
if (!res.ok) throw new Error(payload.error.code);
const data = payload.data;

Python (requests)
import os, requests

res = requests.post(
    "https://www.servicevin.com/api/v1/tasks/create",
    headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
    json={
    "title": "…",
    "priority": "normal"
},
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(payload["error"]["code"])
data = payload["data"]

MCP tools/call — https://www.servicevin.com/api/mcp
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "tasks.create",
    "arguments": {
      "title": "…",
      "priority": "normal"
    }
  }
}

Refusals

The four gates run in this order on every surface, and the order is not arbitrary — see Authentication.

StatusCodeWhen
404not_foundThe id is unknown, or the feature is not enabled for this account. Deliberately the same answer for both.
403forbiddenThis login does not hold inbox.access.
403insufficient_scopeThe credential is read-only and this capability writes.
422validation_errorAn argument was wrong. The message names the field.
429rate_limitedToo many write calls. Back off and retry.
500internal_errorSomething failed on our side. Nothing was changed.