# Xyvenra Data REST API by Ouroboros

The early access REST API gives one bearer key access to a single Xyvenra Data project. Keys are shown once, stored as SHA-256 digests, and can be revoked from the Data console. Keep the key on a trusted server; do not put it in a browser app, source repository, or public log.

In the signed-in Data console, you can create, rename, export, restore, and delete projects; create and delete tables; and edit table fields. Renaming a project keeps its tables, records, files, and API keys attached. Deleting a project removes its tables and records and attempts to remove its private files. Restoring a project creates a new project with new IDs.

## Edit a table's fields

Select a table in the Data console to add an optional field, rename a field, change its type, make it required or optional, or remove it. New fields are optional so existing records remain valid. A table must keep at least one field and can have up to 24. Removing a field deletes that field's values from every record; export a table JSON backup first if you may need them later. A required field must be made optional before removal. Making a field required succeeds only when every existing record already contains it.

Type changes convert existing non-null values in the same database transaction as the schema change. Number conversion accepts a number or numeric text; boolean conversion accepts true/false or 1/0 (including text); text conversion preserves text and serializes other JSON values as text. JSON fields accept the existing JSON value. An incompatible value rejects the entire change, leaving the schema and records as they were. Record update timestamps can advance when a rename, type change, or removal rewrites their data.

The console sends the table's `schema_version` with each change. A concurrent schema update returns `409 Conflict`; refresh and review the latest field list before retrying. These changes operate on Xyvenra's logical JSON tables, not SQL columns or a dedicated Postgres database. A project API key can read and write records within its scope but cannot change table schemas.

## Scope and limits

- A project can have one active key at a time. Revoke it before creating a replacement.
- Choose read-only or read/write scope when creating a key.
- API keys can access only the project they were created for. Table and record operations also verify the project, table, and owner on every query.
- Each key is limited to 60 requests per minute. A `429` response includes `Retry-After`.
- The existing early access data caps still apply: 3 projects per account, 10 tables per project, 250 records per account, and 16 KB per record.
- The REST API accepts a validated batch of up to 250 records in one request. The batch is written as one database insert, so validation or database-limit errors reject the whole batch. Account record caps still apply; a retry after a network timeout may duplicate a batch if the first request committed but its response was lost.
- This API manages JSON records in shared platform Postgres. The Data console also supports private project files, capped at 1 MB each, 100 files and 50 MB per account. It is not a dedicated database and does not provide SQL or an external Postgres connection string.
- The same project API key can list, upload, download, and (with a read/write key) delete private project files. File endpoints share the 60-request-per-minute API key limit.

## Browse records in the console

The table editor loads the account's current bounded record set, then searches record IDs and nested JSON values locally. A field filter supports text/JSON contains and equality, typed number comparisons, boolean equality, and empty/non-empty checks. Results can be sorted by record ID, timestamps, or a typed table field and are shown 25 per page. **Duplicate** copies a record's data fields into the new-record form without its ID or timestamps; review or edit the JSON, then choose **Add record** to save a separate record. Duplicating does not write to storage by itself. Search, filtering, sorting, and pagination do not change stored records or the REST API query semantics. **Export table CSV** and **Export table JSON** include all loaded records, even when the console currently displays a filtered view. **Export matching CSV** includes every loaded record matching the current search and field filter, across all pages, in the current sort order. With no active search or field filter, it includes every loaded record. The matching CSV keeps record IDs and timestamps, encodes JSON fields as JSON text, and quotes values that could be treated as spreadsheet formulas. JSON export includes the field definitions and typed record values, omits record IDs and timestamps, and can be imported as a new table with **Import JSON records**. Both exports are prepared in your browser; keep the downloaded files private.

When you save an edited record, the console submits the version it loaded. If another save changed that record in the meantime, the server returns a conflict and asks you to refresh and review the latest version before saving again. REST API partial record patches also detect concurrent updates and return `409 Conflict`.

