---
name: mika-image
description: Generate images through MIKA (mikaart.top) — text-to-image and image-to-image via an API key. Requires MIKA_API_KEY.
---

# MIKA image generation

MIKA is an image generation service at `https://mikaart.top`. This skill turns a natural-language
drawing request into a real image file on disk, billed in MIKA credits.

Use `scripts/mika.mjs` for everything. Do not hand-roll `curl` calls against the API: a generation
takes 30 seconds to 3 minutes, and driving submit/poll/download from the model directly leads to
duplicate submissions (double charges), mistaking an in-progress state for a failure, and blowing up
the context with large responses. The script collapses all of that into one command and one JSON object.

The script has zero dependencies and needs Node 18 or newer. It works the same in PowerShell, Git
Bash and any POSIX shell because it is invoked as `node`, not as a shell script.

## 1. Setup

The script needs one credential: `MIKA_API_KEY`, a string starting with `mk_live_`.

It looks in this order:

1. the `MIKA_API_KEY` environment variable;
2. `~/.mika/config.json` containing `{"apiKey":"mk_live_..."}` — create it with mode `600`.

If neither exists the script exits `2` with code `NO_API_KEY`. When that happens:

- Tell the user to sign in at `https://mikaart.top`, open **Agent API** in the sidebar, and generate a
  key. On mobile the entry is under the **More** menu.
- The key is displayed exactly once. If the user lost it, they must revoke it and create a new one.
- Ask them to put it in `.env` or `~/.mika/config.json` **themselves**.
- Never guess a key, never try to sign in with the user's site password, never ask for it in chat, and
  never echo it back. The script strips the key from every message it prints; keep it that way in your
  own output too.

Two optional variables: `MIKA_API_BASE` overrides the API base URL (default `https://mikaart.top`).

**First run convention.** Before the first generation in a session, run:

```bash
node scripts/mika.mjs models
```

This confirms the key works and returns every parameter value you are allowed to use. Report the
tiers, ratios, per-task image count and credit prices back to the user in one short sentence.

## 2. Parameters

Every parameter value comes from `GET /api/v1/models` — that is what the `models` command prints.

**Do not hardcode model keys, tier names, aspect ratios, quality values, pixel sizes or credit
prices.** They are account- and model-specific and they change without an API version bump. A price
you remember from an earlier session may already be wrong, and quoting a stale price to the user is
worse than saying you will check.

The `models` response gives you, per model:

| Field | What it tells you |
|---|---|
| `modelKey` | what to pass as `--model`; omit the flag to use the first capable model |
| `supportsGeneration` / `supportsEdit` | whether `generate` / `edit` is allowed at all |
| `supportsTransparent` | whether `--transparent` / `transparent: true` is accepted (treat a missing field as false) |
| `tiers[].tier` | the resolution tiers, e.g. `1K`, `2K`, `4K` |
| `tiers[].points` | **credits charged per image at that tier** — read this, never assume it |
| `tiers[].ratios[]` | each ratio with the `width` and `height` the server will **ask the provider for** |
| `qualities` | accepted `--quality` values; omit the flag to let the server pick |
| `constraints` | `minWidth`/`maxWidth`/`minHeight`/`maxHeight`/`step`/`maxPixels` for custom sizes |
| `maxReferences` | how many reference images `edit` accepts |
| `limits.maxCountPerTask` | hard cap on images per task |
| `limits.maxConcurrent` | how many tasks may run at once (usually 1) |
| `limits.dailyTasksRemaining` | tasks left today across the site, key and account budgets |

Rules worth knowing up front:

- Prefer `--tier` + `--ratio`. The server resolves the pixel size, so the tier you ask for, the tier
  you are routed to and the tier you are billed for are guaranteed to agree.
- **The `width`/`height` you see — in `models`, in the task response and in `assets[]` — is the size
  we request from the provider, not a measurement of the file you get.** Measured on 2026-10-13 across
  60 stored images: `4K` `1:1` came back exactly 2880x2880, but `1K` `1:1` came back 1254x1254 and
  `2K` `3:4` came back 1086x1448 — the provider answers with its own pixel budget (about 1.57 Mpx for
  those cases) while keeping the aspect ratio you asked for. So: never quote an API dimension as if it
  were the file's dimension. Measure the downloaded file (`sharp`, `file`, or your own decoder) when
  the exact size matters, and tell the user what you measured.
