# 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](/en/products/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`:

| Kind | Written when |
|---|---|
| `api.call` | any authenticated call, refused ones (`403`, `429`) included |
| `record.write`, `record.delete` | a record is created, changed or deleted (one per operation of a batch) |
| `blob.write`, `blob.read`, `blob.delete` | a database blob is uploaded, downloaded or deleted |
| `file.upload.completed`, `file.download`, `file.delete` | a file is written, downloaded or deleted |
| `file.upload.started`, `file.processed` | written 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:

| Parameter | Meaning |
|---|---|
| `from`, `to` | Unix milliseconds, UTC; `from` inclusive, `to` exclusive |
| `kind` | one or more kinds, comma separated |
| `file_sha256` | 64 lowercase hex characters: every event of that file |
| `limit` | 1 to 500, default 100 |
| `cursor` | the `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](/en/guides/authentication/)).

1. **Make a call to log.** Write a record in [Data](/en/products/data/) or upload a file in [Files](/en/products/files/).
2. **Read the latest events.** `GET /v1/activity?limit=10` (see [the samples](#example)). 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

| Limit | Value |
|---|---|
| Retention | one year; the last 30 days answer at once |
| Range of one read | 366 days |
| Page size | 1 to 500 (default 100) |
| Delay before an event is readable | a few seconds |
| Rate | 600 requests per minute per credential holder, per location |

**Pricing:** on request.

## Errors

| Status | Code | What it means and what to do |
|---|---|---|
| 400 | `invalid_query` | A parameter is unknown, repeated, empty or invalid; fix the query. |
| 401 | `invalid_credentials` | Send a valid credential. |
| 403 | `forbidden` | The credential lacks the activity read permission. |
| 429 | `rate_limited` | Slow down. |
| 503 | `service_unavailable` | The 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.

## Related

- [Data](/en/products/data/), [Files](/en/products/files/), [Events](/en/products/events/)
- [AI chat](/en/products/ai.chat/): usage and cost per key and route
- [Authentication](/en/guides/authentication/), [Errors](/en/guides/errors/), [Limits](/en/guides/limits/)

## Endpoints

- GET /v1/activity

## Example

```sh
curl -X GET "https://data.inovacc.dev/v1/activity" -H "Authorization: Bearer $INOVACC_API_KEY"
```
