Ikrames Docs
Developers

MCP server

Connect an AI agent to Ikrames — list brands, read costs, generate images, and build reel drafts over the Model Context Protocol.

Ikrames exposes an MCP server so an agent can work inside your workspace: read what is there, spend credits to generate, and fetch the result.

It is built for programs, not for people. Everything here needs an API key. If you are looking for the app itself, start at Getting Started.

One key belongs to exactly one workspace. Every tool is scoped to that workspace, and the workspace is never an argument you pass — so it is not something a caller can get wrong.

AI labelling and transparency

Read this before you build anything that distributes what you generate.

Images from generate_image are machine-generated. Ikrames signs them with a C2PA manifest, embedded in the file itself, declaring the content as produced by a trained algorithm. That signature travels with the file wherever it goes.

What you get over this API is the master — and the master carries no visible label. A visible "AI generated" mark is applied by Ikrames' own publishing paths, at the moment a post goes out to a platform. It is written onto a separate copy. Fetch the file over MCP and you receive the unlabelled original.

This shifts a duty onto you.

Under the EU AI Act (Article 50), the machine-readable marking is the provider's job — Ikrames does that for every file it produces. The visible disclosure is a separate duty that falls on whoever publishes. It applies to AI content that presents as realistic — people, places, events, including entirely fictional people — and the Act reads that broadly. If you take a file out over this API and publish it yourself, that disclosure is yours to make. Platforms that read C2PA (TikTok, Meta, YouTube) may add their own label on upload, but a platform label does not replace your duty. This describes where the boundary sits; it is not legal advice.

get_asset reports what a file carries:

"markierung": { "c2pa_signed": true, "visible_label": false }

get_asset is strict about this. It reads the stored file and checks for the signature before handing out an address. If the signature is missing, the call fails — you get an error, not a quiet unsigned download. Elsewhere in Ikrames a signing failure is logged and the render continues; over this API it is not, because here the file leaves the building.

Compositions are different, and weaker. create_composition produces a draft inside your workspace. It does not create a publishable post, and it does not yet record an origin marking of its own — that marking is attached later, by whatever turns the draft into a post. Treat a composition as unlabelled material until it has gone through Ikrames' publishing path.

Audio is not covered yet. See Not available yet.

Connecting

Endpointhttps://ikrames.com/api/mcp
TransportStreamable HTTP (POST)
Protocol2025-06-18
Sessionnone — every request carries its own key

Two headers are required on every call. The protocol header is not optional: without it the transport rejects the request before your key is ever read.

Authorization: Bearer <YOUR_KEY>
Mcp-Protocol-Version: 2025-06-18

Claude Desktop

{
  "mcpServers": {
    "ikrames": {
      "type": "http",
      "url": "https://ikrames.com/api/mcp",
      "headers": {
        "Authorization": "Bearer <YOUR_KEY>"
      }
    }
  }
}

curl

curl -s https://ikrames.com/api/mcp \
  -H "Authorization: Bearer $IKRAMES_KEY" \
  -H "Content-Type: application/json" \
  -H "Mcp-Protocol-Version: 2025-06-18" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": { "name": "curl", "version": "1" }
    }
  }'
{
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": { "listChanged": true } },
    "serverInfo": { "name": "ikrames", "version": "1.0.0" }
  },
  "jsonrpc": "2.0", "id": 1
}

Responses arrive as either plain JSON or a single Server-Sent Event frame, so send Accept: application/json, text/event-stream and be ready to strip a data: prefix.

Authentication and scopes

A key is a bearer token. It is stored only as a hash — Ikrames cannot show it to you again, so keep it the way you would keep a password, out of source control and out of chat logs.

There are two scopes:

ScopeTools
readlist_brands, get_costs, get_asset
generategenerate_image, create_composition

A read key does not see the generate tools at all. They are not registered for it: they are absent from tools/list, and calling one returns "Tool generate_image not found" — not "forbidden". That wording is deliberate. An error that distinguishes "exists but you may not" from "does not exist" tells an unauthorised caller what to go looking for.

