curl --request POST \
--url https://api.rial.io/v1/audits/sync \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form image=@example-file \
--form checks=ai \
--form 'metadata={"orderId":"ORD-4471"}' \
--form 'webhook_url=<string>'import requests
url = "https://api.rial.io/v1/audits/sync"
files = { "image": ("example-file", open("example-file", "rb")) }
payload = {
"checks": "ai",
"metadata": "{\"orderId\":\"ORD-4471\"}",
"webhook_url": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('image', '<string>');
form.append('checks', 'ai');
form.append('metadata', '{"orderId":"ORD-4471"}');
form.append('webhook_url', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.rial.io/v1/audits/sync', 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.rial.io/v1/audits/sync",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nai\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\"orderId\":\"ORD-4471\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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.rial.io/v1/audits/sync"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nai\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\"orderId\":\"ORD-4471\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.rial.io/v1/audits/sync")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nai\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\"orderId\":\"ORD-4471\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.rial.io/v1/audits/sync")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nai\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\"orderId\":\"ORD-4471\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "vfy_01HXYZABCDEFGHJKMNPQRSTVWX",
"status": "completed",
"capture_url": "https://verify.rial.io/v/vfy_01HXYZABCDEFGHJKMNPQRSTVWX",
"created_at": "2026-10-14T19:06:57.718Z",
"expires_at": "2026-10-14T20:06:57.718Z",
"captures_count": 1,
"verdict": {
"rial": false,
"signals": [
{
"type": "ai_detected",
"confidence": 0.93
}
]
}
}{
"id": "vfy_01HXYZABCDEFGHJKMNPQRSTVWX",
"status": "partially_captured",
"capture_url": "https://verify.rial.io/v/vfy_01HXYZABCDEFGHJKMNPQRSTVWX",
"created_at": "2026-10-14T19:06:57.718Z",
"expires_at": "2026-10-14T20:06:57.718Z",
"captures_count": 1
}{
"error": {
"code": "missing_image",
"message": "image is required"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key"
}
}{
"error": {
"code": "plan_limit_reached",
"message": "Plan limit reached"
}
}{
"error": {
"code": "image_too_large_for_sync",
"message": "Image over 4 MB"
}
}{
"error": {
"code": "unsupported_image_type",
"message": "Unsupported image type"
}
}{
"error": {
"code": "checks_not_supported",
"message": "Only checks=ai is supported here"
}
}{
"error": {
"code": "too_many_requests",
"message": "Rate limit exceeded"
}
}{
"error": {
"code": "storage_unavailable",
"message": "Try again"
}
}Audit a photo for AI generation in one call
Check a photo for AI generation and get the verdict in the same response. Up to 4 MB; 202 means it finishes in the background.
curl --request POST \
--url https://api.rial.io/v1/audits/sync \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form image=@example-file \
--form checks=ai \
--form 'metadata={"orderId":"ORD-4471"}' \
--form 'webhook_url=<string>'import requests
url = "https://api.rial.io/v1/audits/sync"
files = { "image": ("example-file", open("example-file", "rb")) }
payload = {
"checks": "ai",
"metadata": "{\"orderId\":\"ORD-4471\"}",
"webhook_url": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('image', '<string>');
form.append('checks', 'ai');
form.append('metadata', '{"orderId":"ORD-4471"}');
form.append('webhook_url', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.rial.io/v1/audits/sync', 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.rial.io/v1/audits/sync",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nai\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\"orderId\":\"ORD-4471\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$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.rial.io/v1/audits/sync"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nai\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\"orderId\":\"ORD-4471\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.rial.io/v1/audits/sync")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nai\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\"orderId\":\"ORD-4471\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.rial.io/v1/audits/sync")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"checks\"\r\n\r\nai\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"metadata\"\r\n\r\n{\"orderId\":\"ORD-4471\"}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"webhook_url\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "vfy_01HXYZABCDEFGHJKMNPQRSTVWX",
"status": "completed",
"capture_url": "https://verify.rial.io/v/vfy_01HXYZABCDEFGHJKMNPQRSTVWX",
"created_at": "2026-10-14T19:06:57.718Z",
"expires_at": "2026-10-14T20:06:57.718Z",
"captures_count": 1,
"verdict": {
"rial": false,
"signals": [
{
"type": "ai_detected",
"confidence": 0.93
}
]
}
}{
"id": "vfy_01HXYZABCDEFGHJKMNPQRSTVWX",
"status": "partially_captured",
"capture_url": "https://verify.rial.io/v/vfy_01HXYZABCDEFGHJKMNPQRSTVWX",
"created_at": "2026-10-14T19:06:57.718Z",
"expires_at": "2026-10-14T20:06:57.718Z",
"captures_count": 1
}{
"error": {
"code": "missing_image",
"message": "image is required"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key"
}
}{
"error": {
"code": "plan_limit_reached",
"message": "Plan limit reached"
}
}{
"error": {
"code": "image_too_large_for_sync",
"message": "Image over 4 MB"
}
}{
"error": {
"code": "unsupported_image_type",
"message": "Unsupported image type"
}
}{
"error": {
"code": "checks_not_supported",
"message": "Only checks=ai is supported here"
}
}{
"error": {
"code": "too_many_requests",
"message": "Rate limit exceeded"
}
}{
"error": {
"code": "storage_unavailable",
"message": "Try again"
}
}Authorizations
Secret API key, created in the dashboard under Settings → API. Send it as Authorization: Bearer rk_secret_… from your server only. Publishable keys (pk_live_…) are for the native SDKs and reach the capture endpoints alone. An unknown key returns 401.
Body
The photo: image/jpeg, image/png, image/webp, image/heic or image/heif. Up to 4 MB; larger photos go through the regular audit flow.
Which checks to run. Only ai (AI-generated) is answered in one call. Default ai.
ai Your own keys as a JSON object, sent as a string field; returned with the result and in webhooks. Same rules as metadata on POST /v1/verifications.
"{\"orderId\":\"ORD-4471\"}"
HTTPS URL that receives the result. Same rules as on POST /v1/verifications.
Response
The analysis finished. verdict is set.
An audit as your API key sees it: the verification body with the audit verdict (rial plus the checks that tripped, each with its confidence).
^vfy_[0-9A-HJKMNP-TV-Z]{26}$Lifecycle state. pending → first capture flips to partially_captured → analysis sets the verdict and transitions to completed. Terminal: completed, expired, failed. abandoned is a side-branch off pending/partially_captured, reported by the capture screen via POST /v1/verifications/:token/progress when the end user leaves before finishing — NOT terminal: a later capture resurrects the row to partially_captured like any other.
pending, partially_captured, completed, expired, failed, abandoned x >= 0Verdict of an audit (mode: "audit", a file that already existed). Includes the Rial/no Rial result and the checks that tripped. This is an analysis result, not proof of live capture.
Show child attributes
Show child attributes
Where the photo was taken, from the device. Absent for uploaded files and when the device reported no fix.
Show child attributes
Show child attributes
Whether the photo was taken at expected_location. verified within GPS tolerance; no_match somewhere else; ungeocoded when the address or the fix could not be resolved; rejected when the device reported a mock location.
Show child attributes
Show child attributes
Object-check result. Present when an object check was requested globally or on an image step. With step-only configuration the status is the worst step result and expected_object is omitted. Independent of the fraud verdict.
Show child attributes
Show child attributes
Object-check results keyed by image step. Missing capture verdicts yield inconclusive; otherwise each value is the worst context result for the step, ignoring not_applicable when live evidence exists.
Show child attributes
Show child attributes
Condition assessment. Present only when the verification was created with condition_aspects — the free-form tenant-defined aspect names (any language, any domain). score is the one-decimal average of aspect scores; label buckets it (>=7.5 good, >=5 fair, else poor). Independent of the fraud verdict.
Show child attributes
Show child attributes
Analysis of a video-step clip, sampled at one frame per second and run through the same checks a photo gets. Written asynchronously after capture — poll or use webhooks; absent until the worker has run.
Show child attributes
Show child attributes
An earlier verification of yours whose photo matches this one. method: "phash": the perceptual hashes are within threshold (Hamming distance 0–64; 0 is bit-identical) — a re-upload. method: "embedding": the hashes differ but the photos are the same scene by embedding (cosine) and a geometric check confirmed it (inliers) — a screenshot or a crop. Present only when a match was found. It does not change rial: a legitimate re-submission and a recycled photo look the same — your reviewer decides.
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Integrity seal over the verification record including answers. Present only when answers exist and sealing is on (seal !== false). The hash proves the stored answers have not changed — it does NOT claim they are true or sensor-attested; answers_provenance carries that distinction explicitly.
- Option 1
- Option 2
Show child attributes
Show child attributes