curl --request POST \
--url https://api.outerlayer.ai/v1/artifacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'X-Outerlayer-App-Id: <api-key>' \
--data '
{
"artifact": {
"bytes": 1,
"caption": "<string>",
"clientArtifactId": "<string>",
"emittedAt": "<string>",
"filename": "<string>",
"mediaType": "<string>",
"sha256": "<string>",
"ci": true,
"commitSha": "<string>",
"criterionId": "<string>",
"gitBranch": "<string>",
"gitRepo": "<string>",
"prNumber": 1,
"replaces": "<string>",
"replacesTargets": [
"<string>"
],
"repository": "<string>",
"tests": [
{
"name": "<string>",
"file": "<string>",
"line": 5000000
}
]
},
"blob": {
"data": "<string>"
},
"schemaVersion": 1
}
'import requests
url = "https://api.outerlayer.ai/v1/artifacts"
payload = {
"artifact": {
"bytes": 1,
"caption": "<string>",
"clientArtifactId": "<string>",
"emittedAt": "<string>",
"filename": "<string>",
"mediaType": "<string>",
"sha256": "<string>",
"ci": True,
"commitSha": "<string>",
"criterionId": "<string>",
"gitBranch": "<string>",
"gitRepo": "<string>",
"prNumber": 1,
"replaces": "<string>",
"replacesTargets": ["<string>"],
"repository": "<string>",
"tests": [
{
"name": "<string>",
"file": "<string>",
"line": 5000000
}
]
},
"blob": { "data": "<string>" },
"schemaVersion": 1
}
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({
artifact: {
bytes: 1,
caption: '<string>',
clientArtifactId: '<string>',
emittedAt: '<string>',
filename: '<string>',
mediaType: '<string>',
sha256: '<string>',
ci: true,
commitSha: '<string>',
criterionId: '<string>',
gitBranch: '<string>',
gitRepo: '<string>',
prNumber: 1,
replaces: '<string>',
replacesTargets: ['<string>'],
repository: '<string>',
tests: [{name: '<string>', file: '<string>', line: 5000000}]
},
blob: {data: '<string>'},
schemaVersion: 1
})
};
fetch('https://api.outerlayer.ai/v1/artifacts', 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/artifacts",
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([
'artifact' => [
'bytes' => 1,
'caption' => '<string>',
'clientArtifactId' => '<string>',
'emittedAt' => '<string>',
'filename' => '<string>',
'mediaType' => '<string>',
'sha256' => '<string>',
'ci' => true,
'commitSha' => '<string>',
'criterionId' => '<string>',
'gitBranch' => '<string>',
'gitRepo' => '<string>',
'prNumber' => 1,
'replaces' => '<string>',
'replacesTargets' => [
'<string>'
],
'repository' => '<string>',
'tests' => [
[
'name' => '<string>',
'file' => '<string>',
'line' => 5000000
]
]
],
'blob' => [
'data' => '<string>'
],
'schemaVersion' => 1
]),
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/artifacts"
payload := strings.NewReader("{\n \"artifact\": {\n \"bytes\": 1,\n \"caption\": \"<string>\",\n \"clientArtifactId\": \"<string>\",\n \"emittedAt\": \"<string>\",\n \"filename\": \"<string>\",\n \"mediaType\": \"<string>\",\n \"sha256\": \"<string>\",\n \"ci\": true,\n \"commitSha\": \"<string>\",\n \"criterionId\": \"<string>\",\n \"gitBranch\": \"<string>\",\n \"gitRepo\": \"<string>\",\n \"prNumber\": 1,\n \"replaces\": \"<string>\",\n \"replacesTargets\": [\n \"<string>\"\n ],\n \"repository\": \"<string>\",\n \"tests\": [\n {\n \"name\": \"<string>\",\n \"file\": \"<string>\",\n \"line\": 5000000\n }\n ]\n },\n \"blob\": {\n \"data\": \"<string>\"\n },\n \"schemaVersion\": 1\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/artifacts")
.header("X-Outerlayer-App-Id", "<api-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"artifact\": {\n \"bytes\": 1,\n \"caption\": \"<string>\",\n \"clientArtifactId\": \"<string>\",\n \"emittedAt\": \"<string>\",\n \"filename\": \"<string>\",\n \"mediaType\": \"<string>\",\n \"sha256\": \"<string>\",\n \"ci\": true,\n \"commitSha\": \"<string>\",\n \"criterionId\": \"<string>\",\n \"gitBranch\": \"<string>\",\n \"gitRepo\": \"<string>\",\n \"prNumber\": 1,\n \"replaces\": \"<string>\",\n \"replacesTargets\": [\n \"<string>\"\n ],\n \"repository\": \"<string>\",\n \"tests\": [\n {\n \"name\": \"<string>\",\n \"file\": \"<string>\",\n \"line\": 5000000\n }\n ]\n },\n \"blob\": {\n \"data\": \"<string>\"\n },\n \"schemaVersion\": 1\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.outerlayer.ai/v1/artifacts")
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 \"artifact\": {\n \"bytes\": 1,\n \"caption\": \"<string>\",\n \"clientArtifactId\": \"<string>\",\n \"emittedAt\": \"<string>\",\n \"filename\": \"<string>\",\n \"mediaType\": \"<string>\",\n \"sha256\": \"<string>\",\n \"ci\": true,\n \"commitSha\": \"<string>\",\n \"criterionId\": \"<string>\",\n \"gitBranch\": \"<string>\",\n \"gitRepo\": \"<string>\",\n \"prNumber\": 1,\n \"replaces\": \"<string>\",\n \"replacesTargets\": [\n \"<string>\"\n ],\n \"repository\": \"<string>\",\n \"tests\": [\n {\n \"name\": \"<string>\",\n \"file\": \"<string>\",\n \"line\": 5000000\n }\n ]\n },\n \"blob\": {\n \"data\": \"<string>\"\n },\n \"schemaVersion\": 1\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "<string>",
"kind": "video",
"prNumber": 123,
"provenance": "session",
"repository": "<string>",
"verification": "pending"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"limit": 123,
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"currentBytes": 123,
"limitBytes": 123,
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}Emit an artifact (evidence for a pull request)
Accepts one artifact — a screenshot, recording, report, or log emitted as proof that a change works — plus its content-addressed blob. The artifact anchors to a pull request directly (prNumber), through the recorded session that produced it, or through git context resolved to a PR later; a request with no anchor at all is refused and nothing is stored. kind is inferred from the media type and provenance (session / ci / local) is derived from the submission path — neither can be supplied by the caller. A session binding must name a session that already synced for the app, with turnIndex within its recorded turns; note the binding proves the session exists, not that the bytes came from it. ci is advisory: it is honored only for a shared (non-actor) API key and downgrades to local otherwise. An XML file whose root is testsuites or testsuite is stored as kind test with its parsed tests; tests selects which of them bind to the criterion. A JUnit file that declares a DOCTYPE, malformed XML bound to a criterion, or a selected test the file lacks is refused with unreadable_test_results. bytes must equal the decoded blob length. emittedAt is display metadata; because it also starts the unmatched grace clock, the stored value is clamped to a bounded window ending at server receipt time. Retrying with the same clientArtifactId returns the already-stored artifact when the content matches and fails with 409 when it does not. replaces names the clientArtifactId of an earlier artifact this one corrects: on success that artifact is marked superseded in the same request, and every display surface hides it from then on (its bytes and deep link still work). replacesTargets names several at once (2-20 ids, no repeats) — one correction retires every listed target. REQUIRED together: when replacesTargets is present, replaces MUST also be present and equal replacesTargets[0] — a request with replacesTargets and no replaces, or a replaces that disagrees with the array’s first element, is refused as malformed rather than guessed at, so a caller unaware of replacesTargets always sees exactly one target retired, never zero. Every named target is resolved by its own id and stamped in a single all-or-nothing operation: an unknown, cross-app, or already-superseded target refuses the WHOLE request, naming the offending id — nothing is created and nothing is stamped.
curl --request POST \
--url https://api.outerlayer.ai/v1/artifacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'X-Outerlayer-App-Id: <api-key>' \
--data '
{
"artifact": {
"bytes": 1,
"caption": "<string>",
"clientArtifactId": "<string>",
"emittedAt": "<string>",
"filename": "<string>",
"mediaType": "<string>",
"sha256": "<string>",
"ci": true,
"commitSha": "<string>",
"criterionId": "<string>",
"gitBranch": "<string>",
"gitRepo": "<string>",
"prNumber": 1,
"replaces": "<string>",
"replacesTargets": [
"<string>"
],
"repository": "<string>",
"tests": [
{
"name": "<string>",
"file": "<string>",
"line": 5000000
}
]
},
"blob": {
"data": "<string>"
},
"schemaVersion": 1
}
'import requests
url = "https://api.outerlayer.ai/v1/artifacts"
payload = {
"artifact": {
"bytes": 1,
"caption": "<string>",
"clientArtifactId": "<string>",
"emittedAt": "<string>",
"filename": "<string>",
"mediaType": "<string>",
"sha256": "<string>",
"ci": True,
"commitSha": "<string>",
"criterionId": "<string>",
"gitBranch": "<string>",
"gitRepo": "<string>",
"prNumber": 1,
"replaces": "<string>",
"replacesTargets": ["<string>"],
"repository": "<string>",
"tests": [
{
"name": "<string>",
"file": "<string>",
"line": 5000000
}
]
},
"blob": { "data": "<string>" },
"schemaVersion": 1
}
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({
artifact: {
bytes: 1,
caption: '<string>',
clientArtifactId: '<string>',
emittedAt: '<string>',
filename: '<string>',
mediaType: '<string>',
sha256: '<string>',
ci: true,
commitSha: '<string>',
criterionId: '<string>',
gitBranch: '<string>',
gitRepo: '<string>',
prNumber: 1,
replaces: '<string>',
replacesTargets: ['<string>'],
repository: '<string>',
tests: [{name: '<string>', file: '<string>', line: 5000000}]
},
blob: {data: '<string>'},
schemaVersion: 1
})
};
fetch('https://api.outerlayer.ai/v1/artifacts', 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/artifacts",
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([
'artifact' => [
'bytes' => 1,
'caption' => '<string>',
'clientArtifactId' => '<string>',
'emittedAt' => '<string>',
'filename' => '<string>',
'mediaType' => '<string>',
'sha256' => '<string>',
'ci' => true,
'commitSha' => '<string>',
'criterionId' => '<string>',
'gitBranch' => '<string>',
'gitRepo' => '<string>',
'prNumber' => 1,
'replaces' => '<string>',
'replacesTargets' => [
'<string>'
],
'repository' => '<string>',
'tests' => [
[
'name' => '<string>',
'file' => '<string>',
'line' => 5000000
]
]
],
'blob' => [
'data' => '<string>'
],
'schemaVersion' => 1
]),
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/artifacts"
payload := strings.NewReader("{\n \"artifact\": {\n \"bytes\": 1,\n \"caption\": \"<string>\",\n \"clientArtifactId\": \"<string>\",\n \"emittedAt\": \"<string>\",\n \"filename\": \"<string>\",\n \"mediaType\": \"<string>\",\n \"sha256\": \"<string>\",\n \"ci\": true,\n \"commitSha\": \"<string>\",\n \"criterionId\": \"<string>\",\n \"gitBranch\": \"<string>\",\n \"gitRepo\": \"<string>\",\n \"prNumber\": 1,\n \"replaces\": \"<string>\",\n \"replacesTargets\": [\n \"<string>\"\n ],\n \"repository\": \"<string>\",\n \"tests\": [\n {\n \"name\": \"<string>\",\n \"file\": \"<string>\",\n \"line\": 5000000\n }\n ]\n },\n \"blob\": {\n \"data\": \"<string>\"\n },\n \"schemaVersion\": 1\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/artifacts")
.header("X-Outerlayer-App-Id", "<api-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"artifact\": {\n \"bytes\": 1,\n \"caption\": \"<string>\",\n \"clientArtifactId\": \"<string>\",\n \"emittedAt\": \"<string>\",\n \"filename\": \"<string>\",\n \"mediaType\": \"<string>\",\n \"sha256\": \"<string>\",\n \"ci\": true,\n \"commitSha\": \"<string>\",\n \"criterionId\": \"<string>\",\n \"gitBranch\": \"<string>\",\n \"gitRepo\": \"<string>\",\n \"prNumber\": 1,\n \"replaces\": \"<string>\",\n \"replacesTargets\": [\n \"<string>\"\n ],\n \"repository\": \"<string>\",\n \"tests\": [\n {\n \"name\": \"<string>\",\n \"file\": \"<string>\",\n \"line\": 5000000\n }\n ]\n },\n \"blob\": {\n \"data\": \"<string>\"\n },\n \"schemaVersion\": 1\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.outerlayer.ai/v1/artifacts")
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 \"artifact\": {\n \"bytes\": 1,\n \"caption\": \"<string>\",\n \"clientArtifactId\": \"<string>\",\n \"emittedAt\": \"<string>\",\n \"filename\": \"<string>\",\n \"mediaType\": \"<string>\",\n \"sha256\": \"<string>\",\n \"ci\": true,\n \"commitSha\": \"<string>\",\n \"criterionId\": \"<string>\",\n \"gitBranch\": \"<string>\",\n \"gitRepo\": \"<string>\",\n \"prNumber\": 1,\n \"replaces\": \"<string>\",\n \"replacesTargets\": [\n \"<string>\"\n ],\n \"repository\": \"<string>\",\n \"tests\": [\n {\n \"name\": \"<string>\",\n \"file\": \"<string>\",\n \"line\": 5000000\n }\n ]\n },\n \"blob\": {\n \"data\": \"<string>\"\n },\n \"schemaVersion\": 1\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "<string>",
"kind": "video",
"prNumber": 123,
"provenance": "session",
"repository": "<string>",
"verification": "pending"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"limit": 123,
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"currentBytes": 123,
"limitBytes": 123,
"message": "<string>"
}
}{
"error": {
"code": "<string>",
"message": "<string>"
}
}Authorizations
Factory id the request is scoped to.
API key (sk_outerlayer_*)
Body
Response
The stored artifact (the existing row on an idempotent retry).
Show child attributes
Show child attributes