Inovacc Developer

API reference

Data

Databases, collections and records with schemas and rules, over one REST/JSON door.

Base URL https://data.inovacc.dev

Overview

Data is your application's database, served over one REST/JSON door at https://data.inovacc.dev. You create databases, give them collections, and read and write records with plain HTTP calls. A collection is either typed, with fields declared by a schema, or free, holding schemaless JSON documents. Who may do what is decided on every request, first by the credential's permissions and then, record by record, by the rules the collection declares.

The problem it solves is running persistent data without running a database: no server, no connection pool, no migrations tool, no backups to schedule. Your application keeps its own code and stores its data here; where the data lives is decided by Inovacc and never shown. The same API serves your servers, your web pages (with a browser key) and the people signed in to your app, and a realtime listener pushes changes as they happen.

Use Data for application state: users' notes, orders, settings, catalogues, anything shaped as records. Store large binary content with Files, read what happened with the Activity log, and notify other systems with Events.

Concepts

Databases. A database is a key your application chooses, such as shop or crm/v2 (a / is written %2F in a path). A database belongs to the application whose credential created it; another application's database answers 404, as if it did not exist. An organization-level credential sees every database of the organization.

Collections: schema or free. A schema collection declares up to 64 fields, each with a type: text, integer, number, bool, datetime, json, relation (a record id in a target collection) or blob (the SHA-256 of a file in the same database). A field can be required, unique, indexed, or fulltext (on text). A free collection declares nothing: each record is a JSON document, queried by dotted path (profile.city), and it can be given a schema later once every document fits. Writing to a collection that does not exist creates a free one, when your credential may change schemas.

Records and versions. A record is {id, version, created_at, updated_at, ...fields}. Its id is made by the service (rec_ plus 26 sortable characters) or chosen by you. Every change raises version by one; updates and deletes must send If-Match: <version>, so two writers never overwrite each other silently.

Rules. A collection declares five rules, list, view, create, update and delete. Each is null (nobody), "" (any server credential of your organization), public (anyone, browser keys included), users (the signed-in people of the owning application, and servers), or an expression such as owner = @auth.id. Rules apply to every credential, an administrator's included.

Credentials. A secret key is for servers. A publishable key (apb_...) is for web pages: it works only from the origins its application lists and only where a rule admits it. A signed-in person's access token carries only record and file permissions.

How it works

Every call carries Authorization: Bearer <credential>. There is no organization header: your organization, and the application, come from the credential, and nothing in a path, query or header can name another. A request without a valid credential, or to a path that is not a route, gets one identical 401.

For each call the service first checks the credential's permission for the action (read, write, create, update, delete, or schema changes) on the database. Anything but "allow" is 403 forbidden with no detail. Then the collection's rule decides per record: a list returns only the records the list rule admits; reading a record the view rule hides is 404, the same as a missing record; a create checks the new data; an update checks both the stored record and the new data.

Writes are versioned. POST /records creates and answers 201 with ETag: "1". PATCH changes the named fields (on a free collection it is a JSON merge patch), PUT replaces the record, and PUT with If-None-Match: * creates it at your chosen id. A wrong If-Match is 409 version_conflict; a missing one is 428.

Lists take filter (field op value joined by && and ||), sort, limit (up to 200) and an opaque cursor, and return next_cursor until the last page; q searches the fulltext fields. A batch applies up to 100 creates, updates and deletes in one transaction: all succeed, or none does.

A realtime listener is a WebSocket on GET .../collections/{c}/listen: it sends ready, then added, modified and removed events for the records your rule lets you list. Every call is recorded in the Activity log.

Get started

