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

Account

GET/api/v1/me

Your plan tier, credit balance, and feature gates.

Returns: { tier, credits, features: { image, video, chat } }

GET/api/v1/costs

Credit 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.

GET/api/v1/characters

List characters you can chat with (own + collected). ?limit=100.

Returns: { characters: [{ id, name, gender, age, occupation, source: "own"|"collected", ... }] }

POST/api/v1/characters

Create 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 }

GET/api/v1/characters/:id

Full profile sheet (owner or collector). Anchor image URLs are owner-only.

PATCH/api/v1/characters/:id

Edit the sheet (creator only; re-moderated when published).

DELETE/api/v1/characters/:id

Delete a character.

Body: { mode: "keep-access" | "everyone" }

POST/api/v1/characters/:id/publish

Set library visibility and pricing (creator only).

Body: { visibility: "private"|"public"|"premium", price_model?, price_credits? }

POST/api/v1/characters/:id/collect

Add a library character to your collection.

Credits: Premium characters debit the publisher's list price.

DELETE/api/v1/characters/:id/collect

Remove from your collection (no refund).

POST/api/v1/characters/:id/anchor

Generate a reference anchor (creator only; slow — up to ~4 min).

Body: { kind: "face" | "clothed" | "nude" }

Returns: { url }

GET / POST/api/v1/characters/:id/anchor/history

List stored anchor versions / restore one (POST { kind, url }). Creator only.

POST/api/v1/characters/:id/profile-selfies

Generate 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.

POST/api/v1/characters/:id/chat

Send 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.

POST/api/v1/characters/:id/chat/opener

She speaks first on an empty conversation (returns { skip: true } if it isn't empty). Free.

Returns: { text, messageId, selfie? } | { skip: true }

POST/api/v1/characters/:id/chat/regenerate

Re-roll one of her replies in place.

Body: { messageId }

Returns: { text }

Credits: 1 × chat.

POST/api/v1/characters/:id/chat/macro

Ghost-write YOUR next message in a style. Free.

Body: { style: "friendly"|"deep"|"insightful"|"flirty"|"dominant"|"submissive"|"sexual" }

Returns: { text }

GET/api/v1/characters/:id/messages

Conversation history, chronological. ?limit=40&before=<ISO> to page back.

Returns: { messages: [{ id, role, content, image_status, image_url, created_at }] }

GET/api/v1/characters/:id/messages/:messageId

One message — poll this for photo status (image_status: generating → done + image_url).

DELETE/api/v1/characters/:id/messages/:messageId

Delete 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.

POST/api/v1/characters/:id/selfie

Wait 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.

POST/api/v1/images

Submit 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).

GET/api/v1/images/:id

Poll 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 } }

GET/api/v1/images

Your gallery (finished images). ?character=<id>&since=<ISO>.

DELETE/api/v1/images/:id

Delete an image.

PATCH/api/v1/images/:id

Favorite / move to folder.

Body: { favorite?: true, folderId?: "<uuid>" | null }

POST / DELETE/api/v1/images/loras

Load 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).

POST/api/v1/videos

Submit 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.

GET/api/v1/videos/:id

Poll a clip — finalizes the finished job; returns video_url + poster_url when done.

GET/api/v1/videos

List your clips, newest first. ?limit=50.

DELETE/api/v1/videos/:id

Delete a clip.

PATCH/api/v1/videos/:id

Favorite / move to folder.

Body: { favorite?: true, folderId?: "<uuid>" | null }

POST / DELETE/api/v1/videos/folders

Create (POST { name }) / delete (DELETE { id }) a video folder.

POST / DELETE/api/v1/videos/loras

Load a video LoRA (POST { url, backend?, token? }) / remove one (DELETE { id }).

Uploads & folders

GET/api/v1/uploads

List your uploaded reference images.

POST/api/v1/uploads

Upload 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 }

DELETE/api/v1/uploads

Delete uploads.

Body: { id } or { ids: [...] }

GET / POST / DELETE/api/v1/folders

List / 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.