- Because of the above, **`2K` does not reliably buy more pixels than `1K`; `4K` does** (measured:
  2880x2880 versus a common ~1.57 Mpx for both lower tiers). If the user wants a genuinely larger or
  sharper image and you are about to charge them the higher tier, say what each tier actually cost and
  delivered in your own run rather than repeating the advertised numbers.
- `--width`/`--height` is for custom sizes only, must be a multiple of `constraints.step`, must fit
  inside `maxPixels`, and cannot be combined with `--tier`/`--ratio`. Billing still follows the area,
  so a custom size can land in a more expensive tier than it looks like.
- **A task whose area exceeds 3,000,000 pixels carries at most one image; otherwise at most two.**
  This is the single most common cause of `INVALID_IMAGE_COUNT`. The script checks it locally before
  spending anything.
- `limits.maxConcurrent` is normally 1. Submitting while a previous task still runs returns
  `ACTIVE_TASK_EXISTS`. Wait for it; do not fire a second one.
- The daily budget resets at the **UTC** day boundary (08:00 Beijing time). Quota errors carry
  `resetsAt` — convert that timestamp instead of guessing.

Tell the user the credit cost **before** you run a generation. The script prints an estimate to
stderr and includes `estimate` and `reservedPoints` in its JSON; use the real number from
`reservedPoints` once the task is accepted.

## 3. Commands

All output is a single JSON object on stdout. Progress messages go to stderr. Parse stdout only.

### `models` — parameter discovery and key self-check

```bash
node scripts/mika.mjs models
```

Returns the full model list plus the current credit balance. Run this first in every session.

### `balance` — credits

```bash
node scripts/mika.mjs balance
```

Returns `available` (spendable now) and `reserved` (held by queued or running tasks).

### `generate` — text to image

```bash
node scripts/mika.mjs generate \
  --prompt "A 1970s screen-printed poster of an atlas silk scarf, halftone dots, paper grain" \
  --tier 2K --ratio 3:4 --quality high --out poster.png
```

Useful flags: `--model`, `--count`, `--width`/`--height` (instead of tier+ratio), `--transparent`
(only on models with `supportsTransparent: true`; the alpha channel lives in the `original` variant),
`--request-key`,
`--max-wait` (seconds, default 300), `--no-wait`, `--variant original|preview|thumb`.

The script checks the balance and the price before submitting, uploads nothing, submits with an
`Idempotency-Key`, polls until the task is terminal, downloads every resulting image, and prints one
JSON object with `taskId`, `settledPoints`, `creditsAfter` and a `files[]` array of absolute paths.

`--out` defaults to `./mika-<taskId>-<index>.png` in the current directory. With several images and an
explicit `--out`, an `-<index>` suffix is inserted before the extension.

`--no-wait` submits and returns immediately with the `taskId`.

### `edit` — image to image

```bash
node scripts/mika.mjs edit \
  --image reference.png \
  --prompt "Keep the exact pose and composition, change the fabric to indigo block print" \
  --tier 1K --ratio 1:1 --out variant.png
```

`--image` is repeatable up to `maxReferences`. The script performs the whole three-step upload
internally: request a credential, send the raw bytes, then submit the task with the returned
`referenceIds`. Reference images must be PNG, JPEG or WebP; the server identifies them by their magic
bytes, so a renamed file of another type is rejected. Uploaded references expire 24 hours later.

### `status` — poll a task you already submitted

```bash
node scripts/mika.mjs status --task-id <taskId>
```

Use this after `--no-wait`, or after a run that returned `status: "running"` because the wait budget
ran out. This is a recovery path, not a failure: the task is still going.

### `download` — fetch an asset again

```bash
node scripts/mika.mjs download --asset-id <assetId> --out shot.png --variant original
```

Use it when a generation succeeded but writing the file failed, or to fetch a lighter derivative.

## 4. Error handling

### Read `retryable`, not the HTTP status

This API returns 409 for both "you got it wrong" and "our side broke", so the status code alone does
not tell you whether retrying makes sense. Every error body carries `error.retryable`. The script
already turns that into an exit code.

### Exit codes

| Exit | Meaning | What to do |
|---|---|---|
| `0` | Success, or still running after the wait budget | Report the file paths, or poll with `status` |
| `2` | Usage error, bad parameter, missing key, or a class A rejection | Fix the input. Retrying unchanged is pointless |
| `3` | Not enough credits | Tell the user the required and available amounts and where to top up |
| `4` | Rate limited or daily quota exhausted | Wait for `resetsAt`, then retry once with the **same** `--request-key` |
| `5` | Server, upstream, moderation or circuit-breaker failure | Retryable: at most twice, same `--request-key`, backoff 30s or more |
| `6` | `TASK_NEEDS_REVIEW` | **Never retry.** See below |

