Skip to main content

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/mcp

On 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:

PrefixSurfaceHow you get it
miximodel_cli_REST API (/api/v1) — PATs + the CLICreate 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:

  1. Open the Create token dialog. Read scopes are pre-checked, except the two sensitive reads (chat:read, vault:read); write scopes and studio:generate are unchecked (explicit opt-in).
  2. 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".
  3. 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.

AbilityGrants
whoami:readRead your authenticated identity
profile:writeEdit every profile section (identity, model details, genres, social links, agency) and declare your role
portfolio:readRead portfolio photos
portfolio:writeUpload and manage portfolio photos
notices:readRead notices and castings
notices:writeCreate, edit, and delete notices
tours:readRead tours and stops
tours:writeCreate tours and stops
bookings:readRead bookings
bookings:writeCreate, accept, and decline bookings
posts:readRead posts and feed
posts:writeCreate and edit posts, and comment
follows:readRead follows and bookmarks
follows:writeFollow, unfollow, and bookmark
stats:readRead account analytics (read-only)
subscription:readRead subscription and billing info (read-only)
account:writeDelete your account, cancel a pending deletion, and request a data export
chat:readRead your private conversations and mark them as read
chat:writeFind members, start conversations, and send messages and photos in them (assistant replies use credits)
conversations:writeBlock or unblock people, delete conversations, and report them on your behalf
disputes:writeOpen disputes about your bookings, in your name, against the other party
preferences:writeChange what you are shown — location visibility and content filters
vault:readRead your private media vault — including photos you have not published
vault:writeUpload to your private vault and organise it into collections (cannot publish anything)
consents:readRead which consents and agreements your account still needs to give
consents:writeAccept consents and agreements in your name
roleChange:writeChange your role — what the account is, which changes what you can do on Miximodel
identity:writeSubmit your government ID and a photo of you holding it, for review
devices:writeLet this device receive push notifications, and stop receiving them on it
studio:generateTrigger 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's nextCursor). Walk until nextCursor is null. No page/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:generate carry tighter sub-limits. A 429 carries a Retry-After header.

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:

StatusKind(s)Meaning
400badRequestThe request body could not be parsed (malformed JSON) or is otherwise malformed. Retrying the same bytes cannot succeed.
401unauthorized, invalidCredentialsMissing / invalid / expired bearer token; on login, a bad email-or-password (never says which).
402paymentRequiredNo remaining credit for the operation.
403forbidden, accountSuspendedThe token lacks the ability for this route (non-enumerating), or the account is suspended and the route is a write.
404notFoundResource not found or the API is switched off — indistinguishable by design.
409roleRequired, roleAlreadyDeclared, conflictA 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.
413payloadTooLargeThe body exceeded the accepted size (rejected before the handler ran). Shrink or compress the upload.
415unsupportedMediaTypeUnsupported content type or Content-Encoding.
422validationFailed, invalidRoleValidation failed (VineJS); or the role slug names no role in the scope called — re-fetch GET /api/v1/roles and re-pick.
429rateLimitedRate limit exceeded (see Retry-After).
500serverErrorAn 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:

  1. Discovery — GET /.well-known/oauth-authorization-server (+ …/oauth-protected-resource).
  2. Dynamic client registration (RFC 7591) — POST /mcp/oauth/register mints a public client_id (no secret; token_endpoint_auth_method: none).
  3. Authorize + consent — the browser opens /mcp/oauth/authorize; you approve the scopes. PKCE S256 is required (the plain method and a missing challenge are rejected).
  4. Token — POST /mcp/oauth/token exchanges 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.
  5. 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

ToolAbilityAnnotations
whoamiwhoami:readreadOnly
get_profilewhoami:readreadOnly — look up a public creator profile by @username
list_portfolioportfolio:readreadOnly
list_noticesnotices:readreadOnly
list_applicantsnotices:readreadOnly (owner-only)
list_tourstours:readreadOnly
list_bookingsbookings:readreadOnly
searchwhoami:readreadOnly — public-safe explore showcase
get_studio_jobstudio:generatereadOnly — poll a generation job

Write / action

ToolAbilityAnnotations
publish_noticenotices:writeNOT idempotent — a retry publishes a second notice
manage_applicantnotices:writedestructive — accept / decline
create_tourtours:write—
manage_bookingbookings:writedestructive — accept / decline / propose_slot
upload_portfolioportfolio:write— returns a signed PUT URL (step 1/2)
confirm_portfolioportfolio:write— confirms the upload (step 2/2)
generate_studiostudio:generatedestructive, 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 generic tool_error on refusal.
  • Uploads can't stream binaries over MCP: upload_portfolio returns a signed PUT URL your host uploads to, then confirm_portfolio persists 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

