Developer portal

Developers

The HTTP API, the MCP endpoint and the OAuth 2.1 authorization server: how to discover, authorize and call them.

makefx.app exposes three programmable surfaces: an HTTP API described by an OpenAPI document, a Model Context Protocol (MCP) endpoint for AI agents, and an OAuth 2.1 authorization server that issues the tokens both accept. Everything on this page is served by the same deployment, so the URLs are live wherever you are reading this.

Start here

WhatWhereFormat
OpenAPI 3.1 document/openapi.jsonapplication/json
API root/apiJSON index of the surfaces below
MCP endpoint/mcp (POST, Streamable HTTP)JSON-RPC
MCP server card/mcp/server-cardapplication/mcp-server-card+json
MCP Registry manifest/server.jsonapplication/json
Authorization server metadata/.well-known/oauth-authorization-serverRFC 8414
Protected resource metadata/.well-known/oauth-protected-resourceRFC 9728
API catalog/.well-known/api-catalogRFC 9727 linkset
Agent guidance/llms.txtMarkdown
Sitemap/sitemap.xmlXML

Every public page also has a Markdown twin: add .md to its path, or send Accept: text/markdown.

Calls that need no token

curl https://makefx.app/api/health
curl https://makefx.app/api
curl https://makefx.app/openapi.json
curl https://makefx.app/p/alv/salt-harbour

The health check reports which environment answered. The discovery documents above are public as well. The /p/{account_id}/{space_id} call returns a public space's canonical read-only JSON snapshot, or the same not_found for a private, deleted, or missing space. The same address renders the space as a page for a caller that asks for text/html; anything else — a bare curl above all — gets the snapshot.

Authorization

The API and the MCP endpoint accept bearer tokens issued by this deployment's own authorization server. The flow is OAuth 2.1: authorization code with PKCE (S256), refresh tokens, and revocation. There is no API-key form and no sales contact: a client registers itself, and the person signing in approves what it may do.

1. Register a client

Registration is open, as RFC 7591 intends, and immediate:

curl -X POST https://makefx.app/api/oauth/register \
  -H 'Content-Type: application/json' \
  -d '{"client_name":"My agent","redirect_uris":["http://127.0.0.1/callback"],"token_endpoint_auth_method":"none"}'

The response carries the client_id and a registration access token for updating or deleting the registration later. Clients that publish a Client ID Metadata Document can use its URL as the client_id instead of registering.

2. Ask the person for consent

Send the browser to the authorization endpoint from the metadata document with response_type=code, your client_id, a redirect_uri you registered, a PKCE code_challenge, the scope you need and, for MCP, resource=https://makefx.app/mcp. The person signs in with Google and approves or declines.

3. Exchange the code

curl -X POST https://makefx.app/api/oauth/token \
  -d grant_type=authorization_code \
  -d code=... -d client_id=... -d redirect_uri=... -d code_verifier=...

Present the access token as Authorization: Bearer .... By default it lives an hour; the refresh token lives thirty days from consent, is single-use, and rotating it never extends that window, after which the person signs in again. Refresh with grant_type=refresh_token; revoke at the revocation endpoint. A request without a valid token answers 401 with a WWW-Authenticate header that points at the protected resource metadata, which is how a client finds this authorization server on its own.

Buy credits with MPP

An account owner can call create_credit_purchase with account_id, product (eur20 or eur100), a purchase request_id, and the account's billing identity on its first purchase. It returns a fixed EUR price including Stripe-calculated tax and a payment_url; it does not charge anything. Use only the person's authorized purchase budget.

An MPP-capable wallet POSTs {} to that URL and handles the HTTP 402 challenge. Keep the makefx Bearer token in Authorization; the challenge advertises Payment-Authorization for the wallet's payment credential. Never put a card or SPT in an MCP tool argument. The wallet must support preserving application authentication through this retry.

get_credit_purchase reads the resulting credit and Stripe-hosted invoice status. Reuse the original request and purchase after an uncertain result instead of starting another purchase. get_account reads the updated account balance. This path buys account credits; generation still uses its existing credit holds. Payments require the deployment's Stripe MPP configuration.

Scopes

