xpickle API
Everything the app can do — chat with characters, generate images and video, manage your gallery — over plain HTTPS + JSON. Requests act as your account with the same credits, plan limits, and content moderation as the app.
Authentication
Create a key under Account → API keys and send it on every request:
Authorization: Bearer xpk_...
The key is shown once at creation. Revoking it stops requests immediately. Base URL: https://app.xpickle.ai
Conventions
- JSON in, JSON out (the streaming chat variant returns
application/x-ndjson). - Errors:
400invalid request ·401bad/revoked key ·402not enough credits ·403moderation block, plan limit, or no access ·404not found. - Paid actions debit credits before the work and auto-refund on job failure. Check prices with
GET /api/v1/costsand your balance withGET /api/v1/me. - Prompts and profile text pass the same moderation as the app — violations record strikes against your account, and a locked account loses API access too.
- Generation is async: submit returns an id, then poll the matching
GET .../:idendpoint (polling also finalizes finished jobs — poll the id, not the list). A 2-5s interval is plenty.
Account
/api/v1/meYour plan tier, credit balance, and feature gates.
Returns: { tier, credits, features: { image, video, chat } }
/api/v1/costsCredit price per paid action (image, chat, video_6s, video_12s, …).
Returns: { costs: { image: 5, chat: 1, ... } }
Characters
Your own creations plus characters you've collected from the library. Creating and editing runs the same moderation as the app.
/api/v1/charactersList characters you can chat with (own + collected). ?limit=100.
Returns: { characters: [{ id, name, gender, age, occupation, source: "own"|"collected", ... }] }
/api/v1/charactersCreate a character from a profile sheet.
Body: { name, gender?, race?, age?, height?, hair_color?, eye_color?, skin_tone?, breast_size?, piercings?, body_type?, face_description?, appearance?, temperament?, occupation?, location?, kinks?, taboos?, interests?, disinterests? }
Returns: { id }
/api/v1/characters/:idFull profile sheet (owner or collector). Anchor image URLs are owner-only.
/api/v1/characters/:idEdit the sheet (creator only; re-moderated when published).
/api/v1/characters/:idDelete a character.
Body: { mode: "keep-access" | "everyone" }
/api/v1/characters/:id/publishSet library visibility and pricing (creator only).
Body: { visibility: "private"|"public"|"premium", price_model?, price_credits? }
/api/v1/characters/:id/collectAdd a library character to your collection.
Credits: Premium characters debit the publisher's list price.
/api/v1/characters/:id/collectRemove from your collection (no refund).
/api/v1/characters/:id/anchorGenerate a reference anchor (creator only; slow — up to ~4 min).
Body: { kind: "face" | "clothed" | "nude" }
Returns: { url }
/api/v1/characters/:id/anchor/historyList stored anchor versions / restore one (POST { kind, url }). Creator only.
/api/v1/characters/:id/profile-selfiesGenerate the character's two profile selfies (creator only; slow).
Chat
Each turn debits chat credits (free tier included — credits gate usage, the tier gates features). Characters have moods and can decline, block, or end a conversation, exactly like the app.
/api/v1/characters/:id/chatSend a message. stream:true relays NDJSON ({type:"text"} chunks then {type:"done"}); default waits and returns the final JSON. A non-null `selfie` means a photo is generating — see Photos.
Body: { message: "...", stream?: false, continue?: true (she keeps going without a new message), tz?: "America/Chicago" }
Returns: { text, messageId, selfie: { messageId, hint } | null, blocked }
Credits: 1 × chat per turn.
/api/v1/characters/:id/chat/openerShe speaks first on an empty conversation (returns { skip: true } if it isn't empty). Free.
Returns: { text, messageId, selfie? } | { skip: true }
/api/v1/characters/:id/chat/regenerateRe-roll one of her replies in place.
Body: { messageId }
Returns: { text }
Credits: 1 × chat.
/api/v1/characters/:id/chat/macroGhost-write YOUR next message in a style. Free.
Body: { style: "friendly"|"deep"|"insightful"|"flirty"|"dominant"|"submissive"|"sexual" }
Returns: { text }
/api/v1/characters/:id/messagesConversation history, chronological. ?limit=40&before=<ISO> to page back.
Returns: { messages: [{ id, role, content, image_status, image_url, created_at }] }
/api/v1/characters/:id/messages/:messageIdOne message — poll this for photo status (image_status: generating → done + image_url).
/api/v1/characters/:id/messages/:messageIdDelete a single message (and its photo).
Photos (selfies)
Photos are character-driven: her reply decides when one goes out. When a chat response includes `selfie`, the photo is already generating server-side.
/api/v1/characters/:id/selfieWait on a photo-bearing message until the image is ready (up to ~4 min). Alternative to polling the message.
Body: { messageId }
Returns: { url, status }
Images
Async image generation (Qwen-Image-Edit-Rapid). Submit, then poll.
/api/v1/imagesSubmit a generation. 0 reference images = text-to-image; 1-3 = reference edit. Reference URLs must be this app's storage URLs (your uploads or prior generations). Prompt is moderated.
Body: { prompt, images?: [{ name, url }] , width?, height? (256-2048, multiples of 16), seed?, steps?, cfg?, negativePrompt?, batchSize? (1-4), characterIds? }
Returns: { generation: { id, status: "pending" } }
Credits: 1 × image per image (batchSize debits per variation).
/api/v1/images/:idPoll a generation — also finalizes the finished job, so poll this (not the list). Failed jobs auto-refund.
Returns: { generation: { id, status: "pending"|"done"|"failed", url } }
/api/v1/imagesYour gallery (finished images). ?character=<id>&since=<ISO>.
/api/v1/images/:idDelete an image.
/api/v1/images/:idFavorite / move to folder.
Body: { favorite?: true, folderId?: "<uuid>" | null }
/api/v1/images/lorasLoad an image LoRA from a Civitai/HF URL (POST { url, token? }) / remove one (DELETE { id }).
Videos
Video generation requires a paid plan (403 on free tier).
/api/v1/videosSubmit a clip. Backends: wan22 (default), hunyuan (needs startImageUrl), wan-rapid, wan-enhanced. Prompt is moderated.
Body: { prompt?, negative?, startImageUrl?, endImageUrl?, backend?, width?, height?, frames?, fps?, seconds? (6|12 — drives pricing), steps?, cfg?, seed?, tier? }
Returns: { clip: { id, status: "processing" } }
Credits: 1 × video_6s or video_12s by clip length. Failed jobs auto-refund.
/api/v1/videos/:idPoll a clip — finalizes the finished job; returns video_url + poster_url when done.
/api/v1/videosList your clips, newest first. ?limit=50.
/api/v1/videos/:idDelete a clip.
/api/v1/videos/:idFavorite / move to folder.
Body: { favorite?: true, folderId?: "<uuid>" | null }
/api/v1/videos/foldersCreate (POST { name }) / delete (DELETE { id }) a video folder.
/api/v1/videos/lorasLoad a video LoRA (POST { url, backend?, token? }) / remove one (DELETE { id }).
Uploads & folders
/api/v1/uploadsList your uploaded reference images.
/api/v1/uploadsUpload a reference image (base64 PNG/JPEG). The returned URL works as an image-gen reference or a video start frame.
Body: { dataBase64, name? }
Returns: { id, url }
/api/v1/uploadsDelete uploads.
Body: { id } or { ids: [...] }
/api/v1/foldersList / create ({ name }) / delete ({ id }) image-gallery folders.
Example: a chat turn with a photo
# 1. She opens the conversation (free)
curl -X POST https://app.xpickle.ai/api/v1/characters/$CHARACTER_ID/chat/opener \
-H "Authorization: Bearer $XPICKLE_API_KEY"
# 2. Reply (debits 1 chat credit)
curl -X POST https://app.xpickle.ai/api/v1/characters/$CHARACTER_ID/chat \
-H "Authorization: Bearer $XPICKLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"message": "hey :) what are you up to right now?"}'
# → { "text": "...", "messageId": "...", "selfie": { "messageId": "..." } | null }
# 3. If selfie is non-null, poll the message until the photo is done
curl https://app.xpickle.ai/api/v1/characters/$CHARACTER_ID/messages/$MESSAGE_ID \
-H "Authorization: Bearer $XPICKLE_API_KEY"
# → { ..., "image_status": "done", "image_url": "https://..." }Example: generate an image
# Submit (debits image credits)
curl -X POST https://app.xpickle.ai/api/v1/images \
-H "Authorization: Bearer $XPICKLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "portrait of a redhead in golden hour light", "width": 768, "height": 1344}'
# → { "generation": { "id": "GEN_ID", "status": "pending" } }
# Poll until done
curl https://app.xpickle.ai/api/v1/images/$GEN_ID \
-H "Authorization: Bearer $XPICKLE_API_KEY"
# → { "generation": { "status": "done", "url": "https://..." } }The API mirrors the app one-to-one — anything you can click, you can call. Multipart-only flows (video frame upload, clip re-timing) are covered by /api/v1/uploads base64 uploads.