BlockRun

Virtual Portrait Enrollment

Enroll an AI-generated character image as a Virtual Portrait and get back a ta_xxxxxxxx id you can pass as real_face_asset_id on any Seedance 2.0 / 2.0 Fast / 2.0 Mini call. Use this when you want the same character across multiple videos without a liveness check.

No liveness check

Use this for AI-generated personas, mascots, avatars, virtual spokespeople — no liveness step needed because the asset is understood to be a synthetic character. For real-person likeness, use RealFace (also no government ID, but requires a brief liveness check on the rights-holder's phone to prove consent).

EndpointPOST https://blockrun.ai/api/v1/portrait/enroll
Price$0.011 USDC per enrollment (one-time, settled to BlockRun's Base wallet via x402)
Authx402 micropayment header — no API key needed
NetworkBase (USDC, EIP-3009 exact)
Returnsta_xxxxxxxx… asset id for use with real_face_asset_id on Seedance 2.0 / 2.0 Fast / 2.0 Mini

You can use the web UI at blockrun.ai/studio/portrait — wallet connect, paste an image URL, sign once, copy the ta_xxx. The endpoint below is for SDK / programmatic use.

Request

{
  "name": "My Spokesperson",
  "image_url": "https://example.com/character.jpg"
}
ParameterTypeRequiredDescription
namestringYesDisplay name for organization in your portrait list (1–64 chars)
image_urlstringYesPublic https:// URL pointing to a JPG / PNG / WEBP image. Server-side fetched and registered as a Virtual Portrait asset. Max 10 MB.

What makes a good portrait image

Seedance conditions video generation on the supplied image as a face/character reference. Best results when:

  • Single subject, face clearly visible (front or 3/4 angle, eyes open)
  • Neutral expression — extreme expressions reduce the model's ability to apply prompted emotions
  • Clean background (or at least not visually competing with the subject)
  • Even lighting — heavy shadows on the face degrade character consistency
  • High resolution — 1024×1024 or larger; we downscale gracefully but blurry inputs propagate

Images that fail the upstream content filter (NSFW, recognizable real-celebrity likeness without consent) will be rejected at enrollment.

Payment flow (x402)

Standard BlockRun two-step:

  1. First request without X-Payment → server returns 402 Payment Required with x402 challenge headers (X-Payment-Required / PAYMENT-REQUIRED base64, plus WWW-Authenticate: X402 requirements="…") and a body of { "x402Version": 2, "accepts": [{ "scheme": "exact", "network": "eip155:8453", "amount": "11000", "payTo": "0x…", "maxTimeoutSeconds": 300 }], "error": "Payment Required", "message": "Enrolling a Virtual Portrait costs $0.0110 USDC. …", "price": { "amount": "0.0110", "currency": "USD" }, "paymentInfo": { "network": "base", "asset": "USDC", "x402Version": 2 } }x402Version/accepts mirror the header's challenge for clients that only read the body
  2. Sign the EIP-3009 transfer authorization for $0.011 USDC on Base ($0.01 enrolment + $0.001 transaction fee — the requirements say 11000 micro-USDC)
  3. Retry the same request with X-Payment: <base64> (Payment-Signature is accepted too) → server verifies, rejects a reused authorization (402, code: "PAYMENT_REPLAY"), registers the portrait, settles the payment after registration succeeds, returns the ta_xxx

Settlement happens after the portrait is successfully enrolled. If enrollment fails (content filter, network error), no payment is taken — the route returns 502 and the caller can retry with a fresh signature. If settlement itself fails after a successful enrollment, BlockRun absorbs the cost rather than leave the user with a paid-but-unrecoverable state.

If you're using clawrouter locally, this flow is fully automatic — just call the endpoint.

Response

{
  "object": "virtual_portrait",
  "asset_id": "ta_abcdef1234567890",
  "group_id": "legacy_rf_…",
  "name": "My Spokesperson",
  "image_url": "https://blockrun.ai/api/media/…",
  "source_image_url": "https://example.com/character.jpg",
  "mirrored": true,
  "created_at": "2026-05-22T14:32:11.000Z",
  "usage": {
    "compatible_models": ["bytedance/seedance-2.0", "bytedance/seedance-2.0-fast", "bytedance/seedance-2.0-mini"],
    "how_to_use": "Pass \"real_face_asset_id\": \"ta_abcdef1234567890\" on a Seedance video generation request."
  },
  "price": { "amount": "0.0110", "currency": "USD" },
  "settlement": {
    "success": true,
    "tx_hash": "0x9f3a…",
    "network": "base"
  }
}

The settlement receipt is also returned in the X-Payment-Response / PAYMENT-RESPONSE headers.

FieldDescription
asset_idThe ta_… id to pass as real_face_asset_id on Seedance
group_idInternal asset-group id, returned for your own reference. No endpoint accepts it — enrollment mints its own group, so there is nothing to pass it back to. Not returned by the portraits listing
image_urlThe BlockRun-hosted mirror of your image (so the listing thumbnail survives a dead source URL); falls back to the original URL if mirroring failed
source_image_url / mirroredThe URL you supplied, and whether the mirror succeeded
usage.compatible_modelsWhich BlockRun video models accept this asset id
settlement.tx_hashThe Base settlement transaction (verify on BaseScan)

Examples

Using the enrolled portrait

Take the returned asset_id and pass it as real_face_asset_id on a Seedance call:

curl -X POST http://localhost:8402/v1/videos/generations \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bytedance/seedance-2.0-fast",
    "prompt": "the character smiles warmly and waves at the camera",
    "real_face_asset_id": "ta_abcdef1234567890"
  }'

The same ta_xxx can be reused across as many video generations as you want — you pay the per-clip token cost each time, but you don't re-pay the enrollment fee.

Listing enrolled portraits

GET https://blockrun.ai/api/v1/wallet/<address>/portraits

Returns the list of portraits the given wallet has enrolled. Free (rate-limited to 120 requests / hour / IP, shared with the wallet-reconciliation bucket; responses cacheable for 30 s). Works for both EVM (0x…) and Solana (base58) addresses, though Virtual Portrait enrollment itself is currently Base-only.

{
  "wallet": "0xCC8c44AD3dc2A58D841c3EB26131E49b22665EF8",
  "portraits": [
    {
      "assetId": "ta_abcdef1234567890",
      "name": "My Spokesperson",
      "imageUrl": "https://example.com/character.jpg",
      "createdAt": "2026-05-22T14:32:11.000Z",
      "enrollmentTxHash": "0x9f3a…"
    }
  ],
  "count": 1
}

The video playground at /models/bytedance-seedance-2.0-fast reads this same list and renders it as a dropdown when the connected wallet has any enrolled portraits.

Error codes

CodeBody errorCause / fix
400Invalid request bodyMissing name, invalid image_url, name > 64 chars
400image_url must be an http(s) URLURL missing https:// scheme
402Payment RequiredFirst request — sign + retry with X-Payment header
402Payment verification failedcode is PAYMENT_INVALID (signature/amount/recipient mismatch — re-sign), PAYMENT_UNFUNDED (insufficient USDC or expired window) or PAYMENT_BLOCKHASH_STALE; a message explains when known, debug carries the raw reason
402Payment authorization already usedcode: "PAYMENT_REPLAY" — each authorization is single-use; sign a fresh one
502variesEnrollment failed (content filter, image too big, network) — no payment taken, safe to retry with a different image
429Rate limit exceededListing endpoint only (120/hour/IP) — back off per Retry-After header

Storage and privacy

  • Wallet → portrait list is stored in BlockRun's GCS (gs://blockrun-prod-2026-logs/portraits/<wallet>.json)
  • The image URL you supplied is kept for previewing in the UI; the canonical image bytes are stored upstream with the inference provider
  • We do not analyze, redistribute, or use your portrait images for any purpose other than serving the asset back to your wallet and forwarding it for Seedance generation
  • Delete is not currently exposed via API — open a GitHub issue or email vicky@blockrun.ai if you need an entry removed

What's next?

Also useful: Portrait Studio UI (web flow for non-developers) · Real-person video walkthrough.