Stage a listing photo with disclosure
A staged listing photo with furniture only, checked that the property itself wasn't changed, with the AB 723 label and a link to the original.
Built on: Studio render, Content credentials, Signed record
Check and disclosure
LivePick a sample and run it. Staging a room takes about a minute: the render waits its turn on a shared GPU.
Watch a recorded run first
Watch: empty rooms staged, checked and labelled
Replay · not liveRecorded on 2026-09-30 from real runs on production (api.decosa.ai): every model call by Qwen3.8-27B through our gateway, with receipts; the furniture drawn by Wan2.2-VACE-Fun-A14B in ComfyUI on the studio GPU. Photos are CC0 (Wikimedia Commons, Poly Haven). Replayed here at a faster pace; the labels link to copies of the recorded pairs on this site.
Furniture is drawn only on the open floor; the chair already in the room, the kitchen, radiators and windows are left exactly as they were. The label links to the unaltered original.
Pick a sample and run it. Staging a room takes about a minute: the render waits its turn on a shared GPU.
Use it your way
Use it from your codeThe hosted API with your key, and prompts to paste into a coding agent
Get an API key
- Call the disclosed virtual staging API from your own code in minutes.
- Every model answer carries a signed receipt.
- Nothing to install; we run the models.
Run it yourself, on request
- The same open models and app, on Wan2.2-VACE-Fun-A14B (fp8) and Qwen3.8-27B: one 96 GB GPU (measured on two shared ones); the check, label, credential and record run on CPU.
- Data never leaves your machines, and there are no Decosa charges.
- One prompt for Claude Code or Codex assembles the whole stack.
- Early access: the container images are not public yet and the source needs access; the prompt says how to ask.
Build with it
Paste one of these into Claude Code, Codex or another coding agent. The first wires your project to the hosted API with your DECOSA_API_KEY. The second pulls our containers and runs the same stack on your own GPU, with no Decosa charges.
- Base URL
- https://api.decosa.ai
- Auth
Authorization: Bearer $DECOSA_API_KEY(or a demo session token)- Tool id
- virtual-staging
Use the hosted API
# Decosa disclosed virtual staging: use the hosted API
You are wiring Decosa's disclosed virtual staging into this project (a listing tool, a photographer's delivery pipeline
or an MLS upload check). It stages an empty-room photo with furniture only, or checks a photo staged by any other tool
against its original, refuses anything that changes the property, and for a photo that passes returns a JPEG with a
burned-in "Virtually staged" label linking to the unaltered original, a C2PA credential naming the original, and a signed
pair record (California Business and Professions Code 10140.8, AB 723). Use only what is listed below. If you need
something else, stop and ask me.
- Base URL: `https://api.decosa.ai`
- Health check: `GET https://api.decosa.ai/healthz`.
- It is an automated check and a disclosure pack, not legal advice or a compliance determination. Never call a listing
"compliant". A person should look at each pair before it is published.
- Use photos you have the right to upload. A disclosed pair is public for 30 days (the label links to it).
## Auth: API key (or a demo session)
1. Preferred: an API key (`dk_…`) from "Get an API key" on the tool page, kept in `DECOSA_API_KEY`, never in code.
Send `Authorization: Bearer $DECOSA_API_KEY`.
2. Without a key: `POST https://api.decosa.ai/demo/session` with `{"vertical": "virtual-staging"}` returns `{"token", "expires_at",
"budget"}`. A demo token runs one run at a time (409); sessions per IP are limited (429 with `Retry-After`).
## Endpoints
- `POST /staging/runs` (token). Body: `{"mode": "stage"|"check", "image_b64": "<the original, JPEG/PNG/WebP, ≤12 MB>",
"staged_b64"?: "<check mode: the staged image>", "room"?: "living"|"bedroom"|"dining"|"office"|"kitchen"|"other",
"style"?: "modern"|"scandinavian"|"traditional"|"mid-century"|"coastal", "instructions"?: "<furniture and decor only, ≤300 chars>",
"zone"?: [[x0,y0,x1,y1]] (0-1000), "attempts"?: 1-3, "accept_warnings"?: bool, "stream"?: bool}`, or `{"sample": "<id>"}`.
- JSON response: `{run_id, status: "disclosed"|"not_disclosed"|"needs_review", verdict: "pass"|"review"|"fail", pair_id,
original_url, pair: {page, original, staged, record}, report, receipts, budget}`.
`report.check.violations`: `[{category: windows|doors|view|walls|floor|fixtures|damage|size|other|outside_zone|geometry, why, box, source}]`.
- 422 `{error, category, layer}` when instructions ask to remove, hide or improve anything (power lines, cracks, views,
paint, floors, a bigger room); nothing renders. 503 when the server has no render worker (check mode still works).
- SSE (`"stream": true`): `ready`, `stage`, `receipt`, `policy`, `map`, `layout`, `render`, `composite`, `check`,
`label`, `credential`, `label_readback`, `pair`, `report`, `done`, `budget`.
- `GET /staging/p/{pair_id}` (public page with the unaltered original), `/staging/p/{pair_id}/original|staged|record`.
- `GET /staging/runs/{run_id}[/export?format=json|record]` (same token, one hour), `POST /record/verify` `{"record"}`,
`GET /staging/info`, `GET /staging/samples`.
## Example: check a vendor's staged photo, then publish the pair (Python, `pip install httpx`)
```python
import base64, httpx, os
API = "https://api.decosa.ai"
H = {"Authorization": f"Bearer {os.environ['DECOSA_API_KEY']}"}
b64 = lambda p: base64.b64encode(open(p, "rb").read()).decode()
r = httpx.post(f"{API}/staging/runs", headers=H, timeout=300,
json={"mode": "check", "image_b64": b64("living-empty.jpg"), "staged_b64": b64("living-staged-vendor.jpg")}).json()
if r["status"] != "disclosed":
for v in r["report"]["check"]["violations"] + r["report"]["check"]["warnings"]:
print(v["category"], "-", v["why"]) # send back to the vendor; do not publish
else:
open("living-staged-labelled.jpg", "wb").write(httpx.get(API + r["pair"]["staged"]).content)
print("publish with the original at", r["original_url"])
```
Run it yourself (containers)
On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.
# Decosa disclosed virtual staging: run it yourself (containers)
You are setting up Decosa's disclosed virtual staging on this machine, so pre-listing photos never leave it. It stages an
empty-room photo with furniture only (or checks a photo staged elsewhere), refuses anything that changes the property,
and for a photo that passes burns in a "Virtually staged" label linking to the original, adds a C2PA credential and seals
a signed pair record (Cal. Bus. & Prof. Code 10140.8, AB 723). Nothing is sent to Decosa's hosted API. It is an automated
aid, not legal advice.
Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with
"not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.
Ask me before any command that needs sudo, and show me the command first.
## Step 0: set up with a coding agent, rehearse on mock data, then go private
This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:
1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
"test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
https://decosa.ai/samples/virtual-staging.zip (578 KB, 13 checks, synthetic or openly licensed: see `licence` in expected.json),
show me what is in it, and run the rehearsal against the local API:
`docker compose exec api python scripts/rehearse.py virtual-staging` (the api image carries the same bundle under /app/rehearsal/virtual-staging/;
with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
`python scripts/rehearse.py virtual-staging --bundle virtual-staging.zip --base-url http://127.0.0.1:<PORT>`.
It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the doctored image is not labelled", "the check fails it", "the changed view is named"). Show me
the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
machine.
For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.
## Steps
1. Docker: if `docker compose version` fails, install Docker Engine and the compose plugin using Docker's official
instructions (docs.docker.com/engine/install). Install the NVIDIA container toolkit and check
`docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`.
2. Fetch the compose file: `mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`. Keep the
`llm` and `api` services. The `llm` service must serve the model **with** its vision tower: remove
`--language-model-only` if present and add `--limit-mm-per-prompt '{"image":4,"video":0}'`. On the `api` set
`DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b`,
`DECOSA_PROVENANCE_DIR=/provenance` (a named volume), `DECOSA_STAGING_ORIGINAL_BASE=<the address that will serve
/staging/p>`, and bind every port to 127.0.0.1. Never set the gateway route on this box.
3. Create the C2PA signing material once: `docker compose run --rm api python scripts/provenance_devcert.py`.
4. Pull and start: `docker compose pull && docker compose up -d`; wait for the `llm` health check.
5. Stage mode needs ComfyUI with `alibaba-pai/Wan2.2-VACE-Fun-A14B` and the `lightx2v/Wan2.2-Lightning` LoRAs (see
{{SITE_URL}}/prompts/virtual-staging-assemble.md, step 5); set `DECOSA_STUDIO_WORKER=command`,
`DECOSA_STUDIO_WORKER_CMD="python /app/scripts/studio_worker.py"` and `DECOSA_STUDIO_COMFY_URL` on the api. Check mode
needs neither.
6. Smoke test: `docker compose exec api python scripts/rehearse.py virtual-staging --base-url http://127.0.0.1:8445` must
pass 13 of 13 checks (a doctored window view refused, an honest staging labelled, read back, credentialed and verified,
"remove the power lines" refused, every receipt `attested`).
7. Report back: `GET /attest/signing-key`, the rehearsal result and how long a check took.
Off by default. Joining as a provider serves other people's requests on this GPU; never do it on a box that holds
pre-listing photos. If I ask for it later, follow the Provide page instead of improvising.
Run it on your own hardwareWhat it needs, and the prompt that sets it up
Run it on your own GPU
Same app, same pinned models, your hardware. Nothing goes to our servers and there are no Decosa charges.
Hardware check
Check your own hardware- CPU only, 64 GB RAMDoesn't fit
Qwen3.8-27B (NVIDIA NVFP4) needs a GPU.
- GeForce RTX 4090lite tierRuns with a smaller tier
The standard tier does not fit: Needs about 54 GB of GPU memory at the smallest settings; 24 GB available. The lite tier fits with changes.
- GeForce RTX 5090lite tierRuns with a smaller tier
The standard tier does not fit: Needs about 62 GB of GPU memory at the smallest settings; 32 GB available. The lite tier fits with changes.
- 2x GeForce RTX 5090lite tierRuns with a smaller tier
The standard tier does not fit: Wan2.2-VACE-Fun-A14B needs about 34 GB on one GPU; each GPU here has 32 GB. The lite tier fits with changes.
- L40Slite tierRuns with a smaller tier
The standard tier does not fit: Needs about 67.6 GB of GPU memory at the smallest settings; 48 GB available. The lite tier fits with changes.
- H100 80 GB (SXM)standard tierRuns
The standard tier fits with changes: Replace Qwen3.8-27B (NVIDIA NVFP4) with Qwen3.8-27B official FP8. This build is NVIDIA NVFP4, which needs a Blackwell GPU.
- RTX PRO 6000 Blackwell 96 GBstandard tierRuns
The standard tier fits (91.6 of 96 GB).
- 2x RTX PRO 6000 Blackwell 96 GBstandard tierRuns
The standard tier fits (91.6 of 192 GB).
- Apple M3 Ultra (Mac Studio), 96 GBlite tierRuns with a smaller tier
The standard tier can't be checked: Wan2.2-VACE-Fun-A14B has no mapped Apple Silicon build The lite tier fits with changes.
- Apple M5 Max, 64 GBlite tierRuns with a smaller tier
The standard tier can't be checked: Wan2.2-VACE-Fun-A14B has no mapped Apple Silicon build The lite tier fits with changes.
Memory per component comes from measured footprints, the tool's stack.json, or an estimate from its parameter count, and each is labelled that way below. Only an RTX PRO 6000 and an M3 Ultra Mac Studio have actually been run.
On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.
- 1
Check the GPU, Docker and the NVIDIA Container Toolkit
The driver must see the GPU, and Docker must be able to pass it into a container.
nvidia-smi docker compose version docker run --rm --gpus all ubuntu nvidia-smi
- 2
Fetch the compose file
One file describes the API and the language model as services.
mkdir -p ~/decosa && cd ~/decosa curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml - 3
Pull and start
The first start downloads pinned model weights, tens of gigabytes.
docker compose pull docker compose up -d
- 4
Check health
Wait until the API reports ok with the language model loaded. Then point your app at the local base URL.
curl -fsS http://localhost:<PORT>/healthz # {"ok": true, "llm": true, ...} curl -fsS -X POST http://localhost:<PORT>/demo/session \ -H 'Content-Type: application/json' -d '{"vertical":"virtual-staging"}'
Set up with a coding agent, rehearse on mock data, then go private
- Set up with a coding agent. Paste the self-host prompt into a coding agent on the machine that will run the service. We recommend Claude Code with Claude Opus 5.5; any capable coding agent works.
- Rehearse on mock data. The agent runs the tool on a bundle of synthetic inputs and checks each answer against the bundle's
expected.json. Every check must print PASS. - Go private. Only then do you run your own data against the local API, yourself, on that machine. Never give the agent real data during setup: a coding agent that runs in the cloud sees everything in its context, so keep real data out of the chat and out of the files it reads.
docker compose exec api python scripts/rehearse.py virtual-staging
Download the mock-data bundle (578 KB, 13 checks)expected.json
An empty old room with arched windows (CC0, Poly Haven) and two images staged from it. In one, the view out of the arched window was swapped for a beach: the check must refuse to label it and name the view. The other only adds furniture: it must pass, get the burned-in 'Virtually staged' label (read back by OCR), a C2PA credential and a signed pair record that verifies, and fails once the original's hash is changed. A request to 'remove the power lines' must be refused before any model call or render.
What the rehearsal checks
- the doctored image is not labelled
- the check fails it
- the changed view is named
- no pair is made for it
- the honest staging is labelled and paired
- the check passes it
- the burned-in label reads back
- it carries a C2PA credential
- the pair record verifies
- a record with the original's hash changed no longer verifies
- 'remove the power lines' is refused by the word patterns
- the refusal happens before any model call
- every model call has a signed receipt
Licence: Old Room by Sergej Majboroda and Fish Hoek Beach by Greg Zaal and Rico Cilliers (Poly Haven, CC0 1.0); the furniture was drawn by Wan2.2-VACE-Fun-A14B in decosa-api. Part of decosa-api, AGPL-3.0-or-later.
Prompt for your coding agent
# Decosa disclosed virtual staging: run it yourself (containers)
You are setting up Decosa's disclosed virtual staging on this machine, so pre-listing photos never leave it. It stages an
empty-room photo with furniture only (or checks a photo staged elsewhere), refuses anything that changes the property,
and for a photo that passes burns in a "Virtually staged" label linking to the original, adds a C2PA credential and seals
a signed pair record (Cal. Bus. & Prof. Code 10140.8, AB 723). Nothing is sent to Decosa's hosted API. It is an automated
aid, not legal advice.
Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with
"not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.
Ask me before any command that needs sudo, and show me the command first.
## Step 0: set up with a coding agent, rehearse on mock data, then go private
This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:
1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
"test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
https://decosa.ai/samples/virtual-staging.zip (578 KB, 13 checks, synthetic or openly licensed: see `licence` in expected.json),
show me what is in it, and run the rehearsal against the local API:
`docker compose exec api python scripts/rehearse.py virtual-staging` (the api image carries the same bundle under /app/rehearsal/virtual-staging/;
with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
`python scripts/rehearse.py virtual-staging --bundle virtual-staging.zip --base-url http://127.0.0.1:<PORT>`.
It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the doctored image is not labelled", "the check fails it", "the changed view is named"). Show me
the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
machine.
For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.
## Steps
1. Docker: if `docker compose version` fails, install Docker Engine and the compose plugin using Docker's official
instructions (docs.docker.com/engine/install). Install the NVIDIA container toolkit and check
`docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`.
2. Fetch the compose file: `mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`. Keep the
`llm` and `api` services. The `llm` service must serve the model **with** its vision tower: remove
`--language-model-only` if present and add `--limit-mm-per-prompt '{"image":4,"video":0}'`. On the `api` set
`DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b`,
`DECOSA_PROVENANCE_DIR=/provenance` (a named volume), `DECOSA_STAGING_ORIGINAL_BASE=<the address that will serve
/staging/p>`, and bind every port to 127.0.0.1. Never set the gateway route on this box.
3. Create the C2PA signing material once: `docker compose run --rm api python scripts/provenance_devcert.py`.
4. Pull and start: `docker compose pull && docker compose up -d`; wait for the `llm` health check.
5. Stage mode needs ComfyUI with `alibaba-pai/Wan2.2-VACE-Fun-A14B` and the `lightx2v/Wan2.2-Lightning` LoRAs (see
{{SITE_URL}}/prompts/virtual-staging-assemble.md, step 5); set `DECOSA_STUDIO_WORKER=command`,
`DECOSA_STUDIO_WORKER_CMD="python /app/scripts/studio_worker.py"` and `DECOSA_STUDIO_COMFY_URL` on the api. Check mode
needs neither.
6. Smoke test: `docker compose exec api python scripts/rehearse.py virtual-staging --base-url http://127.0.0.1:8445` must
pass 13 of 13 checks (a doctored window view refused, an honest staging labelled, read back, credentialed and verified,
"remove the power lines" refused, every receipt `attested`).
7. Report back: `GET /attest/signing-key`, the rehearsal result and how long a check took.
Off by default. Joining as a provider serves other people's requests on this GPU; never do it on a box that holds
pre-listing photos. If I ask for it later, follow the Provide page instead of improvising.
Help me customise for my hardware
Pick your GPU or Mac, or enter its memory. You get the tier that fits, the model swaps it needs, measured speed where we have it, and a setup prompt with those choices written in.
GeForce RTX 5090: 32 GB GDDR7, 1,792 GB/s, FP8 and NVFP4. NVIDIA product page
Runs with a smaller tierDisclosed virtual staging on GeForce RTX 5090: use the Lite · check only (any staging tool) tier
The standard tier does not fit: Needs about 62 GB of GPU memory at the smallest settings; 32 GB available. The lite tier fits with changes.
Lite · check only (any staging tool): what changesuses estimates
- Qwen3.8-27B (NVIDIA NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.
Memory per component
- Maps the original's windows, doors, fixtures,...: Qwen3.8-27B (NVIDIA NVFP4). ~57.6 GB (at least ~28 GB), weights 21.4 GB (from stack.json). Qwen3.8-27B NVFP4: Weights 19.9 GiB (21.4 GB), measured (field stack.json). The compose file gives the server 0.60 of a 96 GB card (57.6 GB) so the rest is FP8 KV cache for several sessions. The 28 GB minimum is an estimate: weights plus a short-context KV cache, which is why several stacks list a 32 GB RTX 5090 as 'estimate'. (stack.json lists 57 GB for this component.)
- The zone, the furniture-only composite, the p...: decosa-api staging module (decosa_api/verticals/staging). CPU. Runs on CPU (vram_gb 0 in stack.json).
Expected speed
Not measured.
Not measured on this hardware. The only measured setups are an RTX PRO 6000 Blackwell and a Mac Studio M3 Ultra.
Setup prompt for this hardware
The self-host prompt for Disclosed virtual staging, with a hardware plan for GeForce RTX 5090 added after Step 0. Loading the full prompt; until then it points your agent at the prompt's URL.
# Set up Disclosed virtual staging on my hardware Fetch https://decosa.ai/prompts/virtual-staging-selfhost.md and follow it (including Step 0: rehearse on mock data first), with the hardware plan below applied. ## Hardware plan for this machine (from https://decosa.ai/self-host/hardware?use=virtual-staging) Target machine: GeForce RTX 5090 (32 GB of GPU memory; CUDA, FP8 and NVFP4). Quality tier: Lite · check only (any staging tool) (lite). Fit check: runs with changes, about 28 GB of 32 GB used; some memory numbers are estimates, not measurements. First, check the machine: run `nvidia-smi` (or `rocm-smi`, or `sysctl hw.memsize` on a Mac) and confirm the GPUs and free memory match the line above. If they do not, stop and tell me before pulling anything. Use these components (the setup below describes the standard tier; change it to match): - Maps the original's windows, doors, fixtures,...: Qwen3.8-27B (NVIDIA NVFP4) (nvidia/Qwen3.8-27B-NVFP4), 57.6 GB. Change: Qwen3.8-27B (NVIDIA NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions. - The zone, the furniture-only composite, the p...: decosa-api staging module (decosa_api/verticals/staging), CPU GPU placement (set each service's device and its vLLM --gpu-memory-utilization to about the share shown): - GPU 0: Qwen3.8-27B (NVIDIA NVFP4) ~28 GB (88%); about 4 GB left During the rehearsal, watch GPU memory. If a model fails to load or runs out of memory, lower its --max-model-len and --max-num-seqs first, then its memory share, and tell me what you changed. The stack's own component list and compose layout: https://decosa.ai/prompts/virtual-staging-assemble.md
The proof
How we tested itEval results and end-to-end checks, hosted and self-hosted, with dates
Verified end to end
Hosted: verified 26 Sep 2026 · measured 26 Sep 2026: · p50 15 s · ~$0.002 per run · 3 receipts
Loading the nightly status…
Self-host: verified 26 Sep 2026 · Fresh clone into a clean directory, docker build of the api image (31 s), the api on host networking with named volumes against the running local vLLM (Qwen3.8-27B with vision) and ComfyUI; then torn down.
Measured cost to run: about $0.39 per 100 images (hosted, 26 Sep 2026). Self-hosting is free: the code is open and the models are open-weight. You pay only for your own hardware and power.
The rehearsal bundle passed 13 of 13 (doctored view refused, honest staging labelled, read back, credentialed and verified, record tamper caught, power-lines refused); a Berlin staging rendered and passed in 12.8 s with attested receipts. When ComfyUI runs outside the api container, set DECOSA_STUDIO_COMFY_INPUT_DIR or delete its input copies yourself.
Known limits (5)
- Hosted verification ran on the pre-release server (decosa-api the pre-release branch on our server, gateway route); production gets this tool when the branch merges.
- Measured on 8 CC0 rooms with planted Pillow edits and our own renders, labelled by the building agent; images staged by other tools were not tested.
- The renderer redraws patterned floors (a tile border) around furniture; the check then refuses to label it, so such rooms often end not disclosed.
- Hairline cracks on patterned walls can be missed (2 of 3 on a marble-effect wall).
- The C2PA credential uses a development certificate; public validators show it as untrusted.
How it's builtThe steps, the models and what each one checks
Get an API key
- Call the disclosed virtual staging API from your own code in minutes.
- Every model answer carries a signed receipt.
- Nothing to install; we run the models.
Run it yourself, on request
- The same open models and app, on Wan2.2-VACE-Fun-A14B (fp8) and Qwen3.8-27B: one 96 GB GPU (measured on two shared ones); the check, label, credential and record run on CPU.
- Data never leaves your machines, and there are no Decosa charges.
- One prompt for Claude Code or Codex assembles the whole stack.
- Early access: the container images are not public yet and the source needs access; the prompt says how to ask.
Stage an empty listing photo with furniture only, check that walls, windows, doors, views, floors and damage were left alone, and ship the AB 723 disclosure: a burned-in label, the original on a public page, a C2PA credential and a signed pair record.
Send an empty-room photo. Qwen3.8-27B maps its windows, doors, fixtures and visible damage; Wan2.2-VACE-Fun-A14B draws furniture on the open floor only, and only the furniture pixels are kept. A structural check then compares the staged image with the original, pixel by pixel and with a side-by-side model comparison, and refuses to label anything that changed the property. Requests like 'remove the power lines' or 'make the room bigger' are refused before anything renders. Check mode runs the same check on images staged by any other tool. A photo that passes gets a 'Virtually staged' label with a link to the unaltered original, a C2PA credential naming the original as its parent, and a signed pair record. For listing agents, brokerages and listing photographers.
- Deployment
- Hosted or self-host
- Regulatory
- Not legal advice or a compliance determination; checked against the primary text on 26 Sep 2026. California Business and Professions Code section 10140.8, added by AB 723 (Pellerin; Stats. 2025, ch. 497; approved and filed 10 Oct 2025; as a regular statute with no date of its own it took effect 1 Jan 2026, Cal. Const. art. IV, sec. 8(c)): a real estate broker or salesperson, or a person acting for them, who includes a digitally altered image in an advertisement or promotional material for the sale of real property must include a reasonably conspicuous statement, on or adjacent to the image, that it has been altered, and a link, URL or QR code to a publicly accessible site that includes and clearly identifies the original, unaltered image; on a website they control, the unaltered image must be in the posting or linked. 'Digitally altered' covers adding, removing or changing elements with photo-editing software or AI, including furniture, fixtures, flooring, walls, paint colour, landscape and elements visible from the property such as utility poles, views through windows and neighbouring properties; lighting, white balance, cropping, exposure and similar adjustments that do not change the representation of the property are excluded. A willful violation of the Real Estate Law is a crime. MLSs and portals may have their own rules (not checked here). Model licences: Apache-2.0 (Qwen3.8-27B, Wan2.2-VACE-Fun-A14B, Wan2.2-Lightning).
Text description
An empty-room photo, optional furniture instructions, or (check mode) an original and an image staged elsewhere go to decosa-api, which can run on your own machine. Instructions pass word patterns and a typed judgment by Qwen3.8-27B (Apache-2.0); anything that would change the property is refused. Qwen3.8-27B maps the original's windows, doors, fixtures and damage; code builds the furniture zone on the open floor with those cut out. Wan2.2-VACE-Fun-A14B (Apache-2.0) draws furniture in the zone on the studio GPU; Qwen3.8-27B boxes each piece and only the pixels that changed inside those boxes are kept. The structural check compares the images pixel by pixel and with a side-by-side model comparison. A photo that passes gets a burned-in label linking to the original's public page, a C2PA credential naming the original as its parent, and a signed pair record. Hosted model calls get gateway receipts, countersigned.
At a glance
- What it checks
- Whether a staged photo changes the property: windows painted over or hidden, doors, the view through a window, wall colour, floor patterns painted out, fixtures, and visible damage hidden or covered, plus framing. Refuses instructions to remove, hide or improve anything.
- What it does not do
- It is not legal advice or a compliance determination, and it can miss things (2 of 40 planted edits in the held-out test, a redrawn floor patch). A person should look at the pair before it is published. It does not check MLS or portal rules.
- Data retention
- Uploads live in a temporary folder for the run and are deleted when it ends. A disclosed pair (the original, the staged image and the record) is kept for 30 days on a public page, because the label links to it: download the pair to host it on your own listing site. Runs are kept in memory for one hour for the token or key that made them. Logs carry ids, verdicts and counts, never images or instructions.
- What leaves the box (hosted demo)
- The photos go to Qwen3.8-27B through our gateway and to the studio GPU, both operated by Decosa. The disclosed pair is public by design. Self-hosted, nothing leaves the machine unless you publish the pair.
- Cost per image
- A fraction of a cent to check an image staged elsewhere (two image calls) and a little more plus a short GPU render to stage one, at the gateway list price. Staging services typically charge tens of dollars per image.
- Output
- A JPEG with 'Virtually staged' and the original's link burned in, a C2PA credential naming the original as its parent, a public page with the unaltered original, and a signed pair record (verify at /record/verify).
Pick the tier for the quality you need
Same app at every tier. What changes is the models, the hardware they need, and whether receipts are signed. Scores are measured with the source named, or marked not measured.
- In the hosted demo
Lite
check only (any staging tool)
No render: check an image staged elsewhere against its original, then label, credential and pair it when it passes. Two image calls per check.
- Models
- Qwen3.8-27B (NVIDIA NVFP4)
- decosa-api staging module (decosa_api/verticals/staging)
- Hardware
- 1x RTX 5090 32 GB for the model (not measured) or the hosted gateway
- Quality evidence
- held-out test: planted edits caught (window removed, wall recoloured, view replaced, crack hidden) / false alarms on clean stagings38 of 40 / 0 of 10decosa-api docs/evals/virtual-staging.md, measured on our server 2026-09-26, gateway route
- burned-in label read back by OCR after resizing to 1024 px and JPEG quality 7030 of 30decosa-api docs/evals/virtual-staging.md, 2026-09-26
- Latency
- measured: seconds per check under load; a fraction of a cent at list price
- Verification
- Proof: strongGateway receipt per image call on the hosted route.
- In the hosted demo
Standard
stage and check (hosted demo)
Furniture drawn on the open floor by Wan2.2-VACE-Fun-A14B, only the furniture kept, the same check; a render the check rejects is drawn again (up to 3 seeds) and never labelled.
- Models
- Qwen3.8-27B (NVIDIA NVFP4)
- Wan2.2-VACE-Fun-A14B
- Wan2.2-Lightning T2V 4-step LoRAs
- decosa-api staging module (decosa_api/verticals/staging)
- Hardware
- Qwen3.8-27B and VACE A14B on two 96 GB cards (measured, shared)
- Quality evidence
- first render passes the check, 8 rooms x 4 seeds18 of 32 (0 of 4 on a tiled floor with a border, 0 of 4 on a cluttered room)decosa-api docs/evals/virtual-staging.md, 2026-09-26
- passing stagings that look furniture-only to a person (the building agent)17 of 18decosa-api docs/evals/virtual-staging.md, 2026-09-26
- Latency
- measured: seconds for one render end to end; a fraction of a cent of model calls plus the render
- Verification
- Proof: partialModel calls are receipted by the gateway; the render is recorded in the pair record and the C2PA credential, not receipted.
Also runs on
- instruction image editingQwen-Image-Edit-2511not servedQwen-Image-Edit-2511 (Apache-2.0) edits by instruction and keeps the rest of the photo by design; not installed here. Hardware: not measured.
We host these ourselves when needed: small models get more of our own compute unless we detect a shortage, so they need no community providers.
Every model in the stack
| Model | Tiers | Params · VRAM | Verification | Details |
|---|---|---|---|---|
Maps the original's windows, doors, fixtures, damage and open floor; boxes each piece of furniture on the render; compares original and staged side by side; the typed judgment on instructions (typed-judgment, vertical 24)Qwen3.8-27B (NVIDIA NVFP4)nvidia/Qwen3.8-27B-NVFP4 on Hugging Face (opens in a new tab) 27.8B · 57 GBProof: strongIn the hosted demo | LiteStandard | 27.8B · 57 GB | Proof: strongIn the hosted demo | |
| ||||
Draws furniture inside the zone (the open floor, with windows, doors, radiators, built-ins and damage cut out): one inpainted still per attempt, VACE control image plus maskWan2.2-VACE-Fun-A14Balibaba-pai/Wan2.2-VACE-Fun-A14B on Hugging Face (opens in a new tab) A14B (two 14B experts, high and low noise) (14B per step active)Proof: partialIn the hosted demo | Standard | A14B (two 14B experts, high and low noise) (14B per step active) | Proof: partialIn the hosted demo | |
| ||||
4-step distillation LoRAs for the renderWan2.2-Lightning T2V 4-step LoRAslightx2v/Wan2.2-Lightning on Hugging Face (opens in a new tab) No proof yetIn the hosted demo | Standard | n/a | No proof yetIn the hosted demo | |
| ||||
The zone, the furniture-only composite, the pixel check (added, removed, recoloured regions; floor pattern; shadows; what each touches), the verdict, the burned-in label and its OCR read-back, the C2PA credential, the public original page and the signed pair record (no model; CPU)decosa-api staging module (decosa_api/verticals/staging) 0 GBProof: partialIn the hosted demo | LiteStandard | 0 GB | Proof: partialIn the hosted demo | |
| ||||
Instruction-following image editing ('add a grey sofa by the left wall') that keeps the rest of the photo by designQwen-Image-Edit-2511Qwen/Qwen-Image-Edit-2511 on Hugging Face (opens in a new tab) 20BNo proof yetSelf-host only | Alternate | 20B | No proof yetSelf-host only | |
| ||||
Does the check catch a doctored listing photo?
Eight CC0 empty rooms were staged by this service; each honest staging was then doctored four ways with ordinary photo edits. Thresholds were tuned on three rooms and frozen; five rooms were held out.
- Planted edits caught (held-out 40)
- 38 of 40both misses are hairline cracks on a marble-effect wall
- Honest stagings flagged
- 0 of 10
- Window removed / view replaced
- 10 of 10 / 10 of 10
- Wall recoloured / crack or damage hidden
- 10 of 10 / 8 of 10
- Label read back after resizing and JPEG q70
- 30 of 30
Where it fails
Hairline cracks on a patterned wall; a patch of floor redrawn around a chair on a floor with little pattern; and our own renderer on a tiled floor with a border, which it redraws (the check refuses those, so the room is not disclosed).
What this does not show
Images staged by other tools, careful retouching, independent labels and legal judgement are not measured. The planted edits are ordinary Pillow edits written by the same author as the checker.
Source: decosa-api docs/evals/virtual-staging.md, 2026-09-26
Tools, services and hardware
Tools
- Cal. Bus. & Prof. Code 10140.8 (AB 723, Stats. 2025, ch. 497) (opens in a new tab)California statute (public)
The disclosure the label, the original's page and the pair record are built for.
- C2PA via c2pa-python (provenance kit) (opens in a new tab)MIT OR Apache-2.0
The credential on the staged image: c2pa.opened (the original as parent ingredient) and c2pa.edited (compositeWithTrainedAlgorithmicMedia).
- Tesseract OCR 5 (opens in a new tab)Apache-2.0
Reads the burned-in label back from the delivered pixels.
- decosa typed-judgment (vertical 24) (opens in a new tab)AGPL-3.0-or-later (decosa-api)
The model layer of the instruction policy: furniture, property change or unclear.
- decosa record (vertical 07) and POST /record/verify (opens in a new tab)AGPL-3.0-or-later (decosa-api)
The signed pair record anyone can re-check.
- scripts/staging_eval.pyApache-2.0
8 CC0 rooms (Poly Haven, Wikimedia Commons), 17 clean stagings and 62 planted edits, split by room into dev and test.
Services
- decosa-api:8445
${DECOSA_REGISTRY}/decosa-api:0.1.0GET /staging/info, /staging/samples; POST /staging/runs (SSE or JSON; stage or check); GET /staging/runs/{id}[/export]; public GET /staging/p/{pair_id} (the original's page). No GPU; Tesseract and c2pa inside. The studio worker renders through ComfyUI.
- decosa-llm:8000
${DECOSA_REGISTRY}/decosa-llm:0.1.0vLLM OpenAI endpoint for Qwen3.8-27B, served with its vision tower (image input). Internal to the compose network.
- ComfyUI with Wan2.2-VACE-Fun-A14B:8189
Stage mode only: the render. No published image yet; install ComfyUI and the weights. Check mode needs no GPU render.
Hardware
- 2x RTX PRO 6000 Blackwell 96 GB (shared) Fits
Measured: the hosted demo's Qwen3.8-27B on one card, ComfyUI (VACE, fp8) on the other beside other studio services.
- 1x RTX PRO 6000 96 GB
Not measured: Qwen3.8-27B NVFP4 (about 20 GB of weights) and VACE A14B fp8 together should fit with a reduced KV cache.
- CPU only Fits
The pixel check, label, credential, pair page and record run on CPU; the map and comparison need the model (check mode), the render needs a GPU (stage mode).
Latency per lane
- check mode (an image staged elsewhere), hosted gateway route5.2 s
Measuredmeasured on our server 2026-09-26: p50 over 50 test checks, under load from other workloads
- stage mode, one render, hosted15.2 s
Measuredmeasured on our server 2026-09-26: the recorded Berlin run (3 model calls, a 6 s render); up to 3 renders when the check rejects one
- stage mode, self-host direct route12.8 s
Measuredmeasured on our server 2026-09-26: fresh-clone api image against the local vLLM and ComfyUI
Run this exact stack on your machine
Paste into Claude Code / Codex to assemble this stack locally. The prompt checks your GPU, pulls the pinned models, writes the compose file and runs a smoke test.
# Assemble Decosa disclosed virtual staging on this machine
You are setting up self-hosted virtual staging on this Linux machine for a brokerage, a listing photographer or an MLS
vendor. It:
- stages an empty-room photo with furniture only (stage mode), or checks a photo staged by any other tool against its
original (check mode);
- checks that walls, windows, doors, views, floors, fixtures and visible damage were left alone, pixel by pixel and with a
side-by-side comparison by Qwen3.8-27B, and refuses requests to remove, hide or improve anything;
- burns in a "Virtually staged" label with a link to the unaltered original, adds a C2PA credential naming the original,
and seals a signed pair record (California Business and Professions Code 10140.8, AB 723).
Work step by step. Show me each command before running anything that needs sudo, and stop if a check fails.
**Before anything else, remind me:**
- This is an automated aid, not legal advice or a compliance determination. A person should look at each pair before it is
published.
- Pre-listing photos stay on this machine: the model route stays local (`direct`). A disclosed pair is meant to be public
(the label links to it): publish it where you choose.
Repeat these points in your final summary.
## Step 0: set up with a coding agent, rehearse on mock data, then go private
This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:
1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
"test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
https://decosa.ai/samples/virtual-staging.zip (578 KB, 13 checks, synthetic or openly licensed: see `licence` in expected.json),
show me what is in it, and run the rehearsal against the local API:
`docker compose exec api python scripts/rehearse.py virtual-staging` (the api image carries the same bundle under /app/rehearsal/virtual-staging/;
with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
`python scripts/rehearse.py virtual-staging --bundle virtual-staging.zip --base-url http://127.0.0.1:<PORT>`.
It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the doctored image is not labelled", "the check fails it", "the changed view is named"). Show me
the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
machine.
For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.
## What you are building
| service | image | model | port |
|---|---|---|---|
| `llm` | `${DECOSA_REGISTRY}/decosa-llm:0.1.0` (vLLM 0.29.0, `vllm/vllm-openai@sha256:c2914767605584b6d8f45686b82de173ecc99e781897aa3d0a66dacd72c51ae1`) | `nvidia/Qwen3.8-27B-NVFP4` @ `482ca0f3832238542f8f5295dde86b5f22711d80`, Apache-2.0, **with its vision tower** | internal 8000 |
| `api` | `${DECOSA_REGISTRY}/decosa-api:0.1.0` (no GPU; Tesseract and c2pa inside) | none | `127.0.0.1:8445` |
| ComfyUI (stage mode only) | installed on the host | `alibaba-pai/Wan2.2-VACE-Fun-A14B` (fp8) + `lightx2v/Wan2.2-Lightning`, both Apache-2.0 | `127.0.0.1:8189` |
Check mode needs only `llm` and `api`.
## 1. Check the GPU, driver and Docker
1. Run `nvidia-smi`. Check mode needs one NVIDIA GPU with at least 48 GB and driver 580 or newer; stage mode adds VACE A14B
(fp8). The measured setup is two RTX PRO 6000 96 GB cards (model on one, ComfyUI on the other); both on one 96 GB card
is not measured.
2. Check `docker --version`, `docker compose version` and `docker run --rm --gpus all ubuntu nvidia-smi`. If Docker or the
NVIDIA Container Toolkit is missing, install them from the official repositories
(`sudo nvidia-ctk runtime configure --runtime=docker`, then restart Docker).
3. Confirm about 60 GB of free disk (about 100 GB more for stage mode).
## 2. Get the images
The images are **on request** while self-host is in early access: ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry (set it as `DECOSA_REGISTRY`), pull access and the compose file.
1. Try `docker pull ${DECOSA_REGISTRY}/decosa-{llm,api}:0.1.0`.
2. If a pull fails, build from source once the `decosa-api` source is published: in it,
`docker build -f docker/api/Dockerfile -t ${DECOSA_REGISTRY}/decosa-api:0.1.0 .`, and `docker compose build llm`.
3. If neither works, stop and tell me.
## 3. Write the compose file
Create `~/decosa-staging/.env`:
```bash
DECOSA_TAG=0.1.0
DECOSA_GPU=0
LLM_MODEL=nvidia/Qwen3.8-27B-NVFP4
LLM_REVISION=482ca0f3832238542f8f5295dde86b5f22711d80
LLM_MAX_LEN=65536
LLM_GPU_UTIL=0.85
DECOSA_SIGNER_NAME="<who signs these records, e.g. Example Realty listing team>"
ORIGINAL_BASE="<the public address that will serve /staging/p, e.g. https://listings.example.com/staging/p>"
```
Create `~/decosa-staging/docker-compose.yml`:
```yaml
name: decosa-staging
x-health: &health
interval: 15s
timeout: 5s
retries: 5
services:
llm:
image: ${DECOSA_REGISTRY}/decosa-llm:${DECOSA_TAG}
deploy: { resources: { reservations: { devices: [ { driver: nvidia, device_ids: ["${DECOSA_GPU:-0}"], capabilities: [gpu] } ] } } }
ipc: host
restart: unless-stopped
volumes: [hf-cache:/root/.cache/huggingface]
# no --language-model-only: the staging check sends images
command: ["${LLM_MODEL}", "--revision", "${LLM_REVISION}", "--served-model-name", "qwen3.8-27b",
"--max-model-len", "${LLM_MAX_LEN}", "--gpu-memory-utilization", "${LLM_GPU_UTIL}", "--max-num-seqs", "16",
"--kv-cache-dtype", "fp8_e4m3", "--speculative-config", '{"method":"mtp","num_speculative_tokens":3}',
"--limit-mm-per-prompt", '{"image":4,"video":0}', "--seed", "0", "--enable-force-include-usage", "--host", "0.0.0.0", "--port", "8000"]
healthcheck: { <<: *health, test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=4)"], start_period: 900s }
api:
image: ${DECOSA_REGISTRY}/decosa-api:${DECOSA_TAG}
restart: unless-stopped
depends_on: { llm: { condition: service_healthy } }
extra_hosts: ["host.docker.internal:host-gateway"]
environment:
DECOSA_LLM_ROUTE: direct # local model only; receipts are signed by this box's key ("attested")
DECOSA_LLM_URL: http://llm:8000/v1
DECOSA_LLM_MODEL: qwen3.8-27b
DECOSA_LOCAL_SIGNING: "on"
DECOSA_SIGNER_NAME: ${DECOSA_SIGNER_NAME}
DECOSA_PROVENANCE_DIR: /provenance # C2PA signing material (step 4)
DECOSA_STAGING_ORIGINAL_BASE: ${ORIGINAL_BASE}
DECOSA_STAGING_PAIR_DAYS: "30"
DECOSA_STUDIO_WORKER: command # stage mode: renders through ComfyUI on the host (step 5)
DECOSA_STUDIO_WORKER_CMD: python /app/scripts/studio_worker.py
DECOSA_STUDIO_COMFY_URL: http://host.docker.internal:8189
DECOSA_STUDIO_WORK_DIR: /work
DECOSA_SESSIONS_PER_IP_HOUR: "1000"
DECOSA_BUDGET_LLM_TOKENS: "200000"
DECOSA_CORS_ORIGIN_REGEX: '^https?://(localhost|127\.0\.0\.1)(:\d+)?$$'
ports: ["127.0.0.1:8445:8445"]
volumes: [decosa-data:/data, decosa-provenance:/provenance]
healthcheck: { <<: *health, test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8445/healthz', timeout=4)"], start_period: 20s }
volumes: { hf-cache: {}, decosa-data: {}, decosa-provenance: {} }
```
Use the named volumes as written: a host bind mount owned by root makes the API fail on `/data/keys.sqlite`.
## 4. Start it and create the C2PA signing material
1. `docker compose up -d llm`, then `docker compose run --rm api python scripts/provenance_devcert.py` (a development CA
and signer in the `decosa-provenance` volume; public C2PA validators will show it as untrusted until you use a
certificate from a C2PA-trusted CA).
2. `docker compose up -d`, then poll `docker compose ps` until both are healthy (the LLM takes 5-10 minutes the first time).
3. `curl -s localhost:8445/staging/info | jq '{render, model: .model.route, record}'`: route `direct`, `record.signed: true`.
## 5. Stage mode: ComfyUI with Wan2.2-VACE-Fun-A14B (optional)
Check mode works without this. For stage mode, on the host (or a second GPU):
1. Install ComfyUI (tested at v0.34.0-36-g30bdda1e) and listen on `0.0.0.0:8189` (or bridge it to the api container).
2. Put `alibaba-pai/Wan2.2-VACE-Fun-A14B` (high- and low-noise experts) in `models/diffusion_models` as
`vace-fun-a14b-high-noise.safetensors` / `vace-fun-a14b-low-noise.safetensors`, `umt5_xxl_fp16.safetensors` in
`models/clip`, `Wan2.1_VAE.pth` in `models/vae`, and the two `lightx2v/Wan2.2-Lightning` T2V 4-step LoRAs in `models/loras`
(the names in `services/animatic/workflows/vace-still.api.json`).
3. The worker uploads a copy of each photo to ComfyUI's `input/` folder. ComfyUI in another container or on the host keeps
those copies: delete `input/decosa-staging-*` and `output/decosa-staging/*` on a schedule.
## 6. Smoke test
```bash
cd ~/decosa-staging && docker compose exec api python scripts/rehearse.py virtual-staging --base-url http://127.0.0.1:8445
```
Pass if all 13 checks pass: the doctored view is refused (`not_disclosed`, a `view` violation), the honest staging is
labelled and paired (`disclosed`), its label reads back, it carries a C2PA credential, its pair record verifies and fails
once tampered, "remove the power lines" is refused with category `surroundings` before any model call, and every receipt
is `attested`. For stage mode also run:
```bash
API=localhost:8445
TOKEN=$(curl -s $API/demo/session -H 'content-type: application/json' -d '{"vertical":"virtual-staging"}' | jq -r .token)
curl -s $API/staging/runs -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
-d '{"sample":"berlin-living","stream":false}' | jq '{status, verdict, pair_id, seconds, receipts: (.receipts|length)}'
```
Expect `disclosed` and `pass` in about 15 s once VACE is loaded (the first render also loads the model).
## 7. Point your listing tools at it
- The console on the Decosa site talks to `NEXT_PUBLIC_DECOSA_API_BASE`; set it to `http://127.0.0.1:8445` for a local build.
- Serve `/staging/p/*` at the address in `ORIGINAL_BASE` (a reverse proxy to the api), or download each pair
(`/staging/p/<id>/original`, `/staged`, `/record`) and publish it on your listing site: the label's link must work.Rules and regulations it checks againstDated, linked to the primary source; not legal advice
Regulation watch
Loading the watch status…
1 law, rule and guidance page cited; 1 watched nightly at the primary source. A change marks this page for a human re-check; nothing is edited automatically. What we cite and how it is watched
Technical detailsModels, where it runs, labels
In short
Last reviewed
- What it is
- Stage an empty listing photo with furniture only, check that walls, windows, doors, views, floors and damage were left alone, and ship the AB 723 disclosure: a burned-in label, the original on a public page, a C2PA credential and a signed pair record.
- Who it's for
- Listing agents, brokerages, listing photographers and MLS vendors publishing staged photos of California homes.
- Where it runs
- Hosted or self-host
- Key numbers
- 38 / 40 Planted edits caught (check) (test split, n = 40)
- 0 / 10 False alarms on clean stagings (test split, n = 10)
- 38 / 40 Planted edits caught, pixel check alone (test split, n = 40)
- 15.2 s Median end-to-end run, hosted (QA sweep 2026-09-26)
- Models
- Wan2.2-VACE-Fun-A14B (furniture inpainting) · Qwen3.8-27B (structure map, furniture boxes, side-by-side check)
- Where
- Hosted or self-host
- Checks
- Receipt per model call; C2PA credential with the original as parent; signed pair record binding both images, the check and the label
- Industry
- Creative and media · Sales and marketing
- Input
- Files and media
- Output
- Media · Signed record or verdict
- Data
- No sensitive data
- Hardware
- 1× 96 GB GPU
- Licence
- Permissive (Apache-2.0, MIT)
- Part of
- Decosa Studio: Brand & Ads
- Runs in
- Decosa hosted · Self-host
- Built from
- Studio render · Content credentials · Signed record
Questions people ask
What does AB 723 require for virtually staged photos?
Since 1 Jan 2026, Cal. Bus. & Prof. Code 10140.8 requires a reasonably conspicuous statement on or next to a digitally altered listing image, and a link, URL or QR code to a public site that shows the original, unaltered image. This product burns the statement and the link into the image and hosts the original's page; it is not legal advice.
Will it remove power lines or hide a crack?
No. Requests to remove, hide or improve anything that is part of the property (power lines, cracks, views, paint, floors, room size) are refused before anything renders. It only adds movable furniture and decor.
How do you know only furniture was added?
The staged image is compared with the original pixel by pixel and by Qwen3.8-27B side by side. On 40 held-out planted edits it caught 38, with no false alarm on 10 honest stagings (measured 26 Sep 2026). A person should still look at the pair.
Can I check photos staged by another tool?
Yes. Check mode takes the original and the staged image from any tool, runs the same check, and labels, credentials and pairs it only when it passes. It costs about $0.0013 per image at the gateway list price.
How good is the staging itself?
Plausible furniture on clear floors, below a human stager's quality. In the eval, 18 of 32 first renders passed the check; on a tiled floor with a border the renderer redrew the border and the check refused every one. Rejected renders are never labelled.
Can it run on our own hardware?
Yes. The API, Qwen3.8-27B and Wan2.2-VACE-Fun-A14B (all Apache-2.0) run on your own GPUs; pre-listing photos then never leave the machine, and every model call is signed by your box.
Ask a question or leave feedbackWe read every message and publish useful answers
Ask about Disclosed virtual staging
We read every message. Questions, comments and our answers show here once we have reviewed and approved them.
Loading questions…