Skip to main content

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 ID 01KRDZGFZ8ZXQNFY8DWFYTDFCG. 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.

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 an ent_owner wallet that will receive its rewards:
Response — 200 OK
What happened on the server:
  • The resource (bot_001, frodobot) was looked up. It did not exist, so it was created and linked to the wallet at ent_owner. Its new ULID is returned as resource_id — keep it so you can identify the resource directly on future calls instead of repeating resource_name + resource_subtype + ent_owner.
  • Because no task_id was 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_progress and event_id: ride_2026_05_20_001. Its ULID is returned as task_run_id.

Useful variations

If you previously registered the resource via 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.
Provide task_id explicitly:
Pass a ULID you generated client-side. The same value comes back in the response and is what you use for task_end / task_validate. Useful if you want to make the request safely retryable from your side.
started_at captures when the robot actually began the operation, separate from when the event was ingested.
When the resource has never been seen before, attach Metaplex-style metadata so the ENT NFT is minted with a real name, image, and attributes instead of placeholder values. Ignored if the ENT already exists.

Task auto-resolution

When task_id is omitted, the API resolves it from the subnet: The returned task_id always reflects the task the run was attached to, regardless of which case applied.

Error responses

All task_start validation errors return HTTP 422 with the shape:
Authentication / authorization errors return 400, 401, or 403 with a slightly different shape:
The table below lists the errors you can hit on task_start, in roughly the order they’re checked.
Cause. The subnet_id in the URL path does not exist. Double-check the ULID — 01KRDZGFZ8ZXQNFY8DWFYTDFCG is just an example.
Cause. The Authorization header is absent, malformed, or the key doesn’t match a live record. API keys must be sent as Authorization: Bearer brb_....
Cause. The API key is valid but belongs to a different subnet than the one in the URL path. Generate a key for 01KRDZGFZ8ZXQNFY8DWFYTDFCG from that subnet’s API Keys page.
Cause. event_id was omitted. It’s required and must be unique per task — see Duplicate event_id.
Cause. You sent neither resource_id nor the resource_name + resource_subtype pair, so the run has no resource to attach to.
Cause. You identified the resource by 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.
Cause. started_at was supplied but could not be parsed. Use a format like 2026-05-20T14:30:00Z.
Cause. The resource_id you sent doesn’t exist. Confirm the ULID — perhaps from the resource_id returned by an earlier register_resource call.
Cause. A resource with that (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.
Cause. The task_id you supplied doesn’t exist. Tasks are created via the BitRobot dashboard; pick one that’s listed there.
Cause. The task_id exists but is not part of this subnet. Tasks are scoped per-subnet; use one of 01KRDZGFZ8ZXQNFY8DWFYTDFCG’s own tasks.
Cause. Auto-resolution only works when the subnet has exactly one task. When you have more than one, you must send task_id explicitly.
Cause. Your 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.
Cause. Almost always a typo in 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 the task_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:
For this walkthrough, we use Blender’s Big Buck Bunny iPhone trailer as a stand-in for the robot’s output — it’s a real, stable, publicly hosted video so you can run the same command and verify you get the same CID:
Response — 200 OK
What happened on the server:
  • The run’s status flipped from in_progress to completed, 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 and task_validate will refuse to award points (see CID verification is asynchronous below).
The 200 response only confirms the event was accepted — it does not confirm that the CID matched the file. CID verification happens after the request returns. Always treat a successful task_end as provisional until you’ve completed task_validate successfully.

Useful variations

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.
You can end a run without raw_data_uri / raw_data_cid. The run will close on-chain against placeholder values, and BitRobot will skip the CID verification step. The run is still eligible for task_validate, but it loses the verifiable link to a real-world artifact.
Only use this for non-output-producing runs. For anything you want validated against real evidence, always include the URI and CID.

CID verification is asynchronous

BitRobot verifies your CID against the file at raw_data_uri in a background job — not during the API request. The verification works like this:
  1. The job fetches the bytes at raw_data_uri.
  2. It recomputes the CID using ipfs add -n --raw-leaves --chunker=size-1048576 -Q.
  3. It compares the result to the raw_data_cid you sent.
A mismatch marks the run as invalid. You won’t see the error here — it surfaces when you call 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_uri changed 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 -Q are required — omitting any of them produces a different CID for the same file.

Error responses

All task_end validation errors return HTTP 422 with the shape:
Authentication / authorization errors return 400, 401, or 403 with the simpler { "error": "..." } shape, exactly as in Step 1.
Cause. task_run_id was omitted. It must be the ULID returned by the matching task_start.
Cause. Either no task run exists with that ULID, or the run belongs to a different subnet than the one in the URL path. Both cases return the same message so existence isn’t leaked across subnets. Confirm you’re sending the task_run_id you got back from task_start against the same subnet you opened the run with.
Cause. ended_at was supplied but could not be parsed. Use a format like 2026-05-20T14:45:00Z.

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 the task_run_id from Step 1 and a vrw_points integer:
Response — 200 OK
What happened on the server:
  • 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_points to the resource owner’s on-chain EpochAccount for the current epoch.
  • The run is now complete. Its share of the epoch’s token reward pool is fixed by vrw_points divided 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

Pass 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.
With 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

All task_validate validation errors return HTTP 422 with the shape:
Authentication / authorization errors return 400, 401, or 403 with the simpler { "error": "..." } shape, exactly as in Step 1.
Cause. task_run_id was omitted. It must be the ULID returned by the matching task_start.
Cause. vrw_points was omitted. It’s required even when you intend to award zero — send "vrw_points": 0 explicitly.
Cause. No task run exists with that ULID. Confirm you’re sending the value returned by task_start.
Cause. The asynchronous CID verification from Step 2 failed — either the file at 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.
Cause. You tried to award 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.
Cause. You tried to award 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.

You’re done

That’s the complete lifecycle: task_starttask_endtask_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_id from 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.