Example agent flow
One end-to-end flow — create, upload, poll, generate, webhook, fetch — REST and MCP.
Story: an agent receives a DXF floor plan and must return a photorealistic visualization URL — autonomously. The same flow is shown as raw REST and as MCP tool calls.
As raw REST
# 1. Create a project
POST /api/v1/projects
Authorization: Bearer norano_sk_live_…
Idempotency-Key: 7b1f…-uuid
{ "name": "Lysbüchelstrasse 74", "usage_type": "wohnen" }
-> 201 { "object":"project", "id":"proj_8a94…" }
# 2. Create the plan upload (two-step). `size` is required (the signed PUT binds
# the exact byte length); omit `kind` when `mode` is "both".
POST /api/v1/projects/proj_8a94…/plans
Idempotency-Key: 9c2a…-uuid
{ "filename":"EG.dxf", "file_kind":"dxf", "mode":"both", "size":284913 }
-> 202 { "object":"plan_upload",
"upload_url":"https://…r2…(PUT, expires ≤300s)",
"upload_headers": { "Content-Type":"application/octet-stream" },
"expires_at":"2026-06-18T10:05:00Z",
"plans":[
{ "id":"plan_56d9…", "kind":"grundriss", "job_id":"job_plan_…a" },
{ "id":"plan_7c01…", "kind":"vermarktung", "job_id":"job_plan_…b" }
] }
# PUT the exact bytes to the signed URL, using upload_headers:
PUT {upload_url} (raw DXF bytes, Content-Length: 284913) -> 200
# 3. Await a plan's parse Job (poll, or webhook job.succeeded). The Job's id is
# job_plan_…b from step 2; the plan id is plan_7c01… from the plans[] above.
GET /api/v1/jobs/job_plan_…b (Prefer: wait=30)
-> 200 { "object":"job", "id":"job_plan_…b", "type":"plan.parse", "status":"succeeded",
"result":null, "urls":{ "self":"/api/v1/jobs/job_plan_…b", "cancel":null } }
# 4. (optional) cost pre-flight, then generate. Visualizations take project_id.
POST /api/v1/visualizations
{ "project_id":"proj_8a94…", "usage_type":"wohnen", "style":"realistisch",
"lighting":"tag", "furnishing":"moebliert", "dry_run":true }
-> 200 { "object":"cost_estimate", "estimated_cost_usd_cents":51,
"would_create":{ "type":"visualization", "usage_type":"wohnen", "style":"realistisch",
"lighting":"tag", "furnishing":"moebliert" } }
POST /api/v1/visualizations
Idempotency-Key: a1b2…-uuid
{ "project_id":"proj_8a94…", "usage_type":"wohnen", "style":"realistisch",
"lighting":"tag", "furnishing":"moebliert" }
-> 202 { "object":"job", "id":"job_gj_1270…", "type":"visualization", "status":"queued" }
# 5. Receive the webhook (signed) instead of polling
POST {agent_callback_url}
Norano-Signature: t=1718…,v1=8f3c… # verify HMAC-SHA256 over "t.body"
{ "id":"evt_…", "type":"job.succeeded",
"data":{ "object":{ "id":"job_gj_1270…", "status":"succeeded",
"result":{ "assets":[{ "id":"asset_a1c2…","kind":"jpg",
"url":"https://…r2…","expires_at":"2026-06-18T10:05:00Z" }] } } } }
# 6. Fetch the asset (re-sign if past expires_at)
GET /api/v1/assets/asset_a1c2…/url -> 200 { "url":"https://…","expires_at":"…" }As MCP tool calls
create_project(name="Lysbüchelstrasse 74", usage_type="wohnen") -> project{id}
upload_plan(project_id, filename="EG.dxf", file_kind="dxf",
mode="both", size=284913) -> {upload_url, upload_headers, plans:[{id,kind,job_id}]}
(agent PUTs the exact bytes to upload_url out-of-band)
wait_for_job(job_id, timeout_seconds=60) -> Job{status:"succeeded"} (emits progress)
estimate_cost(operation="visualization", project_id, usage_type="wohnen",
style="realistisch", lighting="tag", furnishing="moebliert") -> {estimated_cost_usd_cents:51}
generate_visualization(project_id, usage_type="wohnen", style="realistisch",
lighting="tag", furnishing="moebliert", idempotency_key="a1b2…")-> Job{id, status:"queued"}
wait_for_job(job.id) -> Job{status:"succeeded", result.assets[]}
get_asset_url(asset_id) -> {url, expires_at}
# the produced image is also available as resource norano://asset/asset_a1c2…Both paths drive the same async loop (see async jobs): start the work, await the Job, then read result.assets[].url. The MCP server is at https://mcp.norano.ai/mcp; add it to any MCP-capable assistant with your API key as the bearer token.