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/integrationsSpec version: 1.3.0

Looking for a step-by-step setup guide or an LLM-ready system prompt?
  • 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-mcp package 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 in claude_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.md content 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.md files 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.

  1. Open your profile IntegrationsCreate token. Pick the read & write preset and copy the secret.
  2. 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.md format, the card types, the per-field length caps, and the html allow-list.
  3. 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.
  4. 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.md

Card 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.md

4. 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.

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

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:

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:

See the OpenAPI spec for the full enumeration.