ScopeGrants
openidConfirm who you are
profileSee your name
emailSee your email address
readRead your data
writeChange your data on your behalf

Reads need read; anything that changes data needs write. A token is limited to the scopes the person approved, and the person can revoke it from their profile at any time.

Spaces use one canonical account_id/space_id, for example alv/salt-harbour, and links use /s/{account_id}/{space_id}. Account viewers can list and read every space in the account, editors can also create spaces and change their content, and owners can additionally delete spaces, set visibility with PATCH /api/spaces/{account_id}/{space_id}, and manage access. A direct viewer or editor grant reaches only its named space. Public spaces are readable by link but are not globally listed.

MCP

The endpoint is /mcp over Streamable HTTP. Unauthenticated initialize succeeds and reports the server's capabilities; everything else needs a bearer token bound to the /mcp resource. Hosted clients such as Claude, ChatGPT and Codex connect by URL, register themselves and open the consent page; nothing is configured on this side.

The tools act on the signed-in person of the presented token:

  • align_audio: Start or re-run durable word alignment for unchanged ready audio. Omit text only for generated speech; use the same request_id when retrying the same admission. This does not create an asset or change audio bytes. (changes data)
  • create_asset: Create one or more assets from a catalog model. With from_asset_id, recipe_mode current uses the catalog model now, while exact replays the source recipe through a currently configured route and never substitutes a newer provider model. An exact alias preserves request identity, but the provider may move its weights. Use when: Generating from a prompt, extracting a video frame, or making a new asset from an existing recipe; choose the model from list_models. Not for: Bringing in an existing file, estimating a price, or editing a ready asset. (changes data)
  • create_credit_purchase: Prepare a fixed credit purchase including Stripe-calculated tax. Requires account ownership. Does not charge a card. Use the returned payment_url with an MPP-capable wallet, preserving account OAuth authorization; never put card data or payment tokens in tool arguments. Reuse request_id on retries. (changes data)
  • create_space: Create a human-readable space in an account where the signed-in person can edit. Use when: Starting a new canvas for assets. Not for: Adding an asset to an existing space. (changes data)
  • delete_asset: Soft-delete one asset while preserving its media, recipe, references, and credit trail. Use when: Removing an asset from ordinary reads and exports. Not for: Refunding credits, restoring an asset, or purging media. (changes data)
  • delete_space: Soft-delete a space while preserving its accountability trail. Use when: An account owner wants a space removed from every read path. Not for: Deleting one asset or recovering credits. (changes data)
  • describe_asset: Write reusable visual traits from a ready image or video's poster for the catalog Describe price. Use when: Prompts need a stable written subject, look, palette, setting, or style. Not for: Audio, captions, or judging quality. (changes data)
  • estimate_credits: Quote a generation without reserving credits. Use when: Quoting a total before spending or checking a request against the balance. Not for: Placing a hold. (read-only)
  • export_space: Export one canonical metadata-only JSON document. Use when: Handing a space to a template or build script. Not for: Fetching or copying media bytes. (read-only)
  • get_account: Read the current credit balance, active holds and top-up link. Use when: Checking funds before estimating or creating assets. Not for: Reading profile details or changing billing. (read-only)
  • get_asset: Read one asset and refresh its result URLs. Use when: Learning whether a generation finished and getting its URLs. Not for: Listing a whole space. (read-only)
  • get_audio_word_timings: Read current canonical word timings and optionally wait on one alignment job. Missing timings are returned as null; this never fetches audio bytes or starts alignment. (read-only)
  • get_credit_purchase: Read whether a purchase has credited the account and get its Stripe-hosted invoice link. Requires account ownership. Use purchase_id from create_credit_purchase; does not start or retry a payment. (read-only)
  • get_profile: Read the signed-in user's profile: id, email and display name. (read-only)
  • get_space: Read the whole space in one call. Use when: Reading the whole canvas: every asset, link, and recipe at once. Not for: Polling one asset (get_asset). (read-only)
  • health_check: Report whether the service is reachable and which environment answered. (read-only)
  • link_assets: Create a manual labeled edge between two active assets. Use when: Connecting assets for story or canvas structure. Not for: Recording recipe references or provenance. (changes data)
  • list_models: Read the catalog, the only source of truth for model params, reference slots and prices. Use when: Learning which models exist, what they take and what they cost before the first generation of a session. Not for: Creating an asset or quoting one configured request. (read-only)
  • list_spaces: List spaces available through account membership or a direct grant. Use when: Choosing a canonical space to read or change. Not for: Reading the assets and links inside a space. (read-only)
  • sync_voices: Refresh an account's ElevenLabs voice mirror. Requires account ownership and never accepts or returns a provider key. (changes data)
  • unlink_assets: Soft-delete a manual edge by link id or endpoint pair. Use when: Removing canvas or story structure. Not for: Changing recipe references or deleting either asset. (changes data)
  • update_asset: Update editable asset metadata, position, or an uploaded asset's declared recipe. Use when: Renaming, annotating, starring, moving, or correcting declared provenance. Not for: Changing media or a generated recipe. (changes data)
  • update_profile: Change the signed-in user's display name. (changes data)
  • upload_asset: Reserve an asset and receive a one-use PUT URL for existing media. Use when: Bringing a local photo, clip, or track onto the canvas with declared provenance. Not for: Sending media bytes through MCP or asking a model to create media. (changes data)