Open **Saved views in this browser** below the record controls to reuse a table's search, field filter, and sort order. Set the controls, enter a name, and choose **Save current view**. To return to those settings, select the name and choose **Apply view**. This reruns the view against the records currently loaded and starts on page 1. To replace a saved view, remove it and save the revised settings under the same name. Names are unique per table, ignoring case. Choose **Export views** to download a versioned JSON backup or **Import views** to review and merge one into the selected table. Import validates every filter and sort against the destination table's current fields before saving; invalid or stale-schema views are rejected. Names that already exist in the destination (ignoring case) are skipped, and existing views are never overwritten. The backup contains view names and query/filter/sort settings only, not records, credentials, or table data. View settings remain local to this browser unless you export them; imported files are read and processed in your browser. Keep backups private if their search terms or filter values contain sensitive information. The portable format is `xyvenra-data-record-views`, version 1, and supports up to 500 views and 2 MB per file.

View settings live in browser storage, including any text you enter in search and filter fields. Use a trusted browser and avoid saving sensitive criteria on shared devices. Views are available for the same table in the same browser profile across visits. Clearing site storage removes them. Project backups and restored tables do not carry these views.

Select a table to add an optional text, number, boolean, or JSON field. Field names must start with a lowercase letter and contain only lowercase letters, numbers, or underscores. Existing records are left unchanged; the new field has no value until you edit a record or import data that includes it. Field additions are serialized in the database, so simultaneous changes cannot overwrite each other's schema. Tables support up to 24 fields.

## Check an API key

Immediately after creating a project API key, the Data console offers **Check connection** when a table is selected. It makes one authenticated, read-only request to that table's records endpoint, reports the number of records returned, and does not display or store the record contents. The check uses the newly created secret held in the current page's memory; the key is still shown only once. If you leave or reload before checking, revoke that key and create a replacement to run this check.

## Import rows from a CSV export

In the signed-in Data console, select a project and choose **Import CSV as new table** to create a table from one CSV export. Xyvenra normalizes the header names, infers simple text, number, boolean, or JSON field types, validates all rows, then imports them. Review the new table and inferred types afterward. The file name becomes the table name. This import accepts up to 250 rows, 24 columns, and a 4 MB file; the existing per-account 250-record, 10-table-per-project, and per-record 16 KB limits still apply. Before asking to confirm a CSV, JSON, or CSV ZIP import, the console refreshes table and record usage and rejects imports that exceed the currently available table or record quota. This is an early check for a clearer result; database quota enforcement remains authoritative if another request changes usage afterward. A failed row batch is removed along with the newly created empty table when possible. Alternatively, select an existing table and choose **Import CSV to table** to append rows after matching CSV headers against its fields. Existing-table imports convert number, boolean (`true`/`false` or `1`/`0`), and JSON columns and validate the whole batch before append. Imports do not replace records or preserve source record IDs automatically.

For a multi-table export, choose **Import CSV ZIP bundle** and select a ZIP containing up to 10 CSV files at its top level or under one common folder. Each file becomes a new table named from its file name; CSV headers are normalized and simple text, number, boolean, or JSON types are inferred. The archive is limited to 12 MB compressed and expanded, each CSV is limited to 4 MB and 250 rows, and non-CSV files or deeper nested folders are skipped. The current project and account quotas still apply. The console checks current table and record capacity before asking to confirm, then creates tables and imports rows sequentially. If usage changes during import or another step fails, it deletes the created tables; cleanup is attempted automatically and may require manual deletion if the network or database prevents cleanup. This migrates table data only. It does not transfer SQL schema features such as indexes, views, triggers, policies, auth settings, functions, or storage configuration. Review inferred field types and the imported records before using them.

For one table, **Export table JSON** preserves its field names, types, required flags, and JSON values in a portable `xyvenra-data-table` file. It excludes source record IDs and timestamps. Choose **Import JSON records** to create a new table from this file; it restores the field definitions and can create an empty table with its schema. Search filters do not limit the export. The file is created in your browser, so keep it private. Generic JSON arrays and `{ "records": [...] }` or `{ "data": [...] }` files continue to import with inferred fields.

