curl --request POST \
--url https://api.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation} \
--header 'Content-Type: application/json' \
--data '
{
"requestId": "Example requestId",
"version": 42,
"input": {},
"context": {},
"captchaToken": "Example captchaToken"
}
'import requests
url = "https://api.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}"
payload = {
"requestId": "Example requestId",
"version": 42,
"input": {},
"context": {},
"captchaToken": "Example captchaToken"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
requestId: 'Example requestId',
version: 42,
input: {},
context: {},
captchaToken: 'Example captchaToken'
})
};
fetch('https://api.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}', 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.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}",
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([
'requestId' => 'Example requestId',
'version' => 42,
'input' => [
],
'context' => [
],
'captchaToken' => 'Example captchaToken'
]),
CURLOPT_HTTPHEADER => [
"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.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}"
payload := strings.NewReader("{\n \"requestId\": \"Example requestId\",\n \"version\": 42,\n \"input\": {},\n \"context\": {},\n \"captchaToken\": \"Example captchaToken\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}")
.header("Content-Type", "application/json")
.body("{\n \"requestId\": \"Example requestId\",\n \"version\": 42,\n \"input\": {},\n \"context\": {},\n \"captchaToken\": \"Example captchaToken\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"requestId\": \"Example requestId\",\n \"version\": 42,\n \"input\": {},\n \"context\": {},\n \"captchaToken\": \"Example captchaToken\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {}
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": true,
"message": "Example message",
"code": "NOT_FOUND",
"details": {},
"correlationId": "Example correlationId",
"error": {}
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}Call a gateway operation of an adapter a published widget uses
The door a widget’s adapter module calls, from the visitor’s browser, for the operations that cannot run in the browser: anything with a secret, a charge in credits or the owner’s storage behind it. A widget that uses such an adapter is handed the address of this route as adapters.url in its widgetic:host message (see the host bridge contract) and calls {adapters.url}/{adapterId}/{operation}. It is not meant to be called by hand, and it takes no API key.
Address. https://api.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}: the root of the API host, NOT under /v1. A client generated from this description for https://api.widgetic.com/v1 must be given the base address https://api.widgetic.com for the calls of the Public Embed tag.
What it serves. Only an adapter, and a version of it, that the widget’s LIVE manifest (what the version that is published uses) lists, and only an operation that runs on the gateway. Anything else is one 404 ADAPTER_NOT_AVAILABLE, whatever the reason, so the route is no way to ask what a widget contains. An adapter an administrator stopped offering to NEW widgets keeps serving the widgets that already use it.
Who may call. The same rule as the entry route: a permitted Origin header, which is the artifact origin the widget runs from or any website when the owner has not restricted the allowed websites (when the owner has, one of the websites the owner listed, Widgetic’s own sites, the visitor’s own machine or the owner’s own platform). Page script cannot forge it. A request without a permitted Origin is 403 ORIGIN_NOT_ALLOWED. A composition that is not served (unpublished, or its owner’s plan does not serve it) is 404, with the codes of the other public embed routes. When the partner of the owner signs the host context, a context that is not signed as the partner requires is refused with 403 CONTEXT_SIGNATURE_REQUIRED, CONTEXT_SIGNATURE_INVALID or CONTEXT_SIGNATURE_EXPIRED.
Cost and retries. An operation priced in credits is charged to the owner of the widget (for a customer a partner manages, to the partner’s administrator, within the monthly credits set for that customer), once for each requestId, before it runs, and given back if it does not answer. A requestId that was charged and not given back is 409 REQUEST_ALREADY_USED and the operation is NOT run a second time; one whose charge was given back may be tried again with the same requestId. A visitor never sees an owner’s balance: a feature that cannot be used because its owner has no credits left, because the customer’s monthly credits are spent, or because an administrator stopped the adapter, is the same 503 ADAPTER_UNAVAILABLE as every other “not right now”. The owner is told through a notice.
Limits. In front of everything, 120 calls a minute for each visitor address and composition and 600 a minute for a composition (429 RATE_LIMITED). Then, for each operation of a widget, a rate for each visitor address a minute and a cap for the widget a day: 30 and 5000 for an operation that is free, 10 and 500 for one that costs credits, unless the adapter says otherwise (never above 120 and 100000). A visitor over the rate is 429 RATE_LIMITED; a widget over its cap is 429 WIDGET_LIMIT_REACHED. Both carry a Retry-After header. The body is at most 64 KiB (413 PAYLOAD_TOO_LARGE) and JSON (415 UNSUPPORTED_MEDIA_TYPE); an operation has 30 seconds to answer (504 ADAPTER_TIMEOUT, and the charge is given back).
curl --request POST \
--url https://api.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation} \
--header 'Content-Type: application/json' \
--data '
{
"requestId": "Example requestId",
"version": 42,
"input": {},
"context": {},
"captchaToken": "Example captchaToken"
}
'import requests
url = "https://api.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}"
payload = {
"requestId": "Example requestId",
"version": 42,
"input": {},
"context": {},
"captchaToken": "Example captchaToken"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
requestId: 'Example requestId',
version: 42,
input: {},
context: {},
captchaToken: 'Example captchaToken'
})
};
fetch('https://api.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}', 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.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}",
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([
'requestId' => 'Example requestId',
'version' => 42,
'input' => [
],
'context' => [
],
'captchaToken' => 'Example captchaToken'
]),
CURLOPT_HTTPHEADER => [
"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.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}"
payload := strings.NewReader("{\n \"requestId\": \"Example requestId\",\n \"version\": 42,\n \"input\": {},\n \"context\": {},\n \"captchaToken\": \"Example captchaToken\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}")
.header("Content-Type", "application/json")
.body("{\n \"requestId\": \"Example requestId\",\n \"version\": 42,\n \"input\": {},\n \"context\": {},\n \"captchaToken\": \"Example captchaToken\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.widgetic.com/embed/compositions/{compositionId}/adapters/{adapterId}/{operation}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"requestId\": \"Example requestId\",\n \"version\": 42,\n \"input\": {},\n \"context\": {},\n \"captchaToken\": \"Example captchaToken\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {}
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": true,
"message": "Example message",
"code": "NOT_FOUND",
"details": {},
"correlationId": "Example correlationId",
"error": {}
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}{
"success": false,
"error": "Example error",
"code": "Example code",
"correlationId": "Example correlationId"
}Path Parameters
The id of the adapter, the name its module is known by, for example upload.
40^[a-z][a-z0-9]*(-[a-z0-9]+)*$The name of one operation of the adapter, as its documentation gives it.
40^[a-z][a-z0-9]*(-[a-z0-9]+)*$Body
Names this attempt: 8 to 64 characters, letters, digits, _ and -, the first a letter or a digit (a UUID fits). A module makes a new one for each action of the visitor. An operation that costs credits is charged once for each requestId, so a repeat of one that was charged and not given back is refused (409 REQUEST_ALREADY_USED) and does no work.
^[A-Za-z0-9][A-Za-z0-9_-]{7,63}$The version of the adapter the widget's code was built against (the vN of its import). When it is left out, the newest version the widget's live manifest lists.
1 <= x <= 1000What the operation is asked to do. Each operation says what it accepts and answers 400, 413 or 422 with a code of its own for what it cannot use. Defaults to an empty object.
The host context the embedding page gave the widget, as the entry route takes it (see the description of createPublicCompositionEntry, with its Signed context). It is not authenticated unless the partner signs it. At most 4096 bytes of JSON, the two signature keys included.
The visitor's CAPTCHA token, for an operation that asks for one (the answer CAPTCHA_REQUIRED says so).
4096