Submit a component's source
Submits a Rust crate as a new version of the component its slug names, creating the component on its first version, and answers 202 while the server builds it in the background.
The version starts queued; read it with getVersion until it is ready or failed.
Guide: Components
A project secret key: vh_sk_... for production, vh_sk_test_... for test. Keep it on your servers.
In: header
Path parameters
The project's id, proj_.... A secret key works only in the project it belongs to.
Request body
application/json
TypeScript definitions
Use the request body type in TypeScript.
The slug of the component to add the version to, created when the project has none: lowercase letters, digits and dashes, at most 63 characters.
The component's title; defaults to the slug for a new component. Given for an existing one, it renames it.
The crate's files, keyed by path. src/lib.rs is required, other modules live under src/, and an optional Cargo.toml may only declare the voidhash-ui dependency.
Examples
curl -X POST "https://api.voidhash.com/api/v1/projects/proj_tz4a98xxat96iws9zmbrgj3a/components/sources" \ -H "Authorization: Bearer vh_sk_..." \ -H "Content-Type: application/json" \ -d '{ "slug": "plan-picker", "files": { "src/lib.rs": "#![no_std]\\n\\nuse voidhash_ui::prelude::*;\\n\\n/// Selectable plans: tapping one selects it, and the button buys it.\\n#[component(\\n title = \\"Plan picker\\",\\n description = \\"Selectable plans.\\",\\n events(selected(product_id: string)),\\n capabilities(commerce),\\n traits(frame(resize = \\"width\\"), appearance, effects),\\n size(width = \\"fill\\", height = \\"hug\\")\\n)]\\npub fn PlanPicker(cx: Cx, plans: Vec<Product>, #[prop(default = \\"#7c3aed\\")] accent: Color) -> View {\\n let selected = cx.state(|| plans.first().map(|plan| plan.id.clone()));\\n let current = selected.get();\\n vstack()\\n .spacing(12)\\n .children(plans.iter().map(|plan| {\\n let id = plan.id.clone();\\n hstack()\\n .key(&plan.id)\\n .child(text(&plan.title).font(Font::HEADLINE))\\n .child(spacer())\\n .child(text(plan.price_per_period()))\\n .border(if current.as_ref() == Some(&plan.id) { 2 } else { 1 }, accent)\\n .corner_radius(14)\\n .on_tap(move |cx| {\\n selected.set(Some(id.clone()));\\n cx.emit(\\"selected\\", object([(\\"productId\\", Value::from(&id))]))\\n })\\n }))\\n .child(button(\\"Continue\\").prominent().on_tap(move |cx| {\\n if let Some(id) = selected.get() {\\n cx.purchase(&id);\\n }\\n }))\\n}\\n" } }'{ "id": "cmp_ver_t6y0u4i8o2p5a9s3d7f1g5h9", "componentId": "cmp_p4z7c1x9v3b8n2m6q5w0e4r7", "version": 4, "sourceKind": "rust", "status": "queued", "contentHash": null, "panelHash": null, "byteSize": null, "manifest": null, "isDeprecated": false, "deprecatedAt": null, "warnings": [], "conformance": null, "createdBy": "user_k3m8q1z6v9c2x5b7n4w0e8r1", "createdAt": "2026-03-18T10:40:00.000Z", "builtAt": null}{ "error": { "type": "invalid_request", "code": "invalid_slug", "message": "The component slug is invalid. \"Plan Picker\" is not a valid slug: use lowercase letters, digits and dashes, starting with a letter or digit, at most 63 characters.", "param": "slug" }}{ "error": { "type": "unauthenticated", "code": "invalid_api_key", "message": "This secret key is not valid." }}{ "error": { "type": "permission_denied", "code": "permission_denied", "message": "Insufficient permissions." }}{ "error": { "type": "not_found", "code": "project_not_found", "message": "There is no project proj_tz4a98xxat96iws9zmbrgj3a, or this credential cannot use it.", "param": "projectId" }}{ "error": { "type": "internal", "code": "internal_error", "message": "Something went wrong on our side. Try again, and contact support with the Request-Id header if it keeps failing." }}Response body
application/json
application/json
application/json
application/json
application/json
application/json
Errors
| Status | type | code | When |
|---|---|---|---|
| 400 | invalid_request | invalid_slug | Naming every problem with the slug or the crate. |
| 400 | invalid_request | invalid_source | Naming every problem with the slug or the crate. |
It can also fail with the errors every operation shares, such as unauthenticated; see Errors.
More in Components
- List components
GET - Upload a component module
POST - Retrieve a component
GET - Update a component
PATCH - Delete a component
DELETE - List a component's versions
GET - Retrieve a component version
GET - Update a component version
PATCH - List component uses
GET
Something wrong or missing on this page? Tell us by email or on Discord.