@acedatacloud/skills 2026.622.3 → 2026.627.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -12,7 +12,6 @@ Skills are located in the `skills/` directory (also mirrored to `.agents/skills/
12
12
  - **fish-audio** — Text-to-speech and voice synthesis with Fish Audio
13
13
 
14
14
  ### AI Image Generation
15
- - **midjourney-image** — Generate, edit, blend, describe, and upscale images with Midjourney
16
15
  - **flux-image** — Generate and edit images with Flux (Black Forest Labs)
17
16
  - **seedream-image** — Generate and edit images with ByteDance Seedream
18
17
  - **nano-banana-image** — Generate and edit images with Google Gemini (NanoBanana)
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  Compatible with **30+ AI coding agents** via the [agentskills.io](https://agentskills.io/) open standard: Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, Cursor, Roo Code, Goose, and more.
14
14
 
15
- ## Available Skills (30)
15
+ ## Available Skills (29)
16
16
 
17
17
  ### AI Music & Audio
18
18
 
@@ -26,7 +26,6 @@ Compatible with **30+ AI coding agents** via the [agentskills.io](https://agents
26
26
 
27
27
  | Skill | Description |
28
28
  |-------|-------------|
29
- | [midjourney-image](skills/midjourney-image/) | Generate, edit, blend, describe, and upscale images with Midjourney |
30
29
  | [flux-image](skills/flux-image/) | Generate and edit images with Flux (Black Forest Labs) |
31
30
  | [seedream-image](skills/seedream-image/) | Generate and edit images with ByteDance Seedream |
32
31
  | [nano-banana-image](skills/nano-banana-image/) | Generate and edit images with Google Gemini (NanoBanana) |
@@ -92,7 +91,7 @@ export ACEDATACLOUD_API_TOKEN="your-token-here"
92
91
  npx skills add acedatacloud/skills --skill '*' --yes
93
92
 
94
93
  # Install specific skills only
95
- npx skills add acedatacloud/skills --skill suno-music --skill midjourney-image --yes
94
+ npx skills add acedatacloud/skills --skill suno-music --skill flux-image --yes
96
95
  ```
97
96
 
98
97
  The [`skills` CLI](https://github.com/vercel-labs/skills) auto-detects your agent and installs to the correct path.
@@ -197,7 +196,6 @@ Skills provide **knowledge** (when to use, parameters, gotchas). MCP servers pro
197
196
  | Skill | MCP Server | Install | Hosted Endpoint |
198
197
  |-------|-----------|---------|-----------------|
199
198
  | suno-music | [mcp-suno](https://pypi.org/project/mcp-suno/) | `pip install mcp-suno` | `https://suno.mcp.acedata.cloud/mcp` |
200
- | midjourney-image | [mcp-midjourney](https://pypi.org/project/mcp-midjourney/) | `pip install mcp-midjourney` | `https://midjourney.mcp.acedata.cloud/mcp` |
201
199
  | google-search | [mcp-serp](https://pypi.org/project/mcp-serp/) | `pip install mcp-serp` | `https://serp.mcp.acedata.cloud/mcp` |
202
200
  | flux-image | [mcp-flux-pro](https://pypi.org/project/mcp-flux-pro/) | `pip install mcp-flux-pro` | `https://flux.mcp.acedata.cloud/mcp` |
203
201
  | luma-video | [mcp-luma](https://pypi.org/project/mcp-luma/) | `pip install mcp-luma` | `https://luma.mcp.acedata.cloud/mcp` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acedatacloud/skills",
3
- "version": "2026.622.3",
3
+ "version": "2026.627.0",
4
4
  "description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -12,7 +12,6 @@
12
12
  "openai-codex",
13
13
  "ai-tools",
14
14
  "suno",
15
- "midjourney",
16
15
  "flux",
17
16
  "luma",
18
17
  "sora",
@@ -8,7 +8,6 @@ Each AceDataCloud service has a corresponding MCP server that provides tool-use
8
8
  |-------|---------|-----------------|
9
9
  | suno-music | `pip install mcp-suno` | `https://suno.mcp.acedata.cloud/mcp` |
10
10
  | producer-music | — | — |
11
- | midjourney-image | `pip install mcp-midjourney` | `https://midjourney.mcp.acedata.cloud/mcp` |
12
11
  | google-search | `pip install mcp-serp` | `https://serp.mcp.acedata.cloud/mcp` |
13
12
  | flux-image | `pip install mcp-flux-pro` | `https://flux.mcp.acedata.cloud/mcp` |
14
13
  | luma-video | `pip install mcp-luma` | `https://luma.mcp.acedata.cloud/mcp` |
@@ -112,7 +112,7 @@ Error response format:
112
112
  | Category | Services | Base Path |
113
113
  |----------|----------|-----------|
114
114
  | **AI Chat** | GPT, Claude, Gemini, Kimi, Grok | `/v1/chat/completions` |
115
- | **Image Gen** | Midjourney, Flux, Seedream, NanoBanana | `/midjourney/*`, `/flux/*`, etc. |
115
+ | **Image Gen** | Flux, Seedream, NanoBanana | `/flux/*`, `/seedream/*`, etc. |
116
116
  | **Video Gen** | Luma, Sora, Veo, Kling, Hailuo, Seedance, Wan | `/luma/*`, `/sora/*`, etc. |
117
117
  | **Music Gen** | Suno, Producer, Fish Audio | `/suno/*`, `/producer/*`, `/fish/*` |
118
118
  | **Search** | Google Search (web/images/news/maps) | `/serp/*` |
@@ -25,6 +25,7 @@ from __future__ import annotations
25
25
  import argparse
26
26
  import gzip
27
27
  import hashlib
28
+ import html as _html
28
29
  import ipaddress
29
30
  import json
30
31
  import os
@@ -287,7 +288,7 @@ _IMG_SKIP = ("hdslb.com", "bilibili.com", "biliimg.com")
287
288
  # matches both HTML <img src="..."> and markdown ![alt](...)
288
289
  # <img ... src = "..."> / '...' (tolerates spaces and either quote style)
289
290
  _HTML_IMG = re.compile(r"""(<img\b[^>]*?\bsrc\s*=\s*)(["'])(https?://[^"']+)(\2)""", re.IGNORECASE)
290
- _MD_IMG = re.compile(r"!\[([^\]]*)\]\((https?://[^)\s]+)\)")
291
+ _MD_IMG = re.compile(r"!\[([^\]]{0,500})\]\((https?://[^)\s]{0,2000})\)") # bounded: no O(n²) on "![" * N
291
292
  UPCOVER = "https://api.bilibili.com/x/article/creative/article/upcover"
292
293
 
293
294
 
@@ -429,6 +430,121 @@ def rehost_images(jar, csrf, content):
429
430
  return content
430
431
 
431
432
 
433
+ # ── Markdown → HTML (专栏 body is HTML; convert if the caller passed Markdown) ──
434
+
435
+ # Bounded so a single token can't scan to EOF — keeps these linear (no O(n²)
436
+ # backtracking on malformed input like "![" * N). Real alt < 500 / URL < 2000.
437
+ _IMG_RE = re.compile(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
438
+ _LINK_RE = re.compile(r"(?<!!)\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
439
+
440
+
441
+ def _looks_like_markdown(s):
442
+ # Three-way classification (no full parser): block-level HTML ⇒ already an
443
+ # HTML document → pass through (even if it also contains markdown-looking
444
+ # text like **bold**); else markdown markers ⇒ render; else inline-only HTML
445
+ # ⇒ pass through; else plain text ⇒ render (md_to_html just wraps/escapes it).
446
+ s = s or ""
447
+ block_html = re.search(
448
+ r"</?(?:p|div|h[1-6]|ul|ol|li|table|thead|tbody|tr|td|th|blockquote|"
449
+ r"pre|figure|figcaption|section|article|header|footer|nav|aside|hr|"
450
+ r"main|details|summary)\b",
451
+ s, re.I,
452
+ )
453
+ if block_html:
454
+ return False
455
+ # ^-anchored (re.M) + horizontal-only whitespace + bounded {0,N} repeats so
456
+ # the scan can't backtrack across newlines or to EOF (no quadratic scanning).
457
+ has_md = re.search(
458
+ r"^#{1,6}\s|!\[[^\]]{0,500}\]\([^)]{0,2000}\)|^[ \t]*[-*+]\s|^[ \t]*\d+\.\s"
459
+ r"|\[[^\]]{0,500}\]\([^)]{0,2000}\)|`[^`]{1,500}`|\*\*[^*]{1,500}\*\*",
460
+ s, re.M,
461
+ )
462
+ if has_md:
463
+ return True
464
+ inline_html = re.search(
465
+ r"</?(?:a|strong|em|b|i|u|s|span|code|br|img|small|mark|sup|sub|"
466
+ r"video|audio|iframe)\b",
467
+ s, re.I,
468
+ )
469
+ return not inline_html
470
+
471
+
472
+ def _attr_url(u):
473
+ # Strip C0 controls/space FIRST, then allow only http/https/mailto — otherwise
474
+ # `\x01javascript:` would slip past a literal scheme check yet be re-normalised
475
+ # to javascript: by the browser. Finally neutralise attribute-breaking quotes.
476
+ u = re.sub(r"[\x00-\x20\x7f]", "", u or "")
477
+ m = re.match(r"(?i)([a-z][a-z0-9+.\-]*):", u)
478
+ if m and m.group(1).lower() not in ("http", "https", "mailto"):
479
+ return "#"
480
+ return u.replace('"', "%22").replace("'", "%27")
481
+
482
+
483
+ def _alt(s):
484
+ return (s or "").replace('"', "&quot;").replace("'", "&#x27;")
485
+
486
+
487
+ def _inline_md(t):
488
+ t = _html.escape(t, quote=False).replace("\x00", "")
489
+ # Stash code spans FIRST so emphasis/link/image markup inside `…` stays
490
+ # literal (e.g. `**x**` must render as text, not bold).
491
+ spans = []
492
+
493
+ def _stash(m):
494
+ spans.append("<code>" + m.group(1) + "</code>")
495
+ return f"\x00{len(spans) - 1}\x00"
496
+
497
+ t = re.sub(r"`([^`]+)`", _stash, t)
498
+ t = _IMG_RE.sub(lambda m: f'<img src="{_attr_url(m.group(2))}" alt="{_alt(m.group(1))}">', t)
499
+ t = _LINK_RE.sub(lambda m: f'<a href="{_attr_url(m.group(2))}">{m.group(1)}</a>', t)
500
+ t = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", t)
501
+ t = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"<em>\1</em>", t)
502
+ t = re.sub(r"\x00(\d+)\x00", lambda m: spans[int(m.group(1))], t)
503
+ return t
504
+
505
+
506
+ def _md_block_to_html(b):
507
+ b = b.strip("\n")
508
+ if not b.strip():
509
+ return None
510
+ f = b.lstrip()
511
+ m = re.match(r"(#{1,6})\s+(.*)", f)
512
+ if m:
513
+ lvl = len(m.group(1))
514
+ return f"<h{lvl}>{_inline_md(m.group(2).strip())}</h{lvl}>"
515
+ if f.startswith(">"):
516
+ inner = re.sub(r"^>\s?", "", b, flags=re.M).replace("\n", " ")
517
+ return "<blockquote><p>" + _inline_md(inner) + "</p></blockquote>"
518
+ if re.match(r"[-*+]\s+", f):
519
+ items = [_inline_md(re.sub(r"^[-*+]\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
520
+ return "<ul>" + "".join(f"<li>{x}</li>" for x in items) + "</ul>"
521
+ if re.match(r"\d+\.\s+", f):
522
+ items = [_inline_md(re.sub(r"^\d+\.\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
523
+ return "<ol>" + "".join(f"<li>{x}</li>" for x in items) + "</ol>"
524
+ only = re.fullmatch(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)", f)
525
+ if only:
526
+ return f'<img src="{_attr_url(only.group(2))}" alt="{_alt(_html.escape(only.group(1), quote=False))}">'
527
+ return "<p>" + _inline_md(b.replace("\n", " ").strip()) + "</p>"
528
+
529
+
530
+ def md_to_html(src):
531
+ src = (src or "").strip()
532
+ out = []
533
+ # Pull fenced code out FIRST (it may itself contain blank lines) so the
534
+ # blank-line block splitter below can never tear a ``` … ``` fence apart.
535
+ for i, part in enumerate(re.split(r"(?ms)^(```.*?(?:\n```[ \t]*$|\Z))", src)):
536
+ if i % 2 == 1: # fenced code segment
537
+ code = re.sub(r"\A```[^\n]*\n?", "", part)
538
+ code = re.sub(r"\n?```[ \t]*\Z", "", code)
539
+ out.append("<pre><code>" + _html.escape(code) + "</code></pre>")
540
+ continue
541
+ for b in re.split(r"\n[ \t]*\n", part): # blank-line split (no cross-newline \s*)
542
+ block = _md_block_to_html(b)
543
+ if block:
544
+ out.append(block)
545
+ return "\n".join(out)
546
+
547
+
432
548
  def cmd_publish(jar, args):
433
549
  if not args.title:
434
550
  die("--title is required")
@@ -461,6 +577,11 @@ def cmd_publish(jar, args):
461
577
  if not args.no_rehost_images:
462
578
  content = rehost_images(jar, csrf, content)
463
579
 
580
+ # 专栏 content is HTML; if the caller passed Markdown, render it (else the
581
+ # article shows literal `##`/`![]()` and runs together as one paragraph).
582
+ if _looks_like_markdown(content):
583
+ content = md_to_html(content)
584
+
464
585
  ref = "https://member.bilibili.com/"
465
586
  base = {
466
587
  "title": args.title, "content": content, "csrf": csrf,
@@ -28,6 +28,7 @@ import base64
28
28
  import gzip
29
29
  import hashlib
30
30
  import hmac
31
+ import html as _html
31
32
  import ipaddress
32
33
  import json
33
34
  import mimetypes
@@ -286,7 +287,7 @@ def cmd_article(jar, args):
286
287
  # Signed signature → Huawei OBS multipart → csdnimg.cn URL. Mirrors our
287
288
  # PlatformPublisher csdn.py flow.
288
289
 
289
- _MD_IMG = re.compile(r"!\[([^\]]*)\]\((https?://[^)\s]+)\)")
290
+ _MD_IMG = re.compile(r"!\[([^\]]{0,500})\]\((https?://[^)\s]{0,2000})\)") # bounded: no O(n²) on "![" * N
290
291
  _IMG_SKIP = ("csdnimg.cn", "csdn.net")
291
292
 
292
293
 
@@ -416,6 +417,126 @@ def rehost_images(jar, content):
416
417
  return _MD_IMG.sub(repl, content)
417
418
 
418
419
 
420
+ # ── Markdown → HTML (CSDN renders the `content` HTML field, not the source) ──
421
+
422
+ # Bounded so a single token can't scan to EOF — keeps these linear (no O(n²)
423
+ # backtracking on malformed input like "![" * N). Real alt < 500 / URL < 2000.
424
+ _IMG_RE = re.compile(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
425
+ _LINK_RE = re.compile(r"(?<!!)\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
426
+
427
+
428
+ def _attr_url(u):
429
+ # Strip C0 controls/space FIRST, then allow only http/https/mailto — otherwise
430
+ # `\x01javascript:` would slip past a literal scheme check yet be re-normalised
431
+ # to javascript: by the browser. Finally neutralise attribute-breaking quotes.
432
+ u = re.sub(r"[\x00-\x20\x7f]", "", u or "")
433
+ m = re.match(r"(?i)([a-z][a-z0-9+.\-]*):", u)
434
+ if m and m.group(1).lower() not in ("http", "https", "mailto"):
435
+ return "#"
436
+ return u.replace('"', "%22").replace("'", "%27")
437
+
438
+
439
+ def _alt(s):
440
+ # alt text is already &/</>-escaped by the line escape; only the attribute
441
+ # quote can still break out, so neutralise it.
442
+ return (s or "").replace('"', "&quot;").replace("'", "&#x27;")
443
+
444
+
445
+ def _inline_md(t):
446
+ t = _html.escape(t, quote=False).replace("\x00", "")
447
+ # Stash code spans FIRST so emphasis/link/image markup inside `…` stays
448
+ # literal (e.g. `**x**` must render as text, not bold).
449
+ spans = []
450
+
451
+ def _stash(m):
452
+ spans.append("<code>" + m.group(1) + "</code>")
453
+ return f"\x00{len(spans) - 1}\x00"
454
+
455
+ t = re.sub(r"`([^`]+)`", _stash, t)
456
+ t = _IMG_RE.sub(lambda m: f'<img src="{_attr_url(m.group(2))}" alt="{_alt(m.group(1))}">', t)
457
+ t = _LINK_RE.sub(lambda m: f'<a href="{_attr_url(m.group(2))}">{m.group(1)}</a>', t)
458
+ t = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", t)
459
+ t = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"<em>\1</em>", t)
460
+ t = re.sub(r"\x00(\d+)\x00", lambda m: spans[int(m.group(1))], t)
461
+ return t
462
+
463
+
464
+ def _md_block_to_html(b):
465
+ b = b.strip("\n")
466
+ if not b.strip():
467
+ return None
468
+ f = b.lstrip()
469
+ m = re.match(r"(#{1,6})\s+(.*)", f)
470
+ if m:
471
+ lvl = len(m.group(1))
472
+ return f"<h{lvl}>{_inline_md(m.group(2).strip())}</h{lvl}>"
473
+ if f.startswith(">"):
474
+ inner = re.sub(r"^>\s?", "", b, flags=re.M).replace("\n", " ")
475
+ return "<blockquote><p>" + _inline_md(inner) + "</p></blockquote>"
476
+ if re.match(r"[-*+]\s+", f):
477
+ items = [_inline_md(re.sub(r"^[-*+]\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
478
+ return "<ul>" + "".join(f"<li>{x}</li>" for x in items) + "</ul>"
479
+ if re.match(r"\d+\.\s+", f):
480
+ items = [_inline_md(re.sub(r"^\d+\.\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
481
+ return "<ol>" + "".join(f"<li>{x}</li>" for x in items) + "</ol>"
482
+ only = re.fullmatch(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)", f)
483
+ if only:
484
+ return f'<img src="{_attr_url(only.group(2))}" alt="{_alt(_html.escape(only.group(1), quote=False))}">'
485
+ return "<p>" + _inline_md(b.replace("\n", " ").strip()) + "</p>"
486
+
487
+
488
+ def md_to_html(src):
489
+ """Minimal stdlib Markdown→HTML for the platforms whose body is HTML.
490
+ Covers headings, paragraphs, standalone images, links, bold/italic/code,
491
+ fenced code, blockquotes, and ordered/unordered lists."""
492
+ src = (src or "").strip()
493
+ out = []
494
+ # Pull fenced code out FIRST (it may itself contain blank lines) so the
495
+ # blank-line block splitter below can never tear a ``` … ``` fence apart.
496
+ for i, part in enumerate(re.split(r"(?ms)^(```.*?(?:\n```[ \t]*$|\Z))", src)):
497
+ if i % 2 == 1: # fenced code segment
498
+ code = re.sub(r"\A```[^\n]*\n?", "", part)
499
+ code = re.sub(r"\n?```[ \t]*\Z", "", code)
500
+ out.append("<pre><code>" + _html.escape(code) + "</code></pre>")
501
+ continue
502
+ for b in re.split(r"\n[ \t]*\n", part): # blank-line split (no cross-newline \s*)
503
+ block = _md_block_to_html(b)
504
+ if block:
505
+ out.append(block)
506
+ return "\n".join(out)
507
+
508
+
509
+ def _looks_like_markdown(s):
510
+ # Three-way classification (no full parser): block-level HTML ⇒ already an
511
+ # HTML document → pass through (even if it also contains markdown-looking
512
+ # text like **bold**); else markdown markers ⇒ render; else inline-only HTML
513
+ # ⇒ pass through; else plain text ⇒ render (md_to_html just wraps/escapes it).
514
+ s = s or ""
515
+ block_html = re.search(
516
+ r"</?(?:p|div|h[1-6]|ul|ol|li|table|thead|tbody|tr|td|th|blockquote|"
517
+ r"pre|figure|figcaption|section|article|header|footer|nav|aside|hr|"
518
+ r"main|details|summary)\b",
519
+ s, re.I,
520
+ )
521
+ if block_html:
522
+ return False
523
+ # ^-anchored (re.M) + horizontal-only whitespace + bounded {0,N} repeats so
524
+ # the scan can't backtrack across newlines or to EOF (no quadratic scanning).
525
+ has_md = re.search(
526
+ r"^#{1,6}\s|!\[[^\]]{0,500}\]\([^)]{0,2000}\)|^[ \t]*[-*+]\s|^[ \t]*\d+\.\s"
527
+ r"|\[[^\]]{0,500}\]\([^)]{0,2000}\)|`[^`]{1,500}`|\*\*[^*]{1,500}\*\*",
528
+ s, re.M,
529
+ )
530
+ if has_md:
531
+ return True
532
+ inline_html = re.search(
533
+ r"</?(?:a|strong|em|b|i|u|s|span|code|br|img|small|mark|sup|sub|"
534
+ r"video|audio|iframe)\b",
535
+ s, re.I,
536
+ )
537
+ return not inline_html
538
+
539
+
419
540
  def cmd_publish(jar, args):
420
541
  if not args.title:
421
542
  die("--title is required")
@@ -450,8 +571,12 @@ def cmd_publish(jar, args):
450
571
  status_code = 2 if args.draft_only else 0
451
572
  body = {
452
573
  "title": args.title,
574
+ # markdowncontent = the editor source; content = the RENDERED HTML CSDN
575
+ # actually displays. Sending raw markdown as `content` shows literal
576
+ # `##`/`![]()` and collapses newlines — it MUST be HTML. Already-HTML
577
+ # callers are passed through untouched (don't double-escape).
453
578
  "markdowncontent": content,
454
- "content": content,
579
+ "content": md_to_html(content) if _looks_like_markdown(content) else content,
455
580
  "readType": "public",
456
581
  "status": status_code,
457
582
  "categories": "",
@@ -1,170 +0,0 @@
1
- ---
2
- name: midjourney-image
3
- description: Generate, edit, blend, upscale, and describe images with Midjourney via AceDataCloud API. Use when creating AI images from text prompts, editing existing images, generating 2x2 grids, upscaling, creating variations, blending multiple images, reverse-prompting from images, or generating video from images. Supports versions 5.2 through 8.
4
- license: Apache-2.0
5
- metadata:
6
- author: acedatacloud
7
- version: "1.0"
8
- compatibility: Requires ACEDATACLOUD_API_TOKEN in .env file (see _shared/authentication.md). Optionally pair with mcp-midjourney for tool-use.
9
- ---
10
-
11
- # Midjourney Image Generation
12
-
13
- Generate and manipulate AI images through AceDataCloud's Midjourney API.
14
-
15
- > **Setup:** See [authentication](../_shared/authentication.md) for token setup.
16
-
17
- ## Quick Start — Generate an Image
18
-
19
- ```bash
20
- curl -X POST https://api.acedata.cloud/midjourney/imagine \
21
- -H "Authorization: Bearer $ACEDATACLOUD_API_TOKEN" \
22
- -H "Content-Type: application/json" \
23
- -d '{"prompt": "a futuristic city at sunset, cyberpunk style --ar 16:9", "callback_url": "https://api.acedata.cloud/health"}'
24
- ```
25
-
26
- > **Async:** See [async task polling](../_shared/async-tasks.md). Poll via `POST /midjourney/tasks` with `{"id": "..."}`.
27
-
28
- ## Generation Modes
29
-
30
- | Mode | Speed | Cost | Best For |
31
- |------|-------|------|----------|
32
- | `fast` | Fast | Standard | Most tasks (default) |
33
- | `relax` | Slow | Cheaper | Batch generation |
34
- | `turbo` | Fastest | Premium | Time-sensitive work |
35
-
36
- ## Midjourney Versions
37
-
38
- | Version | Notes |
39
- |---------|-------|
40
- | `8` | Latest, best quality |
41
- | `7` | Great quality, fast |
42
- | `6.1` | Stable, well-tested |
43
- | `6` | Previous generation |
44
- | `5.2` | Legacy |
45
-
46
- ## Core Workflows
47
-
48
- ### 1. Generate Images (Imagine)
49
-
50
- Creates a 2x2 grid of 4 image variations.
51
-
52
- ```json
53
- POST /midjourney/imagine
54
- {
55
- "prompt": "a serene mountain lake at dawn, photorealistic --ar 16:9 --v 7",
56
- "mode": "fast",
57
- "translation": true,
58
- "split_images": true
59
- }
60
- ```
61
-
62
- Set `translation: true` to auto-translate non-English prompts. Set `split_images: true` to get individual images besides the grid.
63
-
64
- ### 2. Upscale / Vary / Pan / Zoom
65
-
66
- After generating a grid, use transform actions on individual images:
67
-
68
- ```json
69
- POST /midjourney/imagine
70
- {
71
- "action": "upscale1",
72
- "image_id": "grid-image-id"
73
- }
74
- ```
75
-
76
- **Available actions:**
77
- - `upscale1`–`upscale4`: Upscale individual quadrant
78
- - `variation1`–`variation4`: Create variation of a quadrant
79
- - `variation_subtle` / `variation_strong`: Subtle/strong variation of full image
80
- - `reroll`: Re-generate with same prompt
81
- - `zoom_out_2x` / `zoom_out_1_5x`: Zoom out
82
- - `pan_left` / `pan_right` / `pan_up` / `pan_down`: Extend canvas
83
-
84
- ### 3. Edit an Image
85
-
86
- Modify an existing image using a text prompt, optionally with a mask.
87
-
88
- ```json
89
- POST /midjourney/edits
90
- {
91
- "image_url": "https://example.com/photo.jpg",
92
- "prompt": "add a rainbow in the sky",
93
- "mode": "fast"
94
- }
95
- ```
96
-
97
- ### 4. Blend Images
98
-
99
- Combine 2–5 images into a new composition.
100
-
101
- ```json
102
- POST /midjourney/imagine
103
- {
104
- "action": "blend",
105
- "image_urls": [
106
- "https://example.com/image1.jpg",
107
- "https://example.com/image2.jpg"
108
- ]
109
- }
110
- ```
111
-
112
- ### 5. Describe an Image (Reverse Prompt)
113
-
114
- Get AI-generated text descriptions of an image (returns 4 options).
115
-
116
- ```json
117
- POST /midjourney/describe
118
- {"image_url": "https://example.com/photo.jpg"}
119
- ```
120
-
121
- ### 6. Generate Video from Image
122
-
123
- Create a video with a reference image and text prompt.
124
-
125
- ```json
126
- POST /midjourney/videos
127
- {
128
- "image_url": "https://example.com/photo.jpg",
129
- "prompt": "the city comes alive with moving traffic",
130
- "resolution": "720p"
131
- }
132
- ```
133
-
134
- ## Prompt Parameters
135
-
136
- Append these to your prompt text:
137
-
138
- | Parameter | Example | Description |
139
- |-----------|---------|-------------|
140
- | `--ar` | `--ar 16:9` | Aspect ratio |
141
- | `--v` | `--v 7` | Midjourney version |
142
- | `--q` | `--q 2` | Quality (0.25, 0.5, 1, 2) |
143
- | `--s` | `--s 750` | Stylization (0–1000) |
144
- | `--c` | `--c 50` | Chaos/variety (0–100) |
145
- | `--no` | `--no text, watermark` | Negative prompt |
146
- | `--seed` | `--seed 12345` | Reproducible generation |
147
-
148
- ## API-Level Parameters (Billing Impact)
149
-
150
- These top-level fields on `POST /midjourney/imagine` affect billing and are separate from inline prompt parameters:
151
-
152
- | Parameter | Type | Description |
153
- |-----------|------|-------------|
154
- | `version` | string | Midjourney version (`"8"`, `"7"`, `"6.1"`, etc.) — used for billing calculation |
155
- | `hd` | boolean | Enable HD 2K resolution (V8 only) — costs 4× GPU time |
156
- | `quality` | string | Quality level: `".25"`, `".5"`, `"1"`, `"2"`, `"4"` — quality `"4"` is V8 only and costs 4× GPU time |
157
- | `style_reference` | boolean | Whether prompt uses `--sref` style references (V8: costs 4× GPU time) |
158
- | `moodboard` | boolean | Whether prompt uses moodboard image references (V8: costs 4× GPU time) |
159
-
160
- ## Gotchas
161
-
162
- - Imagine returns a **2x2 grid** — use upscale/variation actions to work with individual images
163
- - Use `split_images: true` to also receive individual cropped images alongside the grid
164
- - Prompt parameters (`--ar`, `--v`, etc.) go **inside the prompt string**, not as separate fields
165
- - `translation: true` auto-translates Chinese/other languages to English before sending to Midjourney
166
- - Video generation requires a reference `image_url` — it cannot generate from text alone
167
- - Available transform actions depend on the image — check `available_actions` in the response
168
- - Get the seed with `POST /midjourney/seed` using the image_id for reproducible results
169
-
170
- > **MCP:** `pip install mcp-midjourney` | Hosted: `https://midjourney.mcp.acedata.cloud/mcp` | See [all MCP servers](../_shared/mcp-servers.md)