Create, edit and share presentations from your app, or ask your assistant to do it. The API and MCP server work on your private library and can return a public, read-only link.
Claude.ai, the Claude desktop and mobile apps, ChatGPT and other MCP clients that support OAuth can connect directly. Add a custom connector with this URL:
https://mdslides.milab.es/mcp
The client opens an MDSlides page in your browser. Sign in with your emailed code if needed, review the requested permissions and approve. The page shows the address you will return to: continue only if you recognise it. You can untick write or AI access before approving; read access is always included. Revoke a connection at any time in Settings → API and MCP → Connected applications.
For the REST API and for assistants that accept a Bearer header (Claude Code, Codex, Cursor, VS Code). Sign in and open Settings → API and MCP → API keys (Spanish interface: Ajustes → API y MCP → Claves de API). Name the connection, choose its permissions and expiry, and copy the key: it is shown only once. Settings also offers ready-to-copy configuration for each client with your key filled in.
| Permission | Allows |
|---|---|
read | List and read presentations, themes, collections and images. Always included. |
write | Create, edit, share and delete presentations. |
ai | Topic generation and image generation with the account's OpenAI (or Draw) key. Incurs charges on that account. |
Keys expire after 30 days, 90 days or 1 year. Rotate issues a new secret with the same name and permissions and disables the old one immediately; Revoke deletes it. Each account can hold 10 keys. Store the key as MDSLIDES_API_KEY in a secret store or server environment, never in public frontend code. Recent activity in Settings lists the last operations made with each key or connected application.
Base URL: https://mdslides.milab.es/api/v1. Send Authorization: Bearer YOUR_KEY; request bodies are JSON. A machine-readable description is published at /api/v1/openapi.json (OpenAPI 3.1, no key needed): import it into Postman or a client generator.
| Method and path | Purpose | Scope |
|---|---|---|
GET /me | Current credential, scopes and rate limit | any |
GET /format?section= | Markdown reference | any |
POST /validate | Check Markdown without saving | any |
GET /themes | Theme IDs with light/dark mode, colours and fonts | read |
GET /collections | Collections (lists) and their deck counts | read |
GET /presentations?q=&collection_id=&limit=&offset= | List decks, newest change first | read |
POST /presentations | Create from md or topic | write (+ai for topic) |
GET /presentations/{id} | Read one deck including its Markdown | read |
PATCH /presentations/{id} | Change title, Markdown, theme or collection | write |
DELETE /presentations/{id} | Delete permanently | write |
GET /presentations/{id}/pdf | Download the deck as a PDF file | read |
GET /presentations/{id}/pdf-link | Signed PDF download link, valid one hour | read |
POST|DELETE /presentations/{id}/share | Enable or revoke the public link | write |
GET /images?q=&presentation_id=&offset= | Library images with ready-to-insert fences | read |
POST /presentations/{id}/images | Generate an image for a deck | write + ai |
curl https://mdslides.milab.es/api/v1/themes \ -H "Authorization: Bearer $MDSLIDES_API_KEY"
Each theme has an id, name, kind, mode (light or dark), main colors and fonts. Built-in IDs include builtin:default, builtin:tokyo and builtin:new-york; personal themes use a UUID. Always send the ID, never the display name.
Read the public format reference or fetch the same rules through the API. section is one of basics, text, slides, appearance, content, charts or themes.
curl 'https://mdslides.milab.es/api/v1/format?section=appearance' \
-H "Authorization: Bearer $MDSLIDES_API_KEY"
curl https://mdslides.milab.es/api/v1/validate \
-H "Authorization: Bearer $MDSLIDES_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"md":"## Results\nFirst sentence.\nSecond sentence."}'Validation returns format_version, valid, slide counts and English errors/warnings with codes and one-based line and slide numbers. Syntax problems return HTTP 200 with valid: false. It checks common syntax issues, not visual fit or factual accuracy.
curl https://mdslides.milab.es/api/v1/presentations \
-H "Authorization: Bearer $MDSLIDES_API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"title":"My presentation","md":"# Hello\n\n---\n\n## Next steps\n\n- Review\n- Publish","theme_id":"builtin:tokyo","share":true}'A successful request returns 201:
{
"id": "PRESENTATION_UUID",
"title": "My presentation",
"revision": 1,
"theme_id": "builtin:tokyo",
"collection_id": null,
"created_at": "2026-10-10T12:00:00.000Z",
"share_url": "https://mdslides.milab.es/view/SHARE_TOKEN",
"edit_url": "https://mdslides.milab.es/presentations/PRESENTATION_UUID",
"validation": { "valid": true, "errors": [], "warnings": [] }
}Every creation runs the validator and returns its report in validation; the deck is saved even when it has warnings or syntax errors, so check the report and fix with PATCH if needed. edit_url opens the deck in the editor.
Send a unique Idempotency-Key header (up to 200 visible ASCII characters, such as a UUID) when creating. Repeating the same request with the same key within 24 hours returns the original response with Idempotent-Replayed: true and creates nothing, including no second AI charge. Reusing a key with a different body returns 422 idempotency_key_reused; a repeat while the first request is still running returns 409 idempotency_in_progress. A failed request releases its key so you can retry it.
curl https://mdslides.milab.es/api/v1/presentations \
-H "Authorization: Bearer $MDSLIDES_API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"topic":"An introduction to teamwork","count":6,"language":"English","style":"Practical, with short examples"}'Requires the ai permission and an OpenAI key saved in MDSlides Settings. It uses your selected model, incurs charges on your OpenAI account and adds model and usage to the response.
| Field | Meaning |
|---|---|
title | Optional, up to 160 characters. Otherwise inferred from the Markdown or generated. |
md | MDSlides Markdown, up to 400,000 characters, with at least one visible slide. |
topic | AI generation instructions, up to 6,000 characters. Send exactly one of md or topic. |
theme_id | Optional. Defaults to builtin:default. The theme CSS is copied into the deck. |
collection_id | Optional collection UUID from GET /collections. |
share | Optional boolean. Defaults to false. |
count, language, style | AI only: 1–40 slides (default 8), slide language (default Spanish) and tone. |
# Find decks by title
curl 'https://mdslides.milab.es/api/v1/presentations?q=teamwork&limit=20' \
-H "Authorization: Bearer $MDSLIDES_API_KEY"
# Read one deck, including its Markdown and current revision
curl https://mdslides.milab.es/api/v1/presentations/PRESENTATION_UUID \
-H "Authorization: Bearer $MDSLIDES_API_KEY"
# Edit: send the revision you read
curl -X PATCH https://mdslides.milab.es/api/v1/presentations/PRESENTATION_UUID \
-H "Authorization: Bearer $MDSLIDES_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"revision":3,"title":"Teamwork, revised","theme_id":"builtin:new-york"}'
# Delete permanently
curl -X DELETE https://mdslides.milab.es/api/v1/presentations/PRESENTATION_UUID \
-H "Authorization: Bearer $MDSLIDES_API_KEY"Lists return presentations, has_more and next_offset; pass offset to fetch the next page (limit 1–100, default 50). PATCH accepts title, md (replaces the whole deck), theme_id (copies the new theme CSS) and collection_id (null removes it from its collection), plus the required revision. If the deck changed since you read it, for example in the editor, the response is 409 revision_conflict: read it again and reapply your change. Changing md also returns a validation report.
curl https://mdslides.milab.es/api/v1/presentations/PRESENTATION_UUID/images \
-H "Authorization: Bearer $MDSLIDES_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"prompt":"Editorial illustration of a team around a table, soft colours","ratio":"16:9","request_id":"A_UUID"}'Generation uses the account image engine and its charges. Reuse the same request_id to retry without paying twice. The response includes markdown, an image fence you can insert into a slide with PATCH. GET /images lists existing library images with the same field.
curl https://mdslides.milab.es/api/v1/presentations/PRESENTATION_UUID/pdf \ -H "Authorization: Bearer $MDSLIDES_API_KEY" -o presentation.pdf curl https://mdslides.milab.es/api/v1/presentations/PRESENTATION_UUID/pdf-link \ -H "Authorization: Bearer $MDSLIDES_API_KEY"
The first call returns the file (application/pdf): one 16:9 page per visible slide with selectable text, the deck theme, images and charts, like Create PDF in the editor. Speaker notes are left out. Rendering takes a few seconds. The second returns download_url, a signed link that anyone can open for one hour to download the PDF without a key, plus expires_at, file_name and slide_count. A deck without visible slides returns 422 no_visible_slides.
curl -X POST https://mdslides.milab.es/api/v1/presentations/PRESENTATION_UUID/share \
-H "Authorization: Bearer $MDSLIDES_API_KEY" \
-H 'Content-Type: application/json' -d '{}'
curl -X DELETE https://mdslides.milab.es/api/v1/presentations/PRESENTATION_UUID/share \
-H "Authorization: Bearer $MDSLIDES_API_KEY"Both return share_url: the active URL, or null after revocation. Revoking a key does not revoke existing public links.
Errors return English text and a stable code you can branch on:
{ "error": "Theme not found. Use list_themes or GET /api/v1/themes.", "code": "theme_not_found" }Common codes: missing_token, invalid_token, insufficient_scope (with required_scope), unknown_parameter, invalid_parameter, md_or_topic_required, invalid_markdown, presentation_not_found, no_visible_slides, pdf_render_failed, theme_not_found, collection_not_found, revision_conflict, nothing_to_update, idempotency_key_reused, idempotency_in_progress, account_limit_reached, rate_limited, ai_key_missing, ai_quota_exceeded, upstream_error and service_unavailable. When an error comes from a provider message that has no English translation yet, the original text is kept in detail.
600 API/MCP requests per hour per key or connected application, 30 creation attempts per hour per account, 10 keys and 200 presentations per account, and 1 MB JSON bodies. Existing AI limits also apply. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds); a 429 adds Retry-After.
The API, MCP endpoint and OAuth token endpoints answer cross-origin requests from any site, without cookies: a key must be sent explicitly in the header. Do not embed a key in a public website; call the API from your server, or use OAuth where available.
Endpoint: https://mdslides.milab.es/mcp. Transport: Streamable HTTP (stateless, JSON responses). Authentication: OAuth (see section 1) or Authorization: Bearer YOUR_KEY.
claude mcp add --transport http mdslides \ https://mdslides.milab.es/mcp \ --header "Authorization: Bearer $MDSLIDES_API_KEY"
Claude Code may store the header in its local configuration; keep that file private.
[mcp_servers.mdslides] url = "https://mdslides.milab.es/mcp" bearer_token_env_var = "MDSLIDES_API_KEY"
Make MDSLIDES_API_KEY available in the environment that starts Codex and restart it.
// ~/.cursor/mcp.json
{
"mcpServers": {
"mdslides": {
"url": "https://mdslides.milab.es/mcp",
"headers": { "Authorization": "Bearer YOUR_KEY" }
}
}
}
// .vscode/mcp.json
{
"servers": {
"mdslides": {
"type": "http",
"url": "https://mdslides.milab.es/mcp",
"headers": { "Authorization": "Bearer ${input:mdslides-key}" }
}
},
"inputs": [
{ "type": "promptString", "id": "mdslides-key", "description": "MDSlides API key", "password": true }
]
}
Use OAuth: add a custom connector with https://mdslides.milab.es/mcp and approve access (section 1). No key is needed.
Tools appear according to the permissions of the key or connection.
| Tool | Purpose | Scope |
|---|---|---|
get_format_reference | English rules and examples; optional section | any |
validate_markdown | Check md before saving | any |
list_themes, list_collections | Theme IDs with colours; collections | read |
list_presentations, get_presentation | Find decks; read Markdown and revision | read |
export_pdf | PDF of a deck: a one-hour download_url; include_file also attaches the file | read |
list_images | Library images with ready-to-insert fences | read |
create_presentation | Create from md or topic; optional idempotency_key | write (+ai for topic) |
update_presentation | Change title, Markdown, theme or collection with revision | write |
delete_presentation | Delete permanently | write |
set_presentation_sharing | Enable or revoke a public link | write |
generate_image | Generate an image for a deck | write + ai |
Resources: mdslides://format (format reference), mdslides://themes and mdslides://presentations/{id} (the Markdown of each deck; the 50 most recent are listed). Prompts: create_deck (topic, slides, theme, language), convert_document (turn pasted text into slides) and improve_deck (review and edit a saved deck).
Try this instruction:
Read the MDSlides format reference and list my themes. Write six slides about teamwork using Tokyo, with a shared confidentiality footer. Validate the Markdown, fix errors, then create it and give me the edit link.
Supply actual data for charts and review the presentation before sharing.
Add | Apply all at the end of a formatting directive to apply it across the deck, including new slides. Without it, the directive applies to its own slide. A local setting overrides the deck default. In the editor, select the Apply all checkbox before inserting a format. Content blocks are inserted into all existing slides; shared headings are used on slides without a heading of the same level.
<!-- footer: Confidential | align: left | color: #dc2626 | size: 12 | Apply all --> <!-- logo: url(https://example.com/logo.png) position(bottom-right) size(50px) | Apply all --> <!-- title-align: right | Apply all --> # Project update | Apply all ## First slide First sentence. Second sentence. --- ## Second slide The shared footer, logo and heading also appear here.
Separate slides with --- on its own line. Use # for a cover and ## for a slide heading. A single newline creates a visible line break; an empty line starts a new paragraph. Lists, tables and fenced code blocks are supported. Speaker notes use <!-- notes: … --> and are omitted from public links.
<!-- title-align: right --> <!-- valign: top --> <!-- footer: Confidential | align: left | color: #dc2626 | size: 12 --> ## Quarterly results A short explanation.
title-align: left|center|right aligns headings only. align: left|center|right aligns slide text. valign: top|center|bottom positions the heading and body vertically. These directives apply only to their slide. The footer stays near the bottom independently of body alignment. Edit its text, alignment, hex color and size (8–32 pixels). A confidentiality label is visible text; it does not restrict sharing or access.
## Monthly results ```chart type: line labels: false points: true | Month | Sales | Target | |-------|-------|--------| | January | 12 | 15 | | February | 18 | 18 | | March | 16 | 20 | ```
Each numeric column is a series, up to seven. points: true displays point markers; false hides them. labels controls value labels. Themes set --chart-color-1 through --chart-color-7, --chart-text, --chart-grid and --chart-font. Other chart types are bar, horizontal-bar, pie, donut and waterfall. Use up to 60 data rows, decimal dots and no thousands separators.
Configuration references: Codex MCP documentation · Claude Code MCP documentation · Official MCP SDK · MCP authorization.