k6 2.3 --once and --scenario: smoke-test the Sume API without spend
Use k6 --once and --scenario for a Sume API smoke test that costs nothing: models, a deliberate 400, and a paced balance read that counts 429s.

Write one k6 script with two named scenarios, then run k6 run --once --scenario smoke sume.js for a single-pass check and k6 run --scenario pace sume.js to watch rate limiting. Both stay on free routes: the model list, a request that Sume rejects with a 400, and the balance read. No video job is created, so no credits are reserved.
k6 v2.3.0 added both flags. The release notes say --scenario selects named scenarios without editing the script, and --once runs a script one time while keeping the scenario's own function. A v1.8.2 security patch followed on 2026-10-12. I ran everything below on k6 v2.3.0 against a local stand-in for the API.
What belongs in a Sume smoke test?
Three cheap facts that fail loudly when a deploy or a key is wrong. The model list is GET /v1/videos/models, and it should include the id your code pins. A request with size must come back 400 unsupported_parameter, because the Video Generation guide says every model reports supported_sizes: null and the API rejects the field. That proves auth, routing and validation without a generation. The balance read, GET /v1/balance, returns data.balance.available_credits per the usage docs.
| Call | Expected | Proves |
|---|---|---|
GET /v1/videos/models | 200, pinned id in data | Key works, model not retired |
POST /v1/videos with size | 400 unsupported_parameter | Validation runs before any job |
GET /v1/balance | 200 or 429 | Reads work, limits are visible |
How do you write the script?
Declare the scenarios and thresholds first. Each scenario names an exported function with exec.
import http from "k6/http";
import { check } from "k6";
import { Counter } from "k6/metrics";
const BASE = __ENV.SUME_BASE || "https://api.sume.com";
const auth = { headers: { Authorization: `Bearer ${__ENV.SUME_API_KEY}`, "Content-Type": "application/json" } };
const limited = new Counter("sume_429");
export const options = {
scenarios: {
smoke: { executor: "shared-iterations", vus: 1, iterations: 1, exec: "smoke" },
pace: { executor: "constant-arrival-rate", rate: 5, timeUnit: "1s", duration: "20s", preAllocatedVUs: 5, exec: "pace" },
},
thresholds: { checks: ["rate==1"], http_req_failed: ["rate==0"] },
};The smoke function has one trap. k6 counts any 4xx as a failed request by default, so the deliberate 400 would trip a http_req_failed threshold. Pass responseCallback: http.expectedStatuses(400) on that one request; in a run without it, the failure rate was 33% and the threshold failed.
export function smoke() {
const models = http.get(`${BASE}/v1/videos/models`, auth);
check(models, { "models 200": (r) => r.status === 200, "wan listed": (r) => r.json("data").some((m) => m.id === "wan-3.0") });
const bad = http.post(`${BASE}/v1/videos`, JSON.stringify({ model: "wan-3.0", prompt: "x", size: "1280x720" }),
{ ...auth, responseCallback: http.expectedStatuses(400) });
check(bad, { "size is a 400": (r) => r.status === 400 && r.json("error.code") === "unsupported_parameter" });
}How do you watch rate limits without breaking the run?
The errors page says responses can include ratelimit-limit, ratelimit-remaining, ratelimit-reset and retry-after, and that a 429 means back off. It does not publish a request rate, so start low and raise it. The pace scenario treats 200 and 429 as expected, counts the 429s in a custom metric, and checks that every limited reply carries retry-after:
export function pace() {
const r = http.get(`${BASE}/v1/balance`, { ...auth, responseCallback: http.expectedStatuses(200, 429) });
if (r.status === 429) limited.add(1);
check(r, { "200 or 429 with retry-after": (x) => x.status === 200 || x.headers["Retry-After"] !== undefined });
}Run it with SUME_API_KEY=... k6 run --scenario pace sume.js. A rising sume_429 count tells you where your workspace's free-read ceiling sits; pace production polling below it.
What does --once refuse to do?
--once runs a single scenario. With both scenarios in the file, k6 run --once sume.js stops with the error --once can run only with a single scenario. Add --scenario smoke and it runs the smoke function once. Keep load scenarios away from POST /v1/videos: each accepted job reserves credits. Test the submit path with the validation 400 here, and with a single real job in CI.
Sources
Related posts
More in Developers
- Kling 4 or Vidu Q4 on Sume: model_not_found and what to call
Sume has no kling-4 or vidu-q4 row, so a submit with those ids fails with 404 model_not_found. Call kling-3, wan-3.0 or sume/auto. Read 2026-10-13.
- Launch-day checklist before moving off kling-3 on Sume
Before you swap kling-3 for a newer Kling or another family, check the catalog, 1080p-only output, 4-15 s, no references, and ratio support. Read 2026-10-13.
- LinkedIn Posts API video post body: the fields and a Python builder
A LinkedIn video post needs author, commentary, visibility, distribution, a urn:li:video media id and lifecycleState PUBLISHED. Version 202510 sunsets Oct 15.
- LinkedIn video post stuck in processing: two status ladders to poll
LinkedIn tracks a video asset (PROCESSING, AVAILABLE) and a post (PUBLISH_REQUESTED, PUBLISHED) separately. Poll both, then compare with a Sume job poll.
Written by Sume