Developers
Connect your agent to Miximodel
Token-scoped access to your own account — a hosted MCP server, a REST API and a command-line interface. Nothing here can act on anyone else's account.
Add the MCP server to Claude Code
claude mcp add --transport http miximodel https://miximodel.com/mcpOn first use a browser page asks you to sign in and approve with your password. Any other MCP client: add https://miximodel.com/mcp as a Streamable-HTTP server. AI agents can start from /llms.txt.
Platform API
Overview
The Platform API gives advanced users and AI agents programmatic, token-scoped access to their own Miximodel account — the same data the app shows them, over a stable REST surface and a hosted MCP server.
There are zero admin capabilities anywhere in this surface — no moderation, no user management, no global configuration. A token can only ever act on its own owner's account, scoped to the abilities it carries.
Authentication
Every request carries a Bearer token in the Authorization header:
Authorization: Bearer miximodel_cli_xxxxxxxxxxxx…There are two token families, row-isolated by type — a token of one family can never authenticate the other surface:
| Prefix | Surface | How you get it |
|---|---|---|
miximodel_cli_ | REST API (/api/v1) — PATs + the CLI | Create at Settings → Personal access tokens, or miximodel login (CLI device flow) |
miximodel_mcp_ | Hosted MCP server (/mcp) | Minted by the OAuth 2.1 flow your MCP client runs — see the MCP server |
Personal access tokens (PATs)
Visit /settings/personal-access-tokens while signed in:
- Open the Create token dialog. Read scopes are pre-checked, except the two sensitive reads (
chat:read,vault:read); write scopes andstudio:generateare unchecked (explicit opt-in). - Confirm with your password and generate the token → the one-time
miximodel_cli_…plaintext is shown once. Copy it now; it is never recoverable. Minting any long-lived credential (a PAT, a CLI device approval, an MCP connection) asks for your password, like deleting the account; an account created with Google or X sets one first through "Forgot password". - The token appears in your list with its scope badges; revoke it any time.
When your password is reset
A password reset assumes the old password was compromised. It revokes every personal access token — including the one miximodel login minted — and every MCP connection, and signs out every session. Afterwards, mint new tokens, run miximodel login again, and reconnect your MCP client: its tokens are refused from then on, so it has to go through consent again.
Abilities (scopes)
A token carries a subset of the ability catalogue. Each /api/v1 route and each MCP tool requires exactly one ability; a token lacking it gets a non-enumerating 403 (REST) / tool_error (MCP). Only what does not mutate is ever pre-selected for you: write abilities and studio:generate must be explicitly granted (studio:generate is never implicit — it spends credits), and so must the two sensitive reads, chat:read and vault:read.
| Ability | Grants |
|---|---|
whoami:read | Read your authenticated identity |
profile:write | Edit every profile section (identity, model details, genres, social links, agency) and declare your role |
portfolio:read | Read portfolio photos |
portfolio:write | Upload and manage portfolio photos |
notices:read | Read notices and castings |
notices:write | Create, edit, and delete notices |
tours:read | Read tours and stops |
tours:write | Create tours and stops |
bookings:read | Read bookings |
bookings:write | Create, accept, and decline bookings |
posts:read | Read posts and feed |
posts:write | Create and edit posts, and comment |
follows:read | Read follows and bookmarks |
follows:write | Follow, unfollow, and bookmark |
stats:read | Read account analytics (read-only) |
subscription:read | Read subscription and billing info (read-only) |
account:write | Delete your account, cancel a pending deletion, and request a data export |
chat:read | Read your private conversations and mark them as read |
chat:write | Find members, start conversations, and send messages and photos in them (assistant replies use credits) |
conversations:write | Block or unblock people, delete conversations, and report them on your behalf |
disputes:write | Open disputes about your bookings, in your name, against the other party |
preferences:write | Change what you are shown — location visibility and content filters |
vault:read | Read your private media vault — including photos you have not published |
vault:write | Upload to your private vault and organise it into collections (cannot publish anything) |
consents:read | Read which consents and agreements your account still needs to give |
consents:write | Accept consents and agreements in your name |
roleChange:write | Change your role — what the account is, which changes what you can do on Miximodel |
identity:write | Submit your government ID and a photo of you holding it, for review |
devices:write | Let this device receive push notifications, and stop receiving them on it |
studio:generate | Trigger AI image generation (explicit opt-in, credit-deducted) |
The catalogue contains no * wildcard and no admin/moderation verb — by construction (a forbidden ability is rejected at mint, at the validator, and at the middleware). A unit test holds this table to the catalogue in code.
Conventions
- IDs are UUIDs. The wire never exposes a numeric database id. Resources are addressed and referenced by
uuid. - Pagination is opaque-cursor. List endpoints accept
?limit=(1–50, default 20) and?cursor=(an opaque token from the previous page'snextCursor). Walk untilnextCursorisnull. Nopage/offset, no total count. - Media URLs are short-lived signed URLs. Treat them as ephemeral; re-fetch the resource to refresh.
- Rate limits are per-token, quotas are per-user. Two tokens of the same user get independent per-minute burst budgets; day-scale quotas (e.g. posts 100/day) are keyed on the ACCOUNT, so rotating an access token does not reset them. Write paths and
studio:generatecarry tighter sub-limits. A429carries aRetry-Afterheader.
Errors — RFC 7807
Every error is a application/problem+json document:
{
"type": "https://miximodel.com/problems/forbidden",
"title": "Forbidden",
"status": 403,
"detail": "This token does not carry the required ability."
}Branch on type (the /problems/<kind> suffix), not on the status alone — some statuses carry several kinds:
| Status | Kind(s) | Meaning |
|---|---|---|
400 | badRequest | The request body could not be parsed (malformed JSON) or is otherwise malformed. Retrying the same bytes cannot succeed. |
401 | unauthorized, invalidCredentials | Missing / invalid / expired bearer token; on login, a bad email-or-password (never says which). |
402 | paymentRequired | No remaining credit for the operation. |
403 | forbidden, accountSuspended | The token lacks the ability for this route (non-enumerating), or the account is suspended and the route is a write. |
404 | notFound | Resource not found or the API is switched off — indistinguishable by design. |
409 | roleRequired, roleAlreadyDeclared, conflict | A role must be declared first; or a role is ALREADY declared (PATCH /me/role is one-time — PATCH /me/role-change changes it); or a generic state conflict. |
413 | payloadTooLarge | The body exceeded the accepted size (rejected before the handler ran). Shrink or compress the upload. |
415 | unsupportedMediaType | Unsupported content type or Content-Encoding. |
422 | validationFailed, invalidRole | Validation failed (VineJS); or the role slug names no role in the scope called — re-fetch GET /api/v1/roles and re-pick. |
429 | rateLimited | Rate limit exceeded (see Retry-After). |
500 | serverError | An unexpected server fault. Genuinely ours — every status above is mapped, so a 500 is never a mislabelled client error. |
Error bodies never leak a database field, a stack trace, an internal id, or a secret.
Hosted MCP server
Connect an MCP client (Claude Code, Codex, …) to your Miximodel account. The server exposes a curated set of workflow tools, each scoped to a token ability, over a Streamable-HTTP transport at https://miximodel.com/mcp.
Like the REST API, there are zero admin tools — no moderation, user management or global configuration. When an operator switches the server off, the whole /mcp* surface answers 404; the .well-known discovery documents resolve regardless (RFC 8414 / 9728).
Add the server
claude mcp add --transport http miximodel https://miximodel.com/mcp--transport http is required: without it Claude Code registers a local stdio command, not a remote server. Any other MCP client takes the same URL as a Streamable-HTTP server and discovers the rest on its own.
On first use the client runs the OAuth 2.1 handshake (below) and opens a browser consent page where you — signed into Miximodel — review the requesting app and its requested scopes, then Approve (with your password) or Deny. On approve, the client receives a miximodel_mcp_… token and the tools become available.
The OAuth 2.1 flow (handled by your MCP client)
The server is a standards OAuth 2.1 Authorization Server; a compliant MCP client runs the whole flow for you:
- Discovery —
GET /.well-known/oauth-authorization-server(+…/oauth-protected-resource). - Dynamic client registration (RFC 7591) —
POST /mcp/oauth/registermints a publicclient_id(no secret;token_endpoint_auth_method: none). - Authorize + consent — the browser opens
/mcp/oauth/authorize; you approve the scopes. PKCE S256 is required (theplainmethod and a missing challenge are rejected). - Token —
POST /mcp/oauth/tokenexchanges the one-time authorization code (+ PKCE verifier) for a short-lived access token (1h) and a rotating refresh token (30d). Refresh-token reuse revokes the whole grant family. - Revoke —
POST /mcp/oauth/revoke(RFC 7009) tears a connection down.
Security posture: redirect URIs are exact-matched, authorization codes are single-use (atomic claim), consent is bound to your session, and no code/token/secret is ever logged.
Scopes
MCP tokens use the same ability catalogue as the REST API. Request only what your workflow needs; a tool whose ability the token lacks returns a non-enumerating tool_error.
Tool catalogue (16 tools)
Each tool is a thin wrapper over the same service its /api/v1 sibling uses, returns the same UUID/signed-URL shape, and is gated on its ability. MCP annotations (readOnlyHint / destructiveHint / idempotentHint) let your client prompt for confirmation on destructive or costly tools.
Read
| Tool | Ability | Annotations |
|---|---|---|
whoami | whoami:read | readOnly |
get_profile | whoami:read | readOnly — look up a public creator profile by @username |
list_portfolio | portfolio:read | readOnly |
list_notices | notices:read | readOnly |
list_applicants | notices:read | readOnly (owner-only) |
list_tours | tours:read | readOnly |
list_bookings | bookings:read | readOnly |
search | whoami:read | readOnly — public-safe explore showcase |
get_studio_job | studio:generate | readOnly — poll a generation job |
Write / action
| Tool | Ability | Annotations |
|---|---|---|
publish_notice | notices:write | NOT idempotent — a retry publishes a second notice |
manage_applicant | notices:write | destructive — accept / decline |
create_tour | tours:write | — |
manage_booking | bookings:write | destructive — accept / decline / propose_slot |
upload_portfolio | portfolio:write | — returns a signed PUT URL (step 1/2) |
confirm_portfolio | portfolio:write | — confirms the upload (step 2/2) |
generate_studio | studio:generate | destructive, non-idempotent — credit-deducted; SFW look-based only (any other engine is refused server-side) |
Notes
- Costly/destructive tools (
generate_studio,manage_applicant,manage_booking) are annotated so your client asks you to confirm. Cost and consent guardrails are enforced server-side — the MCP layer never re-implements them; it surfaces a generictool_erroron refusal. - Uploads can't stream binaries over MCP:
upload_portfolioreturns a signed PUT URL your host uploads to, thenconfirm_portfoliopersists it. - Revoke a connection with
POST /mcp/oauth/revoke(RFC 7009). Resetting your Miximodel password revokes every MCP connection at once.
See authentication for the token model and the REST endpoints for the surface these tools mirror.
REST endpoints
Base URL: https://miximodel.com/api/v1 · Auth: Authorization: Bearer miximodel_cli_… · See authentication, abilities, errors and pagination.
Every authenticated route requires the listed ability; list routes are cursor-paginated (?limit=1..50&cursor=…). IDs on the wire are UUIDs. This is the token-scoped core of /api/v1, not every route the first-party apps call.
Liveness
| Method | Path | Notes |
|---|---|---|
GET | /api/v1/ping | Liveness probe — no token, 200 even while the API is switched off. |
GET | /api/v1/_authcheck | Verifies a token (whoami:read) — 200 if valid + scoped. |
Reference data (auth depends on the scope)
| Method | Path | Auth | Notes |
|---|---|---|---|
GET | /api/v1/roles?scope=signup | none | Default. The roles a SIGNUP may pick — exactly what the web signup panel offers (Agency / Client excluded). { data: [{ slug, name }] }. Unauthenticated: a client needs it before it has a token. Cached 6h. |
GET | /api/v1/roles?scope=declaration | bearer (no ability) | The roles PATCH /api/v1/me/role accepts — the FULL catalogue, Agency and Client included, exactly what the web post-signup declaration prompt offers. Same shape, same cache. 401 without a token. |
Two scopes, on purpose. The signup picker and the post-signup declaration prompt are different lists on the web too: signup refuses Agency (it needs extra mandatory fields) and Client (an unused persona), while the declaration prompt is fed the whole catalogue. ?scope= is how a client asks for the one it needs; an unknown scope is a 422 validationFailed, never a silent fallback. Declaring agency requires agencyName, registrationNumber and phone in the same PATCH /api/v1/me/role body (website optional).
Why only one of them needs a token. Exposure follows each scope's web twin: the signup list is served to anonymous visitors on the public /signup page, while the declaration list only ever reaches an authenticated (and role-pending) browser through the availableRoles Inertia prop. Any token is enough — the gate is authentication, not a scope, so a read-only token still renders the screen.
Roles are identified on the wire by their stable slug (makeup-artist), never a numeric id — the same identifier campaign links use. Echo that slug back on POST /api/v1/auth/register (role) and PATCH /api/v1/me/role (role). A slug outside the scope you are calling is a 422 invalidRole.
Identity & profile
| Method | Path | Ability | Notes |
|---|---|---|---|
GET | /api/v1/me | whoami:read | Your authenticated identity (profile, role, social links). |
PATCH | /api/v1/me/profile | profile:write | Edit bio, location, social links. Separate from whoami:read so a read token can never mutate the profile. |
GET | /api/v1/me/stats | stats:read | Consolidated account analytics. totalPostCount = EVERY post row you own (see "Two post counts" below). |
GET | /api/v1/me/subscription | subscription:read | Your subscription / billing (nullable). |
PATCH | /api/v1/me/role | profile:write | Declare your role — ONE-TIME, role-pending accounts only. Body: role (a slug from GET /api/v1/roles?scope=declaration) + agencyName, registrationNumber, phone when declaring agency. 422 invalidRole for a slug outside that list; 409 roleAlreadyDeclared once a role is set (PATCH /api/v1/me/role-change changes it, not this endpoint) — the claim is atomic, so concurrent calls yield exactly one winner. |
Portfolio
| Method | Path | Ability | Notes |
|---|---|---|---|
GET | /api/v1/me/portfolio | portfolio:read | Your portfolio photos (cursor page). Polaroids are not in this list. |
POST | /api/v1/me/portfolio/upload-url | portfolio:write | Step 1/2 — returns a short-lived signed PUT URL + a session token. You PUT the bytes to that URL directly (the server never fetches a client URL → no SSRF). |
POST | /api/v1/me/portfolio/confirm | portfolio:write | Step 2/2 — confirm the uploaded object to persist the photo. |
PATCH | /api/v1/me/photos/:uuid | portfolio:write | Move one of your photos between the portfolio grid and your polaroids. Body: section (photos or polaroids). Answers { data: { uuid, section } }. 404 for a photo that is not yours; 403 when moving into polaroids on an account that is not a model; 422 when your polaroids are full (30). Idempotent. |
Notices (castings)
| Method | Path | Ability | Notes |
|---|---|---|---|
GET | /api/v1/me/notices | notices:read | Your notices (cursor page). |
GET | /api/v1/me/notices/:uuid/applicants | notices:read | Applicants of a notice you own (owner-only; 403 otherwise). |
POST | /api/v1/me/notices | notices:write | Create a notice. |
PATCH | /api/v1/me/notices/:uuid | notices:write | Edit a notice. |
DELETE | /api/v1/me/notices/:uuid | notices:write | Delete a notice. |
POST | /api/v1/me/notices/:uuid/applicants/:applicantUuid/accept | notices:write | Accept an applicant. |
POST | /api/v1/me/notices/:uuid/applicants/:applicantUuid/decline | notices:write | Decline an applicant. |
Tours
| Method | Path | Ability | Notes |
|---|---|---|---|
GET | /api/v1/me/tours | tours:read | Your tours (cursor page). |
GET | /api/v1/me/tours/:stopUuid/bookings | bookings:read | Bookings for a tour stop you own (owner-only). |
POST | /api/v1/me/tours | tours:write | Create a tour with one or more stops (cityId + ISO start/end per stop). |
POST | /api/v1/me/tours/:uuid/stops | tours:write | Add a stop to an existing tour. |
Bookings
| Method | Path | Ability | Notes |
|---|---|---|---|
GET | /api/v1/me/bookings | bookings:read | Your bookings (cursor page). |
POST | /api/v1/me/bookings | bookings:write | Create a booking request. |
POST | /api/v1/me/bookings/:uuid/accept | bookings:write | Accept (confirm the proposed slot). |
POST | /api/v1/me/bookings/:uuid/decline | bookings:write | Decline (cancel). |
POST | /api/v1/me/bookings/:uuid/propose-slot | bookings:write | Counter-propose price / city / datetime. |
Posts & social
| Method | Path | Ability | Notes |
|---|---|---|---|
GET | /api/v1/me/posts | posts:read | Your posts (cursor page). |
GET | /api/v1/feed | posts:read | The authenticated feed (?tab=recommendations|following, opaque ?cursor). Shadow-banned authors are excluded and sensitive media is covered server-side. |
POST | /api/v1/posts | posts:write | Create a feed post — multipart: content (required) + up to 4 still-image photos[]. Poll / scheduling / video are out of V1 scope (422). Echoes the post in the /feed row shape. |
GET | /api/v1/me/follows/followers | follows:read | Your followers (cursor page). |
GET | /api/v1/me/follows/following | follows:read | Accounts you follow. |
GET | /api/v1/me/follows/bookmarks | follows:read | Your bookmarks. |
Public profiles
| Method | Path | Ability | Notes |
|---|---|---|---|
GET | /api/v1/users/:username | whoami:read | A user's public profile header: identity, banner, role badges, stats (age / location / geoLocation), posts + follower + following counts, isFollowing (yours), socials. |
GET | /api/v1/users/:username/photos | portfolio:read | Their PUBLISHED portfolio grid (cursor page). Unpublished photos are absent — enforced at the query, never client-side. Polaroids are not in this list. |
GET | /api/v1/users/:username/polaroids | portfolio:read | Their polaroids (digitals), the set their public portfolio shows: published, SFW, at most 30, so always one page (nextCursor: null, hasMore: false). Same envelope as /photos. Empty for an account that is not a model. |
Two post counts, on purpose
The API exposes two different post totals. They answer different questions and are expected to disagree — the names say which is which:
| Field | Scope | Audience |
|---|---|---|
/api/v1/me/stats.totalPostCount | EVERY row you own, including moderation-hidden, still-scheduled and expired posts. | Owner analytics only. |
/api/v1/users/:username.postsCount | The posts the CALLING token can actually see on that profile (live-post gate), so it always matches the grid /api/v1/users/:username links to. An author viewing their OWN profile also sees their expired posts counted, exactly as their own grid shows them. | Anyone. |
The invariant is postsCount <= totalPostCount for the same user, and the difference is made entirely of rows that viewer cannot see. postsCount (plus followersCount / followingCount) is served from a 2-minute cache and is busted on follow / unfollow and on post create / delete, so it is normally exact; isFollowing is never cached.
Search
| Method | Path | Ability | Notes |
|---|---|---|---|
GET | /api/v1/search | whoami:read | Public-safe explore showcase (cursor page). Returns only what the public teaser query would — SFW, published, non-shadow-banned. |
Creator Studio (AI image generation)
| Method | Path | Ability | Notes |
|---|---|---|---|
POST | /api/v1/me/studio/generate | studio:generate | Queue an SFW look-based generation from one of your source photos. Credit-deducted. Only SFW engines are available; any other is refused server-side. Returns the queued job. |
GET | /api/v1/me/studio/jobs/:uuid | studio:generate | Poll one job's status + (on success) its signed outputs. |
GET | /api/v1/me/studio/jobs | studio:generate | List your generation jobs (cursor page). |
Cursor pagination example
curl -s "https://miximodel.com/api/v1/me/portfolio?limit=20" \
-H "Authorization: Bearer $MIXIMODEL_TOKEN" -H "Accept: application/json"
# → { "data": [ … ], "nextCursor": "eyJ…" } (walk until nextCursor === null)Command-line interface
A single-binary command-line interface over /api/v1: read and write commands for your notices, tours, bookings, portfolio, profile and Studio jobs. It acts only on its owner's account, with the abilities its token carries — a read-only token is refused with a 403 on any write.
Not publicly downloadable yet. The binary is released privately while the distribution channel is prepared. The hosted MCP server and personal access tokens work today; this page describes how the CLI signs in so you can plan for it.
Sign in
The CLI signs in with the OAuth 2.0 device authorization grant (RFC 8628):
miximodel login # prints a one-time code and opens the approval page
miximodel whoami # GET /api/v1/memiximodel login prints a code and opens https://miximodel.com/device in your browser. Sign in to Miximodel, check that the code matches, and approve with your password. The CLI then stores a miximodel_cli_… token — a personal access token like any other, listed and revocable under Settings → Personal access tokens.
- Scopes. By default the CLI asks for read abilities only, and neither of the sensitive reads (
chat:read,vault:read). Pass--scope "whoami:read notices:write …"to request others,studio:generateincluded. - Where the token lives.
$XDG_CONFIG_HOME/miximodel/credentials.json(~/.config/miximodel/credentials.jsonby default). - Another host. Set
MIXIMODEL_API_URLto point the CLI at a non-production server. - After a password reset the token is revoked: run
miximodel loginagain.
Commands
Run miximodel --help for the full reference. Every command calls one /api/v1 route and needs the ability that route lists in the endpoint reference.