deskhand

Docs

API reference

Everything the console does goes through the same HTTP API your agent uses, at /api on this origin. Requests and responses are JSON.

Connect an agent

  1. Sign in, open Agents, name the agent and choose its scopes.
  2. Copy the key. It is shown once and stored hashed.
  3. Add Deskhand to your agent. For Claude Code, one line:
claude mcp add --transport http deskhand https://deskhand.grain64.com/api/mcp \
  --header "Authorization: Bearer $DESKHAND_KEY"

The agent shows as connected in the console after its first call.

Any MCP client

The endpoint is POST https://deskhand.grain64.com/api/mcp: MCP over streamable HTTP, JSON-RPC, one JSON response per request, no session to keep. Authenticate with the agent key as a bearer token. A generic client configuration:

{
  "mcpServers": {
    "deskhand": {
      "type": "http",
      "url": "https://deskhand.grain64.com/api/mcp",
      "headers": { "Authorization": "Bearer ${DESKHAND_KEY}" }
    }
  }
}

Protocol versions 2025-06-18, 2025-03-26 and 2024-11-05 are accepted. Tools are checked against the key's scopes, its rate limit and its pause state on every call. A refused call comes back as a tool error with a machine-readable code, so the agent can act on it. A paused or revoked agent gets an HTTP 403 instead.

MCP tools

ToolScopeWhat it does
search_contactsread_crmFind contacts by text, status, source, company or a custom field, with a cursor for more
upsert_contactwrite_crmCreate or update by email; links or creates the company from company_domain or company_name; fields and tags merge
update_contactwrite_crmChange a contact found by id or email
create_deal, move_dealwrite_crmOpen a deal, optionally linked to a contact by id or email; move it between stages
add_notewrite_crmAttach a note to a contact or deal

Write tools accept an optional idempotency_key: repeating a call with the same key and arguments returns the first result instead of writing again. Every write is recorded in Activity with the agent, the run and the before and after values.

Plain HTTP

The same operations are available as REST calls on this origin, and the console uses them too:

curl https://deskhand.grain64.com/api/me \
  -H "Authorization: Bearer $DESKHAND_KEY"

Import and export

The Import screen takes a CSV, shows what would be created, filled in or skipped before anything is written, and undoes the whole import in one click. HubSpot and Attio contact exports are recognised and mapped; columns with no home become custom fields. Settings downloads everything as one JSON file, or any record type as CSV.

Scopes

ScopeAllows
read_crmSearch and read contacts, companies, deals, notes
write_crmCreate, upsert, update and delete those records
draft_email, send_emailEmail drafting and sending
manage_tasksTasks and reminders

CRM endpoints

EndpointPurpose
GET /api/contactsList. Filters: q, status, source, company_id, field.<name>=<value>, limit, cursor
POST /api/contacts/upsertCreate or update by email. Optional company_name and company_domain link or create the company. fields merge into existing custom fields
POST /api/contacts, GET/PATCH/DELETE /api/contacts/:idCreate, read, update, delete
/api/companies, POST /api/companies/upsertSame shape, de-duplicated by domain
/api/dealsStages: lead, contacted, qualified, proposal, won, lost. Move a deal with PATCH {"stage":"qualified"}
POST /api/notes, GET /api/notes?contact_id=Notes on a contact or deal

Contacts and deals take a free-form source tag and a small fields object for custom columns.

Retries and runs

Send an Idempotency-Key header on writes. Repeating the same request with the same key returns the stored response instead of writing again; reusing a key with a different body returns 422.

Calls are grouped into runs. Send X-Run-Id (and optionally X-Run-Label) to name a run yourself; otherwise calls within 30 minutes of each other share one.

Errors

Errors return {"error":{"code","message","request_id"}}.

StatusCodeMeaning
401invalid_key, key_revokedUnknown key, or a key that was rotated or revoked
403agent_paused, agent_revokedThe operator paused or revoked this agent. Stop and report it
403missing_scopeThe key lacks the scope named in required_scope
403operator_onlySetting that field is reserved to the human operator
429rate_limitedPer-minute limit for this agent reached