## Export a project backup

Choose **Export records JSON** to download a JSON file containing the selected project's table names, field definitions, and records. Choose **Export complete ZIP** to also include every private file attached to that project. ZIP backups use a Xyvenra manifest and `files/<id>` entries; archives are limited to 55 MB compressed and expanded and 100 attachments. Keep both formats private if they contain sensitive information. **Restore project backup** accepts Xyvenra JSON backups and complete ZIP backups and creates a new project. JSON schema and records are validated and restored in one database transaction. For ZIPs, the archive paths, manifest, and attachment bytes are checked before the records are restored and attachments uploaded. If an attachment upload fails, the service attempts to remove the incomplete new project; if that cleanup also fails, the console reports that manual deletion is needed. The destination creates new project, table, record, and file IDs; source record IDs and timestamps are not reused. Restoring into the same account counts against its current project, table, record, file, and storage quotas.

This is a CSV record import, not a one-click account migration. It does not copy source users, authentication settings, storage files, SQL schema, functions, secrets, backups, or provider configurations.

## Endpoint

```text
https://xyvenra.com/api/data/v1/{project_id}/{table_name}/records
```

Private project files use a separate project-scoped endpoint:

```text
https://xyvenra.com/api/data/v1/{project_id}/files
https://xyvenra.com/api/data/v1/{project_id}/files/{file_id}
```

Send the key in the `Authorization` header:

```bash
curl \
  -H "Authorization: Bearer YOUR_XYVENRA_DATA_KEY" \
  "https://xyvenra.com/api/data/v1/PROJECT_ID/tasks/records?limit=100&offset=0"
```

The API supports:

| Method | URL | Body | Permission |
| --- | --- | --- | --- |
| `GET` | `/{project_id}/{table}/records` | None | Read only or read/write |
| `POST` | `/{project_id}/{table}/records` | One record object, or `{"batch":[{"title":"Build it"},{"title":"Review it"}]}` for a batch of up to 250 records | Read/write |
| `GET` | `/{project_id}/{table}/records/{record_id}` | None | Read only or read/write |
| `PATCH` | `/{project_id}/{table}/records/{record_id}` | Partial update: `{"patch":{"active":true}}`; full replacement remains available as `{"data":{"title":"Updated","active":true}}` | Read/write |

| `DELETE` | `/{project_id}/{table}/records/{record_id}` | None | Read/write |

Include `expected_updated_at` from a record response with `patch` or `data` to reject stale updates and full replacements with `409 Conflict`. This field is optional for existing callers. The JavaScript SDK accepts `{ expectedUpdatedAt }` as the third argument to `records.patch` and `records.replace`.
| `GET` | `/{project_id}/files` | None | Read only or read/write; returns file metadata and account usage |
| `POST` | `/{project_id}/files` | `multipart/form-data` with one `file` field | Read/write; 1 MB maximum |
| `GET` | `/{project_id}/files/{file_id}` | None | Read only or read/write; attachment download |
| `DELETE` | `/{project_id}/files/{file_id}` | None | Read/write |

List requests accept `limit` from 1 to 100 and `offset` from 0 to 249. For sequential reads, pass the opaque `next_cursor` from a response as `cursor` on the next request. Use either `offset` or `cursor`, not both. A cursor is tied to the project, table, search, filter, and sort; it expires with `409 Conflict` if its anchor record changes or is removed. Sorting uses record ID as a stable tie-breaker. Pages read the current data, so concurrent inserts or edits to other rows can still change later pages. Responses include `total` (after filters), `has_more`, and `next_cursor` (`null` after the final page). Optional query parameters are `q` (case-insensitive search across IDs and JSON values), `filter` (a declared table field), `op` (`contains`, `equals`, `not-equals`, `greater-than`, `less-than`, `is-empty`, or `is-not-empty`), and `value` (required except for empty checks). Number comparisons require a number field; boolean fields allow equality and empty checks. Optional `order` accepts `id`, `created_at`, `updated_at`, or `field:<name>`; `direction` is `asc` or `desc`. Default ordering is creation time ascending. Filtering, ordering, and pagination operate over the account's bounded record set, so current caps remain in effect. Record fields must match the selected table's field names and types, and required fields must be included.

