# Crystal Data API — Full Reference Status: preview. This document describes the current contract for Crystal's self-serve Data API. It is not yet publicly available. Do not call an endpoint that isn't listed here — if you can't find what you're looking for in this document, it doesn't exist; ask rather than guessing at a plausible-looking route. ## Base URL https://api.crystalknows.com ## Authentication Send your API key as a bearer token on every request: Authorization: Bearer Generate a key at https://data.crystalknows.com/api-keys (requires a verified email). Treat it like a password regardless — anyone with it can call the API as you. ## Rate limits - `GET /v4/profile`: 120 requests/minute per token. - `POST /v4/predictions`: equal to or more generous than the above — submitting a job is cheap; the queue smooths actual processing. Exceeding a limit returns `429` with a `Retry-After` header. ## Credits Two credit types are spent by this API: - **API credits** — spent when `POST /v4/predictions` successfully finds or creates a profile (not charged on a "not found" result), and when `POST /v4/content/revise_email` successfully returns a revision (not charged on a validation error or an upstream failure). Both draw from the same pool. Unlike fetch/content credits below, this pool is not idempotent: revising a second draft for the same person costs another credit, since it's new work every time, not a repeat fetch of the same resource. - **Fetch/content credits** — spent on `GET /v4/profile` lookups of already-known profiles and on the read-only content endpoints below. Re-fetching the same profile again within your current billing cycle is free. `POST /v4/content/generate_prompt` is free and spends no credit of either kind - see that endpoint below. ## Endpoints ### GET /v4/profile Real-time lookup of a profile Crystal already knows. Does not create new profiles and does not spend a prediction credit — only a fetch/content credit, and only if this exact profile hasn't already been fetched this billing cycle. Returns `404` if the profile isn't already known; use `POST /v4/predictions` to search for and create a new one. Query parameters (all optional, provide enough to identify one person): | Param | Type | Description | | --- | --- | --- | | `full_name` | string | Full name of the profile | | `email` | string | Email of the profile | | `linkedin_url` | string | LinkedIn URL of the profile | | `job_title` | string | Job title of the profile | | `company_name` | string | Company name of the profile | | `phone` | string | Phone number of the profile | Response `200`: { "id": "string", "first_name": "string", "last_name": "string", "photo_url": "string", "url": "string", "verified": true, "personalities": { ... }, "content": { ... } } Response `404`: profile not found. ### POST /v4/predictions Async profile creation. Submits a query and returns immediately with a job id; Crystal calls out to external data providers in the background (this is what makes it async — that lookup is inherently variable-latency). Poll-only — jobs typically complete within tens of seconds, so poll on a short interval (a few seconds) rather than treating this like a long-running batch job. Request body: { "query": { "full_name": "string", "email": "string", "linkedin_url": "string", "job_title": "string", "company_name": "string", "phone": "string" }, "record_id": "your-idempotency-key" } `record_id` is a client-supplied idempotency key — resubmitting the same `record_id` will not double-charge. Response `202`: { "job_id": "string", "status": "queued" } A prediction credit is spent only if the job completes with a profile found — not on a "not found" result, and not on submission. ### GET /v4/predictions/:job_id Poll for the result of a job submitted to `POST /v4/predictions`. Response `200` while in progress: { "job_id": "string", "status": "queued" | "processing" } Response `200` on completion — the full profile, same shape as `GET /v4/profile`: { "job_id": "string", "status": "completed", "profile": { "id": "string", "first_name": "string", "last_name": "string", "photo_url": "string", "url": "string", "verified": true, "personalities": { ... }, "content": { ... } } } Response `200` if nothing was found: { "job_id": "string", "status": "not_found" } ### Content endpoints The endpoints below are read-only over profiles you already have an id for. Each spends a fetch/content credit (free re-fetch of the same resource within your current billing cycle). `generate_prompt` and `revise_email`, documented separately right after this list, are the exceptions - one is free, the other spends an API credit and isn't a repeatable-for-free fetch - see the Credits section above. - `GET /v4/content/profile/:id` — full personality content for a profile. - `GET /v4/content/profile/:id/selling_to` — a "selling to this person" playbook. - `POST /v4/content/profiles/communication_advice` — body `{ "profile_ids": ["...", "..."] }`, returns communication advice for each profile given. - `POST /v4/content/profiles/relationship_matrix` — body `{ "profile_ids": ["...", "..."] }`, returns a relationship matrix across the given profiles. ### POST /v4/content/generate_prompt — free, no credit spent Returns a ready-to-inject, DISC-personality-tuned prompt for communicating with a person — guidance you paste into your own AI workflow. Generation is deterministic; no model runs on Crystal's side, which is why it's free. Use this if you're bringing your own LLM. Identify the person with a Crystal Profile `id` (preferred) or a raw `disc_type` (e.g. `"D"`, `"Di"`, `"Sc"`) when no profile exists - pass exactly one, not both. `objective` is optional — a task to focus the prompt on (e.g. `"write a follow-up email after a demo"`). Request body, identifying by profile `id`: { "id": "string", "objective": "string" } Request body, identifying by `disc_type` instead (no profile exists): { "disc_type": "string", "objective": "string" } Response `200`: { "prompt": "string", "disc_type": "string", "archetype": "string", "guidance": { ... } } ### POST /v4/content/revise_email — costs 1 API credit Rewrites a draft email so it lands well with the recipient, adapting tone, structure, and directness to their DISC type. Unlike `generate_prompt`, this runs a real model call on Crystal's own infrastructure, so it costs 1 API credit per call, charged only on a successful revision — not on a validation error or an upstream failure. If you'd rather avoid the cost and bring your own LLM, use `generate_prompt` instead and do the rewrite yourself. `email` is the draft to revise. Identify the recipient the same way as `generate_prompt` — profile `id` (preferred) or a raw `disc_type`, not both. Request body, identifying by profile `id`: { "email": "string", "id": "string" } Request body, identifying by `disc_type` instead: { "email": "string", "disc_type": "string" } Response `200`: { "revised_email": "string", "changes": ["string", "..."] } Response `402`: out of API credits. ## Errors Errors are returned as: { "error": { "code": "string", "message": "string" } } | Status | Meaning | | --- | --- | | 400 | Malformed request — missing/invalid parameters. | | 401 | Missing or invalid `Authorization` header. | | 402 | Out of credits (e.g. `POST /v4/content/revise_email`). | | 404 | Resource not found (e.g. `GET /v4/profile` with no match). | | 429 | Rate limited — see `Retry-After` header. | | 500 | Unexpected server error. | ## MCP Crystal also exposes an MCP server (`https://api.crystalknows.com/_mcp`) for connecting to Claude, ChatGPT, or Copilot directly, as an alternative to calling the REST API. If you're working in Claude Code specifically, install the official plugin rather than wiring up MCP by hand: https://github.com/crystal-project-inc/crystal-knows-plugin — it handles auth via OAuth and ships two skills (`premeeting`, `transcript-analysis`) on top of the raw MCP tools. Per-client setup steps (Claude Desktop, ChatGPT, Copilot, and a URL-embedded-token convention for headerless clients) are at https://data.crystalknows.com/mcp — sign-in required.