Rejected keys all get the same answer, for the same reason: an unknown key, a revoked key, and a malformed header each return 401 with no detail. The real reason goes to the server log.

Keys can be revoked. A revoked key stops working immediately, on the next request — there is no cache to wait out.

Getting a key: contact us.

There is no self-service key management in the app yet — keys are issued by hand. This is an open gap, not a page that is missing.

Tool reference

list_brands

The brands in your workspace. Takes no arguments — and means it: passing any argument is an error.

{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
  "params": { "name": "list_brands", "arguments": {} } }
{
  "brands": [
    { "id": "fcdbdac6…", "display_name": "yourlife.ricke",
      "slug": "yourlifericke", "locale": "de", "is_active": true }
  ]
}

id is what you pass as brand_id to the generating tools.

get_costs

What this workspace has spent, at cost. Figures are the purchase price from the provider, without margin — this is a cost report, not an invoice.

ArgumentTypeMeaning
vonstring (ISO timestamp) · optionalFrom, inclusive. Omit for all time.
bisstring (ISO timestamp) · optionalTo, inclusive.
gruppierung"context" | "model" | "ref" · optionalHow to group. Default context.
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call",
  "params": { "name": "get_costs",
              "arguments": { "gruppierung": "context" } } }
{
  "zeitraum": { "von": null, "bis": null },
  "llm": [
    { "schluessel": "agent_run", "anzahl": 102, "usd": 8.098616 },
    { "schluessel": "fill_slide_generate", "anzahl": 59, "usd": 0.562623 }
  ],
  "provider": [
    { "schluessel": "mcp_image_generate", "anzahl": 2, "usd": 0.006291 }
  ],
  "summe_usd": 12.664687
}

Group by ref to see spend per generated asset rather than per feature.

get_asset

Fetch one generated image: its status and, once it is finished, a time-limited address.

ArgumentTypeMeaning
asset_idstringThe id returned by generate_image.
{ "jsonrpc": "2.0", "id": 4, "method": "tools/call",
  "params": { "name": "get_asset",
              "arguments": { "asset_id": "1f6d8b22…" } } }
{
  "asset_id": "1f6d8b22…",
  "status": "ready",
  "origin": "ai_generated",
  "url": "https://…supabase.co/…?token=…",
  "markierung": { "c2pa_signed": true, "visible_label": false }
}

While the image is still being produced, url and markierung are null and status is generating. An unknown id and someone else's id both return the same error — see Errors.

generate_image

Generate an image from a prompt. Costs credits. Runs asynchronously: the call returns as soon as the job is accepted, not when the image exists.

ArgumentTypeMeaning
brand_idstringA brand in this workspace.
promptstringWhat to draw. At least 3 characters.
modelKeystringModel id, e.g. flux_schnell, flux_dev.
imageSizestring · optionalFormat preset, e.g. square_hd.
numImagesnumber · optionalHow many; capped by the model.
seednumber · optionalFor repeatable output.
{ "jsonrpc": "2.0", "id": 5, "method": "tools/call",
  "params": { "name": "generate_image",
              "arguments": { "brand_id": "fcdbdac6…",
                             "prompt": "a quiet harbour at dawn",
                             "modelKey": "flux_schnell" } } }
{
  "asset_id": "1f6d8b22…",
  "status": "generating",
  "fal_request_id": "…",
  "cost_credits": 1,
  "new_balance": 4988,
  "hinweis": "Fertigstellung mit get_asset abfragen (status wird 'ready')."
}

The asynchronous flow

  1. Call generate_image. Credits are deducted now, and you get an asset_id.
  2. The image is produced elsewhere and stored when it arrives. This is also when it is signed.
  3. Poll get_asset with that asset_id until status turns from generating to ready.
  4. ready brings the url and the marking state.

There is no callback and no streaming progress. Poll at a human pace — a few seconds apart is plenty.

If the job cannot be submitted, the credits are returned automatically and the call reports the failure. You are not charged for a generation that never started.

create_composition

Plan and assemble a reel draft from an idea. Costs credits — a language-model plan plus speech synthesis.

