{
 "service": "Crust Data",
 "version": "1.0.0",
 "summary": "Company data for AI agents; an agent signs its user up inside the chat with one emailed tap and 100 free credits.",
 "channels": {
  "rest": {
   "universal": true,
   "clients": [
    "Claude Code (curl)",
    "Claude Desktop (built-in browser pane)",
    "claude.ai (built-in browser pane)",
    "Cursor",
    "Codex"
   ],
   "agent_page": "https://crustdata.dileepadari.dev/agent"
  },
  "mcp": {
   "optional": true,
   "url": "https://crustdata.dileepadari.dev/mcp",
   "use_when": [
    "mobile (no browser pane)",
    "a permanent connection",
    "connector-only clients"
   ]
  }
 },
 "steps": [
  {
   "n": 0,
   "name": "catalog",
   "method": "GET",
   "url": "https://crustdata.dileepadari.dev/v1/catalog",
   "cost": 0
  },
  {
   "n": 1,
   "name": "sign-up",
   "method": "POST",
   "url": "https://crustdata.dileepadari.dev/v1/agent/sign-up",
   "body": {
    "human_email": "<the person's email, the one your client knows>",
    "agent_name": "<your client>"
   },
   "terms": "none in the request: the emailed link opens a page carrying the Terms link; the tap is the recorded acceptance",
   "then": "say say_to_user once (it names the terms link); never ask for a code"
  },
  {
   "n": 2,
   "name": "verify",
   "method": "GET",
   "url": "https://crustdata.dileepadari.dev/v1/agent/verify?signin_id={signin_id}&wait=25",
   "headers": {
    "Authorization": "Bearer <poll_token>"
   },
   "until": "status != pending_tap",
   "max_minutes": 10
  },
  {
   "n": 3,
   "name": "search",
   "method": "POST",
   "url": "https://crustdata.dileepadari.dev/v1/search",
   "headers": {
    "Authorization": "Bearer <_agent.grant>",
    "Idempotency-Key": "<uuid>"
   },
   "cost": "1 + rows"
  },
  {
   "n": "1-long",
   "name": "terms then start (when the person's own words are in the chat)",
   "method": "GET",
   "url": "https://crustdata.dileepadari.dev/v1/terms",
   "then": "POST https://crustdata.dileepadari.dev/v1/signin/start with email + terms_accepted (human_statement = their exact reply) or terms_shown"
  }
 ],
 "mailbox_connector": {
  "complete_with": "https://crustdata.dileepadari.dev/v1/signin/{signin_id}/complete-with-token",
  "rule": "detect, never request; never open the sign-in page in a browser you control"
 },
 "returning": {
  "renew": "https://crustdata.dileepadari.dev/v1/grants/renew",
  "credentials_file": "~/.config/crust/credentials",
  "touches": 0
 },
 "touches": {
  "claude_code_first": 1,
  "claude_desktop_builtin_browser_first": 1,
  "claude_ai_via_connector_first": 2,
  "with_mailbox_connected": 0,
  "when_the_email_is_not_known": "+1 (the person names it)",
  "returning": 0,
  "note": "the one touch is the tap on the emailed link; it is also the recorded terms acceptance"
 },
 "limits": {
  "free_credits": 100,
  "grant_hours": 24,
  "renew_days": 30,
  "link_minutes": 15,
  "search_limit_default": 25,
  "search_limit_max": 100,
  "human_statement_max_chars": 280
 },
 "email_rule": "Use the email your client already knows; ask for one only if you know none; never invent one; never send it anywhere else.",
 "playbook": {
  "affirmative_replies": [
   "yes",
   "yeah",
   "yep",
   "ok",
   "okay",
   "sure",
   "go",
   "go ahead",
   "continue",
   "proceed",
   "do it",
   "fine",
   "please",
   "that works"
  ],
  "rules": {
   "channel": "Pick the one your client has, in this order: (1) a shell with network: curl the REST calls; (2) the built-in browser pane (Claude Desktop): open /agent and run the page scripts; (3) Crust Data connector tools (start_signin, wait_for_signin, search_companies): use them; (4) fetch-only (GET, no POST): you cannot sign anyone in; say so in one line and give the person the one thing that unlocks it (see the failure table). Never claim you signed up when no POST was made. Append ?t=<unix seconds> to every GET you make through a fetch tool: fetch caches are keyed by URL and may hand you an old copy of this guide or of /v1/terms.",
   "email": "Use the address your client already knows: Claude Code puts the user's email in your context, a connected mailbox shows it, the person may have said it. Send the sign-up to it without a form. If you know none, ask the person for one; any affirmative reply to a suggested address counts (yes, yeah, yep, ok, okay, sure, go, go ahead, continue, proceed, do it, fine, please, that works), you decide whether their words mean yes. Never invent one, never make them retype one you already have.",
   "terms": "You submit no terms and no consent: POST /v1/agent/sign-up (or start_signin with the email only) sends an email whose link opens a page carrying the Crust Data Terms link; the person's tap there is the acceptance and is recorded. Say the say_to_user sentence once, word for word; it names the terms link. If the person objects, stop. The long form (their exact words as human_statement in terms_accepted) exists for clients that relay the chat; a 428 terms_acceptance_required only ever comes from the long route, so switch to the sign-up route.",
   "the link": "If a Gmail or Outlook connector is available in this chat, read the sign-in email yourself (sender login@stytch.com, subject in email_subject, sent after sent_at), take the token from its link and complete the sign-in with it; the person taps nothing. Otherwise ask the person to tap the link and wait. Never open the link in a browser you control; never ask for a code, there is none.",
   "secrets": "Never show the grant, poll_token or refresh_token; on the Desktop agent page they never leave the page. Give the person claim_url once."
  },
  "failures": [
   {
    "step": "channel",
    "you_see": "no browser tool in this chat (Claude Desktop)",
    "do_this": "tell the person: turn on the browser in the tools menu of this chat, or start a normal (not incognito) chat; incognito chats have no tools"
   },
   {
    "step": "channel",
    "you_see": "fetch is GET-only and there is no shell (claude.ai on the web)",
    "do_this": "tell the person to add the Crust Data connector with the one-click link on this page; that add is their one setup step; then start again"
   },
   {
    "step": "channel",
    "you_see": "the guide or /v1/terms you fetched looks old (another terms version than the live one)",
    "do_this": "append ?t=<unix seconds> to the URL and fetch again; never submit a terms version you did not just fetch"
   },
   {
    "step": "start",
    "you_see": "428 terms_acceptance_required",
    "do_this": "you skipped the notice: show notice_text, get the reply, call start again"
   },
   {
    "step": "start",
    "you_see": "your client's permission check refuses the call",
    "do_this": "send terms_shown instead of terms_accepted (no consent in the request); if it still asks the person to allow the call, that is their one click; never retry around a refusal"
   },
   {
    "step": "start",
    "you_see": "409 terms_version_superseded",
    "do_this": "the terms changed: show the new notice_text from the error, get a fresh reply, new idempotency key"
   },
   {
    "step": "start",
    "you_see": "422 email_invalid / email_domain_not_allowed",
    "do_this": "ask the person for another address (disposable domains are refused); nothing was created"
   },
   {
    "step": "start",
    "you_see": "422 message_not_sent",
    "do_this": "the mail provider refused that address; do not retry it; ask for another address"
   },
   {
    "step": "start",
    "you_see": "429 rate_limited",
    "do_this": "wait retry_after seconds, then repeat with the SAME idempotency key"
   },
   {
    "step": "start",
    "you_see": "503 verification_provider_unavailable / service_frozen",
    "do_this": "wait retry_after, repeat with the SAME key; after two failures tell the person to try later"
   },
   {
    "step": "wait",
    "you_see": "still pending after 10 minutes",
    "do_this": "say the email may be in spam; offer a resend (start again with resend=true after resend_available_at) or a different address"
   },
   {
    "step": "wait",
    "you_see": "410 signin_expired / link_already_used",
    "do_this": "the link expired or a mail scanner opened it: start again with a NEW key; nothing is lost"
   },
   {
    "step": "wait",
    "you_see": "409 signin_mismatch",
    "do_this": "the person signed in with a different email: ask which address to use; the page offers both"
   },
   {
    "step": "wait",
    "you_see": "browser script timed out (Desktop)",
    "do_this": "crust.wait() returns within 30 s; call it again until state is not pending"
   },
   {
    "step": "search",
    "you_see": "401 grant_missing / grant_expired / grant_revoked",
    "do_this": "renew with the stored credential (crust.ready() on Desktop, POST /v1/grants/renew elsewhere) or start the sign-in again"
   },
   {
    "step": "search",
    "you_see": "402 insufficient_credits",
    "do_this": "lower limit, or send the person to claim_url; do not retry"
   },
   {
    "step": "search",
    "you_see": "402 price_exceeds_cap",
    "do_this": "raise max_credits with the person or lower limit"
   },
   {
    "step": "search",
    "you_see": "422 validation_error / invalid_query",
    "do_this": "a filter value is not in the catalog: the error lists the allowed values (country ISO-2, funding_stage, industry); fix and retry"
   },
   {
    "step": "search",
    "you_see": "0 rows",
    "do_this": "free (a search that matches nothing is not charged): check the values against /v1/catalog, try the title variants in the hint, widen recent_hire_within_days"
   },
   {
    "step": "returning",
    "you_see": "401 refresh_invalid / refresh_expired / refresh_revoked",
    "do_this": "delete the stored credential and run the sign-in again; the account and credits are kept"
   }
  ]
 },
 "never": [
  "print the grant, poll_token or refresh_token",
  "ask for a code",
  "paraphrase notice_text",
  "suggest adding a connector on Claude Code, Desktop or claude.ai"
 ],
 "terms": {
  "version": "2026-10-27",
  "url": "https://crustdata.dileepadari.dev/terms/2026-10-27",
  "privacy_url": "https://crustdata.dileepadari.dev/privacy/2026-10-27"
 },
 "links": {
  "llms_txt": "https://crustdata.dileepadari.dev/llms.txt",
  "openapi": "https://crustdata.dileepadari.dev/openapi.json",
  "api_catalog": "https://crustdata.dileepadari.dev/.well-known/api-catalog",
  "server_card": "https://crustdata.dileepadari.dev/mcp/server-card",
  "claim": "https://crustdata.dileepadari.dev/claim"
 }
}