curl --request POST \
--url https://api.outerlayer.ai/v1/hosts/heartbeat \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'X-Outerlayer-App-Id: <api-key>' \
--data '
{
"cliVersion": "<string>",
"host": "<string>",
"pollSeconds": 43200,
"slotsTotal": 2,
"slotsUsed": 1,
"runnerProtocol": 500000,
"workRequest": {
"ok": true
}
}
'import requests
url = "https://api.outerlayer.ai/v1/hosts/heartbeat"
payload = {
"cliVersion": "<string>",
"host": "<string>",
"pollSeconds": 43200,
"slotsTotal": 2,
"slotsUsed": 1,
"runnerProtocol": 500000,
"workRequest": { "ok": True }
}
headers = {
"X-Outerlayer-App-Id": "<api-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Outerlayer-App-Id': '<api-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
cliVersion: '<string>',
host: '<string>',
pollSeconds: 43200,
slotsTotal: 2,
slotsUsed: 1,
runnerProtocol: 500000,
workRequest: {ok: true}
})
};
fetch('https://api.outerlayer.ai/v1/hosts/heartbeat', 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.outerlayer.ai/v1/hosts/heartbeat",
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([
'cliVersion' => '<string>',
'host' => '<string>',
'pollSeconds' => 43200,
'slotsTotal' => 2,
'slotsUsed' => 1,
'runnerProtocol' => 500000,
'workRequest' => [
'ok' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"X-Outerlayer-App-Id: <api-key>"
],
]);
$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.outerlayer.ai/v1/hosts/heartbeat"
payload := strings.NewReader("{\n \"cliVersion\": \"<string>\",\n \"host\": \"<string>\",\n \"pollSeconds\": 43200,\n \"slotsTotal\": 2,\n \"slotsUsed\": 1,\n \"runnerProtocol\": 500000,\n \"workRequest\": {\n \"ok\": true\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Outerlayer-App-Id", "<api-key>")
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.outerlayer.ai/v1/hosts/heartbeat")
.header("X-Outerlayer-App-Id", "<api-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"cliVersion\": \"<string>\",\n \"host\": \"<string>\",\n \"pollSeconds\": 43200,\n \"slotsTotal\": 2,\n \"slotsUsed\": 1,\n \"runnerProtocol\": 500000,\n \"workRequest\": {\n \"ok\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.outerlayer.ai/v1/hosts/heartbeat")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Outerlayer-App-Id"] = '<api-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"cliVersion\": \"<string>\",\n \"host\": \"<string>\",\n \"pollSeconds\": 43200,\n \"slotsTotal\": 2,\n \"slotsUsed\": 1,\n \"runnerProtocol\": 500000,\n \"workRequest\": {\n \"ok\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"blocked": {
"reason": "<string>"
},
"cliVersion": "<string>",
"host": "<string>",
"lastError": {
"at": "<string>",
"code": "<string>",
"message": "<string>"
},
"lastHeartbeatAt": "<string>",
"lastSuccessAt": "<string>",
"lastUpdate": {
"at": "<string>",
"from": "<string>",
"outcome": "updated",
"reason": "<string>",
"to": "<string>"
},
"lowDisk": {
"free": 123,
"needed": 123
},
"pollSeconds": 123,
"runnerProtocol": 123,
"runnerVersion": "<string>",
"slotsTotal": 123,
"slotsUsed": 123,
"status": "ok"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}Report a heartbeat from a runner host
Upserts one row for host, stamping the platform’s own clock for the heartbeat time and, when workRequest is present, for the last success or the last error. runnerProtocol is the gateway contract the runner was built against; a heartbeat without it is stored as 0, below every minimum. workRequest is omitted when this poll made no request for work; its error, when present, is only what the platform already returned to this host. lastUpdate is the runner’s last self-update; omitted, it leaves the stored one as it was. lowDisk is the free and needed bytes of a runner that holds off new work for want of disk; omitted, it clears the stored figures. blocked is the reason a runner claims nothing because a check of its own failed (no /dev/kvm, no Claude credential, a placeholder command, builds that do not fit in memory); omitted, it clears the stored reason. Answers with the host in the same shape GET /v1/hosts lists it in, plus runnerVersion, the @outerlayer/cli version the factory’s runner update setting picks: the version this gateway was built from (follow, the default), the runner’s own cliVersion (paused), or the held version (pinned).
curl --request POST \
--url https://api.outerlayer.ai/v1/hosts/heartbeat \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'X-Outerlayer-App-Id: <api-key>' \
--data '
{
"cliVersion": "<string>",
"host": "<string>",
"pollSeconds": 43200,
"slotsTotal": 2,
"slotsUsed": 1,
"runnerProtocol": 500000,
"workRequest": {
"ok": true
}
}
'import requests
url = "https://api.outerlayer.ai/v1/hosts/heartbeat"
payload = {
"cliVersion": "<string>",
"host": "<string>",
"pollSeconds": 43200,
"slotsTotal": 2,
"slotsUsed": 1,
"runnerProtocol": 500000,
"workRequest": { "ok": True }
}
headers = {
"X-Outerlayer-App-Id": "<api-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Outerlayer-App-Id': '<api-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
cliVersion: '<string>',
host: '<string>',
pollSeconds: 43200,
slotsTotal: 2,
slotsUsed: 1,
runnerProtocol: 500000,
workRequest: {ok: true}
})
};
fetch('https://api.outerlayer.ai/v1/hosts/heartbeat', 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.outerlayer.ai/v1/hosts/heartbeat",
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([
'cliVersion' => '<string>',
'host' => '<string>',
'pollSeconds' => 43200,
'slotsTotal' => 2,
'slotsUsed' => 1,
'runnerProtocol' => 500000,
'workRequest' => [
'ok' => true
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"X-Outerlayer-App-Id: <api-key>"
],
]);
$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.outerlayer.ai/v1/hosts/heartbeat"
payload := strings.NewReader("{\n \"cliVersion\": \"<string>\",\n \"host\": \"<string>\",\n \"pollSeconds\": 43200,\n \"slotsTotal\": 2,\n \"slotsUsed\": 1,\n \"runnerProtocol\": 500000,\n \"workRequest\": {\n \"ok\": true\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Outerlayer-App-Id", "<api-key>")
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.outerlayer.ai/v1/hosts/heartbeat")
.header("X-Outerlayer-App-Id", "<api-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"cliVersion\": \"<string>\",\n \"host\": \"<string>\",\n \"pollSeconds\": 43200,\n \"slotsTotal\": 2,\n \"slotsUsed\": 1,\n \"runnerProtocol\": 500000,\n \"workRequest\": {\n \"ok\": true\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.outerlayer.ai/v1/hosts/heartbeat")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Outerlayer-App-Id"] = '<api-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"cliVersion\": \"<string>\",\n \"host\": \"<string>\",\n \"pollSeconds\": 43200,\n \"slotsTotal\": 2,\n \"slotsUsed\": 1,\n \"runnerProtocol\": 500000,\n \"workRequest\": {\n \"ok\": true\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"blocked": {
"reason": "<string>"
},
"cliVersion": "<string>",
"host": "<string>",
"lastError": {
"at": "<string>",
"code": "<string>",
"message": "<string>"
},
"lastHeartbeatAt": "<string>",
"lastSuccessAt": "<string>",
"lastUpdate": {
"at": "<string>",
"from": "<string>",
"outcome": "updated",
"reason": "<string>",
"to": "<string>"
},
"lowDisk": {
"free": 123,
"needed": 123
},
"pollSeconds": 123,
"runnerProtocol": 123,
"runnerVersion": "<string>",
"slotsTotal": 123,
"slotsUsed": 123,
"status": "ok"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}Authorizations
Factory id the request is scoped to.
API key (sk_outerlayer_*)
Headers
HTTP Message Signature (RFC 9421) made with the runner key's host key: runner=:<base64>:.
The signature's covered components and parameters: runner=("@method" "@path" "@query" "content-digest");created=<unix seconds>;nonce="<random>";keyid="<host key fingerprint>";alg="ed25519".
RFC 9530 digest of the request body, always sent, including for a request with none: sha-256=:<base64>:.
Body
1 - 641 - 2551 <= x <= 86400x >= 1x >= 0Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
0 <= x <= 1000000- Option 1
- Option 2
Show child attributes
Show child attributes
Response
The heartbeat is recorded.
Show child attributes
Show child attributes