Tools marked read-only can run without confirmation. Call tools/list for the current schemas; the server card lists the protocol versions it speaks. A call that fails is a result with isError rather than a protocol error, and its structuredContent is always the same shape: code, message, retryable, and details with the HTTP status and, for a scope refusal, the scope that was missing.

Sandbox

The stage environment runs the same code against its own database and object store:

curl https://stage.makefx.app/api/health

Register clients, sign in and exercise the API there without touching production. Its discovery documents point at itself, so a client configured for the sandbox never leaks into production by accident.

Command line

The repository ships a CLI that signs in through the browser, stores the tokens and can bridge a local MCP client to the deployed endpoint over stdio:

pnpm run cli login
pnpm run cli mcp
pnpm run cli login --env stage
pnpm run cli voices sync --account acme
pnpm run cli models --space acme/studio --kind audio

voices sync is owner-only and refreshes the account's ElevenLabs voice mirror without accepting or returning the provider key. models --space ACCOUNT/SPACE reads the speech voices for the account that will pay for generation; omit --space for the platform catalog. Add --json to either command to print the tool's structured result unchanged.

Versioning

The API is versioned by date, and the current version is 2026-09-10. Send API-Version: 2026-09-10 to pin a client to the version it was written against, or omit the header to get the current one. Every response under /api carries API-Version naming the version that answered, and a request naming a version that does not exist is refused with 400 unsupported_version rather than served something it did not ask for.

A version leaves in two steps. A breaking change ships under a new date, and the previous date keeps answering for at least ninety days as a deprecated version: every response to a request that pins it carries a Deprecation header (RFC 9745: @ and Unix seconds) naming the day it was deprecated, a Sunset header (RFC 8594) naming the last day it answers, and a Link with rel="deprecation" pointing here. After the sunset day the version is removed and a request that still pins it is refused with 400. The current version never carries these headers, so a client can watch for Deprecation alone.

Rate limits

Limits are per minute, per bucket, and enforced at the edge. Every response under /api names the policy it passed under in RateLimit-Policy and RateLimit-Limit; a refused request answers 429 with Retry-After and a RateLimit header showing the bucket empty.

BucketRequests per minute
Authorization, client registration and client configuration, per address20
Token and revocation requests, per client and address600
MCP tool calls, per person and address60
Every other API request, per address1200
Generation requests, per person20

Errors

Every error under /api has one shape, ApiError in the OpenAPI document: error is a short code where one exists and a sentence otherwise, and error_description, when present, says more for a person. The OAuth endpoints use the RFC 6749 codes (invalid_request, invalid_client, invalid_grant, slow_down). A request without a valid token answers 401 with a WWW-Authenticate header that points at the protected resource metadata; a valid token without the needed scope answers 403 insufficient_scope. Unknown paths answer 404, with a JSON body under /api that names the OpenAPI document and a Markdown body elsewhere that says where to look next.

Getting help

Integration questions go to krasnoperov.me. Vulnerabilities go through the same channel, marked as security reports.