You need a secret key with permission to create databases and collections (Authentication).

  1. Create a database. PUT /v1/databases/notes with no body. The answer is 201 with {key, created_at}; repeating it answers 200.
  2. Declare a collection. PUT /v1/databases/notes/collections/items with fields owner and body and rules (see the samples). The answer is the collection with schema_version 1.
  3. Write a record. POST .../collections/items/records with the fields. The answer is 201 with the record, its id and version 1.
  4. Update it. PATCH .../records/{id} with If-Match: 1. The answer has version 2; sending If-Match: 1 again is 409 version_conflict.
  5. List. GET .../records?filter=owner%20%3D%20%22me%22&limit=10 returns your record and next_cursor: null.

Use cases

Per-user data in a web app. A notes app declares a collection whose five rules say owner = @auth.id. The page calls Data directly with the signed-in person's token; each person lists and edits only their own notes, and another person's note is a 404. No server code enforces it: the rules do.

A public catalogue. An online shop keeps products as a free collection with list and view set to public and the writes closed. The storefront reads it from the browser with a publishable key; the back office writes it from a server with a secret key.

Live dashboards. An operations screen opens a listener on orders filtered to today's open orders. It runs the query after ready, then applies added, modified and removed events as they arrive, without polling.

Limits and pricing

LimitValue
JSON request body1 MiB
One record900 KiB
A json field64 KiB
Fields per collection; collections per database64; 64
Records per page200 (default 50)
Operations per batch100, in one transaction
Filter2,048 characters, 40 values, nesting depth 8
Query string16 KiB
Full-text search q16 terms, 256 characters
Listeners per collection; one listener event1,000; 512 KiB
Rate600 requests per minute per credential holder, per location (429 rate_limited)

Pricing: on request.

Errors

StatusCodeWhat it means and what to do
400invalid_body, invalid_name, unknown_field, missing_field, invalid_valueThe body or a name breaks the rules; fix it.
400record_too_large, field_too_large, reserved_fieldShrink the record; do not send id, version, created_at, updated_at in a body.
400invalid_schema, invalid_rule, schema_change_unsupportedThe collection definition is not valid, or a field was changed in place.
400invalid_filter, invalid_sort, invalid_cursor, invalid_limit, invalid_searchFix the list query; start again without the cursor.
400invalid_if_match, batch_too_large, cross_shard_batchCheck the header or split the batch.
401invalid_credentialsSend a valid credential.
403forbiddenThe credential's permissions or the collection's rule refuse it.
403origin_not_allowed, secret_key_in_browserA browser key from an unlisted origin, or a secret key in a browser.
404database_not_found, collection_not_found, record_not_foundIt does not exist, or you may not see it.
409version_conflictSomeone changed the record; read it again and retry.
409already_exists, unique_violation, not_empty, schema_violationThe id or value is taken; empty it before deleting; documents do not fit the schema.
412, 428precondition_failed, precondition_requiredThe record already exists; send If-Match.
413payload_too_largeThe body is over 1 MiB.
429rate_limited, too_many_listenersSlow down; close listeners you do not need.
503service_unavailableRetry later; nothing was written.

Best practices

  • Always send If-Match with the version you read, and on 409 version_conflict re-read before retrying.
  • Close every collection by default and open it rule by rule; a collection born from a write admits any server credential until you tighten it.
  • Never put a secret key in a web page: use a publishable key and public or @auth rules.
  • Choose your own record ids for imports, so a retried import creates nothing twice (409 already_exists).
  • Use a batch when several writes must succeed together.
  • Index the fields you filter and sort on, and follow next_cursor instead of large pages.
  • With a listener, wait for ready before querying, then drop events not newer than what the query returned.

Authentication

Every call carries your key; your organization comes from it. See the authentication guide.

HeaderAuthorizationBearer <API key>

Endpoints

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

Example

Language

⋮
GET /v1/databasesExample

cURL

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());

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())

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))
}

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(())
}

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());

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);

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}"

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());
    }
}

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()}");

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()}")
}

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))
Source details

Catalogue entry

Id
identity/component-taxonomy/capabilities#data
Repository
identity
Path
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82

Example

Id
identity/component-taxonomy/quickstart#data
Repository
identity
Path
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82
Updated 2026-10-10.