Integrations API
Push flashcard decks into Cramdeck from ChatGPT custom GPTs, Claude tools, or any HTTP client. This page shows authentication, the two import body formats (markdown and JSON), and copy-paste configs for the most common AI tools.
API base URL: https://dev.cramdeck.com/api/v1/integrations — Spec version: 1.3.0
- End-user setup guide (PL) — how to wire up ChatGPT or Claude with your PAT, with both author-only and tool-use patterns.
- Test locally with Claude — developer-focused walkthrough for verifying the round-trip on your own machine (PAT → Claude →
curl→ deck in app), with self-signed cert notes and a one-shot script. - Claude Desktop connector — the
packages/cramdeck-mcppackage in this repo is a stdio MCP server that exposes this API as five tools (cramdeck_whoami,cramdeck_list_decks,cramdeck_get_deck,cramdeck_import_deck,cramdeck_update_deck). Build it once, register inclaude_desktop_config.json, and Claude Desktop calls the API directly. See the package README for setup. - AI agent — author system prompt — drop-in instructions for an LLM that produces
.deck.mdcontent for a human to import. - AI agent — tool-use system prompt — for autonomous agents that call this API directly (custom GPT actions, Claude tool-use, MCP).
- Sample decks — five canonical
.deck.mdfiles covering every card feature (text, markdown, html, latex, image cards, oneSided, info hints).
Quick start: generate a deck with Claude
The fastest path that works on any deployment (including this local dev host): let Claude write a .deck.md file, then curl it to the import endpoint. No tool-use plumbing required.
- Open your profile → Integrations → Create token. Pick the read & write preset and copy the secret.
- Open Claude (web, desktop, or any client). In the system prompt / project instructions, paste the contents of ai-agent-author-prompt.md. That file teaches Claude the
.deck.mdformat, the card types, the per-field length caps, and the html allow-list. - Ask Claude for a deck. Example: “Generate a 15-card deck on the Krebs cycle in English. Mix definition, explanation, and pitfall cards.” Save the output to a file like
krebs.deck.md. - Run the curl command below from the same machine (replace the bearer token with your PAT and the file path with your deck):
curl -X POST https://dev.cramdeck.com/api/v1/integrations/decks/import \
-H "Authorization: Bearer cdk_pat_AbCd1234_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: text/markdown" \
--data-binary @deck.deck.mdCard types & metadata
Every card SHOULD set extras.cardType — it controls the layout family, accent colour, and category label rendered on the card. The legacy extras.template field is deprecated; the importer normalises it and emits a warning.
Nine canonical types are supported: basic, definition, explanation, comparison, procedure, pitfall, formula, name-formula, and code-behavior. Image is a content choice — attach frontMedia / backMedia to any card type. Pair the type with optional subject, topic, direction, prompt, difficulty, and tags metadata for richer rendering and recall.
For type-by-type guidance plus example markdown blocks, see the author system prompt, the samples README, or the card-types preview for a visual gallery of the chrome surface.
1. Get a Personal Access Token
Open your profile, switch to the Integrations tab, click Create token, give it a name, and pick a preset (read-only or read & write). Copy the secret immediately — it's shown exactly once.
Tokens are scoped to a single user and can be revoked at any time. Format: cdk_pat_<public>_<secret>. Pass it as Authorization: Bearer <token>.
2. Test your token
curl https://dev.cramdeck.com/api/v1/integrations/whoami \
-H "Authorization: Bearer cdk_pat_AbCd1234_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"3. Import a deck
The import endpoint accepts two body formats:
JSON (recommended for AI tools)
curl -X POST https://dev.cramdeck.com/api/v1/integrations/decks/import \
-H "Authorization: Bearer cdk_pat_AbCd1234_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"deck": { "title": "French Verbs — A1", "lang": "fr", "tags": ["french", "verbs"] },
"cards": [
{ "front": "manger", "back": "to eat", "extras": { "cardType": "basic" } },
{ "front": "boire", "back": "to drink","extras": { "cardType": "basic" } }
]
}'Markdown (.deck.md)
Same format as the export feature in the deck editor — round-trip compatible.
curl -X POST https://dev.cramdeck.com/api/v1/integrations/decks/import \
-H "Authorization: Bearer cdk_pat_AbCd1234_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: text/markdown" \
--data-binary @deck.deck.md4. Update an existing deck
Add ?mode=update&targetDeckId=<id> to replace every card in a deck while keeping its id and access settings. Useful for “regenerate this deck” flows.
5. ChatGPT plugin (OAuth)
The preferred ChatGPT path is a plugin + remote MCP server. ChatGPT links your CramDeck account with OAuth 2.1 (no PAT to paste). After linking, ask ChatGPT to create a deck; it calls create_flashcard_deck and returns study / edit links.
Discovery document: /.well-known/oauth-protected-resource. Profile → Integrations shows the connection once linking is enabled for this environment. PATs remain available for Claude Desktop and Custom GPTs.
Operators: set INTEGRATIONS_OAUTH_ISSUER and INTEGRATIONS_OAUTH_AUDIENCE after registering the CramDeck API resource in Entra External ID. Optional: OPENAI_APPS_CHALLENGE_TOKEN for domain verification at /.well-known/openai-apps-challenge.
6. ChatGPT Custom GPT (PAT)
In the GPT editor, open Configure → Actions → Create new action, paste the OpenAPI snippet below, and add an Authentication entry of type API Key (Custom) with header name Authorization and value Bearer cdk_pat_….
Or skip the snippet entirely and import our full spec from /api/v1/integrations/openapi.json — it advertises the same base URL you are visiting now and always reflects the live contract.
{
"openapi": "3.1.0",
"info": {
"title": "Cramdeck",
"version": "1.3.0"
},
"servers": [{ "url": "https://dev.cramdeck.com/api/v1/integrations" }],
"paths": {
"/decks/import": {
"post": {
"operationId": "importDeck",
"summary": "Create a new deck from JSON",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ImportDeckJson" }
}
}
},
"responses": {
"201": { "description": "Created" }
}
}
}
}
}6. Claude tool
Anthropic's tool-use API takes a JSON tool definition. Paste the snippet below into your tool list and call it from your prompt with the deck content. After Claude emits a tool-call, your wrapper makes the HTTP request to https://dev.cramdeck.com/api/v1/integrations/decks/import with the arguments as the JSON body.
{
"name": "cramdeck_import_deck",
"description": "Create a flashcard deck in Cramdeck from a structured JSON payload. Each card SHOULD set extras.cardType (basic | definition | explanation | comparison | procedure | pitfall | formula | name-formula | code-behavior). Image is a content choice — attach frontMedia/backMedia to any card type. Hard caps: 4000/4000 chars per side, 1000 per extras.info, 250 cards per deck. The html content mode is sandboxed to a small allow-list (p, h1-h6, br, hr, sub, sup, small, mark, kbd, abbr, span). KaTeX runs with trust:false (no \\href / \\includegraphics).",
"input_schema": {
"type": "object",
"required": ["deck", "cards"],
"properties": {
"deck": {
"type": "object",
"required": ["title"],
"properties": {
"title": { "type": "string", "maxLength": 200 },
"description": { "type": "string", "maxLength": 5000 },
"lang": { "type": "string", "default": "en" },
"visibility": {
"type": "string",
"enum": ["public", "unlisted", "private"],
"default": "private"
},
"tags": { "type": "array", "items": { "type": "string", "maxLength": 40 }, "maxItems": 20 }
}
},
"cards": {
"type": "array",
"minItems": 1,
"maxItems": 250,
"items": {
"type": "object",
"properties": {
"front": { "type": "string", "maxLength": 4000 },
"back": { "type": "string", "maxLength": 4000 },
"oneSided": { "type": "boolean", "default": false },
"frontMedia": {
"type": "object",
"properties": {
"imageUrl": { "type": "string", "format": "uri", "maxLength": 2048 },
"imageAlt": { "type": "string", "maxLength": 500 }
}
},
"backMedia": {
"type": "object",
"properties": {
"imageUrl": { "type": "string", "format": "uri", "maxLength": 2048 },
"imageAlt": { "type": "string", "maxLength": 500 }
}
},
"extras": {
"type": "object",
"properties": {
"cardType": {
"type": "string",
"enum": [
"basic", "definition", "explanation", "comparison",
"procedure", "pitfall", "formula", "name-formula",
"code-behavior"
]
},
"frontType": { "type": "string", "enum": ["text", "markdown", "html", "latex"] },
"backType": { "type": "string", "enum": ["text", "markdown", "html", "latex"] },
"subject": { "type": "string", "maxLength": 60 },
"topic": { "type": "string", "maxLength": 80 },
"prompt": { "type": "string", "maxLength": 200 },
"info": { "type": "string", "maxLength": 1000 },
"direction": { "type": "string", "enum": ["name->formula", "formula->name"] },
"difficulty":{ "type": "string", "enum": ["easy", "medium", "hard"] },
"tags": { "type": "array", "items": { "type": "string", "maxLength": 30 }, "maxItems": 10 }
}
}
}
}
}
}
}
}If you don't want to write a tool wrapper, the Quick start section above shows the copy-paste-and-curl workflow that works without one.
Sample decks
Five canonical .deck.md files demonstrate every feature this API supports. They're served as static files, round-trip-tested via the import endpoint, and safe to share with an LLM as reference material.
- french-a1-essentials.deck.md — plain text Q&A, language vocab.
- js-array-methods.deck.md — markdown content type with fenced code blocks.
- chemistry-common-compounds.deck.md — HTML content type for sub/superscripts;
extras.infohints. - physics-mechanics-formulas.deck.md — LaTeX equations with
cardType: formula,name-formula, andpitfall. - world-flags-europe.deck.md —
frontMediaimages attached tobasicandexplanationcards (renderer auto-applies the image-dominant 65/35 layout for short captions).
See the samples README for the full card-meta block reference.
Limits & security
Every write path enforces the same caps. Authoring tools (LLMs included) should plan for these up front — the parser rejects imports that violate them and lists the first 5 offenders so you can shorten manually.
Per-field length caps
front/back: 4,000 characters each.extras.info: 1,000;prompt: 200;subject: 60;topic: 80.extras.tags: 10 entries × 30 chars each.imageUrl: 2,048;imageAlt: 500.- Cards per deck: 250. Split larger requests across multiple decks.
HTML content mode is sandboxed
When a side uses frontType: html or backType: html, only this allow-list of tags survives sanitisation: p, h1–h6, br, hr, sub, sup, small, mark, kbd, abbr, span. The class attribute is permitted on span only. Do not emit style, event handlers, anchors, images, or media tags — they will be stripped.
Image URLs
Must be https://… absolute URLs or root-relative paths (/api/media/…). javascript:, data:, vbscript:, and file: URLs are rejected on write.
KaTeX
Math runs with trust: false — commands that emit arbitrary HTML or URLs (\href, \url, \includegraphics, \htmlClass) render as inert text. Stick to math.
Rate limits
Two independent buckets:
- Integrations API (
/api/v1/integrations/*) — 60 req/min, 2,000 req/day per Personal Access Token. - Session card-write API (
/api/cardsets/.../cards*) — 120 req/min, 5,000 req/day per user.
When a bucket fills, the response is HTTP 429 with a Retry-After header (seconds). Back off and retry.
Errors
All errors share a common envelope:
{ "error": { "code": "DECK_NOT_FOUND", "message": "Deck not found." } }Common codes:
UNAUTHORIZED(401) — missing / invalid PAT.FORBIDDEN(403) — token lacks the required scope.DECK_NOT_FOUND(404).INVALID_PAYLOAD/CARD_FIELD_TOO_LONG/INVALID_IMAGE_URL(400) — write-time validation failed.RATE_LIMITED/CARD_WRITE_RATE_LIMITED(429) — back off usingRetry-After.
See the OpenAPI spec for the full enumeration.