MethodPathNotes
GET/api/v1/pingLiveness probe — no token, 200 even while the API is switched off.
GET/api/v1/_authcheckVerifies a token (whoami:read) — 200 if valid + scoped.

Reference data (auth depends on the scope)

MethodPathAuthNotes
GET/api/v1/roles?scope=signupnoneDefault. 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=declarationbearer (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

MethodPathAbilityNotes
GET/api/v1/mewhoami:readYour authenticated identity (profile, role, social links).
PATCH/api/v1/me/profileprofile:writeEdit bio, location, social links. Separate from whoami:read so a read token can never mutate the profile.
GET/api/v1/me/statsstats:readConsolidated account analytics. totalPostCount = EVERY post row you own (see "Two post counts" below).
GET/api/v1/me/subscriptionsubscription:readYour subscription / billing (nullable).
PATCH/api/v1/me/roleprofile:writeDeclare 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

MethodPathAbilityNotes
GET/api/v1/me/portfolioportfolio:readYour portfolio photos (cursor page). Polaroids are not in this list.
POST/api/v1/me/portfolio/upload-urlportfolio:writeStep 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/confirmportfolio:writeStep 2/2 — confirm the uploaded object to persist the photo.
PATCH/api/v1/me/photos/:uuidportfolio:writeMove 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)

MethodPathAbilityNotes
GET/api/v1/me/noticesnotices:readYour notices (cursor page).
GET/api/v1/me/notices/:uuid/applicantsnotices:readApplicants of a notice you own (owner-only; 403 otherwise).
POST/api/v1/me/noticesnotices:writeCreate a notice.
PATCH/api/v1/me/notices/:uuidnotices:writeEdit a notice.
DELETE/api/v1/me/notices/:uuidnotices:writeDelete a notice.
POST/api/v1/me/notices/:uuid/applicants/:applicantUuid/acceptnotices:writeAccept an applicant.
POST/api/v1/me/notices/:uuid/applicants/:applicantUuid/declinenotices:writeDecline an applicant.

Tours

MethodPathAbilityNotes
GET/api/v1/me/tourstours:readYour tours (cursor page).
GET/api/v1/me/tours/:stopUuid/bookingsbookings:readBookings for a tour stop you own (owner-only).
POST/api/v1/me/tourstours:writeCreate a tour with one or more stops (cityId + ISO start/end per stop).
POST/api/v1/me/tours/:uuid/stopstours:writeAdd a stop to an existing tour.

Bookings

MethodPathAbilityNotes
GET/api/v1/me/bookingsbookings:readYour bookings (cursor page).
POST/api/v1/me/bookingsbookings:writeCreate a booking request.
POST/api/v1/me/bookings/:uuid/acceptbookings:writeAccept (confirm the proposed slot).
POST/api/v1/me/bookings/:uuid/declinebookings:writeDecline (cancel).
POST/api/v1/me/bookings/:uuid/propose-slotbookings:writeCounter-propose price / city / datetime.

Posts & social

MethodPathAbilityNotes
GET/api/v1/me/postsposts:readYour posts (cursor page).
GET/api/v1/feedposts:readThe authenticated feed (?tab=recommendations|following, opaque ?cursor). Shadow-banned authors are excluded and sensitive media is covered server-side.
POST/api/v1/postsposts:writeCreate 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/followersfollows:readYour followers (cursor page).
GET/api/v1/me/follows/followingfollows:readAccounts you follow.
GET/api/v1/me/follows/bookmarksfollows:readYour bookmarks.

Public profiles

MethodPathAbilityNotes
GET/api/v1/users/:usernamewhoami:readA 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/photosportfolio:readTheir 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/polaroidsportfolio:readTheir 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:

FieldScopeAudience
/api/v1/me/stats.totalPostCountEVERY row you own, including moderation-hidden, still-scheduled and expired posts.Owner analytics only.
/api/v1/users/:username.postsCountThe 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.

MethodPathAbilityNotes
GET/api/v1/searchwhoami:readPublic-safe explore showcase (cursor page). Returns only what the public teaser query would — SFW, published, non-shadow-banned.

Creator Studio (AI image generation)

MethodPathAbilityNotes
POST/api/v1/me/studio/generatestudio:generateQueue 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/:uuidstudio:generatePoll one job's status + (on success) its signed outputs.
GET/api/v1/me/studio/jobsstudio:generateList 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/me

miximodel 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:generate included.
  • Where the token lives. $XDG_CONFIG_HOME/miximodel/credentials.json (~/.config/miximodel/credentials.json by default).
  • Another host. Set MIXIMODEL_API_URL to point the CLI at a non-production server.
  • After a password reset the token is revoked: run miximodel login again.

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.