curl --request POST \
--url https://api.kiteml.com/v1/twins \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"source": {
"repo_id": "lerobot/svla_so101_pickplace",
"type": "huggingface"
}
}
'import requests
url = "https://api.kiteml.com/v1/twins"
payload = { "source": {
"repo_id": "lerobot/svla_so101_pickplace",
"type": "huggingface"
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({source: {repo_id: 'lerobot/svla_so101_pickplace', type: 'huggingface'}})
};
fetch('https://api.kiteml.com/v1/twins', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.kiteml.com/v1/twins",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'source' => [
'repo_id' => 'lerobot/svla_so101_pickplace',
'type' => 'huggingface'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.kiteml.com/v1/twins"
payload := strings.NewReader("{\n \"source\": {\n \"repo_id\": \"lerobot/svla_so101_pickplace\",\n \"type\": \"huggingface\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.kiteml.com/v1/twins")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"source\": {\n \"repo_id\": \"lerobot/svla_so101_pickplace\",\n \"type\": \"huggingface\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kiteml.com/v1/twins")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"source\": {\n \"repo_id\": \"lerobot/svla_so101_pickplace\",\n \"type\": \"huggingface\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"progress": 0.5,
"source": {
"repo_id": "<string>",
"type": "huggingface"
},
"object": "twin",
"stage": "<string>",
"status_message": "<string>",
"eta_seconds": 123,
"error": {
"code": "<string>",
"message": "<string>",
"resumable": true,
"stage": "<string>"
},
"camera": {
"scene": "<string>",
"wrist": "<string>",
"available": [
"<string>"
]
},
"config": {},
"stages": [
{
"stage": "<string>",
"ok": true,
"attempts": 123,
"seconds": 123,
"measurement": "<string>"
}
],
"download": {
"url": "<string>",
"requires_auth": true,
"filename": "<string>",
"bytes": 123,
"sha256": "<string>",
"scene": "<string>",
"expires_at": "<string>"
},
"webhook_metadata": {},
"created_at": "<string>"
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}Create Twin
Build a twin from one episode of a Hugging Face LeRobot dataset. Only source is required.
The dataset is checked first (the same checks as /v1/twins/validate), so a twin that cannot work is refused
here, for free. Returns 202 with a queued twin; a twin takes about 100 minutes. Poll GET /v1/twins/,
or subscribe to twin.completed / twin.failed / twin.canceled. Send an Idempotency-Key to make retries safe:
a retry with the same key and body returns the same twin, even while the first request is still in flight.
curl --request POST \
--url https://api.kiteml.com/v1/twins \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"source": {
"repo_id": "lerobot/svla_so101_pickplace",
"type": "huggingface"
}
}
'import requests
url = "https://api.kiteml.com/v1/twins"
payload = { "source": {
"repo_id": "lerobot/svla_so101_pickplace",
"type": "huggingface"
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({source: {repo_id: 'lerobot/svla_so101_pickplace', type: 'huggingface'}})
};
fetch('https://api.kiteml.com/v1/twins', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.kiteml.com/v1/twins",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'source' => [
'repo_id' => 'lerobot/svla_so101_pickplace',
'type' => 'huggingface'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.kiteml.com/v1/twins"
payload := strings.NewReader("{\n \"source\": {\n \"repo_id\": \"lerobot/svla_so101_pickplace\",\n \"type\": \"huggingface\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.kiteml.com/v1/twins")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"source\": {\n \"repo_id\": \"lerobot/svla_so101_pickplace\",\n \"type\": \"huggingface\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.kiteml.com/v1/twins")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"source\": {\n \"repo_id\": \"lerobot/svla_so101_pickplace\",\n \"type\": \"huggingface\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"progress": 0.5,
"source": {
"repo_id": "<string>",
"type": "huggingface"
},
"object": "twin",
"stage": "<string>",
"status_message": "<string>",
"eta_seconds": 123,
"error": {
"code": "<string>",
"message": "<string>",
"resumable": true,
"stage": "<string>"
},
"camera": {
"scene": "<string>",
"wrist": "<string>",
"available": [
"<string>"
]
},
"config": {},
"stages": [
{
"stage": "<string>",
"ok": true,
"attempts": 123,
"seconds": 123,
"measurement": "<string>"
}
],
"download": {
"url": "<string>",
"requires_auth": true,
"filename": "<string>",
"bytes": 123,
"sha256": "<string>",
"scene": "<string>",
"expires_at": "<string>"
},
"webhook_metadata": {},
"created_at": "<string>"
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}{
"error": {
"type": "<string>",
"code": "<string>",
"message": "<string>",
"param": "<string>",
"request_id": "<string>",
"details": {}
}
}Authorizations
A Kite API key (kite_...), from Platform API → Keys in the dashboard.
Headers
255The dated contract your code was written against. Without it you get 2026-09-27; every response says which version served it.
2026-09-27 Body
Build a digital twin from one episode of a Hugging Face LeRobot dataset. Only source is required.
Where the episode comes from. The same shape as an augmentation's source.
Show child attributes
Show child attributes
A short name for the twin
100Which objects to author: the ones the robot manipulates. Defaults to a general manipulation brief
4000How to describe the room to the reconstructor
4000Substring of the camera that seeds the room (see /v1/twins/validate)
200200Floor-to-ceiling height: the scale anchor
Opaque metadata echoed on the resource and its webhook events
Response
The twin, queued
One digital twin.
twin_ + a time-ordered ULID
queued, processing, canceling, succeeded, failed, canceled 0 <= x <= 1Where the episode comes from. The same shape as an augmentation's source.
Show child attributes
Show child attributes
twin "twin"The stage running now
While queued or processing: time left, from the stages' typical durations
Show child attributes
Show child attributes
What the dataset check resolved
Show child attributes
Show child attributes
The build options given at create
Every finished stage, in order
Show child attributes
Show child attributes
Once succeeded: the whole scene as one .tar.gz. Its file list is GET /v1/twins/{id}/files
Show child attributes
Show child attributes