About this walkthrough
This page walks through a single task run from start to finish against a real subnet, showing each request, the success response, and the error responses you can expect along the way. It complements Task Management, which covers the conceptual model. The examples use the subnet ID01KRDZGFZ8ZXQNFY8DWFYTDFCG. Replace it with your own and replace brb_... with the API key you generated from your subnet’s API Keys page.
All requests target the production API at https://api.bitrobot.ai. Swap in https://api-stage.bitrobot.ai to try things out against staging first.
Every event request can carry an Idempotency-Key header with a value that is unique per attempt. If you retry with the same key within 24 hours, the stored response is replayed (with an Idempotency-Replayed: true header) instead of creating a second event, so a lost response never turns into a duplicate run.
Step 1 — Start the task run
task_start opens a new task run for a resource. It is the only event that returns a fresh task_run_id — keep that value; you will need it for task_end and task_validate.
Happy path
The minimum request identifies the resource by name and provides anent_owner wallet that will receive its rewards. Resource names are limited to 32 characters for ENTs (the on-chain NFT name limit).
ent_owner_email or ent_owner_discord_id. It is required whenever the resource is created by name, and when a resource_id refers to a resource that has no owner yet. A resource_id that already has an owner needs no identity; sending one again re-resolves it and can reassign the owner. The identity is resolved to a BitRobot account (a placeholder is created for an unknown address or ID), and that account receives the resource’s VRW points.- The resource
(bot_001, frodobot)was looked up. It did not exist, so it was created and linked to the wallet atent_owner. Its new ULID is returned asresource_id— keep it so you can identify the resource directly on future calls instead of repeatingresource_name+resource_subtype+ent_owner. - Because no
task_idwas supplied, the subnet’s single task was used automatically. (See Task auto-resolution below for what happens when the subnet has zero or many tasks.) - A new task run was created with
status: in_progressandevent_id: ride_2026_05_20_001. Its ULID is returned astask_run_id.
Useful variations
Use a known resource_id instead of name + subtype
Use a known resource_id instead of name + subtype
register_resource (or another task_start), reuse its resource_id. You can omit resource_name, resource_subtype, and ent_owner — the ENT already exists on-chain. On email- or Discord-identity subnets you can also omit ent_owner_email / ent_owner_discord_id once the resource has an owner.Pick a specific task when the subnet has multiple
Pick a specific task when the subnet has multiple
task_id explicitly:Supply your own task_run_id
Supply your own task_run_id
task_end / task_validate. Useful if you want to make the request safely retryable from your side.Record the real-world start time
Record the real-world start time
started_at captures when the robot actually began the operation, separate from when the event was ingested.Provide ENT metadata for first-time resources
Provide ENT metadata for first-time resources
Task auto-resolution
Whentask_id is omitted, the API resolves it from the subnet:
task_id always reflects the task the run was attached to, regardless of which case applied.
Error responses
Alltask_start validation errors return HTTP 422 with the shape:
400, 401, or 403 with a slightly different shape:
task_start, in roughly the order they’re checked.
400 — Invalid subnet_id
400 — Invalid subnet_id
subnet_id in the URL path does not exist. Double-check the ULID — 01KRDZGFZ8ZXQNFY8DWFYTDFCG is just an example.401 — Invalid or missing API key
401 — Invalid or missing API key
Authorization header is absent, malformed, or the key doesn’t match a live record. API keys must be sent as Authorization: Bearer brb_....422 — Missing event_id
422 — Missing event_id
event_id was omitted. It’s required and must be unique per task — see Duplicate event_id.422 — Must provide either resource_id or both resource_name and resource_subtype
422 — Must provide either resource_id or both resource_name and resource_subtype
resource_id nor the resource_name + resource_subtype pair, so the run has no resource to attach to.422 — Missing ent_owner
422 — Missing ent_owner
resource_name + resource_subtype but didn’t supply ent_owner. The owner wallet is required when the resource is being created or claimed for the first time. If the resource already exists, switch to resource_id instead.422 — Missing ent_owner_email: this subnet grants Subnet Points to email identities
422 — Missing ent_owner_email: this subnet grants Subnet Points to email identities
resource_id with no owner yet) without ent_owner_email. Discord-identity subnets return the same error for ent_owner_discord_id. Add the owner’s identity; a resource that already has an owner does not need it.422 — Name must be 32 characters or fewer for ENT resources
422 — Name must be 32 characters or fewer for ENT resources
resource_name is longer than 32 characters. The ENT is minted as an on-chain NFT whose name is capped at 32 bytes, so pick a shorter, stable name (for example a short device ID rather than a hash).422 — Invalid started_at: expected ISO 8601 format
422 — Invalid started_at: expected ISO 8601 format
started_at was supplied but could not be parsed. Use a format like 2026-05-20T14:30:00Z.422 — Resource not found with id
422 — Resource not found with id
resource_id you sent doesn’t exist. Confirm the ULID — perhaps from the resource_id returned by an earlier register_resource call.422 — Resource is already registered to a different owner
422 — Resource is already registered to a different owner
(resource_subtype, resource_name) already exists and is owned by a different wallet than the ent_owner you supplied. Either send the correct ent_owner, or call the new owner’s flow via the dashboard.422 — Task not found with id
422 — Task not found with id
task_id you supplied doesn’t exist. Tasks are created via the BitRobot dashboard; pick one that’s listed there.422 — Task belongs to a different subnet
422 — Task belongs to a different subnet
task_id exists but is not part of this subnet. Tasks are scoped per-subnet; use one of 01KRDZGFZ8ZXQNFY8DWFYTDFCG’s own tasks.422 — Multiple tasks found for subnet
422 — Multiple tasks found for subnet
task_id explicitly.422 — A task run with event_id already exists
422 — A task run with event_id already exists
event_id collides with one already used on this task. event_id is unique per task, so retries must reuse the original (idempotent), or pick a fresh value. For truly safe retries, send the Idempotency-Key header.422 — Unknown event type
422 — Unknown event type
event_type. The valid task lifecycle values are task_start, task_end, and task_validate.Step 2 — End the task run
task_end closes out the run created in Step 1 and hands BitRobot the raw output (typically a video) for on-chain verification.
Happy path
You need thetask_run_id from Step 1, a publicly reachable URL pointing at the raw output, and the IPFS CID of that file computed locally with the documented Kubo command:
- The run’s status flipped from
in_progresstocompleted, and the on-chain run is closed using the supplied URI and CID. - A background job was enqueued to download the file at
raw_data_uri, recompute its CID with the same flags, and compare. If the CID matches, the file is stored and the run becomes eligible for validation. If it doesn’t, the run is marked invalid andtask_validatewill refuse to award points (see CID verification is asynchronous below).
Useful variations
Record the real-world end time
Record the real-world end time
ended_at captures when the robot actually finished the operation, separate from when the event was ingested. Pair it with the started_at you sent on task_start for accurate run durations.Force-end a run with no raw output
Force-end a run with no raw output
force_end: true. BitRobot then ends the run on chain against placeholder values, skips CID verification, and flags the row as force-ended so downstream validation can treat it differently.raw_data_uri and raw_data_cid are ignored if you include them alongside force_end: true; the row stays flagged as force-ended and no data is recorded.Use this only when no artifact will ever exist for the run — not as a way to defer sending data. A task_end without force_end and without both raw_data_uri and raw_data_cid is rejected with HTTP 422.CID verification is asynchronous
BitRobot verifies your CID against the file atraw_data_uri in a background job — not during the API request. The verification works like this:
- The job fetches the bytes at
raw_data_uri. - It recomputes the CID using
ipfs add -n --raw-leaves --chunker=size-1048576 -Q. - It compares the result to the
raw_data_cidyou sent.
task_validate, which returns "Task run data is invalid". To recover, fix the CID (or upload a corrected file at a new URI) and retry from task_end for a new run.
Common causes of a mismatch:
- The file at
raw_data_urichanged between when you computed the CID and when BitRobot fetched it. - The URL is private, time-limited, or returns a redirect to an error page.
- The CID was computed with different flags. The documented flags
-n --raw-leaves --chunker=size-1048576 -Qare required — omitting any of them produces a different CID for the same file.
Error responses
Alltask_end validation errors return HTTP 422 with the shape:
400, 401, or 403 with the simpler { "error": "..." } shape, exactly as in Step 1.
422 — Missing task_run_id
422 — Missing task_run_id
task_run_id was omitted. It must be the ULID returned by the matching task_start.422 — Task run not found
422 — Task run not found
task_run_id you got back from task_start against the same subnet you opened the run with.422 — Invalid ended_at: expected ISO 8601 format
422 — Invalid ended_at: expected ISO 8601 format
ended_at was supplied but could not be parsed. Use a format like 2026-05-20T14:45:00Z.422 — raw_data_uri and raw_data_cid are required on task_end unless force_end is true
422 — raw_data_uri and raw_data_cid are required on task_end unless force_end is true
task_end without one (or both) of raw_data_uri / raw_data_cid, and did not set force_end: true. Either include both fields with the URI and CID for the run’s raw output, or — if no artifact will ever exist — re-send with force_end: true. See Force-end a run with no raw output above.409 — Resource is currently committed to another task_run
409 — Resource is currently committed to another task_run
task_end here rather than letting it sit waiting indefinitely — on chain, an ENT can only participate in one task at a time. The error names the blocking task_run_id and event_id; end that one first (either with the real raw data or with force_end: true), then retry the current task_end.Step 3 — Validate the task run
task_validate is the final step. It awards the run’s Verifiable Robotic Work (VRW) points to the resource owner on-chain, which is what makes the run count toward token reward distribution at the end of the epoch.
Happy path
You need thetask_run_id from Step 1 and a vrw_points integer:
- The run was confirmed eligible for validation: the raw data CID matched (from Step 2’s async verification), the resource has a wallet that can receive rewards, and there is an active BitRobot epoch open.
- A background job was enqueued to award
vrw_pointsto the resource owner’s on-chainEpochAccountfor the current epoch. - The run is now complete. Its share of the epoch’s token reward pool is fixed by
vrw_pointsdivided by the total VRW points awarded across the epoch.
vrw_points represents how much Verifiable Robotic Work this single run accomplished — it’s an on-chain quantity, separate from Subnet Points. Pick a value that’s proportional to the work done (distance traveled, frames captured, objects manipulated — whatever your subnet treats as the unit of work). Consistency across runs matters more than any specific scale.Useful variations
Validate a run without awarding points
Validate a run without awarding points
vrw_points: 0 to mark a run as validated but not award anything on-chain. This is useful for runs that completed cleanly but didn’t produce work worth rewarding — for example, a calibration pass or a run that was cut short by the operator.vrw_points: 0, the pre-flight checks for wallet and active epoch are skipped, so this also works as a way to close out runs whose resource has no wallet or whose epoch boundary has just passed.Error responses
Alltask_validate validation errors return HTTP 422 with the shape:
400, 401, or 403 with the simpler { "error": "..." } shape, exactly as in Step 1.
422 — Missing task_run_id
422 — Missing task_run_id
task_run_id was omitted. It must be the ULID returned by the matching task_start.422 — Missing vrw_points
422 — Missing vrw_points
vrw_points was omitted. It’s required even when you intend to award zero — send "vrw_points": 0 explicitly.422 — Task run not found
422 — Task run not found
task_start.422 — Task run data is invalid
422 — Task run data is invalid
raw_data_uri could not be fetched, or its recomputed CID did not match the raw_data_cid you sent. The run is permanently marked invalid; to recover, start a fresh run from task_start with a corrected URI and CID.422 — Resource has no wallet
422 — Resource has no wallet
vrw_points > 0 to a resource that isn’t linked to an owner wallet. Re-register the resource with an ent_owner, or send vrw_points: 0 to validate without awarding. Subnets that pay VRW points to email or Discord identities get Resource has no email owner / Resource has no Discord owner instead; fix it by sending task_start for the resource with ent_owner_email / ent_owner_discord_id.422 — No active epoch found
422 — No active epoch found
vrw_points > 0 outside of any open BitRobot epoch. This is rare in production and usually means an epoch is being rolled over. Retry shortly, or send vrw_points: 0.Step 4 — Find the run’s Subnet Points
Whenvrw_points is greater than 0, task_validate also grants that many Subnet Points to the resource owner. The grant runs in the background, so it appears in your points history shortly after the 200 response.
Filter points history by the task_run_id from Step 1:
user_id is the BitRobot user who received the points for this run.
You’re done
That’s the complete lifecycle:task_start → task_end → task_validate. The run is now permanently recorded, the resource owner has been awarded VRW points on-chain, and the run will contribute to that resource owner’s share of the epoch’s token reward distribution when the epoch closes.
From here you can:
- Inspect the run in the Task Management page of the BitRobot dashboard.
- Repeat the flow for the next run — reuse the
resource_idfrom Step 1 so you don’t have to resend the resource name, subtype, and owner each time. - Bulk-import historical runs via the Task Batch Upload endpoint, which runs each row through this same start → end → validate flow asynchronously.