API reference
Midjourney tasks
Asynchronous Midjourney API — submit with POST /mj/submit/imagine and friends, poll GET /mj/task/{id}/fetch for the result. Compatible with the common Midjourney Proxy protocol; examples for upscale, vary, zoom out, vary region, describe and video, with per-call pricing.
Midjourney works as an asynchronous task API, unlike the synchronous OpenAI-format endpoints: submit a task, get its ID, then poll until the image is ready. Paths and fields follow the common Midjourney Proxy protocol, so existing MJ code only needs https://www.tutujin.com as its address and a key from this site.
https://www.tutujin.com/mj/submit/imagineHeaders
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer sk-..., an API key from the console. The mj-api-secret: sk-... header used by Midjourney Proxy clients works too |
Content-Type | Yes | application/json |
The body has no model field: each route and action is billed as its own model.
Endpoints
| Endpoint | Purpose | Billed as |
|---|---|---|
POST /mj/submit/imagine | Text to image; base64Array adds reference images | mj_imagine |
POST /mj/submit/action | Press a button on a result: upscale, vary, strong / subtle vary, reroll, zoom out, pan | mj_upscale, mj_variation, mj_high_variation, mj_low_variation, mj_reroll, mj_zoom, mj_pan |
POST /mj/submit/modal | Second step of vary region and custom zoom | mj_modal |
POST /mj/submit/describe | Image to prompts | mj_describe |
POST /mj/submit/edits | Edit an image with a prompt | mj_edits |
POST /mj/submit/video | Text / image to video, four clips per call | mj_video |
POST /mj/submit/upload-discord-images | Upload images and get URLs to use in prompts | mj_upload |
GET /mj/task/{id}/fetch | Get one task | Free |
POST /mj/task/list-by-condition | Get several tasks with {"ids": [...]} | Free |
Prices per operation are in the model list at the bottom of this page and in the model catalog. Operations missing from the catalog (currently Blend, Shorten and face swap) are not supported: such a request answers "no available channel" and is not billed.
Request body
| Parameter | Type | Used by | Description |
|---|---|---|---|
prompt | string | imagine, edits, video, modal | Prompt; Midjourney parameters such as --ar 16:9 are allowed. --fast, --relax and --turbo are ignored: the speed tier is fixed by this site |
base64Array | array | imagine, upload | Images as data:image/png;base64,...; reference images for imagine |
taskId | string | action, modal | ID of the earlier task to continue from |
customId | string | action | Button ID from the earlier task's buttons; picks the operation |
maskBase64 | string | modal | Mask image (base64) for vary region |
base64 | string | describe | Image (base64) to describe |
image | string | edits, video | URL of the reference image or the video's first frame |
motion | string | video | low or high |
Request example
Submit an imagine task, then poll until status is SUCCESS or FAILURE:
curl https://www.tutujin.com/mj/submit/imagine \
-H "Authorization: Bearer $TUTUJIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "An orange cat sunbathing by the window, watercolor style --ar 3:2"
}'
# poll with the task ID returned in "result"
curl https://www.tutujin.com/mj/task/$TASK_ID/fetch \
-H "Authorization: Bearer $TUTUJIN_API_KEY"import os
import time
import requests
BASE = "https://www.tutujin.com"
HEADERS = {"Authorization": "Bearer " + os.environ["TUTUJIN_API_KEY"]}
submit = requests.post(
f"{BASE}/mj/submit/imagine",
headers=HEADERS,
json={"prompt": "An orange cat sunbathing by the window, watercolor style --ar 3:2"},
timeout=60,
).json()
if submit["code"] != 1:
raise RuntimeError(submit.get("description"))
task_id = submit["result"]
while True:
time.sleep(5)
task = requests.get(f"{BASE}/mj/task/{task_id}/fetch", headers=HEADERS, timeout=30).json()
if task["status"] in ("SUCCESS", "FAILURE"):
break
print(task["status"], task.get("imageUrl") or task.get("failReason"))
for b in task.get("buttons") or []:
print(b["label"], b["customId"])const BASE = 'https://www.tutujin.com'
const headers = { Authorization: `Bearer ${process.env.TUTUJIN_API_KEY}`, 'Content-Type': 'application/json' }
const submit = await fetch(`${BASE}/mj/submit/imagine`, {
method: 'POST',
headers,
body: JSON.stringify({ prompt: 'An orange cat sunbathing by the window, watercolor style --ar 3:2' }),
}).then((r) => r.json())
if (submit.code !== 1) throw new Error(submit.description)
let task
do {
await new Promise((r) => setTimeout(r, 5000))
task = await fetch(`${BASE}/mj/task/${submit.result}/fetch`, { headers }).then((r) => r.json())
} while (!['SUCCESS', 'FAILURE'].includes(task.status))
console.log(task.status, task.imageUrl ?? task.failReason)
for (const b of task.buttons ?? []) console.log(b.label, b.customId)An image usually takes from a few dozen seconds to a couple of minutes. Polling every 3–10 seconds is plenty.
Next steps: upscale, vary, zoom out, pan
A finished task lists the Midjourney buttons in buttons. Send a button's customId together with the task ID to /mj/submit/action; that starts a new task, polled the same way:
curl https://www.tutujin.com/mj/submit/action \
-H "Authorization: Bearer $TUTUJIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"taskId": "1794500000000001",
"customId": "MJ::JOB::upsample::1::2f4a9c1e7b3d5a8f0c6e1b92"
}'upsample buttons upscale (U1–U4, 2x / 4x), variation buttons vary (V1–V4), reroll regenerates, Outpaint zooms out and pan_* pans.
Vary region and custom zoom take two steps. First send the Inpaint or CustomZoom button to action: it answers code 21 with a new task ID and is not billed. Then send that ID with a new prompt (plus a maskBase64 mask for vary region) to /mj/submit/modal, billed as mj_modal.
Response example
Submitted:
{ "code": 1, "description": "Submit success", "result": "1794500000000001" }Task:
{
"id": "1794500000000001",
"action": "IMAGINE",
"status": "SUCCESS",
"progress": "100%",
"imageUrl": "https://www.tutujin.com/mj/image/1794500000000001",
"failReason": "",
"buttons": [
{ "customId": "MJ::JOB::upsample::1::2f4a9c1e7b3d5a8f0c6e1b92", "label": "U1" },
{ "customId": "MJ::JOB::variation::1::2f4a9c1e7b3d5a8f0c6e1b92", "label": "V1" }
]
}| Field | Description |
|---|---|
code | Submit result: 1 accepted; 21 means a modal step follows (vary region, custom zoom); anything else failed, see description |
result | Task ID |
status | NOT_START, SUBMITTED, IN_PROGRESS, SUCCESS, FAILURE |
progress | Progress such as 67% |
imageUrl | The result image (grid or single upscale), served through this site's domain; download it soon |
videoUrls | Video URLs of a video task |
promptEn | Prompts produced by describe |
buttons | Operations available next |
failReason | Why the task failed |
Billing
- Per call: every operation is billed as its own model, priced in the list below. Only an accepted submit (
code1) is charged; a rejected submit costs nothing, and a task that ends in failure is refunded. - Every upscale button (U1–U4, 2x, 4x) is billed as
mj_upscaleand zoom out asmj_zoom, so all buttons of one model cost the same. - The first step of vary region / custom zoom is free; the second is billed as
mj_modal. - Polling is free.
Notes
- The API is asynchronous: do not hold one HTTP request open for the result. Store the task ID and poll, or check back later.
- Callbacks are not supported (
notifyHookis ignored); poll for results. - The speed tier is fixed by this site, so
--fast,--relaxand--turboin a prompt have no effect. - A prompt that trips Midjourney's moderation fails at submit or ends as
FAILURE. Change the prompt and submit again. - Image URLs are for short-term use; download results into your own storage.
Models that support this endpoint
| Model | Vendor | Cheapest group | Input ¥/1M | Output ¥/1M | Per request ¥ |
|---|---|---|---|---|---|
| Midjourney Upload | Midjourney | Standard | — | — | ¥0.02 |
| Midjourney Describe | Midjourney | Standard | — | — | ¥0.1 |
| Midjourney Imagine | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Variation | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Vary (Strong) | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Vary (Subtle) | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Reroll | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Upscale | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Zoom Out | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Pan | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Vary Region / Custom Zoom | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Edit | Midjourney | Standard | — | — | ¥0.4 |
| Midjourney Video | Midjourney | Standard | — | — | ¥2.8 |
Prices in CNY, cheapest group for each model; updated Nov 13, 2026, 6:13 PM.
Still stuck? Add our WeChat support JsnonoChat and send the request id from the error message, and we will look into it.