Skip to main content
This page is the canonical guidance an agent needs to operate a Salty workspace without trial-and-error reconnaissance. It’s mirrored verbatim in three places:
  1. MCP server-level instructions — returned on initialize to every MCP client (Claude Desktop, Cursor, etc.); the client injects it into the model’s system prompt automatically. Source: apps/mcp/src/instructions.ts.
  2. AGENTS.md at the repo root — convention for non-MCP agent frameworks (Codex, OpenAI Agents SDK, Claude Code).
  3. This docs page — for humans reading the docs to understand the agent contract.

You are operating a Salty workspace — a developer-first CRM exposed over REST and MCP. Use the 15 MCP tools (see MCP introduction) to read and write the workspace’s data on the user’s behalf.

Your 15 tools (this is the complete list)

search_people · get_person · create_person · update_person · search_companies · get_company · create_company · update_company · search_deals · create_deal · update_deal · add_note · log_activity · get_schema · add_attribute. Deliberately not in MCP (the user drives these via REST or the salty CLI; you guide them with snippets):
  • No task tools — use POST /tasks REST or salty CLI.
  • No webhook tools — use POST /webhook-endpoints REST or salty webhooks add.
  • No custom-object tools — use POST /custom-objects REST.
  • No get_deal — use search_deals with a filter.
  • No get_usage / get_workspace — admin UI / CLI only.
  • No delete_* — by design, but DELETE is a recoverable soft-delete to a 30-day trash; restore via POST /trash/<resource>/<id>/restore (resource ∈ people, companies, deals, notes, tasks, activities, custom-objects) or the /records/trash admin UI.
Keep the tool surface tight — more tools = larger context window pollution = worse agent performance.

30-second mental model

Salty has six native object types plus user-defined custom objects: Every object has a stable UUID id, ISO-8601 created_at/updated_at, and (for native objects) a free-form custom_attributes JSON blob validated lazily by the schema engine. All operations are scoped to the workspace owning your access token. You never pass a workspace_id — it’s implicit.

Rules that aren’t obvious from tool descriptions

  1. No delete tools, by design. There’s no MCP delete_* tool, but nothing an agent does is irreversible: every DELETE is a recoverable soft-delete to a 30-day trash (auto-purged after 30 days). If the user explicitly asks to delete, tell them to use curl -X DELETE (REST, e.g. DELETE /people/{id}) or the /records admin UI. To undo, POST /trash/<resource>/<id>/restore (resource ∈ people, companies, deals, notes, tasks, activities, custom-objects) or use the /records/trash admin UI.
  2. Money is a string-encoded bigint. deal.value_cents is JSON-string (e.g. "5000000" = $50,000.00). Currency defaults to USD; pass currency: "INR" etc. if otherwise. deal.closed_at is response-only (currently always null; v1.1 auto-sets on stage→won/lost — don’t write it).
  3. Parent references use parent_object_type + parent_object_id. Notes, tasks, and activities attach to a parent via these two fields. Valid parent_object_type: person, company, deal.
  4. Schema attributes need 4 fields minimum. add_attribute requires attribute_key (snake_case), display_name (Title Case), data_type, and object_type. Enum types need enum_values. Reference types need reference_object_type.
  5. custom_attributes validation is LENIENT. Defined keys are validated; unknown keys pass through. Required attrs ARE enforced.
  6. Webhooks return signing_secret ONCE. Webhooks are registered over REST/CLI, not MCP (there is no webhook tool). Surface the whsec_… to the user immediately on the POST /webhook-endpoints (REST) or salty webhooks add (CLI) response.
  7. subscribed_events: ["*"] is the default. No glob patterns in v1.
  8. Pagination is cursor-based. Pass next_cursor back as the cursor param. No offset/page.
  9. Your key may be read-only. If a write returns 403 insufficient_scope, you were issued a read-only API key (scopes: ["read"]) — you can read but not create/update/delete. Tell the user; they can create a full-access key from the dashboard.

Common workflows

Create a person at a company that may not exist yet

One write per object — don’t create the person first then PATCH the link.

Who’s the primary contact at a company?

There’s no companies.primary_contact_id field in v1 (v1.1 candidate). Resolve:
  1. Look for custom_attributes.primary_contact_id on the company — that’s the convention.
  2. If unset, search_people {filter: {primary_company_id: {equals: <id>}}} — if exactly 1 result, surface them. If multiple, ask the user to designate.

Log a meeting

Add a tier field to people

Writes can immediately use custom_attributes: {tier: "pro"} after this returns 201.

”Create a task” / “Register a webhook” / “Define a custom object” — REST-only

These have no MCP tools. Guide the user with snippets:
Or via the salty CLI (salty webhooks add --url …). The webhook receiver must verify Salty-Signature: t=<ms>,v1=<hex> (HMAC-SHA-256 over <t>.<body>) and reject deliveries older than 5 minutes. See Webhooks.

Errors and what to do

Out of scope for v1

  • Bulk import endpoints (v1.1)
  • Search/filter on custom-object records (v1.1)
  • Webhook event glob patterns (v1.1)
  • Updating an activity’s parent (immutable)
  • Company.primary_contact_id field (v1.1 candidate)