Inovacc Developer

API reference

Activity log

Read your organization's own log of API calls and file and record events.

Base URL https://data.inovacc.dev

Overview

The Activity log is your organization's own record of what happened on Inovacc: every API call, and every file and record event those calls caused. You read it with one endpoint, GET /v1/activity on https://data.inovacc.dev, filtered by time, kind of event or file hash.

The problem it solves is answering "who did what, and when" without building your own audit trail. The log is kept by Inovacc for every call to the Data, AI and Events APIs, whether or not your application remembers to write anything. Recording is a fact of the service, not an option, and an organization only ever sees its own records.

Use it for audits, for investigating a failed integration ("which calls were refused this morning, and with what code?"), for proving a file was delivered (the download event carries the file's SHA-256), and for a per-key view of traffic. To see what your AI usage cost, use the usage summary of AI chat (GET /v1/usage/summary): the Activity log records events, never prices.

Concepts

Events and kinds. Each record is one event with a kind:

KindWritten when
api.callany authenticated call, refused ones (403, 429) included
record.write, record.deletea record is created, changed or deleted (one per operation of a batch)
blob.write, blob.read, blob.deletea database blob is uploaded, downloaded or deleted
file.upload.completed, file.download, file.deletea file is written, downloaded or deleted
file.upload.started, file.processedwritten by other Inovacc services, such as AI uploads and conversions

What a record holds. The time (at_ms, Unix milliseconds), the organization, who acted (actor_kind: user, key or service, and actor_id), the source service, the method, the route (a template such as /v1/databases/{database}/collections/{collection}/records/{record_id}, never the ids themselves), the status, the outcome, the error_code of a failure, the latency, sizes in and out, and for a file its size, type and SHA-256. related points at the database, collection and record, or the upload or job, concerned. Every key is always present, null when it does not apply.

What it never holds. A prompt, a response body or a file's content. AI calls never record a file name.

Country. An api.call record carries country: the caller's country as Inovacc's network edge sees it (two letters, XX when unknown). It cannot be set by the caller, and the log holds no city, IP address or coordinates.

Retention. Records are kept one year. The last 30 days answer at once; older records come from the archive and take longer.

How it works

Call GET /v1/activity with Authorization: Bearer <credential>. The credential needs the activity read permission at the organization level; a credential limited to records does not have it. The organization is always the credential's: there is no parameter that names another.

Every parameter is optional:

ParameterMeaning
from, toUnix milliseconds, UTC; from inclusive, to exclusive
kindone or more kinds, comma separated
file_sha25664 lowercase hex characters: every event of that file
limit1 to 500, default 100
cursorthe next_cursor of the previous page, unchanged

Any other parameter, a repeated or empty one, or an invalid value is 400 invalid_query, and nothing is read. The answer is newest first: {"items":[...],"next_cursor":<string|null>,"archive_searched":<bool>}. Follow next_cursor until it is null. archive_searched tells you the read reached past the last 30 days. A read can reach back at most 366 days per call.

Records appear a few seconds after the call, not instantly. Recording happens after the response and never changes it: if the log cannot be written, the call is answered as usual. A download from Data or Files is recorded when it starts; a download from the AI API is recorded when the transfer completes. The AI API also offers GET /v1/activity on https://ai.inovacc.dev for its own calls, with from and to as RFC 3339 instants; reading it there is not billed.

Get started

You need a credential with the activity read permission (Authentication).

  1. Make a call to log. Write a record in Data or upload a file in Files.
  2. Read the latest events. GET /v1/activity?limit=10 (see the samples). Your call is there as api.call, with its route template and status, followed by its record.write or file.upload.completed.
  3. Filter by kind. GET /v1/activity?kind=api.call&limit=50 shows only calls; look at status and error_code to find refusals.
  4. Follow one file. Take the file_sha256 of your upload and call GET /v1/activity?file_sha256=<hash>: every upload, download and delete of that content is listed.
  5. Page. Pass the next_cursor back as cursor until it is null.

Use cases

An audit answer. A customer asks who deleted a record last Tuesday. Filter kind=record.delete with from and to around that day; the event names the actor and related names the database, collection and record.

Proof of delivery. A compliance team must show a report reached a partner. The file.download event carries the SHA-256 and size of the bytes sent, and the time, which can be matched against the original file.

Integration health. An integration starts failing at night. Filtering kind=api.call for that window shows the status and error_code of every call by the integration's key, for example a run of 429 rate_limited that points at a missing backoff.

Limits and pricing

LimitValue
Retentionone year; the last 30 days answer at once
Range of one read366 days
Page size1 to 500 (default 100)
Delay before an event is readablea few seconds
Rate600 requests per minute per credential holder, per location

Pricing: on request.

Errors

StatusCodeWhat it means and what to do
400invalid_queryA parameter is unknown, repeated, empty or invalid; fix the query.
401invalid_credentialsSend a valid credential.
403forbiddenThe credential lacks the activity read permission.
429rate_limitedSlow down.
503service_unavailableThe log cannot be read now; retry later.

Best practices

  • Filter on the server: pass kind, from and to instead of reading everything and filtering in your code.
  • Keep the newest at_ms you processed and read from there on the next run, following cursors to the end.
  • Use file_sha256 to trace a file across uploads, downloads and AI processing.
  • Expect a few seconds of delay; do not treat a missing just-made event as a failure.
  • Ignore keys you do not know: records may gain fields.
  • Give each integration its own key, so actor_id tells you which one acted.

Authentication

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

HeaderAuthorizationBearer <API key>

Endpoints

GET /v1/activity

Example

Language

⋮
GET /v1/activityExample

cURL

curl "https://data.inovacc.dev/v1/activity" \
  -H "Authorization: Bearer $INOVACC_API_KEY"

TypeScript

const url = "https://data.inovacc.dev/v1/activity";

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/activity",
    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/activity"

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/activity")
        .bearer_auth(api_key)
        .send()?;
    println!("{} {}", response.status(), response.text()?);
    Ok(())
}

JavaScript

const url = "https://data.inovacc.dev/v1/activity";

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/activity');
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/activity')

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/activity"))
                .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/activity";
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/activity"))
        .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/activity")!
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#activity
Repository
identity
Path
contracts/component-taxonomy/catalog/capabilities.json
Commit
08ef3cd75d82

Example

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