Codex can run a local script that submits one Higgsfield video request, keeps its identity through a timeout, and saves the resulting file for you to watch. The useful result is the checked video on disk. A key, a successful POST, or a media link is only a step toward it.
This guide is for a reader with local Codex access, a permitted text prompt, Higgsfield API access to Seedance 2.0, and authority to approve one paid generation. It uses the documented text-to-video endpoint with five seconds, 720p, 16:9, and audio disabled. BIG CHANGE checked the primary documentation on 21 October 2026. We did not log into an account, submit a request, generate a video, inspect media, or measure a charge. The code below has only been checked offline.
The big change
- What changed: Higgsfield exposes Seedance 2.0 text-to-video through a submit, status, and media-download API. Codex can run a local script around that documented lifecycle.
- Why it matters: One saved request identity connects your approval to the eventual file. A lost response or failed download need not become a second paid generation.
- What to watch: Model access, the price for your exact account and settings, prompt rights, the terminal status, and the quality of the actual video all need your own checks. Documentation cannot establish any of those outcomes for your account.
1. Prepare the account and the one request
In the Higgsfield API console, check that your account can use Seedance 2.0 text-to-video. Read the current API price for five seconds, 720p, 16:9, audio off, your available balance, and any account-specific terms. Higgsfield's public pricing page shows changing model and promotional rates; a “from” rate is not a quote for your request. API billing is separate from any creator subscription. Set a maximum acceptable charge for exactly one generation before continuing.
Use a prompt that you have permission to submit and a result you are permitted to use. Higgsfield's privacy policy describes processing of prompts, inputs, and outputs for service operation and possible improvement; running the command locally does not keep the prompt local. Review the terms for input rights, likenesses, output responsibility, and any terms specific to your account. Do not assume exclusive output rights or automatic clearance for downstream use.
Create a new private local directory, for example video-job, outside a synced or shared repository. Put the approved text prompt in prompt.txt there. Create an empty video_job.py beside it and paste the complete script in step 2. Use Python 3.10 or newer; the script uses only the standard library. The directory will contain the prompt and a status record that may contain a signed media URL. Limit its filesystem access and do not commit or share it. In Codex, open that directory as the local project, inspect the script, and check the actual command's file and network permissions. Codex permission to run a command does not itself approve a paid generation.
Create an API credential in the console. Supply the complete copied value as HF_KEY in the local process environment. The REST header is Authorization: Key <complete copied value>; do not add Key inside the variable or split the value for this script. Do not paste the credential into Codex chat, prompt.txt, source code, logs, a committed .env file, or browser JavaScript. In PowerShell, a private session can prompt for it without echoing:
$env:HF_KEY = Read-Host 'Complete Higgsfield API key' -AsSecureString | ForEach-Object { [System.Net.NetworkCredential]::new('', $_).Password }That environment variable remains in the current shell and child processes until you remove it or close the shell. If your Codex command runs in a different shell, supply the secret through that environment's approved secret mechanism. Avoid letting the assistant read or print it.
2. Save the local one-request script
Save this as video_job.py. It writes the exact endpoint, body, and a UUID idempotency key before any POST. init has no network side effect. submit is the paid boundary and requires a separate, literal authorization flag. replay is only for an uncertain submission: it resends the same saved endpoint, body, and key. resume polls a saved request, and download retrieves its completed media without an API credential on the media request.
import argparse
import hashlib
import json
import os
import pathlib
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
import uuid
from datetime import datetime, timezone
STATE = pathlib.Path("request-private.json")
ENDPOINT = "https://api.higgsfield.ai/bytedance/seedance-2.0/text-to-video"
TERMINAL = {"completed", "failed", "nsfw", "canceled"}
STATUSES = TERMINAL | {"queued", "in_progress"}
class APIHTTPError(RuntimeError):
def __init__(self, status):
self.status = status
super().__init__(f"API HTTP {status}; inspect account/request before retry")
def now():
return datetime.now(timezone.utc).isoformat()
def save(state):
# Exclusive first write; atomic replacement thereafter. Keep this directory private.
temp = STATE.with_name(STATE.name + ".tmp")
with open(temp, "x", encoding="utf-8") as f:
json.dump(state, f, indent=2, ensure_ascii=False)
f.flush()
os.fsync(f.fileno())
if os.name != "nt":
os.chmod(temp, 0o600)
os.replace(temp, STATE)
def load():
if STATE.with_name(STATE.name + ".tmp").exists():
raise ValueError("Interrupted save: reconcile request-private.json and its .tmp file locally before proceeding")
with open(STATE, encoding="utf-8") as f:
state = json.load(f)
if state["endpoint"] != ENDPOINT:
raise ValueError("Saved endpoint differs from this recipe; stop and inspect")
return state
def key():
value = os.environ.get("HF_KEY", "")
if not value or value.startswith("Key "):
raise ValueError("Set HF_KEY to the complete copied credential only")
return value
def api_request(method, url, body=None, idem=None):
headers = {"Authorization": "Key " + key(), "Accept": "application/json"}
if body is not None:
headers["Content-Type"] = "application/json"
if idem:
headers["Idempotency-Key"] = idem
request = urllib.request.Request(url, data=body, headers=headers, method=method)
# A redirect must not carry the Authorization header to another host.
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, newurl):
raise ValueError("API redirected; stop before forwarding credential")
opener = urllib.request.build_opener(NoRedirect)
try:
with opener.open(request, timeout=30) as response:
raw = response.read()
correlation = response.headers.get("X-Request-ID") or response.headers.get("X-Correlation-ID")
result = json.loads(raw)
if not isinstance(result, dict):
raise ValueError("API response was not an object")
return result, correlation
except urllib.error.HTTPError as e:
# Do not log response bodies or the credential. Preserve state for diagnosis.
raise APIHTTPError(e.code) from None
def checked_status_url(state):
url = state["status_url"]
parsed = urllib.parse.urlparse(url)
expected = "/requests/" + urllib.parse.quote(state["request_id"], safe="") + "/status"
if (parsed.scheme, parsed.netloc, parsed.path, parsed.query, parsed.fragment) != (
"https", "api.higgsfield.ai", expected, "", ""
):
raise ValueError("Unexpected status URL; stop before sending the credential")
return url
def init():
if STATE.exists() or STATE.with_name(STATE.name + ".tmp").exists():
raise ValueError("Existing request state; never initialize over it")
prompt = pathlib.Path("prompt.txt").read_text(encoding="utf-8").strip()
if not prompt:
raise ValueError("prompt.txt is empty")
body = {"prompt": prompt, "duration": 5, "resolution": "720p",
"aspect_ratio": "16:9", "generate_audio": False}
state = {"endpoint": ENDPOINT, "body": body, "idempotency_key": str(uuid.uuid4()),
"prepared_at_utc": now(), "phase": "prepared", "request_id": None,
"status_url": None, "status": None, "correlation_id": None}
save(state)
print("Prepared one request in request-private.json. Inspect it and the account price.")
def post(state, replay=False):
if replay:
if state["request_id"] or state["phase"] not in {"transmitting", "uncertain"}:
raise ValueError("Replay only an uncertain submission with no saved request ID")
elif state["phase"] != "prepared":
raise ValueError("Already submitted or uncertain; use resume or replay")
if not replay:
state["phase"] = "transmitting"
state["first_post_at_utc"] = now()
save(state)
wire = json.dumps(state["body"], separators=(",", ":"), ensure_ascii=False).encode("utf-8")
try:
result, correlation = api_request("POST", state["endpoint"], wire,
state["idempotency_key"])
request_id = result.get("request_id")
status_url = result.get("status_url")
if not isinstance(request_id, str) or not request_id or not isinstance(status_url, str):
raise ValueError("Acceptance lacked request_id or status_url")
state["request_id"] = request_id
state["status_url"] = status_url
checked_status_url(state)
state["correlation_id"] = correlation
state["status"] = result.get("status")
state["phase"] = "accepted"
state["accepted_at_utc"] = now()
save(state)
print("Accepted request_id:", request_id, "— run resume")
except Exception as e:
state["phase"] = "rejected" if isinstance(e, APIHTTPError) and e.status < 500 else "uncertain"
state["last_uncertain_at_utc"] = now()
save(state)
raise
def resume(state):
if not state["request_id"]:
raise ValueError("No request ID. Inspect state; replay the identical intent only if POST may have reached the API")
url = checked_status_url(state)
deadline = time.monotonic() + 15 * 60
delay = 2.0
while time.monotonic() < deadline:
try:
result, correlation = api_request("GET", url)
except APIHTTPError as e:
if e.status < 500:
raise
print("Status read failed:", str(e), "— request identity retained")
time.sleep(delay)
delay = min(delay * 1.5, 10.0)
continue
except (urllib.error.URLError, TimeoutError) as e:
print("Status read failed:", str(e), "— request identity retained")
time.sleep(delay)
delay = min(delay * 1.5, 10.0)
continue
status = result.get("status")
if result.get("request_id") != state["request_id"] or status not in STATUSES:
raise ValueError("Malformed or mismatched status response; retained request ID, stop and inspect")
state["status"] = status
state["last_status_at_utc"] = now()
state["correlation_id"] = correlation or state.get("correlation_id")
if status == "completed":
video = result.get("video")
media_url = video.get("url") if isinstance(video, dict) else None
state["video_url"] = media_url if isinstance(media_url, str) and media_url else None
save(state)
print("Status:", status)
if status in TERMINAL:
if status == "completed" and not state.get("video_url"):
print("Completed without video.url; inspect the request before any new generation")
return
time.sleep(delay)
delay = min(delay * 1.5, 10.0)
print("Polling deadline reached; generation may still be running. Run resume later.")
def download(state):
if state.get("status") != "completed" or not state.get("video_url"):
raise ValueError("Need completed status and video.url; use resume")
url = state["video_url"]
parsed = urllib.parse.urlparse(url)
if parsed.scheme != "https" or not parsed.hostname:
raise ValueError("Unexpected media URL; stop")
target = pathlib.Path("video-output.mp4")
part = pathlib.Path("video-output.mp4.part")
if target.exists() or part.exists():
raise ValueError("Output or partial file exists; inspect it and retain any confirmed incomplete .part copy before retrying download")
# Deliberately no Authorization header on this request, including redirects.
class HTTPSRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, newurl):
if urllib.parse.urlparse(newurl).scheme != "https":
raise ValueError("Media redirected outside HTTPS")
return super().redirect_request(req, fp, code, msg, newurl)
opener = urllib.request.build_opener(HTTPSRedirect)
request = urllib.request.Request(url, headers={"User-Agent": "video-job/1"})
try:
with opener.open(request, timeout=60) as response, open(part, "xb") as out:
final_url = urllib.parse.urlparse(response.geturl())
if final_url.scheme != "https":
raise ValueError("Media redirected outside HTTPS")
while True:
chunk = response.read(1024 * 1024)
if not chunk:
break
out.write(chunk)
out.flush()
os.fsync(out.fileno())
except Exception:
print("Download interrupted; inspect the partial file. Do not generate again.")
raise
if part.stat().st_size == 0:
raise ValueError("Downloaded zero bytes; inspect media URL and retry download only")
os.replace(part, target)
hasher = hashlib.sha256()
with open(target, "rb") as saved:
for chunk in iter(lambda: saved.read(1024 * 1024), b""):
hasher.update(chunk)
digest = hasher.hexdigest()
state["download"] = {"file": str(target), "bytes": target.stat().st_size,
"sha256": digest, "url_host": parsed.hostname,
"downloaded_at_utc": now()}
save(state)
print("Saved:", target, "bytes:", target.stat().st_size, "SHA-256:", digest)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("action", choices=["init", "submit", "replay", "resume", "download"])
parser.add_argument("--authorize-paid-one", action="store_true")
args = parser.parse_args()
if args.action == "init":
init()
return
state = load()
if args.action == "submit":
if not args.authorize_paid_one:
raise ValueError("One paid generation needs --authorize-paid-one")
post(state)
elif args.action == "replay":
post(state, replay=True)
elif args.action == "resume":
resume(state)
else:
download(state)
if __name__ == "__main__":
try:
main()
except Exception as e:
print(type(e).__name__ + ": " + str(e), file=sys.stderr)
sys.exit(1)The status record is a recovery journal, not a public artifact. Review it without exposing the prompt or URL. A request-private.json.tmp file signals an interrupted local save. Compare both JSON files privately; if the temp file has the latest valid request ID and matching endpoint/body/key, preserve it as the main record. Otherwise preserve the main record. Remove the stale temp only after that reconciliation. The script stops while the temp file exists so a later command cannot silently discard an accepted ID. Do not delete state and begin a new intent because the process exited.
3. Authorize and submit once
Run these commands from the private project directory in the same shell that holds HF_KEY:
python video_job.py initInspect request-private.json locally in a private editor: verify the saved endpoint, your actual prompt, five-second body, and idempotency key without printing the prompt into a shared Codex transcript. Check the account's live model entitlement, exact current price and balance again. Record your one-request budget and explicit approval outside the script's logs. The next command is the paid boundary. Ask Codex to show the command and obtain the human's authorization for one Seedance 2.0 generation with the inspected body before running it; command permission alone does not supply that decision.
python video_job.py submit --authorize-paid-oneThe script prints the saved request_id after an acceptance response. Keep the private JSON until the work is resolved. The quick start describes the POST response as request_id, status_url, and cancel_url; this recipe does not call cancel. Stopping the local script also does not cancel a generation.
4. Recover the same request and wait for status
If submit reports a timeout, network failure, server error, or an acceptance body without the required fields, inspect request-private.json and your account's Requests view first. A failed local write or lost response does not show whether the service accepted the generation. If the saved phase is uncertain and there is no request_id, use:
python video_job.py replayThis replays the same endpoint, saved JSON body, and Idempotency-Key. Higgsfield's idempotency rules say that a repeat of the same intent returns the original request identity. The page does not state how long that mapping is retained. For delayed recovery, first reconcile the saved key and request with your account's Requests view or Higgsfield support; if you cannot establish that the same intent is still protected, stop before replay rather than treating the key as an indefinite guarantee. Do not edit the body, switch models, or generate a new idempotency key to work around a timeout or 422 parameter mismatch. A replay response can carry an earlier acceptance status; fetch the current status next. An authentication or validation rejection needs diagnosis and a separate decision before any changed paid request. If the account shows a request ID that the local record missed, keep that ID and reconcile it with the saved key and body before any replay.
For an accepted ID, run:
python video_job.py resumeThe script checks that the status URL belongs to the Higgsfield API before sending the secret. It polls from two seconds up to ten, stops at completed, failed, nsfw, or canceled, and returns after a 15-minute local deadline. That deadline is not cancellation and says nothing about whether the service will later complete. Run resume again against the saved ID if it is still nonterminal. If a status read cannot be decoded, returns a missing ID, or repeatedly fails, inspect the saved ID, account, and request status documentation. Do not submit a replacement to solve a status-read error. The polling guidance distinguishes retrying a GET from starting another generation.
5. Download and judge the actual video
After completed includes video.url, run:
python video_job.py downloadThis writes to an unused video-output.mp4 path and records bytes, SHA-256, and media URL host in the private journal. The download sends no Higgsfield API key to the media host. If video-output.mp4.part remains after an interrupted download, inspect it locally. Once you confirm it is incomplete, rename only that partial file to an unused private name in the same directory, such as video-output.incomplete-review.part. Keep request-private.json and its request ID where they are, then run python video_job.py download again. An expired or inaccessible media URL calls for a fresh status read or account support, not a second POST.
Now reopen video-output.mp4 in a local video player and watch it from start to end. On Windows PowerShell, Invoke-Item .\video-output.mp4 opens the saved file with its associated player. Check whether the depicted content meets the approved prompt and whether artifacts make it unusable. Inspect the actual duration, frame dimensions/aspect ratio, and audio track with your player. If ffprobe is installed locally, this command prints the technical fields:
ffprobe -v error -show_entries format=duration:stream=codec_type,width,height -of json video-output.mp4Compare the observed properties with the requested five seconds, 16:9, and no audio. Record the player/probe observations, the file hash from the journal, the request ID, and your human decision: accept, revise, or reject. A nonempty file, a .mp4 extension, or completed alone cannot establish playable, acceptable media. If you decide to revise, that is a new paid generation requiring a revised approved prompt, a new idempotency key, a fresh price check, and explicit paid authorization. The API credential need not be rotated merely because you request a new video. This script deliberately has no automatic revision path.
Finally, read the charge or usage for that request in your actual Higgsfield account and record the amount and currency, or mark it unknown until billing catches up. Do not infer a charge from a public “from” rate or claim that a creator plan pays for API use. Your completed record is the accepted or rejected playable file, technical observations, SHA-256, request ID, and observed charge or unresolved billing status.
Sources & further reading
- Seedance 2.0 text-to-video API reference — model path and allowed request settings.
- Higgsfield quick start, authentication, idempotency, polling, errors, and status response — the request lifecycle and recovery rules.
- API pricing, privacy policy, and terms — account, data, and use conditions to check before submission.
- Codex permissions — local command access varies by client and configuration.
- BIG CHANGE's Codex skill guide describes turning already accepted work into a reusable skill. This task first requires one checked video; a skill is optional later.



