# AI agents and Ankinize

Give an AI agent an Ankinize API key and it can create decks, write flashcards, search your notes and read what is due, through the REST API or the TypeScript SDK. The cards sync to Anki Desktop, AnkiDroid and AnkiMobile like any others. This is how you ankinize anything an agent can read: an article, a lecture, a chat, a codebase.

## Give the agent access

1. Create a key under *API Keys & Devices*. Use the `read` scope for an agent that only looks things up, and `write` for one that adds cards. See [Authentication](https://ankinize.com/docs/authentication).
2. Put it in the agent's environment as `ANKINIZE_API_KEY`. Never paste it into a prompt.
3. Point the agent at the skills below, or hand it the tools in the next section.

## For AI agents

Agent skills are plain Markdown files an agent loads to learn the API. They are generated from the OpenAPI document, so they match the server.

| Resource | What it is |
| --- | --- |
| [https://ankinize.com/.well-known/agent-skills/ankinize-sdk/SKILL.md](https://ankinize.com/.well-known/agent-skills/ankinize-sdk/SKILL.md) | The agent skill for the TypeScript SDK: set-up, every function, batches, errors, retries and type-safe names. Load this when the task is TypeScript or JavaScript. |
| [https://ankinize.com/.well-known/agent-skills/ankinize-api/SKILL.md](https://ankinize.com/.well-known/agent-skills/ankinize-api/SKILL.md) | The agent skill for the REST API: authentication, conventions and every endpoint. Use it from any other language or with plain HTTP. |
| [https://ankinize.com/.well-known/agent-skills/index.json](https://ankinize.com/.well-known/agent-skills/index.json) | The discovery index of both skills, with the SHA-256 of each file. |
| [https://ankinize.com/llms.txt](https://ankinize.com/llms.txt) | The llms.txt index: what Ankinize is, and a link to every page and skill. |
| [https://ankinize.com/llms-full.txt](https://ankinize.com/llms-full.txt) | Every documentation page in one Markdown file. |
| [https://ankinize.com/api/v1/openapi.json](https://ankinize.com/api/v1/openapi.json) | The OpenAPI document: exact parameters and responses. SDK functions are named after its operation ids. |

Any page of these docs is also available as Markdown: add `.md` to its address (`https://ankinize.com/docs/sdk.md`), or ask for it with `Accept: text/markdown`.

## A working agent in 85 lines

This script gives a Claude model three tools, backed by the SDK: list decks, search notes and add cards. Run it with a request in plain language. The model decides which tool to call and the loop feeds the results back until it answers.

agent.ts

```
import Anthropic from "@anthropic-ai/sdk";
import { client, createNotes, listDecks, listNotes } from "@ankinize/sdk";

client.setConfig({ auth: process.env.ANKINIZE_API_KEY });

const tools: Anthropic.Tool[] = [
  {
    name: "list_decks",
    description: "List the decks in the user's Anki collection.",
    input_schema: { type: "object", properties: {} },
  },
  {
    name: "search_notes",
    description: "Search notes with Anki search syntax, such as 'deck:German tag:a1'.",
    input_schema: {
      type: "object",
      properties: { query: { type: "string" } },
      required: ["query"],
    },
  },
  {
    name: "add_cards",
    description: "Add Basic flashcards to a deck.",
    input_schema: {
      type: "object",
      properties: {
        deck: { type: "string" },
        cards: {
          type: "array",
          items: {
            type: "object",
            properties: { front: { type: "string" }, back: { type: "string" } },
            required: ["front", "back"],
          },
        },
      },
      required: ["deck", "cards"],
    },
  },
];

async function run(name: string, input: any): Promise<unknown> {
  if (name === "list_decks") return (await listDecks()).decks;
  if (name === "search_notes") return (await listNotes({ q: input.query, limit: 20 })).notes;
  if (name === "add_cards") {
    const result = await createNotes(
      input.cards.map((card: { front: string; back: string }) => ({
        notetypeName: "Basic",
        deckName: input.deck,
        fields: { front: card.front, back: card.back },
      })),
    );
    return { created: result.created, failed: result.failed };
  }
  throw new Error(`unknown tool ${name}`);
}

const anthropic = new Anthropic();
const messages: Anthropic.MessageParam[] = [
  { role: "user", content: process.argv[2] ?? "Add 5 cards about the French Revolution to a deck called History." },
];

for (;;) {
  const reply = await anthropic.messages.create({
    model: "claude-sonnet-5-5",
    max_tokens: 2048,
    tools,
    messages,
  });
  messages.push({ role: "assistant", content: reply.content });
  if (reply.stop_reason !== "tool_use") {
    console.log(reply.content.flatMap((part) => (part.type === "text" ? [part.text] : [])).join("\n"));
    break;
  }
  const results: Anthropic.ToolResultBlockParam[] = [];
  for (const part of reply.content) {
    if (part.type !== "tool_use") continue;
    try {
      results.push({ type: "tool_result", tool_use_id: part.id, content: JSON.stringify(await run(part.name, part.input)) });
    } catch (error) {
      results.push({ type: "tool_result", tool_use_id: part.id, is_error: true, content: String(error) });
    }
  }
  messages.push({ role: "user", content: results });
}
```

Run

```
bun add @ankinize/sdk @anthropic-ai/sdk
ANKINIZE_API_KEY=... ANTHROPIC_API_KEY=... bun agent.ts "Add 5 cards on the French Revolution to History"
```

The same three functions fit any agent framework or tool-calling API: only the tool schemas change.

> Use MCP instead of writing a loop
> 
> If your client speaks MCP (Claude, Cursor, VS Code and others), skip the script: connect it to `https://ankinize.com/mcp` and it gets every SDK function as a tool, with sign-in handled for you. See [the MCP server](https://ankinize.com/docs/mcp).

## Letting an agent write safely

| Concern | What to do |
| --- | --- |
| The agent retries a call | Send an `Idempotency-Key` on writes. The first answer is replayed instead of adding the card twice. |
| Anki is syncing | A write answers `423` with a `Retry-After`. Wait that many seconds and try again. |
| Too many requests | Past the plan limit the API answers `429` and changes nothing. Use the batch calls: one request can create many notes. |
| A bad batch | Each entry reports its own `ok` and error, so one bad card does not hide the others. Pass `atomic` to write all or nothing. |
| Changing a note type | Adding a field forces a Full Sync on every device, so the API asks for `acceptFullSync=true` first. Do not set it blindly from an agent. |
| Reading only | Use a `read` key. Every write is then refused. |

## More

Put a card in one click with a [browser extension](https://ankinize.com/docs/tutorials/browser-extension). Coming from AnkiConnect? See the [comparison](https://ankinize.com/docs/ankiconnect-alternative).
