curl --request POST \
--url https://api.vainona.ai/v1/namespaces/{ns}/groups \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"key": "<string>",
"aggregate": {
"count": true,
"count_where": {},
"sum": [
"<string>"
],
"avg": [
"<string>"
],
"min": [
"<string>"
],
"max": [
"<string>"
],
"quantiles": [
"<string>"
]
},
"match": {},
"keep_keys": "<string>"
}
'import requests
url = "https://api.vainona.ai/v1/namespaces/{ns}/groups"
payload = {
"name": "<string>",
"key": "<string>",
"aggregate": {
"count": True,
"count_where": {},
"sum": ["<string>"],
"avg": ["<string>"],
"min": ["<string>"],
"max": ["<string>"],
"quantiles": ["<string>"]
},
"match": {},
"keep_keys": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
key: '<string>',
aggregate: {
count: true,
count_where: {},
sum: ['<string>'],
avg: ['<string>'],
min: ['<string>'],
max: ['<string>'],
quantiles: ['<string>']
},
match: {},
keep_keys: '<string>'
})
};
fetch('https://api.vainona.ai/v1/namespaces/{ns}/groups', 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.vainona.ai/v1/namespaces/{ns}/groups",
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([
'name' => '<string>',
'key' => '<string>',
'aggregate' => [
'count' => true,
'count_where' => [
],
'sum' => [
'<string>'
],
'avg' => [
'<string>'
],
'min' => [
'<string>'
],
'max' => [
'<string>'
],
'quantiles' => [
'<string>'
]
],
'match' => [
],
'keep_keys' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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.vainona.ai/v1/namespaces/{ns}/groups"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"key\": \"<string>\",\n \"aggregate\": {\n \"count\": true,\n \"count_where\": {},\n \"sum\": [\n \"<string>\"\n ],\n \"avg\": [\n \"<string>\"\n ],\n \"min\": [\n \"<string>\"\n ],\n \"max\": [\n \"<string>\"\n ],\n \"quantiles\": [\n \"<string>\"\n ]\n },\n \"match\": {},\n \"keep_keys\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.vainona.ai/v1/namespaces/{ns}/groups")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"key\": \"<string>\",\n \"aggregate\": {\n \"count\": true,\n \"count_where\": {},\n \"sum\": [\n \"<string>\"\n ],\n \"avg\": [\n \"<string>\"\n ],\n \"min\": [\n \"<string>\"\n ],\n \"max\": [\n \"<string>\"\n ],\n \"quantiles\": [\n \"<string>\"\n ]\n },\n \"match\": {},\n \"keep_keys\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vainona.ai/v1/namespaces/{ns}/groups")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"key\": \"<string>\",\n \"aggregate\": {\n \"count\": true,\n \"count_where\": {},\n \"sum\": [\n \"<string>\"\n ],\n \"avg\": [\n \"<string>\"\n ],\n \"min\": [\n \"<string>\"\n ],\n \"max\": [\n \"<string>\"\n ],\n \"quantiles\": [\n \"<string>\"\n ]\n },\n \"match\": {},\n \"keep_keys\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"name": "<string>",
"key": "<string>",
"aggregate": {
"count": true,
"count_where": {},
"sum": [
"<string>"
],
"avg": [
"<string>"
],
"min": [
"<string>"
],
"max": [
"<string>"
],
"quantiles": [
"<string>"
]
},
"keep_keys": "<string>",
"status": "building",
"created_at": "2023-11-07T05:31:56Z",
"match": {},
"template": "<string>",
"job_id": "<string>",
"readers": [
"<string>"
],
"live_keys": 1,
"warnings": [
"group_over_cap"
],
"as_of": 1,
"rows": [
{
"key": "<string>",
"values": {}
}
]
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}Create a group
Relations (§6.5.3). A group keeps aggregates per value of a key
attribute, over the documents matching match, so a judgment can
read the population its document belongs to (context.group), and
GET can report them. The aggregates are exact as of the last merge
(a namespace with a group reader merges at least hourly).
Creating one starts a group_build job that computes its first
aggregates with no fan-out; judgments that read it wait for the job.
On a template prefix it exists in every namespace under it, each with
its own aggregates. At most 8 groups per namespace, counting
inherited ones, and at most 1,000 live key values per group: a key
attribute with more at create is invalid_request. A key value with
no matching document newer than keep_keys is not live.
curl --request POST \
--url https://api.vainona.ai/v1/namespaces/{ns}/groups \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"key": "<string>",
"aggregate": {
"count": true,
"count_where": {},
"sum": [
"<string>"
],
"avg": [
"<string>"
],
"min": [
"<string>"
],
"max": [
"<string>"
],
"quantiles": [
"<string>"
]
},
"match": {},
"keep_keys": "<string>"
}
'import requests
url = "https://api.vainona.ai/v1/namespaces/{ns}/groups"
payload = {
"name": "<string>",
"key": "<string>",
"aggregate": {
"count": True,
"count_where": {},
"sum": ["<string>"],
"avg": ["<string>"],
"min": ["<string>"],
"max": ["<string>"],
"quantiles": ["<string>"]
},
"match": {},
"keep_keys": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
key: '<string>',
aggregate: {
count: true,
count_where: {},
sum: ['<string>'],
avg: ['<string>'],
min: ['<string>'],
max: ['<string>'],
quantiles: ['<string>']
},
match: {},
keep_keys: '<string>'
})
};
fetch('https://api.vainona.ai/v1/namespaces/{ns}/groups', 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.vainona.ai/v1/namespaces/{ns}/groups",
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([
'name' => '<string>',
'key' => '<string>',
'aggregate' => [
'count' => true,
'count_where' => [
],
'sum' => [
'<string>'
],
'avg' => [
'<string>'
],
'min' => [
'<string>'
],
'max' => [
'<string>'
],
'quantiles' => [
'<string>'
]
],
'match' => [
],
'keep_keys' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"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.vainona.ai/v1/namespaces/{ns}/groups"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"key\": \"<string>\",\n \"aggregate\": {\n \"count\": true,\n \"count_where\": {},\n \"sum\": [\n \"<string>\"\n ],\n \"avg\": [\n \"<string>\"\n ],\n \"min\": [\n \"<string>\"\n ],\n \"max\": [\n \"<string>\"\n ],\n \"quantiles\": [\n \"<string>\"\n ]\n },\n \"match\": {},\n \"keep_keys\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.vainona.ai/v1/namespaces/{ns}/groups")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"key\": \"<string>\",\n \"aggregate\": {\n \"count\": true,\n \"count_where\": {},\n \"sum\": [\n \"<string>\"\n ],\n \"avg\": [\n \"<string>\"\n ],\n \"min\": [\n \"<string>\"\n ],\n \"max\": [\n \"<string>\"\n ],\n \"quantiles\": [\n \"<string>\"\n ]\n },\n \"match\": {},\n \"keep_keys\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vainona.ai/v1/namespaces/{ns}/groups")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"key\": \"<string>\",\n \"aggregate\": {\n \"count\": true,\n \"count_where\": {},\n \"sum\": [\n \"<string>\"\n ],\n \"avg\": [\n \"<string>\"\n ],\n \"min\": [\n \"<string>\"\n ],\n \"max\": [\n \"<string>\"\n ],\n \"quantiles\": [\n \"<string>\"\n ]\n },\n \"match\": {},\n \"keep_keys\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"name": "<string>",
"key": "<string>",
"aggregate": {
"count": true,
"count_where": {},
"sum": [
"<string>"
],
"avg": [
"<string>"
],
"min": [
"<string>"
],
"max": [
"<string>"
],
"quantiles": [
"<string>"
]
},
"keep_keys": "<string>",
"status": "building",
"created_at": "2023-11-07T05:31:56Z",
"match": {},
"template": "<string>",
"job_id": "<string>",
"readers": [
"<string>"
],
"live_keys": 1,
"warnings": [
"group_over_cap"
],
"as_of": 1,
"rows": [
{
"key": "<string>",
"values": {}
}
]
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}{
"error": {
"code": "invalid_request",
"message": "<string>",
"details": {
"suggested_context": {
"recipe": {
"fields": [
"<string>"
],
"last_n": {},
"window": {},
"max_tokens": 2,
"related": {},
"group": {},
"previous": {
"fields": [
"<string>"
],
"anchor": "<string>"
}
},
"cost_per_answer": 1,
"whole_state_cost_per_answer": 1,
"sampled_documents": 2,
"excluded": [
{
"path": "<string>",
"reason": "id"
}
]
}
}
}
}Authorizations
An organization API key. Keys carry a role (read_write or
read_only) and may be restricted to a namespace prefix such as
acme/*, or to one namespace such as acme/prod/tenant_1. A prefix
matches on a / boundary: acme/prod/tenant_1* covers
acme/prod/tenant_1 and everything under acme/prod/tenant_1/,
never acme/prod/tenant_12.
Headers
Returns the original response verbatim while the node still caches it. Correctness never depends on it.
1Path Parameters
A namespace name, or a template prefix ending in /* (§7.7), with any
/ sent as %2F: acme%2Fprod%2Ftenant_123 or acme%2Fprod%2F*.
A namespace name, or a template prefix: a namespace path ending in
/*, such as acme/prod/*, which every namespace under acme/prod/
inherits judgments from (§7.7). Up to 256 bytes.
1 - 256^[A-Za-z0-9._:/-]+(/\*)?$Body
Relations (§6.5.3). A group definition.
A judgment, attribute or threshold name. Names are path segments in field references, so they never contain ..
1 - 128^[A-Za-z0-9_-]+$The attribute whose values the group is kept per, such as attributes.plan. A document with no value there belongs to no key value.
^attributes\.[A-Za-z0-9_-]+$Relations (§6.5.3). What the group keeps per key value, over the
documents matching match, exact as of the last merge. At most 8
paths in total. quantiles keeps a mergeable sketch per path, which
position reads and GET reports as p50, p90 and p99. count_where
counts documents by plain judgments' answers, with the roll-up rules;
sum and avg over answers are invalid_request.
Show child attributes
Show child attributes
Which documents count. All documents when absent.
Show child attributes
Show child attributes
A key value whose newest matching document was created longer ago than this is not live, and leaves the rows and the 1,000 cap. Defaults to 90d.
^[1-9][0-9]*[smhd]$Response
The group, building, with its group_build job in job_id.
Relations (§6.5.3). A group, its status and, on get in a namespace, its rows.
A judgment, attribute or threshold name. Names are path segments in field references, so they never contain ..
1 - 128^[A-Za-z0-9_-]+$^attributes\.[A-Za-z0-9_-]+$Relations (§6.5.3). What the group keeps per key value, over the
documents matching match, exact as of the last merge. At most 8
paths in total. quantiles keeps a mergeable sketch per path, which
position reads and GET reports as p50, p90 and p99. count_where
counts documents by plain judgments' answers, with the roll-up rules;
sum and avg over answers are invalid_request.
Show child attributes
Show child attributes
A whole number and a unit (s, m, h or d), such as 7d or 1h.
^[1-9][0-9]*[smhd]$building until its group_build job has computed the aggregates, then ready. Judgments that read it wait for it.
building, ready RFC 3339, UTC.
Entities, E1 (§6.5). Which documents something applies to, by their
attributes. Each key is an attribute path; a value is equality, and a
list of values is In, as in query filters. Keys combine with And.
Show child attributes
Show child attributes
Present when the group is inherited, the template prefix it comes from.
1 - 256^[A-Za-z0-9._:/-]+(/\*)?$The group_build job, while it runs.
The judgments that read the group. Deleting it is conflict while there are any.
A judgment, attribute or threshold name. Names are path segments in field references, so they never contain ..
1 - 128^[A-Za-z0-9_-]+$Live key values now; null on a template's prefix path.
x >= 0group_over_cap: more than 1,000 live key values, so its readers read stale (group_over_cap) until key values age out past keep_keys.
group_over_cap The seq of the merge the rows are exact at. Absent from lists and on a template's prefix path.
x >= 0On get in a namespace, one row per live key value, the largest count first. Null on a template's prefix path, absent from lists.
1000Show child attributes
Show child attributes