# Example agent flow

**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

```http
# 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 /docs/api/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.
