Private Preview - features may change without notice
GramSpec
Start free
Developer Reference

GRAM API

A small, JSON-over-HTTP surface for remote apps. You bring the LLM and the database; the API hands you a graph-grounded contract to build on: the exact system prompt the GramSpec pipeline uses, or a complete kit for running your own analyst agent loop.

Enterprise plan required. The GRAM API is part of the Enterprise tier. Generate an API key from your profile once your plan is active. See Pricing for tier details.

Base URL

https://gramspec.com/gramapi

Authentication

Every request must carry an X-Api-Key header with a per-user API key. Keys are issued from your profile and begin with the prefix gramspec_. A key identifies a single GramSpec user and inherits that user's permissions; only Enterprise and Admin accounts can use the API. If the plan behind a key lapses, the key stops working immediately.

X-Api-Key: gramspec_your_api_key_here

The same key is also accepted as a bearer token — Authorization: Bearer gramspec_… — which is the form MCP clients standardize on. The two carriers are interchangeable; X-Api-Key wins when both are present.

Missing or invalid keys get 401 Unauthorized. A valid key on a non-Enterprise account gets 403 Forbidden. Keep keys out of client-side code — treat them like a password.

The MCP server

Everything below is also served over the Model Context Protocol, so any MCP-capable agent — Claude Code, Claude Desktop, Cursor, or your own app on an agent framework — becomes a working analyst over your graphs with one connection:

https://gramspec.com/mcp

