curl --request GET \
--url https://api.sorraia.app/v1/site-catalog \
--header 'X-Sorraia-Site-Key: <api-key>'import requests
url = "https://api.sorraia.app/v1/site-catalog"
headers = {"X-Sorraia-Site-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-Sorraia-Site-Key': '<api-key>'}};
fetch('https://api.sorraia.app/v1/site-catalog', 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.sorraia.app/v1/site-catalog",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-Sorraia-Site-Key: <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"
"net/http"
"io"
)
func main() {
url := "https://api.sorraia.app/v1/site-catalog"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-Sorraia-Site-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.sorraia.app/v1/site-catalog")
.header("X-Sorraia-Site-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sorraia.app/v1/site-catalog")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-Sorraia-Site-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"site": {
"name": "<string>",
"domain": "<string>"
},
"widgets": [
{
"type": "form",
"name": "<string>",
"embedId": 123,
"paused": true
}
]
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}List a site's embeddable widgets
Every embeddable widget on ONE site — forms, booking calendars, shops, AI chat agents and WhatsApp launchers — shaped for a picker. This is the only operation that authenticates with a site key rather than an API key: pass the site’s sor_site_... value in the X-Sorraia-Site-Key header. Find it in the Sorraia dashboard under Site → Embed.
A site key grants this one read on its one site and nothing else. It cannot read submissions, bookings, orders or any other resource, and it is never accepted as an API key.
embedId is the identifier that widget’s embed URL actually takes: the numeric widget id for forms, calendars, shops and WhatsApp, and the string publicId for AI chat. Archived widgets are omitted entirely; paused widgets are listed with paused: true. There is no pagination — the full catalogue is returned, ordered by type then name.
curl --request GET \
--url https://api.sorraia.app/v1/site-catalog \
--header 'X-Sorraia-Site-Key: <api-key>'import requests
url = "https://api.sorraia.app/v1/site-catalog"
headers = {"X-Sorraia-Site-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-Sorraia-Site-Key': '<api-key>'}};
fetch('https://api.sorraia.app/v1/site-catalog', 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.sorraia.app/v1/site-catalog",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-Sorraia-Site-Key: <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"
"net/http"
"io"
)
func main() {
url := "https://api.sorraia.app/v1/site-catalog"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-Sorraia-Site-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.sorraia.app/v1/site-catalog")
.header("X-Sorraia-Site-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sorraia.app/v1/site-catalog")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-Sorraia-Site-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"site": {
"name": "<string>",
"domain": "<string>"
},
"widgets": [
{
"type": "form",
"name": "<string>",
"embedId": 123,
"paused": true
}
]
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}{
"error": "<string>",
"code": "<string>"
}Authorizations
A per-site key (sor_site_...), shown on the site's Embed card in the dashboard (Site → Embed) and regenerable there. It authorises three operations for the single site it belongs to — GET /site-catalog, GET /site-verification and POST /site-verification/attempt — and nothing else. It is NOT an API key: it carries no scopes, reads no customer data, and Authorization: Bearer will not accept it.
It is not purely read-only: the attempt endpoint can mark the site's domain verified. It does so only by asking Sorraia to fetch the site's verification token back from the registered hostname — the key proves account access, never hostname control, so it cannot claim a domain it cannot serve the token from.