# wan2.7-image

Canonical page: https://agentsky.dev/models/wan/wan2.7-image
JSON record: https://gateway.agentsky.dev/v1/endpoints/wan/wan2.7-image

wan2.7-image is a image model from Wan, callable through the AgentSky API as wan/wan2.7-image and billed per image ($0.0215).

Generate one to four images. If the run is RUNNING, poll GET /v1/runs/{runId} until COMPLETED. This endpoint supports text prompts only; image editing and image sets are not accepted. Billing uses the number of successfully generated images reported by the provider. Availability requires a configured Singapore workspace.

After COMPLETED, image URLs are in run.output.output.choices[].message.content[].image; the image count is run.output.usage.image_count. Download these temporary URLs promptly. Outputs include a watermark.

## Facts

- **Id:** `wan/wan2.7-image`
- **Provider:** Wan
- **Kind:** image model
- **Billed per:** image
- **Timeout:** 5 min
- **Max request:** 1 MB
- **Character limit:** prompt up to 5,000

## Call it

`POST https://gateway.agentsky.dev/v1/run` with `{"provider": "wan", "endpoint": "wan2.7-image", "input": {…}}` and `Authorization: Bearer $AGENTSKY_API_KEY`.

### Input fields

| Field | Type | Required | Description |
|---|---|---|---|
| `prompt` | string | yes | What to draw, in Chinese or English. Max: 5000. |
| `n` | integer | no | Requested image count. Default: 1. Min: 1. Max: 4. |
| `size` | string | no | Output resolution; text-only images are square. Allowed: 1K, 2K. Default: "2K". |

## Example

### curl

```bash
# Set once per job; reuse this key and the identical body after a timeout.
: "${AGENTSKY_IDEMPOTENCY_KEY:?Set a unique, saved key for this job}"
curl --fail-with-body https://gateway.agentsky.dev/v1/run \
  -H "authorization: Bearer $AGENTSKY_API_KEY" \
  -H "content-type: application/json" \
  -H "Idempotency-Key: $AGENTSKY_IDEMPOTENCY_KEY" \
  -d '{"provider":"wan","endpoint":"wan2.7-image","input":{"prompt":"A blue ceramic cup on a white table","n":1,"size":"1K"}}'
# If status is READY/RUNNING, poll GET /v1/runs/{runId} with the same API key.
```

### JavaScript

```js
// Node.js 20+. Save one unique key per job; reuse it with the identical body.
const key = process.env.AGENTSKY_IDEMPOTENCY_KEY;
if (!key || !process.env.AGENTSKY_API_KEY) {
  throw new Error("Set AGENTSKY_API_KEY and a saved AGENTSKY_IDEMPOTENCY_KEY first");
}
const base = "https://gateway.agentsky.dev";
const headers = {
  authorization: `Bearer ${process.env.AGENTSKY_API_KEY}`,
  "content-type": "application/json",
  "Idempotency-Key": key,
};
const body = JSON.stringify({
  "provider": "wan",
  "endpoint": "wan2.7-image",
  "input": {
    "prompt": "A blue ceramic cup on a white table",
    "n": 1,
    "size": "1K"
  }
});
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function request(path, options = {}) {
  for (let attempt = 0; attempt < 3; attempt++) {
    let response;
    let data;
    try {
      response = await fetch(base + path, {
        ...options, headers, signal: AbortSignal.timeout(180_000),
      });
      const text = await response.text();
      try { data = JSON.parse(text); } catch { /* Proxy timeouts may return HTML. */ }
    } catch (error) {
      if (attempt === 2) throw error; // Resume with the same key and body.
      await sleep(2000);
      continue;
    }
    // Failed runs can have non-2xx HTTP status. Never regenerate them automatically.
    if (data?.runId && data?.status) return data;
    if (response.ok && data) throw new Error("Unexpected response: missing runId/status");
    if (attempt < 2 && (response.status >= 500 || response.status === 408 || response.status === 429)) {
      await sleep(2000);
      continue; // POST retries keep the original key and body.
    }
    throw new Error(`HTTP ${response.status}: ${JSON.stringify(data ?? "Non-JSON response; retry with the same key and body")}`);
  }
}

let run = await request("/v1/run", { method: "POST", body });
console.log("Save runId:", run.runId);
const deadline = Date.now() + 30 * 60_000;
while (["READY", "RUNNING"].includes(run.status)) {
  if (Date.now() >= deadline) {
    throw new Error(`Still pending: poll /v1/runs/${run.runId} later; do not create a new job`);
  }
  await sleep(5000);
  run = await request(`/v1/runs/${encodeURIComponent(run.runId)}`);
}
console.log("Status:", run.status, "Charge:", run.price?.amount);
if (run.status !== "COMPLETED") {
  throw new Error(`Run ${run.runId}: ${JSON.stringify(run.error ?? run.status)}`);
}
console.log(run.output); // Output shape depends on the endpoint.
// READY/RUNNING price is provisional. Use the terminal receipt for reconciliation.
```

## Pricing

$0.0215 per image

Read the final customer charge from price.amount.value after the run finishes. A RUNNING response is not the final bill.

## Questions

### What does wan2.7-image do?

Generate one to four images. If the run is RUNNING, poll GET /v1/runs/{runId} until COMPLETED. This endpoint supports text prompts only; image editing and image sets are not accepted. Billing uses the number of successfully generated images reported by the provider. Availability requires a configured Singapore workspace.

### How much does wan2.7-image cost?

$0.0215 per image, using your plan when signed in. The customer charge is returned as price.amount.value with its currency; READY/RUNNING values are provisional.

### What does wan2.7-image take?

prompt is required; the rest of the fields are optional. Send them in the input object of a POST to /v1/run.

### What are the limits on wan2.7-image?

The catalog lists an execution timeout of 5 min. A request can return READY or RUNNING before completion; poll GET /v1/runs/{runId} with the same API key until it reaches a terminal status. An HTTP timeout does not prove the generation stopped.

### How do I retry safely and check the final charge?

Save a unique Idempotency-Key for each job. After a network error or HTML timeout, resend the identical POST body with the same key and API key to recover the existing run, then poll its runId. A new key can start another billable generation. An HTTP error or timeout does not establish the run outcome or final charge. Pending runs can still settle; check the terminal price.amount and billing state, and retain the runId for reconciliation. Do not automatically create a new job after a terminal failure.