The connected agent gets eight tools composing the complete loop: list_projects, get_graph, graph_overview, describe_entity, search_graph, generate_sql, validate_sql, and run_query (read-only execution against a connected graph's home database, behind the same guard stack in-app Reason uses). Every tool declares itself read-only, so clients can relax per-call confirmation prompts. When a question has no path through the graph, the tools say exactly what is missing: the refusal contract, as a callable.

Alongside the tools, the canonical graph is an attachable resource at gramspec://project/{projectId}/graph, and the analyst_system_prompt prompt takes projectName and databaseType and turns any agent into the GramSpec analyst for that project.

A graph with no home connection still serves every non-executing tool, so you can reason over a model before it is wired to a database. generate_sql spends the account's AI allowance under the same limit as Query below; the rest are budget-free. Orientation is cheap on purpose: graph_overview returns a compact index of every table and column, which on a large warehouse graph costs a small fraction of get_graph.

Claude Code — one command:

claude mcp add --transport http gramspec https://gramspec.com/mcp \
    --header "X-Api-Key: gramspec_your_api_key_here"

Cursor — in mcp.json:

{
  "mcpServers": {
    "gramspec": {
      "url": "https://gramspec.com/mcp",
      "headers": { "X-Api-Key": "gramspec_your_api_key_here" }
    }
  }
}

claude.ai and Claude Desktop — add a custom connector with the URL above and sign in with your GramSpec account when prompted (OAuth; PKCE; the connection asks for your consent once). An organization admin can alternatively enter the API key as a fixed request header where that beta is available.

Your own app — any framework with MCP support connects the same way; a complete branded-analyst example in about a hundred lines ships in the Builder's Guide.

Rate limits

Requests are throttled per user. The prompt-and-export endpoints (SystemPrompt, AnalystKit, Projects, Graph, BuildPrompt, Rules) allow up to 120 requests per minute; Query, which spends AI budget, allows 30 per minute. Over the limit you get 429 Too Many Requests with body { "error": "rate_limited" } — wait a moment and retry. This is separate from the Query-only AI-budget 429 (budget_cooldown, see Error responses).

Endpoints

GET /gramapi/SystemPrompt

The primary endpoint. Returns the complete LLM system message — GRAM rules, SQL dialect profile, and your project's knowledge graph, stitched into one string. Cache it, pair it with the user's question, and send it to whatever model you like. GramSpec does not run the LLM and does not see your data.

Parameters

FieldRequiredNotes
projectNameYes (or projectId)Exact project title as shown in Grammar. Case-insensitive; URL-encode spaces.
projectIdYes (or projectName)Stable GUID from /gramapi/Projects; survives renames.
databaseTypeYessqlserver, snowflake, postgresql, mysql, mariadb, databricks. Pass the dialect your app executes SQL against.
promptModeNochat (default): markdown fences, multi-query, chart configs. singleshot: raw SQL only.

Request

curl "https://gramspec.com/gramapi/SystemPrompt?projectName=YourProjectName&databaseType=snowflake" \
  -H "X-Api-Key: gramspec_your_api_key_here"

Response 200

{
  "systemPrompt": "<complete string to send as the LLM system role>",
  "projectName":  "YourProjectName",
  "projectId":    "6f1e7a5c-4b12-4d9e-9a31-6c0f88b2a4d2",
  "databaseType": "snowflake",
  "promptMode":   "chat",
  "graphHash":    "sha256:7a8b..."
}

Send systemPrompt verbatim as the system role. Do not append, prepend, or re-wrap it — the graph and rules are already inside.

Caching

The response carries an ETag header equal to graphHash. Send it back on later calls:

curl "https://gramspec.com/gramapi/SystemPrompt?projectName=YourProjectName&databaseType=snowflake" \
  -H "X-Api-Key: gramspec_your_api_key_here" \
  -H 'If-None-Match: "sha256:7a8b..."'

Unchanged → 304 Not Modified, no body; keep your cache. The hash covers the fully assembled prompt, so any graph edit, dialect update, or rule change invalidates it automatically.

GET /gramapi/AnalystKit

The agentic path. Everything needed to stand up your own Analyst-style tool loop against your own data plane: the assembled analyst system prompt, the full graph, a compact name index, and the JSON Schemas for the seven analyst tools (run_query, search_graph, describe_entity, ask_user, record_finding, record_ledger_entry, end_turn). You run the loop; there is no server orchestration on this endpoint.

Parameters

FieldRequiredNotes
projectName / projectIdYes (one)Same resolution rules as SystemPrompt.
databaseTypeYesSame dialect list as SystemPrompt.
connectionNameNoDisplay name echoed into the prompt's "Connected data source" line.
syncedSqlSchemaNoPhysical schema qualifier, when your copy of the data lives under one.

Response 200

{
  "systemPrompt": "<assembled analyst system prompt>",
  "graph":        { "entities": [...], "factTypes": [...] },
  "graphJson":    "{\"entities\":[...]}",
  "compactIndex": "<name-only graph index>",
  "entityDossiers": {
    "sales.SalesOrder": "<pre-rendered markdown dossier>",
    "catalog.Product":  "<...>"
  },
  "tools": [
    {
      "name": "run_query",
      "description": "...",
      "inputSchema": { "type": "object", "properties": { ... } },
      "inputSchemaJson": "{...}"
    }
  ],
  "databaseType": "sqlserver",
  "projectId":    "6f1e7a5c-...",
  "projectName":  "YourProjectName",
  "promptHash":   "sha256:9c2d..."
}

Register the seven tools with your LLM exactly as returned — the system prompt references them by name, and their order is stable for prompt caching. Implement the handlers locally: run_query executes read-only SQL against your database, describe_entity returns entityDossiers[name] (a pre-rendered dossier per table — key, columns, aliases, enums, sample values, derived formulas, relationships — from the same renderer GramSpec's own apps use), search_graph searches the returned graph across names, aliases, readings, and sample values, and end_turn terminates the loop. promptHash covers the prompt and the dossiers and works with If-None-Match for 304 caching, same as SystemPrompt.

GET /gramapi/Projects

Lists the Grammar projects owned by the authenticated user. Use it to render a project picker, or to cache the stable projectId.

curl https://gramspec.com/gramapi/Projects \
  -H "X-Api-Key: gramspec_your_api_key_here"
[
  {
    "projectId":   "6f1e7a5c-4b12-4d9e-9a31-6c0f88b2a4d2",
    "title":       "<project title>",
    "modifiedUtc": "2026-08-01T14:22:03Z"
  }
]

GET /gramapi/Graph

Exports a project's GRAM knowledge graph as JSON, without building a prompt. Useful when your app renders the graph in its own explorer — the graph is already inside systemPrompt, so the LLM call doesn't need this.

Parameters

FieldRequiredNotes
projectName / projectIdYes (one)Same resolution rules as SystemPrompt.
downloadNo1 or true returns the CANONICAL artifact as an attachment named <title>.gramspec-graph.json — sorted, id-free, timestamp-free, the file a repo tracks.
versionIdNoServe a sealed HISTORICAL version's graph (see Versions below) instead of the live one. Its ETag never changes — the artifact keeps the title frozen at seal time, so a later project rename cannot invalidate history.

Inline (default)

curl "https://gramspec.com/gramapi/Graph?projectName=YourProjectName" \
  -H "X-Api-Key: gramspec_your_api_key_here"
{
  "projectId": "6f1e7a5c-4b12-4d9e-9a31-6c0f88b2a4d2",
  "title":     "<project title>",
  "graph":     { "entities": [...], "factTypes": [...] },
  "graphJson": "{\"entities\":[...],\"factTypes\":[...]}"
}

graph is the parsed object; graphJson is the raw string, byte-identical to what SystemPrompt embeds. Prefer graphJson when you round-trip the graph back to the API.

File download

curl "https://gramspec.com/gramapi/Graph?projectName=YourProjectName&download=1" \
  -H "X-Api-Key: gramspec_your_api_key_here" \
  -OJ

Every Graph response also carries canonicalJson and contentHash (sha256 of the canonical bytes), and an ETag of "sha256:<contentHash>". The hash is the graph's meaning identity: layout changes and re-exports never move it, a modeling change always does.

GET /gramapi/Versions

The project's version history: one entry per sealed version — versions mint automatically at editing-session granularity inside GramSpec, plus named milestones. Each entry carries the version's canonical contentHash and its witnessed renameJournal (renames recorded from the editor's own stable identities, never inferred).

curl "https://gramspec.com/gramapi/Versions?projectName=YourProjectName" \
  -H "X-Api-Key: gramspec_your_api_key_here"
{
  "projectId": "6f1e7a5c-...",
  "versions": [
    { "versionId": "…", "label": "Launch cut", "contentHash": "…",
      "createdUtc": "…", "renameCount": 1,
      "renameJournal": "[{\"kind\":\"entity\",\"from\":\"Customer\",\"to\":\"Client\"}]" }
  ]
}

Version your graph in git

The product does this for you: connect a GitHub repository once from Grammar's History panel and every sealed version commits the canonical artifact with a message that reads as the change. The recipe below is the pull-based alternative, for teams who want their own CI cadence, a non-GitHub host, or pull rather than push.

Because the artifact is canonical, mirroring it into a repo gives you dbt-style change management with diffs that read as modeling changes, never noise. The whole recipe is a scheduled pull that commits when the hash moves:

# nightly CI step — commit only when the graph's meaning changed
curl -sS "https://gramspec.com/gramapi/Graph?projectName=YourProjectName&download=1" \
  -H "X-Api-Key: $GRAMSPEC_API_KEY" \
  -H "If-None-Match: $(cat graph.etag 2>/dev/null)" \
  -o your-project.gramspec-graph.json -D headers.txt -w "%{http_code}" | grep -q 200 && {
    grep -i '^etag:' headers.txt | cut -d' ' -f2 > graph.etag
    git add -A && git commit -m "graph: $(date -u +%F)" && git push
  }

A ready-made Node version of this recipe (ETag sidecar, exit codes for CI) ships in the GramSpec repo as tests/graph-mirror/mirror.ts. A rename shows in the PR as one entity block moving plus one-line entityName updates in each referencing role; a constraint change is a one-line isMandatory / isUnique flip. Anyone on the team can read the diff aloud.

POST /gramapi/BuildPrompt

Same assembly as SystemPrompt, returned as structured pieces: the full prompt plus the rules template, the dialect block, and the graph as separate fields. Accepts a raw graphJson string as an alternative to a project reference — useful for inspecting how a graph you hold locally would be framed.

Parameters (JSON body)

FieldRequiredValues
graphJsonOne of these threeRaw GRAM knowledge-graph JSON string.
projectNameExact project title as shown in Grammar.
projectIdGUID from /gramapi/Projects.
databaseTypeYesSame dialect list as SystemPrompt.
promptModeNochat (default) or singleshot.

Response 200

{
  "prompt":       "<assembled GRAM system prompt>",
  "graphJson":    "{\"entities\":[...]}",
  "graph":        { "entities": [...], "factTypes": [...] },
  "rules":        "<rules template, graph slot empty>",
  "dialect":      "<dialect profile>",
  "databaseType": "sqlserver",
  "promptMode":   "chat"
}

GET /gramapi/Rules

The GRAM rules template and dialect profile with no graph embedded. For inspection — use SystemPrompt for production assembly, which handles the substitution correctly.

curl "https://gramspec.com/gramapi/Rules?databaseType=snowflake" \
  -H "X-Api-Key: gramspec_your_api_key_here"
{
  "dialect": "<dialect profile>",
  "rules":   "<GRAM rules template>"
}

POST /gramapi/Query

The managed path: runs a natural-language prompt through GramSpec's own LLM pipeline against a saved dataset and returns the parsed result. This is the one endpoint that spends your GramSpec AI allowance — if you run your own LLM, use SystemPrompt instead and keep model control local.

Parameters (JSON body)

FieldRequiredNotes
promptYesNatural-language question.
chatConfigIdYesGUID of a saved dataset you own.
providerNogemini (default), chatgpt, claude, grok.
promptModeNochat (default) or singleshot.

Request

curl -X POST https://gramspec.com/gramapi/Query \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: gramspec_your_api_key_here" \
  -d '{
    "prompt":       "Top 10 customers by revenue last quarter",
    "chatConfigId": "b1d2e3f4-5678-4abc-9def-1234567890ab"
  }'

Response 200

{
  "content":     "Here are the top 10 customers...",
  "sqlBlocks":   [ "SELECT TOP 10 ..." ],
  "sharePointBlocks": [],
  "chartConfig": {
    "type":    "bar",
    "xColumn": "CustomerName",
    "yColumn": "Revenue",
    "title":   "Top customers by revenue"
  },
  "raw":         "<full model output>",
  "provider":    "gemini"
}

sqlBlocks is an array of SQL strings — a question can legitimately produce more than one query, so loop the array rather than taking the first element. chartConfig is present only when the model proposed a chart; its optional seriesColumn adds multi-series grouping. If the model call itself fails, the response is still 200 with an error field set and empty content — transport worked, the model call didn't.

Supported databases

databaseType accepts the dialects the GramSpec prompt engine ships rules for:

  • sqlserver — Microsoft SQL Server (T-SQL)
  • snowflake — Snowflake
  • postgresql — PostgreSQL
  • mysql — MySQL
  • mariadb — MariaDB
  • databricks — Databricks SQL

Anything else falls back to a generic SQL profile. SharePoint graphs are not supported by this API — databaseType: "sharepoint" is rejected with 400 on every endpoint, because synced SharePoint data lives on GramSpec's private storage that remote callers cannot execute against.

End-to-end example

Python

import requests

API_KEY  = "gramspec_your_api_key_here"
BASE_URL = "https://gramspec.com/gramapi"
HEADERS  = {"X-Api-Key": API_KEY}

# Fetch (or revalidate) the system prompt for a project
resp = requests.get(
    f"{BASE_URL}/SystemPrompt",
    headers=HEADERS,
    params={"projectName": "YourProjectName", "databaseType": "sqlserver"},
)
resp.raise_for_status()
body = resp.json()
system_prompt = body["systemPrompt"]
graph_hash    = body["graphHash"]   # send back via If-None-Match next time

# Hand system_prompt to your LLM of choice as the system role,
# with the user's question as the user message.

C# / .NET

using System.Net.Http;
using System.Net.Http.Json;

var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Api-Key", "gramspec_your_api_key_here");

var body = await client.GetFromJsonAsync<JsonElement>(
    "https://gramspec.com/gramapi/SystemPrompt" +
    "?projectName=YourProjectName&databaseType=sqlserver");

string systemPrompt = body.GetProperty("systemPrompt").GetString();
string graphHash    = body.GetProperty("graphHash").GetString();

Error responses

StatusMeaning
400Missing or invalid parameters; unsupported databaseType; or a typed condition — connection_password_expired (the dataset owner must re-enter their database password) or graph_member_unresolved (a composed dataset references a graph that no longer resolves).
401Missing, invalid, revoked, or expired X-Api-Key.
403Valid key without an active Enterprise plan — or a dataset you don't own.
404Project or dataset not found for this user.
429Two cases, both JSON: rate_limited — per-user request rate exceeded (any endpoint; see Rate limits); or budget_cooldown — the account's AI allowance is used for now, body includes resetsAtUtc (Query only). Distinguish them by the error field.
500Server error while loading or exporting the project.
503Authentication service temporarily unavailable; retry.

Error responses are JSON of the form { "error": "message" }. One deliberate exception: an LLM-level failure on Query returns 200 with the error field set on the parsed response, so your parser handles one shape for both outcomes.

Getting an API key

  1. Make sure your GramSpec account is on the Enterprise plan. See Pricing.
  2. Open your profile and scroll to the API keys section.
  3. Generate a new key, copy it immediately (the full value is shown only once), and store it in your secret manager. Keys are perpetual by default; set an optional expiry in days at creation if your security policy wants rotating credentials.
  4. Send it as X-Api-Key on every request.

If you suspect a key has leaked, revoke it from the same profile section and issue a new one — revoked keys stop working immediately.

See also