### The four error classes

**A — your input.** `INVALID_PROMPT`, `INVALID_TIER`, `INVALID_RATIO`, `INVALID_IMAGE_COUNT`,
`INVALID_QUALITY`, `INVALID_DIMENSION_STEP`, `MAX_PIXELS_EXCEEDED`, `TOO_MANY_REFERENCES`,
`REFERENCES_TOO_LARGE`, `UNSUPPORTED_IMAGE_TYPE`, `IMAGE_DECODE_FAILED`, `CONTENT_BLOCKED`,
`MODEL_CAPABILITY_UNAVAILABLE`, `TRANSPARENT_NOT_SUPPORTED`, `IDEMPOTENCY_KEY_CONFLICT`. Nothing was charged. Fix the request and
relay the reason to the user in plain language.

**B — quota or balance.** `INSUFFICIENT_CREDITS` (carries `required` and `available`),
`OPEN_API_DAILY_LIMIT`, `TASKS_DAILY_LIMIT`, `RATE_LIMITED` (carries `Retry-After`). Nothing was
charged. Tell the user when the quota resets, converted to their time zone from `resetsAt`.

**C — our side.** `PROVIDER_DISABLED`, `MODEL_PROVIDER_UNAVAILABLE`, `GLOBAL_CONCURRENCY_LIMIT`,
`ACTIVE_TASK_EXISTS`, `SITE_BUSY`, `OPEN_API_DISABLED`, `MODERATION_UNAVAILABLE`, `UPSTREAM_FAILED`,
`MEDIA_DISK_SPACE_LOW`, `MEDIA_CAPACITY_EXCEEDED`, 5xx. Reserved credits were either never taken or
fully refunded. Retry at most twice under the same `--request-key` with a backoff of 30 seconds or
more. `OPEN_API_DISABLED` and `SITE_BUSY` will not clear by retrying quickly — tell the user instead.

**D — result uncertain.** `TASK_NEEDS_REVIEW`, exit code `6`. The upstream provider gave an
ambiguous answer, so the system refuses to guess: **the reserved credits stay frozen, and the task is
neither refunded nor resubmitted automatically.** Retrying would risk paying the upstream twice for
one image. Tell the user: the result is under manual review, the credits are frozen, and they should
contact `support@mikaart.top` quoting the `taskId` and the `requestId`. Then stop.

### Polling discipline

The script polls every 8 seconds, backing off to at most 15 seconds, for a total budget of
`--max-wait` (default 300 seconds). 3 consecutive failed status queries abort the run with a
class C error. Exceeding the budget is **not** a failure: you get `{"status":"running","taskId":...}`
and exit `0`, because treating it as a failure invites a resubmission that collides with the task
still running. Continue with `status --task-id`.

Status polling is exempt from the per-IP request gate, so this cadence does not eat anti-abuse quota.

### Download immediately

Generated images expire **48 hours** after creation; uploaded references expire after 24 hours. After
that the asset id returns 404 `ASSET_NOT_AVAILABLE` and the only way to get the image back is to
generate it again — which costs credits again. A URL is not a deliverable. `generate` and `edit`
download for you; hand the user the local file path, not the link.

`--variant original` (the default) is what the credit price pays for. `preview` (1536px WebP) and
`thumb` (256px WebP) are lighter derivatives for a quick look only — never present them as the result
when the user paid for a higher tier.

### Support

Every `/api/v1` error carries a `requestId` that maps one-to-one to a server-side audit row. When
something looks wrong — a charge you cannot explain, a `CONTENT_BLOCKED` you believe is a false
positive, a task stuck in review — give the user `support@mikaart.top` together with the `requestId`
and the `taskId`.

## 5. Prompt craft for MIKA

MIKA sends your prompt to the image model as-is. There is no translation and no rewriting on this
API, so the prompt you write is the prompt the model sees. These rules are specific to how this
pipeline behaves; they are not generic advice.

### Structure

Build the prompt in this order and keep it one paragraph:

```
subject → scene/environment → lighting → composition & camera → style & medium → colour & mood → quality tags → aspect ratio
```

Example:

```
An elderly Uyghur atlas silk weaver at a wooden loom, workshop interior with dust in the air,
soft rim light from a high window on the left, overcast diffusion, 35mm lens at f/1.8, shallow
focus, three-quarter low angle, documentary photograph with visible film grain, muted earth
tones with one band of saturated ikat red, fine skin texture and individual thread fibres
```

Set the aspect ratio with `--ratio`, not by writing "16:9" into the prompt.

### Observable attributes, not verdicts

`cinematic`, `beautiful`, `stunning`, `high quality`, `8k`, `masterpiece`, `best quality` carry almost
no information and cost you prompt budget. Replace each one with something a camera or a material
could actually show:

| Instead of | Write |
|---|---|
| `cinematic lighting` | `soft rim light from the left, overcast diffusion, long shadow across the floor` |
| `highly detailed` | `visible warp and weft in the woven silk, metallic thread catching light` |
| `professional photo` | `35mm lens, f/1.8, shallow focus, three-quarter low angle` |
| `vintage style` | `1970s screen print, halftone dots, paper grain, slight misregistration` |
| `8k, ultra HD` | `intricate embroidery stitches, individual hair strands, skin pores, dust in the light shaft` |

This is not folklore. Two runs of the same subject — one built from verdict words, one from observable
attributes — came back visibly different: the verdict version was clean but generic, with airbrushed
skin and a simplified hat pattern, while the observable version carried real pore texture, separated
beard hairs, wool nap, a visible collar seam and the requested low three-quarter angle. Say the
material and the light, and you get them.

### Getting more detail

When the user asks for a finer, more detailed image, do two things — and note that the second one
matters more than the first:

1. Add **micro-detail nouns**: `intricate embroidery stitches`, `individual hair strands`, `skin
   pores`, `micro-texture on the leather`, `dust particles in the light shaft`, `fibres separating at
   the torn edge`.
2. Raise the tier — but know what each tier actually buys. Detail density comes from real pixels, not
   from keywords, and writing `8k` into a `1K` prompt changes nothing except your credit bill. In our
   own runs `4K` delivered real pixels (2880x2880) while `1K` and `2K` both came back at roughly the
   same ~1.57 Mpx, so `2K` is not a dependable upgrade step — see §2 on requested versus delivered
   size. Set `--quality high` as well.

Verified on a `4K` `1:1` still life: the model rendered individual satin stitches, separate pomegranate
arils and distinct hammer dents with per-dent reflections in the brass — the micro-detail nouns were
legible at full size, not smeared into texture. Expect a `4K` square to come back around 2880x2880 and
roughly 20 MB as PNG, so budget download bytes accordingly, and expect roughly a minute end to end
(`1K` and `2K` settled in about 35 seconds, `4K` in about 50).

Warn the user that a higher tier costs more credits — read the price from `models` and say the number.

### Text and typography

Wrap the exact wording in quotes: `the word "MIKA" in condensed grotesk, centred`.

Text is far more reliable than its reputation if you keep it sparse and large. One measured `2K` `3:4`
poster carried **three separate text blocks and all three came out letter-perfect**: `the word "MIKA"
in condensed grotesk capitals across the top third`, a middle line with the Uyghur greeting
`"ياخشىمۇسىز"` in bold naskh lettering reading right to left, and a bottom line of small caps reading
`"ATLAS SILK AND CERAMICS FESTIVAL"` — with the requested `1970s screen-print, halftone dots, paper
grain` treatment intact and no interference between the three. Arabic-script output was correct in
this run, so do not refuse a Uyghur or Arabic-script request on the assumption it cannot work.

What that sample did, and what to copy:

- one short phrase per block, not a paragraph;
- a large share of the frame for each block, with generous negative space around it;
- capitals or a bold display weight, and letter-spacing called out in words;
- the script named explicitly (`naskh calligraphic lettering reading right to left`), and the position
  of every block stated separately;
- `2K` or above with `--quality high`, so the glyph edges survive.

Still treat every text render as unverified until you have looked: errors are silent and the model will
not warn you. Open the downloaded file and read the text back before telling the user it is done. If it
is wrong, resubmit **once** under the same `--request-key` after tightening the wording — and for
client-critical typography, plan to set the type afterwards in a layout tool and use the image as the
background. Dense body copy, very small type and lowercase handwriting were not tested.

### Consistency and image-to-image

`edit` is how you keep a subject or a composition stable.

