Dados
Bancos, coleções e registros com esquemas e regras, por uma única porta REST/JSON.
Visão geral
Dados é o banco de dados da sua aplicação, servido por uma única porta REST/JSON em https://data.inovacc.dev. Você cria bancos de dados, dá a eles coleções e lê e grava registros com chamadas HTTP simples. Uma coleção é tipada, com campos declarados por um esquema, ou livre, contendo documentos JSON sem esquema. Quem pode fazer o quê é decidido em cada requisição, primeiro pelas permissões da credencial e depois, registro a registro, pelas regras que a coleção declara.
O problema que ele resolve é manter dados persistentes sem operar um banco de dados: sem servidor, sem pool de conexões, sem ferramenta de migração, sem backups para agendar. Sua aplicação mantém o próprio código e armazena seus dados aqui; onde os dados vivem é decidido pela Inovacc e nunca é exibido. A mesma API atende os seus servidores, as suas páginas web (com uma chave de navegador) e as pessoas autenticadas no seu aplicativo, e um ouvinte em tempo real envia as mudanças conforme elas acontecem.
Use Dados para o estado da aplicação: notas de usuários, pedidos, configurações, catálogos, qualquer coisa com formato de registro. Armazene conteúdo binário grande com Arquivos, consulte o que aconteceu com o Registro de atividade e avise outros sistemas com Eventos.
Conceitos
Bancos de dados. Um banco de dados é uma chave que a sua aplicação escolhe, como shop ou crm/v2 (uma / é escrita %2F em um caminho). Um banco de dados pertence à aplicação cuja credencial o criou; o banco de dados de outra aplicação responde 404, como se não existisse. Uma credencial no nível da organização enxerga todos os bancos de dados da organização.
Coleções: com esquema ou livres. Uma coleção com esquema declara até 64 campos, cada um com um tipo: text, integer, number, bool, datetime, json, relation (um id de registro em uma coleção target) ou blob (o SHA-256 de um arquivo no mesmo banco de dados). Um campo pode ser required, unique, indexed ou fulltext (em text). Uma coleção livre não declara nada: cada registro é um documento JSON, consultado por caminho com pontos (profile.city), e pode receber um esquema mais tarde, quando todos os documentos se encaixarem nele. Gravar em uma coleção que não existe cria uma coleção livre, quando a sua credencial pode alterar esquemas.
Registros e versões. Um registro é {id, version, created_at, updated_at, ...fields}. Seu id é gerado pelo serviço (rec_ mais 26 caracteres ordenáveis) ou escolhido por você. Toda alteração aumenta version em um; atualizações e exclusões devem enviar If-Match: <version>, para que dois escritores nunca se sobrescrevam silenciosamente.
Regras. Uma coleção declara cinco regras, list, view, create, update e delete. Cada uma é null (ninguém), "" (qualquer credencial de servidor da sua organização), public (qualquer pessoa, chaves de navegador incluídas), users (as pessoas autenticadas da aplicação proprietária, e os servidores) ou uma expressão como owner = @auth.id. As regras valem para todas as credenciais, inclusive a de um administrador.
Credenciais. Uma chave secreta é para servidores. Uma chave publicável (apb_...) é para páginas web: funciona apenas a partir das origens que a sua aplicação lista e apenas onde uma regra a admite. O token de acesso de uma pessoa autenticada carrega apenas permissões de registros e arquivos.
Como funciona
Toda chamada leva Authorization: Bearer <credential>. Não há cabeçalho de organização: a sua organização, e a aplicação, vêm da credencial, e nada em um caminho, consulta ou cabeçalho pode nomear outra. Uma requisição sem credencial válida, ou para um caminho que não é uma rota, recebe um 401 idêntico.
Em cada chamada, o serviço verifica primeiro a permissão da credencial para a ação (leitura, escrita, criação, atualização, exclusão ou alterações de esquema) no banco de dados. Qualquer coisa diferente de "permitir" é 403 forbidden sem detalhes. Depois a regra da coleção decide por registro: uma listagem devolve apenas os registros que a regra list admite; ler um registro que a regra view oculta é 404, igual a um registro inexistente; uma criação verifica os dados novos; uma atualização verifica tanto o registro armazenado quanto os dados novos.
As escritas são versionadas. POST /records cria e responde 201 com ETag: "1". PATCH altera os campos informados (em uma coleção livre é um JSON merge patch), PUT substitui o registro, e PUT com If-None-Match: * o cria no id que você escolheu. Um If-Match errado é 409 version_conflict; um ausente é 428.
As listagens aceitam filter (field op value unidos por && e ||), sort, limit (até 200) e um cursor opaco, e devolvem next_cursor até a última página; q pesquisa os campos fulltext. Um lote aplica até 100 criações, atualizações e exclusões em uma única transação: todas têm sucesso, ou nenhuma.
Um ouvinte em tempo real é um WebSocket em GET .../collections/{c}/listen: ele envia ready e depois eventos added, modified e removed para os registros que a sua regra permite listar. Toda chamada é registrada no Registro de atividade.
Primeiros passos
Você precisa de uma chave secreta com permissão para criar bancos de dados e coleções (Autenticação).
- Crie um banco de dados.
PUT /v1/databases/notessem corpo. A resposta é 201 com{key, created_at}; repetir a chamada responde 200. - Declare uma coleção.
PUT /v1/databases/notes/collections/itemscom os camposownerebodye as regras (veja os exemplos). A resposta é a coleção comschema_version1. - Grave um registro.
POST .../collections/items/recordscom os campos. A resposta é 201 com o registro, seuideversion1. - Atualize-o.
PATCH .../records/{id}comIf-Match: 1. A resposta temversion2; enviarIf-Match: 1de novo resulta em 409version_conflict. - Liste.
GET .../records?filter=owner%20%3D%20%22me%22&limit=10devolve o seu registro enext_cursor: null.
Casos de uso
Dados por usuário em um aplicativo web. Um aplicativo de notas declara uma coleção cujas cinco regras dizem owner = @auth.id. A página chama Dados diretamente com o token da pessoa autenticada; cada pessoa lista e edita apenas as próprias notas, e a nota de outra pessoa resulta em 404. Nenhum código de servidor garante isso: as regras garantem.
Um catálogo público. Uma loja online mantém products como uma coleção livre com list e view definidos como public e as escritas fechadas. A vitrine a lê do navegador com uma chave publicável; o back office a escreve a partir de um servidor com uma chave secreta.
Painéis em tempo real. Uma tela de operações abre um ouvinte em orders filtrado pelos pedidos abertos de hoje. Ela executa a consulta após ready, depois aplica os eventos added, modified e removed conforme chegam, sem fazer polling.
Limites e preços
| Limite | Valor |
|---|---|
| Corpo da requisição JSON | 1 MiB |
| Um registro | 900 KiB |
Um campo json | 64 KiB |
| Campos por coleção; coleções por banco de dados | 64; 64 |
| Registros por página | 200 (padrão 50) |
| Operações por lote | 100, em uma única transação |
| Filtro | 2.048 caracteres, 40 valores, profundidade de aninhamento 8 |
| Query string | 16 KiB |
Busca de texto completo q | 16 termos, 256 caracteres |
| Ouvintes por coleção; um evento de ouvinte | 1.000; 512 KiB |
| Taxa | 600 requisições por minuto por titular de credencial, por localização (429 rate_limited) |
Preços: sob consulta.
Erros
| Status | Código | O que significa e o que fazer |
|---|---|---|
| 400 | invalid_body, invalid_name, unknown_field, missing_field, invalid_value | O corpo ou um nome viola as regras; corrija-o. |
| 400 | record_too_large, field_too_large, reserved_field | Reduza o registro; não envie id, version, created_at, updated_at em um corpo. |
| 400 | invalid_schema, invalid_rule, schema_change_unsupported | A definição da coleção não é válida, ou um campo foi alterado no lugar. |
| 400 | invalid_filter, invalid_sort, invalid_cursor, invalid_limit, invalid_search | Corrija a consulta da listagem; recomece sem o cursor. |
| 400 | invalid_if_match, batch_too_large, cross_shard_batch | Confira o cabeçalho ou divida o lote. |
| 401 | invalid_credentials | Envie uma credencial válida. |
| 403 | forbidden | As permissões da credencial ou a regra da coleção a recusam. |
| 403 | origin_not_allowed, secret_key_in_browser | Uma chave de navegador vinda de uma origem não listada, ou uma chave secreta em um navegador. |
| 404 | database_not_found, collection_not_found, record_not_found | Não existe, ou você não pode vê-lo. |
| 409 | version_conflict | Alguém alterou o registro; leia-o novamente e tente de novo. |
| 409 | already_exists, unique_violation, not_empty, schema_violation | O id ou valor já está em uso; esvazie antes de excluir; os documentos não se encaixam no esquema. |
| 412, 428 | precondition_failed, precondition_required | O registro já existe; envie If-Match. |
| 413 | payload_too_large | O corpo excede 1 MiB. |
| 429 | rate_limited, too_many_listeners | Diminua o ritmo; feche os ouvintes de que não precisa. |
| 503 | service_unavailable | Tente novamente mais tarde; nada foi gravado. |
Boas práticas
- Envie sempre
If-Matchcom a versão que você leu e, em 409version_conflict, leia de novo antes de tentar novamente. - Feche toda coleção por padrão e abra-a regra a regra; uma coleção nascida de uma escrita admite qualquer credencial de servidor até que você a restrinja.
- Nunca coloque uma chave secreta em uma página web: use uma chave publicável e regras
publicou@auth. - Escolha os seus próprios ids de registro nas importações, para que uma importação repetida não crie nada duas vezes (409
already_exists). - Use um lote quando várias escritas precisarem ter sucesso juntas.
- Indexe os campos em que você filtra e ordena e siga
next_cursorem vez de usar páginas grandes. - Com um ouvinte, espere por
readyantes de consultar, e descarte depois os eventos que não sejam mais novos do que o que a consulta devolveu.
Relacionados
- Arquivos: conteúdo binário ao lado dos seus registros
- Registro de atividade: o que foi gravado e por quem
- Eventos: avise outros sistemas sobre uma mudança
- Autenticação, Erros, Limites
Autenticação
Toda chamada leva a sua chave; a sua organização vem dela. Veja o guia de autenticação.
AuthorizationEndpoints
GET /v1/databases
PUT /v1/databases/{database}
PUT /v1/databases/{database}/collections/{collection}
POST /v1/databases/{database}/collections/{collection}/records
POST /v1/databases/{database}/batch
Exemplo
Linguagem
⋮
cURL
curl "https://data.inovacc.dev/v1/databases" \
-H "Authorization: Bearer $INOVACC_API_KEY"curl "https://data.inovacc.dev/v1/databases" \
-H "Authorization: Bearer $INOVACC_API_KEY"
TypeScript
const url = "https://data.inovacc.dev/v1/databases";
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
},
});
console.log(response.status, await response.text());const url = "https://data.inovacc.dev/v1/databases";
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
},
});
console.log(response.status, await response.text());
Python
import os
import urllib.request
request = urllib.request.Request(
"https://data.inovacc.dev/v1/databases",
method="GET",
headers={
"User-Agent": "inovacc-python-sample",
"Authorization": "Bearer " + os.environ["INOVACC_API_KEY"],
},
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())import os
import urllib.request
request = urllib.request.Request(
"https://data.inovacc.dev/v1/databases",
method="GET",
headers={
"User-Agent": "inovacc-python-sample",
"Authorization": "Bearer " + os.environ["INOVACC_API_KEY"],
},
)
with urllib.request.urlopen(request) as response:
print(response.status, response.read().decode())
Go
package main
import (
"fmt"
"io"
"net/http"
"os"
)
const url = "https://data.inovacc.dev/v1/databases"
func main() {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INOVACC_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println(res.Status, string(out))
}package main
import (
"fmt"
"io"
"net/http"
"os"
)
const url = "https://data.inovacc.dev/v1/databases"
func main() {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INOVACC_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println(res.Status, string(out))
}
Rust
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = std::env::var("INOVACC_API_KEY")?;
let response = reqwest::blocking::Client::new()
.get("https://data.inovacc.dev/v1/databases")
.bearer_auth(api_key)
.send()?;
println!("{} {}", response.status(), response.text()?);
Ok(())
}// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = std::env::var("INOVACC_API_KEY")?;
let response = reqwest::blocking::Client::new()
.get("https://data.inovacc.dev/v1/databases")
.bearer_auth(api_key)
.send()?;
println!("{} {}", response.status(), response.text()?);
Ok(())
}
JavaScript
const url = "https://data.inovacc.dev/v1/databases";
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
},
});
console.log(response.status, await response.text());const url = "https://data.inovacc.dev/v1/databases";
const response = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.INOVACC_API_KEY}`,
},
});
console.log(response.status, await response.text());
PHP
<?php
$curl = curl_init('https://data.inovacc.dev/v1/databases');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('INOVACC_API_KEY'),
],
]);
$response = curl_exec($curl);
echo curl_getinfo($curl, CURLINFO_HTTP_CODE), ' ', $response, "\n";
curl_close($curl);<?php
$curl = curl_init('https://data.inovacc.dev/v1/databases');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('INOVACC_API_KEY'),
],
]);
$response = curl_exec($curl);
echo curl_getinfo($curl, CURLINFO_HTTP_CODE), ' ', $response, "\n";
curl_close($curl);
Ruby
require "net/http"
require "uri"
uri = URI('https://data.inovacc.dev/v1/databases')
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('INOVACC_API_KEY')}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts "#{response.code} #{response.body}"require "net/http"
require "uri"
uri = URI('https://data.inovacc.dev/v1/databases')
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('INOVACC_API_KEY')}"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts "#{response.code} #{response.body}"
Java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("INOVACC_API_KEY");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://data.inovacc.dev/v1/databases"))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("INOVACC_API_KEY");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://data.inovacc.dev/v1/databases"))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}
C#
var url = "https://data.inovacc.dev/v1/databases";
var apiKey = Environment.GetEnvironmentVariable("INOVACC_API_KEY");
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Authorization", $"Bearer {apiKey}");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");var url = "https://data.inovacc.dev/v1/databases";
var apiKey = Environment.GetEnvironmentVariable("INOVACC_API_KEY");
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Authorization", $"Bearer {apiKey}");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");
Kotlin
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
fun main() {
val apiKey = System.getenv("INOVACC_API_KEY")
val request = HttpRequest.newBuilder()
.uri(URI.create("https://data.inovacc.dev/v1/databases"))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build()
val response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString())
println("${response.statusCode()} ${response.body()}")
}import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
fun main() {
val apiKey = System.getenv("INOVACC_API_KEY")
val request = HttpRequest.newBuilder()
.uri(URI.create("https://data.inovacc.dev/v1/databases"))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build()
val response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString())
println("${response.statusCode()} ${response.body()}")
}
Swift
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
let apiKey = ProcessInfo.processInfo.environment["INOVACC_API_KEY"] ?? ""
let url = URL(string: "https://data.inovacc.dev/v1/databases")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
let status = (response as? HTTPURLResponse)?.statusCode ?? 0
print(status, String(decoding: data, as: UTF8.self))import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
let apiKey = ProcessInfo.processInfo.environment["INOVACC_API_KEY"] ?? ""
let url = URL(string: "https://data.inovacc.dev/v1/databases")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
let status = (response as? HTTPURLResponse)?.statusCode ?? 0
print(status, String(decoding: data, as: UTF8.self))
Detalhes da fonte
- Id
- identity/component-taxonomy/capabilities#data
- Repositório
- identity
- Caminho
- contracts/component-taxonomy/catalog/capabilities.json
- Commit
- 08ef3cd75d82
- Id
- identity/component-taxonomy/quickstart#data
- Repositório
- identity
- Caminho
- contracts/component-taxonomy/catalog/capabilities.json
- Commit
- 08ef3cd75d82