The result is a composition inside your workspace. It is not a publishable post; turning it into one happens in Ikrames.

ArgumentTypeMeaning
brand_idstringA brand in this workspace.
ideastringWhat it should be about.
duration_target_secnumberTarget length, 3 to 60.
mode"fit_to_time" | "fit_to_idea"Hold the length, or hold the whole idea.
aspect_ratio"9:16" | "16:9" · optionalDefault 9:16.
language_code"de" | "en" · optionalDefault: the brand's language.
voicestring · optionalVoice name. Default Rachel.
caption_stylestring · optionalDefault tiktok_karaoke.
template_idstring · optionalAn existing composition template.
{ "jsonrpc": "2.0", "id": 6, "method": "tools/call",
  "params": { "name": "create_composition",
              "arguments": { "brand_id": "fcdbdac6…",
                             "idea": "three pricing mistakes founders make",
                             "duration_target_sec": 30,
                             "mode": "fit_to_time" } } }
{
  "composition_id": "c41b90e7…",
  "total_duration_frames": 900,
  "estimated_duration_sec": 29.4,
  "exceeds_target": false,
  "plan": { "…": "the scene plan that was built" },
  "hinweis": "Komposition angelegt, noch nicht veroeffentlichbar — …"
}

The app splits this into a free preview and a paid assembly, because a person wants to look at the plan before spending. An agent cannot look at anything, so both steps happen in one call; the returned plan shows what was built.

Errors

Tool failures come back as a normal result with isError: true and the reason as text. Protocol-level problems come back as JSON-RPC errors.

What happenedYou get
Invalid, unknown, or revoked keyHTTP 401, body {"error":"unauthorized"} — no reason given
Key with no scopes at allHTTP 403, body {"error":"no_scopes"}
Tool not in your scope-32602 · "Tool generate_image not found"
An argument the tool does not define-32602 · unrecognized_keys
A brand or asset outside your workspaceisError: true · "forbidden: …"
Not enough creditsisError: true · insufficient_credits: N noetig, M vorhanden
Too many requestsisError: true · rate_limited: in Ns erneut versuchen
File has no C2PA signatureisError: true · c2pa_unsigned: …

The server's error text is German.

The strings above are quoted exactly as they come back, because that is what you would match on. The machine-readable parts — HTTP status, JSON-RPC code, isError — are language-independent, and those are what you should branch on.

Unknown arguments are rejected, not ignored. Send {"days": 7} to get_costs and you get an error, not a silent all-time total. This matters most for agents: a model that guesses a plausible argument name should find out it guessed wrong, rather than receive a plausible-looking answer to a different question.

Unknown and forbidden look identical on purpose. An asset that does not exist and an asset belonging to another workspace both return "forbidden: unbekanntes Asset". Telling them apart would let a caller map what exists by trying ids.

Credits and cost

Generating costs credits; reading does not. list_brands, get_costs, and get_asset are free.

A flux_schnell image at square_hd costs 1 credit. Other models cost more — the exact amount comes back in cost_credits on every generate_image response, along with your new_balance, so you never have to guess.

Every generation also writes a cost line you can read back with get_costs, and each line is tied to the asset it paid for. Group by ref to see spend per asset:

{ "jsonrpc": "2.0", "id": 7, "method": "tools/call",
  "params": { "name": "get_costs",
              "arguments": { "gruppierung": "ref" } } }

If a generation fails after credits were taken, they are returned automatically.

Not available yet

Named plainly, because a gap you know about is easier to work around than one you discover:

  • No speech or audio tools. There is no generate_tts. Ikrames' signing pipeline covers images and MP4 video; it does not yet cover audio files, so an audio tool would hand out material carrying no marking at all. Until that is solved, the tool does not exist.
  • No self-service keys. Keys are issued by hand — see Authentication.
  • No video generation over MCP. create_composition builds a draft; rendering happens in the app.
  • No publishing over MCP. Nothing here posts to a platform. That is also what keeps the visible-labelling duty in one place, described at the top of this page.

On this page