- Pass the previous result as `--image` and describe **only the delta**:
  `keep the exact pose, framing and background, change the fabric to indigo block print`.
- Adding an element is more reliable than removing one.
- Change one thing per run. Two simultaneous edits make it impossible to tell which one worked.
- To keep a character or product consistent across a series, reuse the same reference image and lock
  the composition in words every time.
- References are normalised server-side, so a very large source image is downscaled before the model
  sees it. Feed it a clean, well-lit source rather than a huge one.

The delta recipe was measured: given the portrait above as `--image` and asked to keep the pose,
framing, facial structure and camera while changing only the garment to `woven atlas silk with metallic
thread` and the light to `warm dusk sidelight from the right`, the result held the same three-quarter
low angle, the same head tilt and the same room, and changed exactly those two things. One caveat worth
stating to the user rather than hiding: the face is re-rendered, so the subject reads as the same type
of person but is not the same individual pixel-for-pixel. Lock composition with words; do not promise a
locked identity across a series. (Add-before-remove is our operating experience, not a controlled
result; the rest of the list above is measured.)

### What to avoid

- **Forty adjectives in one paragraph.** Past a handful of descriptors they start cancelling out.
- **Contradictory instructions.** They do not average out. A run asking for `minimalist flat vector
  icon of a fox` and `highly detailed maximalist baroque ornament … ultra realistic photograph` in the
  same prompt came back as a photoreal baroque fox head with the icon instruction silently discarded —
  no warning, no compromise, and the failure is invisible unless you look. Pick one style system per
  prompt.
- **Negations.** The model obeys `no X` weakly. Rewrite as a positive statement: not `no people` but
  `an empty street`.
- **Literal translations from Chinese.** Stiff word-for-word English reads as noise. Prefer the
  photographic or material vocabulary above: `留白` → `generous negative space`; `高级感` → a concrete
  material and lighting description; `氛围感` → `haze in the backlight, deep shadow falloff`.
- **Real people, brands and protected characters.** Named public figures, film and game characters,
  logos and trademarks are rejected by moderation — either ours (`CONTENT_BLOCKED`, nothing charged)
  or the provider's (`failureReason: content_policy`, refunded). Describe the archetype instead: not a
  named celebrity but `a weathered face, deep laugh lines, grey stubble`.
- **Retrying a content rejection unchanged.** Rephrase, then submit once.

### Choosing tier and ratio

| Job | Sensible starting point |
|---|---|
| Poster or cover | `2K` or `4K`, `3:4` or `4:3` |
| Wide banner or header | `1K`/`2K`, `16:9` or `21:10` |
| Social square | `1K`, `1:1` |
| Vertical video thumbnail | `1K`/`2K`, `9:16` |
| Detail study, texture, print | `4K`, the ratio of the subject |

Always confirm the exact ratio list from `models` — it is the authority, not this table. And remember
the count rule: above 3,000,000 pixels a task carries one image, so `4K` plus `--count 2` will be
rejected with `INVALID_IMAGE_COUNT`. Remember too that the tier label describes what you are billed,
not necessarily what is delivered — see §2 on requested versus delivered size.

### After a failure

| Symptom | Fix |
|---|---|
| `CONTENT_BLOCKED` or `failureReason: content_policy` | Remove named people, brands, graphic violence and nudity; describe the shape, material and mood abstractly instead. One rewrite, one retry |
| `MAX_PIXELS_EXCEEDED` / `INVALID_DIMENSION_STEP` | Stop using `--width`/`--height`; pick a `--tier` + `--ratio` preset from `models` |
| `INVALID_IMAGE_COUNT` | Drop `--count` to 1, or move to a smaller tier |
| `UNSUPPORTED_IMAGE_TYPE` / `IMAGE_DECODE_FAILED` | Re-export the reference as a clean PNG or JPEG; the server checks real bytes, not the file extension |
| `ACTIVE_TASK_EXISTS` | The previous task is still running. Poll it with `status` instead of submitting again |
| `UPSTREAM_FAILED` | Credits were refunded. Wait 30 seconds and retry once with the **same** `--request-key`. Do not rewrite the prompt — the prompt was not the problem |
| `MODERATION_UNAVAILABLE` | Nothing was created and nothing was charged. Wait and retry with the **same** `--request-key`: a run that failed this way succeeded minutes later on the identical key and was billed once |
| `TASK_NEEDS_REVIEW` | Nothing to fix. Credits are frozen for manual review. Tell the user and stop |