Example: find active work items containing “release”, newest first:

```bash
curl \
  -H "Authorization: Bearer YOUR_XYVENRA_DATA_KEY" \
  "https://xyvenra.com/api/data/v1/PROJECT_ID/tasks/records?q=release&filter=active&op=equals&value=true&order=created_at&direction=desc&limit=25&offset=0"
```

## Example: create a record

```bash
curl -X POST \
  -H "Authorization: Bearer YOUR_XYVENRA_DATA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Build it","active":true}' \
  "https://xyvenra.com/api/data/v1/PROJECT_ID/tasks/records"
```

The project and table IDs are visible in the signed-in Xyvenra Data workspace. Keys never appear in list responses; revoke a lost key and create a replacement.

## Patch a record

Send only the fields that should change inside a `patch` object. Fields omitted from the patch retain their current values. Xyvenra validates the merged record against the declared table schema and compares the current `updated_at` value before saving. The API returns `409` if another request changed the record first. Full replacements can also opt into stale-write checks by sending `expected_updated_at` from the record response with the `data` object.

```js
await xyvenra.records.patch(recordId, { active: true });
```

## Import a record batch through the API

Send `{"batch":[...]}` to the table endpoint. The service checks every item against the declared fields and types before submitting a single database insert. A successful response returns `{ "records": [...], "imported": 2 }`. If any item is invalid, too large, or exceeds an account limit, the batch is rejected. The batch counts as one request against the API key's per-minute limit, but every row still counts toward the account's 250-record preview cap. Requests are limited to 4 MB and 250 rows. A timed-out request may have committed; check the table before retrying because batch idempotency keys are not supported.

```js
const result = await xyvenra.records.createMany([
  { title: 'First imported item', active: true },
  { title: 'Second imported item', active: false }
]);
console.log(`Imported ${result.imported} records`);
```

## JavaScript SDK

The Data console's **Download JavaScript SDK** button downloads `xyvenra-data-client.mjs`. It wraps record list/create/read/replace/delete and private file list/upload/download/delete calls. It has no third-party dependencies, uses the same REST API quotas and project permissions, and refuses HTTP redirects so a bearer key cannot be forwarded to an unexpected host. Keep the API key in a server-side environment variable; never bundle it into a browser application.

The Data console also links the machine-readable [OpenAPI 3.1 description](https://xyvenra.com/api/data/openapi). It describes the current record and private-file endpoints, bearer authentication, query parameters, request shapes, responses, and key limits. Import the JSON in a compatible API client or code generator, then set the project ID, table name, and bearer key in that tool's local environment. The specification does not contain a project ID or API key, and generated clients still need the same server-side key handling and quotas.

```js
import { createXyvenraDataClient } from './xyvenra-data-client.mjs';

const xyvenra = createXyvenraDataClient({
  projectId: process.env.XYVENRA_DATA_PROJECT_ID,
  tableName: 'tasks',
  apiKey: process.env.XYVENRA_DATA_API_KEY
});

const { records } = await xyvenra.records.list({
  filter: 'active', op: 'equals', value: true,
  order: 'created_at', direction: 'desc', limit: 25
});
```

To continue a filtered list with cursor pagination, reuse the same filters and sort while passing only the returned cursor (do not send `offset`):

```js
const query = { filter: 'active', op: 'equals', value: true, order: 'created_at', direction: 'desc', limit: 25 };
let page = await xyvenra.records.list(query);
const records = [...page.records];
while (page.has_more && page.next_cursor) {
  page = await xyvenra.records.list({ ...query, cursor: page.next_cursor });
  records.push(...page.records);
}
```

For file uploads, pass a `File` or `Blob` and an optional filename: `await xyvenra.files.upload(new Blob(['notes']), 'notes.txt')`. Downloads return a `Blob`. The SDK throws an `Error` containing the service's message for non-success responses.
