API reference

https://api.valtrix.com/v1

The table examples on this page use sample data. Sign in to see this reference generated from your own published tables.

Getting started

Mint an API key in the console under Developers, then install the SDK and generate a typed client from your published tables. Your first table read is a findMany call; the same read works over plain REST with the key in the Authorization header.

$ export VALTRIX_API_KEY=vlt_...
$ curl https://api.valtrix.com/v1/tables/customers_clean/rows \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "limit=5"
{ "rows": [...], "next_cursor": null, "schema_version": "v1" }

Overview

Valtrix turns the raw data living in the third-party systems your organizations use into clean tables you query like a database. Ingestion and modeling happen upstream, so by the time data reaches this API it is already in your schema, the same shape for every organization and every connector.

The Valtrix data lifecycle
1

Records

Organizations connect their third-party systems through Connect. Valtrix syncs their data continuously and stores it as Records.

2

Transformations

Your Transformation rules clean, map, and validate those Records into a data model you design once. Steps run separately within each organization's data; rows never mix across organizations.

3

Tables

Publishing a Transformation produces typed, versioned Tables where data from every organization shares one shape.

4

Ready for consumption

Read rows from tables with filters, query records directly for ad hoc reads, follow change feeds to stay in sync, and write records back through the endpoints below.

Authentication

Authenticate every request with an API key in the Authorization header; requests without a valid key return 401. Keys come in two kinds. Platform-wide keys, minted in the console under Developers, reach every organization granted to you. Organization-scoped keys, minted through POST /v1/keys, are pinned to one organization: reads and writes default to it, the org parameter becomes optional, and naming any other organization returns 403 org_not_scoped. To mint your first key, sign in to the console, open Developers, and click Create API key. Name the key, leave the scope on Entire platform (all organizations), and pick an access level. The full key is shown once, on creation, so copy it there; a lost key cannot be recovered, only revoked and replaced. Every key also carries an access level: read, write, or manage. Read-only keys can call every read endpoint except key management (GET /v1/keys needs manage) but cannot write or delete records or create connect sessions (those return 403 key_access_denied); write is the default and covers records and connect sessions; manage exists only on platform-wide keys and additionally governs transformation deploys and key management. A key's power is visible in its prefix: scoped keys start with vlt_org_ instead of vlt_, and read-only keys with vlt_read_ or vlt_org_read_, so a key found in a log or a leak is immediately recognizable. Hand a scoped key to each app or environment that acts for a single organization, a read-only key to anything that only consumes data, and keep platform-wide keys on your own servers.

$ export VALTRIX_API_KEY=vlt_...
$ curl https://api.valtrix.com/v1/tables \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Build with AI

There are two ways to build with AI on Valtrix, split by when the model is involved. Through the MCP server, Claude and ChatGPT query and write the data live during a session, with the model in the request path at runtime. In Embedded coding agents, a coding agent writes an app against the SDK and the generated client, and the model's involvement ends when the code ships.

MCP server

The MCP server gives Claude, ChatGPT, and any other MCP client live access to the same tables and records as the REST API, for writes as well as reads. It speaks the Model Context Protocol over streamable HTTP at /mcp and follows the same semantics as the REST endpoints: organization-scoped keys pin every call to their organization, platform-wide keys accept an org argument, and write access follows the key. Seven tools mirror the REST surface: get_schema, list_tables, query_table, query_records, get_record, upsert_record, and create_connect_link. In claude.ai or chatgpt.com, add the /mcp URL as a custom MCP server, click Connect, and paste your API key on the Valtrix authorization page; Claude Code, Cursor, VS Code, and Codex authenticate through the same OAuth flow, and clients without OAuth support can send the key as a bearer header instead. The MCP setup guide has step-by-step instructions for every client. Where the Embedded coding agents path produces an app that queries the API on its own, with no model at runtime, the MCP server keeps the model itself in the conversation, reading live data and writing records back.

Server URL
https://api.valtrix.com/mcp
Authorization header
Authorization: Bearer YOUR_API_KEY

Embedded coding agents

For embedded apps written by a coding agent (i.e. vibe-coded tools your users spin up inside your product). These apps run on organization-scoped keys and the SDK. For example, a builder on your platform asks the agent for a tool that flags unpaid vendor bills, and the app it gets back queries the bills table with a key pinned to that builder's organization and nothing else. Provision the key and the generated client in the app template, and the agent opens a project with the schema already in its editor and the ground rules already in the repository. The rules ship as the AGENTS.md file inside @valtrix/sdk: query tables live instead of mirroring them, go through the generated client, never handle keys.

Mint a key per appyour backend

When a user creates an app, mint an organization-scoped key named after it with POST /v1/keys and inject it into the app's environment as VALTRIX_API_KEY. The key never appears in source or in the agent's conversation. Revoke it when the app is deleted.

Generate the clientyour app template

Add @valtrix/sdk as a dependency and run npx valtrix generate while scaffolding, so valtrix/client.ts is already in the tree with the full schema when the agent first opens the project.

Copy in AGENTS.mdyour app template

The instructions file ships at node_modules/@valtrix/sdk/AGENTS.md and versions with the SDK. Copy it to the project root so every coding agent that opens the repository reads the same rules.

Guard the schema in CIyour app template

Run npx valtrix generate --check next to the typecheck, so a published schema change surfaces as a failing build the agent knows how to fix.

$ curl -X POST https://api.valtrix.com/v1/keys \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-d '{ "org": "org_2d9x4v", "name": "snag-tracker" }'
$ npm install @valtrix/sdk
$ npx valtrix generate
$ cp node_modules/@valtrix/sdk/AGENTS.md .

Errors

Failed requests return a human readable error and a stable code. Branch on the code, not the message. The SDK throws the same shape as a ValtrixError, with code and status on the error.

invalid_filter400

A row filter is not in <op>.<value> form, or its value does not match the column type.

invalid_order400

order is not in <column>.<asc|desc> form.

invalid_limit400

limit is not a positive integer.

invalid_cursor400

The cursor is malformed. Always pass next_cursor back verbatim.

unknown_column400

A filter, select, or order names a column that is not in the table or entity schema.

unknown_connector400

No connector with that slug. GET /v1/connectors lists the valid ones.

org_required400

Record endpoints need an org parameter, and POST /v1/keys needs an org in the body.

invalid_name400

An API key name is empty or longer than 100 characters.

invalid_external_id400

external_id is missing or longer than 255 characters.

invalid_fields400

Record data failed validation. The response includes a fields array naming each issue.

invalid_scope400

scope is not a valid list of entity types.

invalid_status400

status must be active, revoked, or expired.

invalid_access400

access must be read or write. Keys minted through POST /v1/keys never carry manage.

invalid_disposition400

disposition on GET /v1/attachments/:attachmentId/url must be inline or attachment.

invalid_definitions400

The definitions payload on POST /v1/transformations/plan or /v1/transformations/apply failed validation. The response includes the failing items.

scope_not_granted403

The grant for this organization does not cover that entity type.

org_not_scoped403

The request names an organization other than the one this organization-scoped key is pinned to.

key_access_denied403

The key's access level does not allow this action. Read-only keys cannot write or delete records or create connect sessions; the response names the missing capability.

manage_required403

This endpoint needs a platform-wide key with manage access. Manage governs both applying transformation deploys and minting, listing, or revoking organization-scoped API keys.

table_not_found404

No published table with that name.

unknown_entity_type404

No entity type with that slug. GET /v1/schema lists the valid ones.

record_not_found404

No record matches. For external_id lookups, nothing with that id was written through this API. For record ID lookups, the id is unknown or is a syn_ prefixed synthetic row id, which has no single record behind it.

org_not_granted404 or 403

No active grant for that organization: 404 when the organization has never granted you access, 403 when the grant exists but is revoked or expired.

org_not_found404

No organization with that id has connected to you.

key_not_found404

No API key with that id exists.

attachment_not_found404

No attachment with that id exists, or its record is outside your granted organizations.

transformation_not_found404

No transformation publishes a table with that name.

external_id_conflict409

The external_id on a connect session already identifies a different organization. Omit org to reuse it, or send the matching org id.

cursor_expired410

The changes cursor is older than the 30 day retention window. Restart from a full rows read.

payload_too_large413

Record data must be under 100KB.

rate_limited429

Over 300 requests per minute on this API key. Retry after the seconds in the Retry-After header.

storage_unavailable503

Attachment storage is temporarily unavailable. Safe to retry after a short wait.

$ curl "https://api.valtrix.com/v1/records/customer/customer_8f2?org=org_2d9x4v" \
-H "Authorization: Bearer $VALTRIX_API_KEY"
HTTP/2 404
{
"error": "No active grant for that organization.",
"code": "org_not_granted"
}

Rate limits

Each API key can make 300 requests per minute, counted across all endpoints. Past the limit, requests return 429 with code rate_limited and a Retry-After header giving the seconds until the window resets; wait that long before retrying rather than retrying immediately. The SDK handles this for you: it waits out Retry-After and retries automatically, and throws a ValtrixRateLimitError, with retryAfterSeconds on the error, only once its retries are exhausted. If you hit the ceiling while reading tables, request more per call instead of calling more often: raise limit to 200 and page with cursors rather than issuing many small reads.

$ curl https://api.valtrix.com/v1/tables/customers_clean/rows \
-H "Authorization: Bearer $VALTRIX_API_KEY"
HTTP/2 429
Retry-After: 12
{
"error": "Rate limit exceeded.",
"code": "rate_limited"
}

Filtering

Filter rows server side with query parameters of the form <column>=<op>.<value>. Repeat parameters to combine conditions; a row must match all of them. Values are validated against the column type from your schema, so number columns compare numerically, and filtering a column that is not in the schema returns unknown_column. The same grammar filters canonical records on GET /v1/records/:entityType, against the entity's fields instead of table columns. The SDK expresses the same grammar as a where object. Store ids rather than copies of rows: keep your own external_id for records you write and the _record_id from rows you read, and either one fetches the current state again later. One id appears under three names: a row's _record_id, a record object's id, and a change event's record_id are the same rec_ prefixed record ID, and reference columns hold that same value.

eq, neqoperators

Equals and not equals.

gt, gte, lt, lteoperators

Range comparisons, typed per column: number columns compare numerically, date columns compare in ISO order.

in.(a,b)operator

Matches any value in a comma separated list.

is.null, not.nulloperators

Whether the column has a value.

_record_idmeta column

Point lookup by row id with eq.<id> or in.(a,b); other operators are rejected. Every row carries its id in _record_id, and within a table an id matches at most one row, so store it to fetch the same row again. rec_ prefixed ids resolve to a record through GET /v1/records/:recordId; syn_ prefixed ids from aggregating or reshaping steps filter the same way but resolve to no record.

reference columnstyped

Columns typed reference hold record ids and filter like ids: eq, neq, in.(a,b), is.null, not.null. Range operators are rejected. To cross-reference, collect the reference values from one read and pass them to the target table as _record_id=in.(a,b). A reference that can point at more than one entity type pairs with a sibling type column naming the linked entity, so a cost row carries payee_id plus payee_type set to vendor or employee.

$ curl https://api.valtrix.com/v1/tables/customers_clean/rows \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "email=not.null" -d "order=created_at.desc"
$ curl https://api.valtrix.com/v1/tables/customers_clean/rows \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "_record_id=eq.rec_9d4f2c81a7b3e650d21c4f8a"

Pagination

List endpoints return up to limit items and a next_cursor. Pass it back as cursor for the next page. On row and record pages, a null next_cursor means you have everything. On the change feed, a null next_cursor means you are caught up for now: keep the last non-null cursor you received and poll with it again later, never store the null. A cursor is a bookmark minted by the server, and it works the same everywhere in the API, on row pages and on the change feed alike: send it back exactly as you received it, never build or edit one, and never treat it as an id. In the SDK, page() exposes the same cursor round-trip when you want explicit control, and iterate() and changes() thread it for you until the feed is drained.

limitnumber

1 to 200, defaults to 50.

cursorstring

The next_cursor from the previous page, sent back exactly as you received it. Omit on the first call.

next_cursorresponse

Send back as cursor to fetch the next page. null means the last page.

$ curl https://api.valtrix.com/v1/tables/customers_clean/rows \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "limit=200" -d "cursor=eyJpIjoiMDFo..."

Tables

The row object

Every row a table returns is your columns plus five meta fields. The shape is identical for every organization and every connector; GET /v1/schema is the contract for the column part.

Fields
<column>per schema

One property per column in the table schema, named by the column key and typed string, number, boolean, date, json, or reference. A reference column holds the _record_id of a related record, so it joins without name matching: filter the target table with _record_id=eq.<value>, or resolve it directly with GET /v1/records/:recordId. Nullable columns can be null.

_record_idstring

The id of the row. Within a table an id matches at most one row, so store it to fetch the same row again with a _record_id filter. rec_ prefixed ids resolve to a record through GET /v1/records/:recordId; ids starting with syn_ belong to derived rows from aggregating or reshaping steps and resolve to no record.

_org_idstring

The organization the row belongs to. Reads span every organization you hold an active grant for, so use it to attribute rows when you serve more than one.

_connectorstring or null

The slug of the connector the row's record was synced from, so you can attribute the row to its origin system. GET /v1/connectors resolves slugs to display names. Rows derived from records you wrote through this API carry the API source. null on syn_ rows, which aggregate or reshape many records and have no single origin.

_synced_atstring

When the row last changed, as an ISO 8601 timestamp. The one meta field you can order by, so order=_synced_at.desc reads freshest rows first.

_frozenboolean

true when the row's record is outside the connector's sync window and no longer refreshed. The row keeps the values last fetched inside the window and stays in the table. false on rows whose record still syncs.

{
"email": "...",
"created_at": "2026-07-01",
"_record_id": "rec_9d4f2c81a7b3e650d21c4f8a",
"_org_id": "org_7f3k2m",
"_connector": "procore",
"_synced_at": "2026-07-08T09:30:00Z",
"_frozen": false
}

The change object

One entry in a table's change feed, describing a row that was created, updated, or removed.

Fields
typestring

upsert when the row was created or updated, delete when it was removed.

cursorstring

A bookmark for this change's position in the feed. Store the last one you processed and resume from it.

occurred_atstring

When the change happened, as an ISO 8601 timestamp.

record_idstring

The id of the affected row, the same value the row carries as _record_id. For deletes it identifies which row to drop.

org_idstring

The organization the row belongs to.

rowobject or null

The full row after the change for upserts, in the row object shape. null for deletes.

{
"type": "upsert",
"cursor": "eyJjIjoiODIzMiJ9",
"occurred_at": "2026-07-08T09:30:00Z",
"record_id": "rec_9d4f2c81a7b3e650d21c4f8a",
"org_id": "org_7f3k2m",
"row": { ... }
}

Retrieve the schema

GET/v1/schema

Your full API contract: every published table with its columns, and every writable entity type. Compare schema_version between reads to detect published changes.

Returns
tablesarray

One entry per published table, with its columns.

recordsarray

One entry per entity type, with its fields and its own schema_version. Entity schemas only gain fields, so a version bump never removes or retypes one.

$ curl https://api.valtrix.com/v1/schema \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"tables": [{ "name": "customers_clean", "entity_type": "customer", "status": "live", "row_count": 1204, "row_identity": "record", "last_built_at": "2026-07-08T09:30:00Z", "schema_version": "v1", "columns": [{ "key": "email", "type": "string", "nullable": true, "writable": true, "description": "..." }] }],
"records": [{ "entity_type": "customer", "label": "Customer", "description": "...", "title_key": "name", "schema_version": "v1", "columns": [...] }]
}

List tables

GET/v1/tables

Lists your published tables in the same shape as /v1/schema. GET /v1/tables/:name returns a single table, or 404 table_not_found. Each table reports row_identity: "record" when every row keeps its source record id, "synthetic" when the transformation aggregates or reshapes so row ids are generated, or "mixed" when both occur; null until the first build.

Returns
tablesarray

Table objects in the same shape as /v1/schema.

The single table form returns one table object, or 404 table_not_found.

$ curl https://api.valtrix.com/v1/tables \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "tables": [{ "name": "customers_clean", "entity_type": "customer", "status": "live", "row_identity": "record", "columns": [...] }] }

List rows

GET/v1/tables/:name/rows

Reads rows across every organization you hold an active grant for. Filtering, ordering, projection, and pagination run server side.

Parameters
namepath

The table name, as listed by /v1/tables.

<column>string, repeatable

Filter as <op>.<value> with eq, neq, gt, gte, lt, lte, plus in.(a,b), is.null, and not.null. Repeat with different columns; all must match.

_record_idstring

Point lookup by row id, as eq.<id> or in.(a,b). Every row carries its id in _record_id, so store it to fetch the same row again. Ids come in two forms, and both filter the same way: rec_ prefixed ids are record ids that resolve through GET /v1/records/:recordId, while syn_ prefixed ids belong to rows generated by aggregating or reshaping steps and resolve to no record. The table's row_identity field tells you which form to expect. Supports eq and in only.

orderstring

One column as <column>.<asc|desc>. _synced_at is also orderable.

selectstring

Comma separated column keys to return.

limitnumber

1 to 200, defaults to 50.

cursorstring

The next_cursor from the previous page, sent back exactly as you received it.

orgstring

Narrow to one organization by id.

Returns
rowsarray

Row objects matching the query.

next_cursorstring or null

The bookmark for the next page. null means the last page.

schema_versionstring

The table's current schema version.

$ curl https://api.valtrix.com/v1/tables/customers_clean/rows \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "email=not.null" -d "order=created_at.desc" -d "limit=50"
{
"rows": [{ "email": "...", "created_at": "2026-07-01", "_record_id": "rec_9d4f2c81a7b3e650d21c4f8a", "_org_id": "org_7f3k2m", "_connector": "procore", "_synced_at": "2026-07-08T09:30:00Z", "_frozen": false }],
"next_cursor": "eyJpIjoiMDFo...",
"schema_version": "v1"
}

The change feed

GET/v1/tables/:name/changes

An ordered, resumable feed of row changes, deletes included. Each change carries a cursor, a bookmark for its position in the feed; it is not the record id. Poll with the last cursor you processed; an empty list means you are caught up. Changes are retained 30 days, after which a stale cursor returns 410 cursor_expired.

Parameters
namepath

The table name, as listed by /v1/tables.

cursorstring

The cursor of the last change you processed, or the last next_cursor. Omit on the first call to read from the start.

limitnumber

1 to 200, defaults to 50.

orgstring

Narrow to one organization by id.

Returns
changesarray

Change objects in feed order. Empty means you are caught up.

next_cursorstring or null

The bookmark to resume from on your next poll. Null when this poll returned no changes: keep the last non-null cursor you received and poll with it again later, never store the null.

schema_versionstring

The table's current schema version.

$ curl https://api.valtrix.com/v1/tables/customers_clean/changes \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "cursor=eyJjIjoiODIzMSJ9" -d "limit=200"
{
"changes": [{ "type": "upsert", "cursor": "eyJjIjoiODIzMiJ9", "occurred_at": "2026-07-08T09:30:00Z", "record_id": "rec_9d4f2c81a7b3e650d21c4f8a", "org_id": "org_7f3k2m", "row": { ... } }],
"next_cursor": "eyJjIjoiODIzMiJ9",
"schema_version": "v1"
}

Records

Records are the canonical store: every entity Valtrix syncs from an organization's connected systems, in the same shape for every connector, plus the records you write back through this API. Query them directly with GET /v1/records/:entityType when you want the canonical data as-is; your tables remain the curated layer on top, with your own column shapes, quality gates, and change feeds. Writes create records, not rows: each write flows through your published transformation, and the response reports the outcome per table as applied, removed, quarantined, filtered, or pending. Fields typed reference in the schema hold record ids and link entities without name matching: a project's customer_id is the id of a customer record, so resolve it with GET /v1/records/:recordId, filter the entity list with customer_id=eq.<id>, or batch the values into a _record_id=in.(a,b) filter on a customer table.

The record object

A record as Valtrix stores it, before transformation. Records synced from connected systems and records you write through this API share this shape.

Fields
idstring

The rec_ prefixed record ID. Table rows derived from this record carry it as their _record_id, and reference fields on other records hold it as their value, so either resolves back to this record through GET /v1/records/:recordId.

entity_typestring

The entity type slug. GET /v1/schema lists them.

external_idstring

The identifier the record was written under: yours for records written through this API, the source system's for synced records.

org_idstring

The organization the record belongs to.

connectorstring

The slug of the connector the record was synced from, so you can attribute it to its origin system. GET /v1/connectors resolves slugs to display names. Records you wrote through this API carry the API source.

dataobject

The record's fields as written or synced, before your transformation runs. The fields each entity type carries are listed under Record entity types below.

synced_atstring

When the record was last written or synced, as an ISO 8601 timestamp.

frozenboolean

true when the record is outside the connector's sync window and no longer refreshed. The data holds the values last fetched inside the window, and the record stays readable here and in your tables.

{
"id": "rec_9d4f2c81a7b3e650d21c4f8a",
"entity_type": "customer",
"external_id": "customer_8f2",
"org_id": "org_7f3k2m",
"connector": "procore",
"data": { "name": "...", "email": "..." },
"synced_at": "2026-07-08T09:30:00Z",
"frozen": false
}

The attachment object

A source document Valtrix captured alongside a record while syncing, such as the scanned vendor bill behind a cost record. Documents arrive as the source system holds them, and many sources hold per-page image scans, so a multi-page upload can arrive as one attachment per page with the page number in the filename. Valtrix stores the document itself, so downloads keep working even when the source system is slow or unreachable.

Fields
idstring

The attachment ID. Pass it to the display link endpoint to show the document in your app, or to the download endpoint to fetch the bytes.

filenamestring or null

The document filename when the source provided one. It names the original upload, so a filename ending in .pdf can belong to an image scan of one of its pages.

media_typestring

The MIME type of the stored document, such as application/pdf or image/jpeg. This is the authoritative format of the bytes; use it to pick the file extension when saving.

bytesnumber

The stored size in bytes.

content_hashstring

SHA-256 hash of the document bytes. Two attachments with the same hash hold the same document, so compare hashes to skip downloads you already have.

created_atstring

When Valtrix first stored the document, as an ISO 8601 timestamp.

{
"id": "clx1a2b3c4d5e6f7g8h9i0j1k",
"filename": "invoice-4417.pdf (page 1)",
"media_type": "image/jpeg",
"bytes": 58369,
"content_hash": "f3a91c0e7b2d...",
"created_at": "2026-07-10T04:00:00Z"
}

List records

GET/v1/records/:entityType

Queries the canonical records of one entity type across every organization you hold an active grant for, synced and API-written alike. The same filter, order, select, and cursor grammar as table rows, applied to the entity's fields from GET /v1/schema. Each result is the entity's fields flat on the object plus meta fields, so a record here and a row from an untransformed table read the same way. Reference fields filter like ids, so following a reference is one query: collect customer_id values from one read and pass them as _record_id=in.(a,b) here, or filter this list by the reference column directly.

Parameters
entityTypepath

The entity type slug. GET /v1/schema lists the valid ones.

<field>string, repeatable

Filter as <op>.<value> with eq, neq, gt, gte, lt, lte, in.(a,b), is.null, and not.null, validated against the entity's field types.

_record_idstring

Point lookup by record ID, as eq.<id> or in.(a,b). Every id here is a rec_ prefixed record ID; there are no synthetic ids at the record layer.

orderstring

One field as <field>.<asc|desc>. _synced_at is also orderable.

selectstring

Comma separated field keys to return.

limitnumber

1 to 200, defaults to 50.

cursorstring

The next_cursor from the previous page, sent back exactly as you received it.

orgstring

Narrow to one organization by id.

connectorstring

Narrow to records synced from one connector, by slug. GET /v1/connectors lists the valid ones. Records you wrote through this API carry the API source.

synced_sincestring

Only records written or synced at or after this ISO 8601 timestamp. A freshness window, not a change feed: deletions never appear here, so stay in sync through a table's change feed.

Returns
recordsarray

One object per record: the entity's fields, plus _record_id, _external_id, _org_id, _connector, _synced_at, and _frozen.

next_cursorstring or null

The bookmark for the next page. null means the last page.

schema_versionstring

The entity schema version, as reported by GET /v1/schema.

$ curl https://api.valtrix.com/v1/records/customer \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "name=not.null" -d "order=_synced_at.desc" -d "connector=procore" -d "limit=50"
{
"records": [{ "name": "...", "email": "...", "_record_id": "rec_9d4f2c81a7b3e650d21c4f8a", "_external_id": "customer_8f2", "_org_id": "org_7f3k2m", "_connector": "procore", "_synced_at": "2026-07-08T09:30:00Z", "_frozen": false }],
"next_cursor": "eyJpIjoiMDFo...",
"schema_version": "v1"
}

Upsert a record

POST/v1/records/:entityType

Creates or updates a record, keyed by your external_id within the organization. Returns 201 on create, 200 on update, with the derived table rows. In tables that keep one row per record, the record id in the response is the same _record_id carried on that row. Aggregated tables report pending instead of a row: the write lands there through a rebuild, as changes to the bucket rows it feeds, under their own row ids.

Parameters
entityTypepath

The entity type slug. GET /v1/schema lists the valid ones.

orgstring, required

The organization the record belongs to. The grant must cover this entity type.

external_idstring, required

Your identifier, up to 255 characters. Same external_id updates the same record.

dataobject, required

An object of entity fields, under 100KB. Unknown fields are rejected. GET /v1/schema lists the writable fields. Updates merge: fields you omit keep their stored values, and sending null clears a field.

Returns
createdboolean

true with status 201 on create, false with status 200 on update.

recordobject

The record object as stored.

tablesarray

The write outcome per table: applied, quarantined, filtered, or pending, with the derived row where one exists.

$ curl -X POST https://api.valtrix.com/v1/records/customer \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "customer_8f2", "data": { "name": "...", "email": "..." } }'
{
"created": true,
"record": { "id": "rec_9d4f2c81a7b3e650d21c4f8a", "entity_type": "customer", "external_id": "customer_8f2", "org_id": "org_7f3k2m", "connector": "valtrix-api", "data": { ... }, "synced_at": "2026-07-08T09:30:00Z" },
"tables": [{ "table": "customers_clean", "status": "applied", "row": { ... }, "quarantined_by": [] }]
}

Retrieve a record

GET/v1/records/:recordId

Fetches any record by its record ID, synced and API-written alike, with the current row it produces in each of your tables. The record ID is the rec_ prefixed id you see everywhere: the _record_id on a table row, a reference field's value, or the id of a record object. Rows from aggregating or reshaping transformations carry syn_ prefixed synthetic row ids with no single record behind them; those return 404 record_not_found. A table's row_identity field tells you upfront whether its rows resolve.

Parameters
recordIdpath

The rec_ prefixed record ID, from a row's _record_id, a reference field, or a record object's id.

Returns
recordobject

The record object. Its entity_type tells you what kind of record the ID resolved to, so you can follow a reference field without knowing its target type upfront.

tablesarray

The current row it produces in each of your tables.

attachmentsarray

The source documents Valtrix captured alongside the record, as attachment objects. Empty for most records.

$ curl https://api.valtrix.com/v1/records/rec_9d4f2c81a7b3e650d21c4f8a \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "record": { "id": "rec_9d4f2c81a7b3e650d21c4f8a", "entity_type": "customer", "external_id": "customer_8f2", ... }, "tables": [{ "table": "customers_clean", "row": { ... } }], "attachments": [] }

Retrieve a record you wrote

GET/v1/records/:entityType/:externalId

Fetches a record by the external_id you supplied when writing it, with the current row it produced in each table. Covers records written through this API only; synced records are fetched by record ID. A null row means it was filtered out or quarantined.

Parameters
entityTypepath

The entity type slug. GET /v1/schema lists the valid ones.

externalIdpath

The external_id you supplied when writing the record.

orgstring, required

The organization the record belongs to.

Returns
recordobject

The record object.

tablesarray

The current row the record produces in each table. A null row means it was filtered out or quarantined.

attachmentsarray

The record's attachment objects. Records written through this API carry no documents, so this is empty today.

$ curl "https://api.valtrix.com/v1/records/customer/customer_8f2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "record": { "id": "rec_9d4f2c81a7b3e650d21c4f8a", "external_id": "customer_8f2", ... }, "tables": [{ "table": "customers_clean", "row": { ... } }], "attachments": [] }

Delete a record

DELETE/v1/records/:entityType/:externalId

Deletes a record you wrote and removes its derived rows. The removals also appear in each change feed.

Parameters
entityTypepath

The entity type slug. GET /v1/schema lists the valid ones.

externalIdpath

The external_id you supplied when writing the record.

orgstring, required

The organization the record belongs to.

Returns
deletedboolean

true when the record and its derived rows were removed.

tablesarray

The removal outcome per table.

$ curl -X DELETE "https://api.valtrix.com/v1/records/customer/customer_8f2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "deleted": true, "tables": [{ "table": "customers_clean", "status": "removed", "row": null, "quarantined_by": [] }] }

List a record's attachments

GET/v1/records/:recordId/attachments

Lists the source documents Valtrix captured alongside a record, such as the scanned vendor bill behind a cost record. Attachments exist where the source system exposes documents and the connector syncs them; most records have none and return an empty list. The list mirrors what is attached in the source system, including repeat uploads of the same document, and a multi-page document can appear as one attachment per page. The record ID is the rec_ prefixed id from a row's _record_id, a reference field, or a record object's id. The response carries metadata only; show the document through the display link endpoint or fetch its bytes through the download endpoint.

Parameters
recordIdpath

The rec_ prefixed record ID.

Returns
attachmentsarray

The record's attachment objects, oldest first.

$ curl https://api.valtrix.com/v1/records/rec_9d4f2c81a7b3e650d21c4f8a/attachments \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "attachments": [{ "id": "clx1a2b3c4d5e6f7g8h9i0j1k", "filename": "invoice-4417.pdf (page 1)", "media_type": "image/jpeg", "bytes": 58369, "content_hash": "f3a91c...", "created_at": "2026-07-10T04:00:00Z" }, { "id": "clx9z8y7x6w5v4u3t2s1r0q9p", "filename": "invoice-4417.pdf (page 2)", "media_type": "image/jpeg", "bytes": 61204, "content_hash": "7e167d...", "created_at": "2026-07-10T04:00:01Z" }] }

Get a display link for an attachment

GET/v1/attachments/:attachmentId/url

Returns a short lived link to the document behind an attachment, for showing it inside your app. By default the link serves the document inline with its media type, so it works as the source of an image tag, an iframe, or a PDF viewer. Your backend requests the link and hands it to your frontend; the API key never reaches the browser. Each link expires within minutes, so request a fresh one per view rather than storing it. Treat the link as an opaque value: the storage host and URL format are not part of the API contract and can change, so embed the link exactly as returned rather than parsing it or pinning its domain.

Parameters
attachmentIdpath

The attachment ID, from the record's attachment list.

dispositionstring

inline (default) serves the document for viewing in the browser; attachment serves it as a file download with the original filename.

Returns
urlstring

The short lived link to the document. Requires no authentication, so it can be embedded directly. Opaque, its host and format can change between requests.

expires_innumber

Seconds until the link stops working.

$ curl https://api.valtrix.com/v1/attachments/clx1a2b3c4d5e6f7g8h9i0j1k/url \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "url": "https://valtrix-attachments.s3.amazonaws.com/...&X-Amz-Signature=...", "expires_in": 300 }

Download an attachment

GET/v1/attachments/:attachmentId/download

Fetches the document behind an attachment. The endpoint responds 302 with a short lived download link, and standard HTTP clients follow it automatically and receive the bytes. Each link expires within minutes, so request a fresh download per fetch rather than storing the link. Store the content_hash from the attachment list if you need to know whether you already hold a document. To show a document inside your app rather than fetch its bytes, use the display link endpoint.

Parameters
attachmentIdpath

The attachment ID, from the record's attachment list.

Returns

A 302 redirect to a short lived download link for the document. Following it returns the raw bytes with the attachment's media type.

$ curl -L https://api.valtrix.com/v1/attachments/clx1a2b3c4d5e6f7g8h9i0j1k/download \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-o invoice-4417-page-1.jpg

Record entity types

The entity types accepted by the record endpoints, with the fields each one takes. These fields make up a record's data object; every record additionally carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at), so no entity declares an id field. The envelope id is the record ID that table rows expose as _record_id and that reference fields on other entities point at. GET /v1/schema returns these same definitions, so your integration can read them at runtime.

Booking

booking

A customer's reservation of an event spot, a staff member's time, or a resource: class registrations, appointments, court hires, and desk bookings all map here. Group occurrences link via event_id; the purchase paying for it maps to order and a consumed pack credit to entitlement.

Writable through POST /v1/records/booking. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/booking/:externalId for records you wrote. The title key is customer_name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

customer_namestring

Customer the booking is for

customer_idreference

Valtrix record ID of the customer the booking is for

event_idreference

Valtrix record ID of the event the booking reserves a spot in, for group occurrences

resource_idreference

Valtrix record ID of the resource the booking reserves, such as a court, room, or desk

employee_idreference

Valtrix record ID of the staff member the booking is with, such as the practitioner or stylist

item_idreference

Valtrix record ID of the catalog item for the booked service

location_idreference

Valtrix record ID of the location the booking takes place at

order_idreference

Valtrix record ID of the order that paid for the booking

entitlement_idreference

Valtrix record ID of the entitlement the booking consumed a credit from

statusstring

Booked, attended, completed, no show, cancelled, or waitlisted

start_atdate

When the booking starts

end_atdate

When the booking ends

created_atdate

When the booking was made in the source

$ curl -X POST https://api.valtrix.com/v1/records/booking \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "customer_name": "...", "status": "...", "customer_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/booking/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Budget change

budget_change

An internal budget adjustment or transfer that moves budget between lines without a client-facing change order. Contractual changes map to change_order.

Writable through POST /v1/records/budget_change. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/budget_change/:externalId for records you wrote. The title key is description.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Budget change number

titlestring

Budget change title

descriptionstring

What the budget change covers

statusstring

Approval status in the source system

amountnumber

Net amount of the change

project_idreference

Valtrix record ID of the project the budget change belongs to

created_atdate

When the budget change was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/budget_change \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "description": "...", "number": "...", "project_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/budget_change/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Budget detail

budget_detail

A computed budget report row per cost code: original, changes, committed, costs to date, projected, and over/under. Read-side rollup only; the budget's raw composition maps to budget_line.

Writable through POST /v1/records/budget_detail. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/budget_detail/:externalId for records you wrote. The title key is cost_code.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

cost_codestring

Cost code the row is budgeted against

cost_code_idreference

Valtrix record ID of the cost code the row is budgeted against

categorystring

Cost category or division

project_idreference

Valtrix record ID of the project the row is budgeted against

original_amountnumber

Original budgeted amount

budget_changesnumber

Approved budget changes

approved_cosnumber

Approved change orders

pending_cosnumber

Pending change orders

revised_amountnumber

Budget after approved changes

committed_costsnumber

Committed costs

direct_costsnumber

Direct costs to date

jtd_costsnumber

Job-to-date costs

projected_costsnumber

Projected total costs

estimated_finalnumber

Estimated cost at completion

over_undernumber

Projected over or under budget

$ curl -X POST https://api.valtrix.com/v1/records/budget_detail \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "cost_code": "...", "category": "...", "cost_code_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/budget_detail/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Budget line

budget_line

A single budgeted row of a project budget: cost code plus original and revised amounts. This is the budget's composition; computed report rollups with committed, to-date, and projected columns map to budget_detail.

Writable through POST /v1/records/budget_line. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/budget_line/:externalId for records you wrote. The title key is description.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

project_namestring

Project or job the line is budgeted against

project_idreference

Valtrix record ID of the project the line is budgeted against

cost_codestring

Cost code the line is budgeted against

cost_code_idreference

Valtrix record ID of the cost code the line is budgeted against

descriptionstring

What the budget line covers

categorystring

Cost category or division

cost_typestring

Cost type (labor, material, subcontractor, ...)

quantitynumber

Budgeted quantity

unit_costnumber

Budgeted cost per unit

original_amountnumber

Original budgeted amount

revised_amountnumber

Revised budget after approved changes

document_typestring

Kind of source document the line belongs to

document_statusstring

Approval status of the source document

created_atdate

When the budget line was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/budget_line \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "description": "...", "project_name": "...", "project_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/budget_line/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Change event

change_event

An early-stage record that something changed on a project, captured before or without a priced change order. Once priced and contractual it maps to change_order or commitment_change_order.

Writable through POST /v1/records/change_event. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/change_event/:externalId for records you wrote. The title key is title.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Change event number

titlestring

Change event title

descriptionstring

What changed and why

scopestring

Whether the change is in or out of scope

statusstring

Lifecycle status in the source system

change_typestring

Kind of change

change_reasonstring

Reason for the change

change_order_idreference

Valtrix record ID of the change order the event became once priced

change_order_typestring

Entity type of the linked change order record, change_order or commitment_change_order

project_idreference

Valtrix record ID of the project the change event belongs to

created_atdate

When the change event was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/change_event \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "title": "...", "number": "...", "change_order_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/change_event/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Change order

change_order

A priced amendment to a client-facing contract, at the header level: construction prime contract change orders, scope or SOW amendments, and lease amendments all land here. Changes against a purchase order or subcontract map to commitment_change_order; unpriced early-stage changes map to change_event.

Writable through POST /v1/records/change_order. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/change_order/:externalId for records you wrote. The title key is title.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Change order number

titlestring

Change order title

statusstring

Approval status in the source system

totalnumber

Total value of the change

contract_idreference

Valtrix record ID of the client-facing contract the change order belongs to

project_idreference

Valtrix record ID of the project the change order belongs to

executedboolean

Whether the change order is executed

created_atdate

When the change order was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/change_order \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "title": "...", "number": "...", "contract_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/change_order/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Commitment change order

commitment_change_order

A priced amendment to a money-out commitment, linked to its parent via contract_id: subcontract change orders and post-issue purchase order revisions land here. Client-facing changes map to change_order.

Writable through POST /v1/records/commitment_change_order. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/commitment_change_order/:externalId for records you wrote. The title key is title.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Change order number

titlestring

Change order title

statusstring

Approval status in the source system

totalnumber

Total value of the change

contract_idreference

Valtrix record ID of the commitment contract the change order belongs to

project_idreference

Valtrix record ID of the project the change order belongs to

executedboolean

Whether the change order is executed

created_atdate

When the change order was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/commitment_change_order \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "title": "...", "number": "...", "contract_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/commitment_change_order/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Compliance document

compliance_document

A signed instrument a party must provide before something else may proceed: lien waivers, insurance certificates, tax forms, liability and consent waivers, and staff certifications all collapse here, discriminated by kind. This is the tracked obligation, with its own signature lifecycle, validity window, and signatory; the rendered file rides along as a record attachment. What it gates is identified by subject_type and subject_id, so one shape covers a waiver blocking payment of a vendor invoice (cost), a certificate held against a subcontract or membership (contract), and a liability or consent form a customer signs before a booking. A standing credential belonging to the signing party rather than to any one document leaves subject_id null and is reached through counterparty_id.

Writable through POST /v1/records/compliance_document. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/compliance_document/:externalId for records you wrote. The title key is title.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

titlestring

Name of the form or template the document was raised from

kindstring

Lien waiver, insurance certificate, tax form, liability waiver, consent form, or certification

statusstring

Where the document sits in its signature lifecycle: pending, signed, declined, expired, or released

subject_typestring

Entity type of the record the document gates, cost, contract, or booking

subject_idreference

Valtrix record ID of the record this signature gates, null for a standing credential held against the counterparty itself

counterpartystring

Vendor, customer, or employee required to sign

counterparty_idreference

Valtrix record ID of the party required to sign

counterparty_typestring

Entity type of the linked counterparty record, vendor, customer, or employee

project_idreference

Valtrix record ID of the project the document belongs to

location_idreference

Valtrix record ID of the location the document belongs to, for sources that hold compliance against a venue or site rather than a project

amountnumber

Amount the document covers, which may be a partial release against the gated record total

effective_atdate

Date the document takes effect from

expires_atdate

Date the document lapses and must be renewed, for coverage and certifications that expire

signed_atdate

When the counterparty signed, null while the document is unsigned

signed_by_namestring

Name of the individual who signed on the counterparty side

signed_by_titlestring

Role or title of the individual who signed

$ curl -X POST https://api.valtrix.com/v1/records/compliance_document \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "title": "...", "kind": "...", "subject_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/compliance_document/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Contact

contact

An individual person at a customer, vendor, or other external party, with their own name and contact details. Not the account itself (customer, vendor) and not the org's own workforce (employee).

Writable through POST /v1/records/contact. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/contact/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Contact name

titlestring

Role or title

party_idreference

Valtrix record ID of the customer or vendor the contact belongs to

party_typestring

Entity type of the linked party record, customer or vendor

emailstring

Primary email address

phonestring

Primary phone number

created_atdate

When the contact was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/contact \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "title": "...", "party_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/contact/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Contract

contract

An agreement with money attached, with either side of the business: prime contracts, subcontracts, purchase orders, work orders, memberships, subscriptions, and leases all collapse here, discriminated by kind. Their lines map to line_item and their changes to change_order or commitment_change_order.

Writable through POST /v1/records/contract. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/contract/:externalId for records you wrote. The title key is title.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Contract number

titlestring

Contract title

kindstring

Prime, subcontract, purchase order, membership, subscription, or lease

counterpartystring

Customer or vendor the agreement is with

counterparty_idreference

Valtrix record ID of the customer or vendor the agreement is with

counterparty_typestring

Entity type of the linked counterparty record, customer or vendor

project_idreference

Valtrix record ID of the project the contract belongs to

statusstring

Contract status in the source system

totalnumber

Total contract value

retainage_percentnumber

Retainage percentage withheld

executedboolean

Whether the contract is executed

contract_atdate

Date of the agreement

$ curl -X POST https://api.valtrix.com/v1/records/contract \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "title": "...", "number": "...", "counterparty_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/contract/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Cost

cost

A money-out document at the header level: vendor invoices, expenses, payroll, and other direct costs all collapse here, discriminated by kind. Its lines map to line_item and the transaction settling it maps to payment. Payroll costs carry per-department allocation lines: line_item rows with document_type payroll and the department column filled.

Writable through POST /v1/records/cost. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/cost/:externalId for records you wrote. The title key is description.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

descriptionstring

What the cost covers

kindstring

Invoice, expense, payroll, or other cost type

statusstring

Approval status in the source system

invoice_numberstring

Vendor invoice reference

payee_namestring

Vendor or employee paid

payee_idreference

Valtrix record ID of the vendor or employee paid

payee_typestring

Entity type of the linked payee record, vendor or employee

project_idreference

Valtrix record ID of the project the cost belongs to

location_idreference

Valtrix record ID of the location the cost belongs to, for sources that book costs against a venue or site rather than a project

totalnumber

Total amount

received_atdate

When the cost was received

paid_atdate

When the cost was paid

$ curl -X POST https://api.valtrix.com/v1/records/cost \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "description": "...", "kind": "...", "payee_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/cost/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Cost code

cost_code

An entry in the org's cost code structure used to code budgets, costs, and time. Structural reference data, not a money document.

Writable through POST /v1/records/cost_code. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/cost_code/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

codestring

Full cost code

namestring

Cost code name

statusstring

Active or inactive in the source system

$ curl -X POST https://api.valtrix.com/v1/records/cost_code \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "code": "..." } }'
$ curl "https://api.valtrix.com/v1/records/cost_code/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Customer

customer

A person or company the org sells to or performs work for, at the account level. Individual people at that account map to contact; parties the org buys from map to vendor.

Writable through POST /v1/records/customer. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/customer/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Full customer name

emailstring

Primary email address

phonestring

Primary phone number

countrystring

Country code or name

statusstring

Lifecycle status in the source system

created_atdate

When the customer was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/customer \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "email": "..." } }'
$ curl "https://api.valtrix.com/v1/records/customer/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Employee

employee

A member of the org's own workforce, at the company level. A person's membership on a specific project maps to project_user, and their logged time maps to time_entry.

Writable through POST /v1/records/employee. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/employee/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Full name

emailstring

Work email address

phonestring

Primary phone number

job_titlestring

Role within the company

departmentstring

Department, team, or division the employee belongs to

annual_salarynumber

Annual base salary or salary-equivalent compensation, in the source currency

employee_idstring

Internal employee number

start_datedate

When the employee started at the company, the hire date in the source system

statusstring

Lifecycle status in the source system

created_atdate

When the employee was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/employee \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "email": "..." } }'
$ curl "https://api.valtrix.com/v1/records/employee/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Entitlement

entitlement

A customer's prepaid or granted balance of visits, credits, or time: class packs, punch cards, session credits, and membership allowances all map here. The purchase creating it maps to order, the agreement granting it maps to contract, and each redemption is a booking holding entitlement_id.

Writable through POST /v1/records/entitlement. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/entitlement/:externalId for records you wrote. The title key is item_name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

item_namestring

Catalog item the entitlement was granted from

item_idreference

Valtrix record ID of the item the entitlement was granted from

customer_idreference

Valtrix record ID of the customer holding the entitlement

contract_idreference

Valtrix record ID of the membership or subscription agreement granting the entitlement

order_idreference

Valtrix record ID of the order that purchased the entitlement

kindstring

What the balance counts: visits, credits, minutes, or currency

quantitynumber

Quantity granted

remainingnumber

Quantity remaining

statusstring

Active, expired, or exhausted in the source system

starts_atdate

When the entitlement becomes usable

expires_atdate

When the entitlement expires

created_atdate

When the entitlement was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/entitlement \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "item_name": "...", "kind": "...", "item_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/entitlement/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Estimate

estimate

A priced proposal issued before any agreement exists: quotes, bids, estimates, and proposals all map here. Its lines map to line_item; once accepted, the resulting agreement maps to contract (linked back via contract_id) and the resulting bill maps to invoice.

Writable through POST /v1/records/estimate. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/estimate/:externalId for records you wrote. The title key is number.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Estimate or quote number

titlestring

Estimate title

customer_namestring

Customer the estimate was issued to

customer_idreference

Valtrix record ID of the customer the estimate was issued to

project_idreference

Valtrix record ID of the project the estimate belongs to

contract_idreference

Valtrix record ID of the contract the estimate became once accepted

statusstring

Lifecycle status in the source system

totalnumber

Total proposed amount

currencystring

ISO currency code

issued_atdate

When the estimate was issued

expires_atdate

When the estimate expires

created_atdate

When the estimate was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/estimate \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "number": "...", "title": "...", "customer_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/estimate/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Event

event

A scheduled occurrence customers book into: group classes, courses, workshops, and open sessions, with capacity and start and end times. Each attendee's spot maps to booking; one-on-one reservations map straight to booking without an event; project schedule rows map to schedule_task.

Writable through POST /v1/records/event. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/event/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Event name

kindstring

Class, course, workshop, or other occurrence type

item_idreference

Valtrix record ID of the catalog item for the service the event delivers

employee_idreference

Valtrix record ID of the employee leading the event, such as the instructor or teacher

resource_idreference

Valtrix record ID of the resource the event occupies, such as a room or court

location_idreference

Valtrix record ID of the location the event takes place at

capacitynumber

Maximum number of bookings

booked_countnumber

Number of spots taken

statusstring

Scheduled, cancelled, or completed in the source system

start_atdate

When the event starts

end_atdate

When the event ends

created_atdate

When the event was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/event \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "kind": "...", "item_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/event/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

GL account

gl_account

An entry in the org's chart of accounts used to code money movements in accounting sources. Structural reference data, not a money document; project-coding structures map to cost_code instead.

Writable through POST /v1/records/gl_account. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/gl_account/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Account number in the chart of accounts

namestring

Account name

kindstring

Account classification: asset, liability, equity, income, or expense

subtypestring

Finer-grained account type in the source system

currencystring

ISO currency code

statusstring

Active or inactive in the source system

$ curl -X POST https://api.valtrix.com/v1/records/gl_account \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "number": "..." } }'
$ curl "https://api.valtrix.com/v1/records/gl_account/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Inventory level

inventory_level

The stock position of one item at one location: quantity on hand as of the source's latest count or running balance. A point-in-time read model; the movements that produce it map to inventory_movement.

Writable through POST /v1/records/inventory_level. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/inventory_level/:externalId for records you wrote. The title key is item_name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

item_namestring

Item the level is for

item_idreference

Valtrix record ID of the item the level is for

location_idreference

Valtrix record ID of the location holding the stock

quantitynumber

Quantity on hand

uomstring

Unit of measure

unit_costnumber

Cost per unit used to value the stock

as_ofdate

When the level was measured or last updated

$ curl -X POST https://api.valtrix.com/v1/records/inventory_level \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "item_name": "...", "quantity": 42, "item_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/inventory_level/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Inventory movement

inventory_movement

A single change to stock: receipts, transfers, adjustments, counts, waste, and sales depletion all map here, discriminated by kind. Transfers carry both locations; the resulting position maps to inventory_level.

Writable through POST /v1/records/inventory_movement. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/inventory_movement/:externalId for records you wrote. The title key is item_name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

kindstring

Receipt, transfer, adjustment, count, waste, or sale

item_namestring

Item the movement is for

item_idreference

Valtrix record ID of the item the movement is for

from_location_idreference

Valtrix record ID of the location the stock moved out of

to_location_idreference

Valtrix record ID of the location the stock moved into

quantitynumber

Quantity moved, negative when stock decreases

uomstring

Unit of measure

unit_costnumber

Cost per unit of the moved stock

occurred_atdate

When the movement happened

created_atdate

When the movement was recorded in the source

$ curl -X POST https://api.valtrix.com/v1/records/inventory_movement \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "item_name": "...", "kind": "...", "item_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/inventory_movement/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Invoice

invoice

A bill the org issues to a customer for money owed to the org. Receivables only: bills the org has to pay map to cost, and the transactions settling either side map to payment.

Writable through POST /v1/records/invoice. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/invoice/:externalId for records you wrote. The title key is number.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Invoice number

customer_namestring

Billed customer name

customer_idreference

Valtrix record ID of the billed customer

project_idreference

Valtrix record ID of the project the invoice bills against

location_idreference

Valtrix record ID of the location the invoice belongs to, for sources that bill per venue or site rather than a project

amountnumber

Total amount due

currencystring

ISO currency code

statusstring

Payment status in the source system

issued_atdate

Issue date

due_atdate

Due date

$ curl -X POST https://api.valtrix.com/v1/records/invoice \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "number": "...", "customer_name": "...", "customer_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/invoice/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Item

item

A catalog master record for a good or service the org sells, stocks, or buys: products, SKUs, menu items, materials, and rate-card services all map here. Usages of an item on a document map to line_item via item_id; stock on hand maps to inventory_level.

Writable through POST /v1/records/item. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/item/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Item name

skustring

SKU or item code in the source system

categorystring

Catalog category or family

uomstring

Default unit of measure

pricenumber

Standard selling price per unit

costnumber

Standard acquisition or production cost per unit

currencystring

ISO currency code

statusstring

Lifecycle status in the source system

created_atdate

When the item was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/item \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "sku": "..." } }'
$ curl "https://api.valtrix.com/v1/records/item/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Journal entry

journal_entry

A general ledger journal entry at the header level, the most general money record an accounting source exposes. Its debit and credit lines map to line_item coded by gl_account_id; documents with business meaning map to invoice, cost, or payment instead.

Writable through POST /v1/records/journal_entry. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/journal_entry/:externalId for records you wrote. The title key is number.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Journal entry number

descriptionstring

What the entry records

statusstring

Posting status in the source system

posted_atdate

When the entry was posted to the ledger

created_atdate

When the entry was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/journal_entry \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "number": "...", "description": "..." } }'
$ curl "https://api.valtrix.com/v1/records/journal_entry/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Line item

line_item

A single line on any financial document: contracts, purchase orders, invoices, orders, costs, estimates, and journal entries, with the parent identified by document_type and document_id. Every per-document line flavor lands here; budget rows are the one exception (budget_line).

Writable through POST /v1/records/line_item. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/line_item/:externalId for records you wrote. The title key is description.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

descriptionstring

What the line covers

document_typestring

Kind of document the line belongs to (prime contract, purchase order, invoice, order, direct cost, ...)

document_idreference

Valtrix record ID of the document the line belongs to, typed by document_type

project_idreference

Valtrix record ID of the project the line belongs to

item_idreference

Valtrix record ID of the catalog item the line sells or consumes

cost_codestring

Cost code the line is coded against

cost_code_idreference

Valtrix record ID of the cost code the line is coded against

gl_account_idreference

Valtrix record ID of the general ledger account the line is coded to, for accounting sources

cost_typestring

Cost type (labor, materials, subcontract, ...)

departmentstring

Department, team, or division the line amount is allocated to, for payroll and labor costs

quantitynumber

Quantity on the line

uomstring

Unit of measure

unit_costnumber

Cost per unit

amountnumber

Line amount

totalnumber

Extended line total

created_atdate

When the line was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/line_item \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "description": "...", "document_type": "...", "document_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/line_item/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Location

location

A standalone physical place record, such as a store, site, or warehouse. Only sources that model places as their own records produce these; address fields on another entity stay on that entity.

Writable through POST /v1/records/location. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/location/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Location name

addressstring

Street address

created_atdate

When the location was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/location \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "address": "..." } }'
$ curl "https://api.valtrix.com/v1/records/location/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Order

order

A customer order for goods or services with a total and fulfilment status, such as a sales, e-commerce, or point-of-sale order. A signed agreement of any kind maps to contract, and the resulting bill maps to invoice.

Writable through POST /v1/records/order. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/order/:externalId for records you wrote. The title key is number.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Order number

customer_namestring

Ordering customer name

customer_idreference

Valtrix record ID of the ordering customer

project_idreference

Valtrix record ID of the project the order belongs to or kicked off

location_idreference

Valtrix record ID of the location the order was placed at, for sources that scope orders to a store or venue

totalnumber

Order total

currencystring

ISO currency code

statusstring

Fulfilment status in the source system

items_countnumber

Number of line items

placed_atdate

When the order was placed

$ curl -X POST https://api.valtrix.com/v1/records/order \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "number": "...", "customer_name": "...", "customer_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/order/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Payment

payment

A payment transaction, issued to a vendor or received from a client, discriminated by kind, typically settling an invoice or cost. The document being settled is not a payment.

Writable through POST /v1/records/payment. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/payment/:externalId for records you wrote. The title key is number.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Payment number

kindstring

Issued to a vendor or received from a client

counterpartystring

Customer or vendor the payment settles with

counterparty_idreference

Valtrix record ID of the customer or vendor the payment settles with

counterparty_typestring

Entity type of the linked counterparty record, customer or vendor

invoice_numberstring

Invoice the payment settles

invoice_idreference

Valtrix record ID of the invoice the payment settles

check_numberstring

Check or reference number

cost_idreference

Valtrix record ID of the cost the payment settles

project_idreference

Valtrix record ID of the project the payment belongs to

location_idreference

Valtrix record ID of the location the payment belongs to, for sources that settle money per venue or site rather than a project

amountnumber

Amount paid

statusstring

Payment status in the source system

paid_atdate

Date of the payment

created_atdate

When the payment was recorded in the source

$ curl -X POST https://api.valtrix.com/v1/records/payment \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "number": "...", "kind": "...", "counterparty_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/payment/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Project

project

A job, project, or engagement that work is performed under, with stage, value, and dates. The scoping parent most project-scoped records hang off; every job or engagement flavor from any source maps here.

Writable through POST /v1/records/project. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/project/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Project name

numberstring

Job or project number

customer_namestring

Customer the project is for

customer_idreference

Valtrix record ID of the customer the project is for

stagestring

Lifecycle stage in the source system

citystring

Site city

countrystring

Site country code or name

valuenumber

Contracted project value

statusstring

Active or inactive in the source system

start_atdate

Planned start date

completion_atdate

Planned completion date

created_atdate

When the project was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/project \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "number": "...", "customer_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/project/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Project user

project_user

A person's membership in a specific project's directory. The person's company-level record maps to employee (internal) or contact (external).

Writable through POST /v1/records/project_user. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/project_user/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Full name

emailstring

Work email address

phonestring

Primary phone number

job_titlestring

Role within the project

person_idreference

Valtrix record ID of the company-level record for this person, an employee for internal staff or a contact for external members

person_typestring

Entity type of the linked person record, employee or contact

project_idreference

Valtrix record ID of the project the person is a member of

statusstring

Active or inactive in the source system

$ curl -X POST https://api.valtrix.com/v1/records/project_user \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "email": "...", "person_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/project_user/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Project vendor

project_vendor

A vendor's assignment to a specific project's directory. The vendor's company-level record maps to vendor.

Writable through POST /v1/records/project_vendor. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/project_vendor/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Vendor company name

tradestring

Trade or specialty the vendor operates in

citystring

City of the primary address

countrystring

Country code or name

vendor_idreference

Valtrix record ID of the company-level vendor record

project_idreference

Valtrix record ID of the project the vendor is assigned to

statusstring

Active or inactive in the source system

$ curl -X POST https://api.valtrix.com/v1/records/project_vendor \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "trade": "...", "vendor_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/project_vendor/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Resource

resource

A bookable asset at a location: courts, rooms, desks, chairs, operatories, lanes, and equipment all map here, discriminated by kind. Reservations of it map to booking; the venue itself maps to location.

Writable through POST /v1/records/resource. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/resource/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Resource name

kindstring

Court, room, desk, chair, operatory, lane, or equipment

location_idreference

Valtrix record ID of the location the resource belongs to

capacitynumber

How many people the resource accommodates at once

statusstring

Active or inactive in the source system

created_atdate

When the resource was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/resource \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "kind": "...", "location_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/resource/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Schedule task

schedule_task

A task or milestone on a project schedule, with WBS position, dates, and percent complete. Any Gantt or schedule row flavor maps here.

Writable through POST /v1/records/schedule_task. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/schedule_task/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Task name

descriptionstring

What the task covers

wbsstring

Work breakdown structure position

resource_namestring

Assigned resource or crew

project_idreference

Valtrix record ID of the project the task is scheduled on

percent_completenumber

Percentage complete

criticalboolean

Whether the task is on the critical path

milestoneboolean

Whether the task is a milestone

start_atdate

Planned start date

finish_atdate

Planned finish date

created_atdate

When the task was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/schedule_task \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "description": "...", "project_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/schedule_task/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Time entry

time_entry

A single logged block of labor time: who worked, on what project and cost code, for how many hours or at what cost. Any timesheet or timecard flavor maps here.

Writable through POST /v1/records/time_entry. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/time_entry/:externalId for records you wrote. The title key is employee_name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

employee_namestring

Who logged the time

employee_idreference

Valtrix record ID of the employee who logged the time

project_namestring

Project or job the time was logged against

project_idreference

Valtrix record ID of the project the time was logged against

cost_codestring

Cost code the time was logged against

cost_code_idreference

Valtrix record ID of the cost code the time was logged against

location_idreference

Valtrix record ID of the location the time was worked at, for sources that schedule labor per venue or site rather than a project

hoursnumber

Hours worked

costnumber

Labor cost of the entry

started_atdate

When the entry started

ended_atdate

When the entry ended

created_atdate

When the entry was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/time_entry \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "employee_name": "...", "project_name": "...", "employee_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/time_entry/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Vendor

vendor

A company the org buys from or subcontracts to, at the company-directory level. A vendor's assignment to a specific project maps to project_vendor; the agreement itself maps to contract.

Writable through POST /v1/records/vendor. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/vendor/:externalId for records you wrote. The title key is name.

The fields below make up the record's data object. Every record also carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Vendor company name

tradestring

Trade or specialty the vendor operates in

emailstring

Primary contact email

phonestring

Primary phone number

citystring

City of the primary address

countrystring

Country code or name

statusstring

Lifecycle status in the source system

created_atdate

When the vendor was created in the source

$ curl -X POST https://api.valtrix.com/v1/records/vendor \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "external_id": "crm-9x2", "data": { "name": "...", "trade": "..." } }'
$ curl "https://api.valtrix.com/v1/records/vendor/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Connect

The Connect session object

An invitation for an organization to connect one of its systems through the hosted Connect flow. Links stay valid for 45 days.

Fields
idstring

The session id. Store it against your own record of who you minted the link for: when the flow completes, org.connected carries it as connect_session_id, which ties the connection back to this session without relying on the browser redirect.

connect_urlstring

The single use hosted Connect link. Send the organization through it; when they finish they land on your redirect_uri. The link embeds a secret, so share it only with the organization and correlate on id, never on this URL.

expires_atstring

When the link stops working, as an ISO 8601 timestamp, 45 days after creation. Mint a new session after expiry.

{
"id": "cts_8m2kq",
"connect_url": "https://valtrix.com/connect/vct_...",
"expires_at": "2026-07-09T09:30:00Z"
}

Create a Connect session

POST/v1/connect/sessions

Mints a single use hosted Connect link. Send an organization through it to create or extend a grant; your tables build from the connection it creates.

Parameters
org_display_namestring

A starting name for a new organization. The person completing Connect can override it, and the organization can rename itself later, so do not rely on it to recognize the organization afterwards; use external_id or the id from GET /v1/orgs.

external_idstring

Your own identifier for the organization, such as your customer id, up to 255 characters. Stored on the grant when the flow completes and echoed as external_id on organization responses and org webhooks. Minting another session with a known external_id targets the same organization, so you can always say connect customer 123 without tracking whether they already exist on Valtrix. Sending it alongside an org id that belongs to a different organization returns 409 external_id_conflict.

orgstring

An existing organization id, to reconnect or extend access.

connectorstring

A connector slug to preselect. GET /v1/connectors lists the valid ones.

redirect_uristring

Where the organization lands after finishing.

scopeobject

Limits the requested grant, as { "entityTypes": ["..."] }. Defaults to full access.

Returns

The Connect session object, with status 201.

$ curl -X POST https://api.valtrix.com/v1/connect/sessions \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org_display_name": "Acme Inc", "external_id": "cus_123", "redirect_uri": "https://yourapp.com/connected" }'
{ "id": "cts_8m2kq", "connect_url": "https://valtrix.com/connect/vct_...", "expires_at": "2026-07-09T09:30:00Z" }

Connectors

The systems your organizations can connect through the hosted Connect flow. Every place the API takes or returns a connector identifies it by slug: the connector parameter on record reads and Connect sessions, the _connector meta field on table rows and records, the connector on a record object, the connections on an organization, and the connector field on webhook events. This endpoint is the source of those slugs, so resolve display names and build connector pickers from it instead of hardcoding the catalog.

The connector object

A system Valtrix syncs data from, as available to you.

Fields
slugstring

The connector's identifier everywhere in this API. Stable, lowercase, safe to store.

display_namestring

The connector's name, for display.

categorystring

The kind of system the connector covers, such as construction or accounts_payable.

entity_typesarray

The entity type slugs this connector syncs. GET /v1/schema describes the shape of each one.

{
"slug": "procore",
"display_name": "Procore",
"category": "construction",
"entity_types": ["project", "customer", "invoice"]
}

List connectors

GET/v1/connectors

Lists every connector available to you, in display name order. The slugs here are the valid values for the connector parameter on record reads and Connect sessions, and entity_types tells you which connectors can supply the data you care about before you send an organization through Connect.

Returns
connectorsarray

Connector objects, in display name order. Not paginated.

$ curl https://api.valtrix.com/v1/connectors \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"connectors": [{ "slug": "procore", "display_name": "Procore", "category": "construction", "entity_types": ["project", "customer", "invoice"] }]
}

Organizations

Every organization that has connected to you through Connect, including those that later revoked access. Webhooks like grant.revoked tell you something changed; these endpoints tell you where things stand now, so they are the source of truth to reconcile your own records against. Data itself is not read here: rows carry their organization in _org_id, and the org parameter narrows any table read. Called with an organization-scoped key, these endpoints see only that key's organization.

The organization object

An organization and the state of its grant to you. Organizations stay listed after revocation or expiry so a missed webhook never leaves your records unreconcilable.

Fields
idstring

The organization id. The same value rows carry as _org_id and the org parameter accepts across the API. Stable for the life of the organization, so this is the value to store on your side.

display_namestring

The organization's name. The organization owns this and can change it at any time, so treat it as display only and never use it to identify an organization in your records; store the id instead.

external_idstring or null

Your identifier for the organization, as sent when minting the Connect session that connected it. Stable and controlled by you, unlike display_name, so it is the field to join organizations back to your own customer records. Null when you have never sent one.

statusstring

active while the grant is live, revoked after the organization revokes your access, expired when the grant passed its expiry. Only active organizations are readable through tables and records.

scopeobject

The entity types the grant covers, as { "entity_types": ["..."] }. A single "*" means full access.

connectionsarray

The organization's connected systems. Each carries the connector slug, a status: pending, connected, error, expired, or disconnected, and last_synced_at, when data last synced from it, null before the first sync.

connected_atstring

When the organization granted you access, as an ISO 8601 timestamp.

expires_atstring or null

When the grant expires, or null when it has no expiry.

revoked_atstring or null

When the organization revoked your access, or null while it has not.

{
"id": "org_7f3k2m",
"display_name": "Acme Inc",
"external_id": "cus_123",
"status": "active",
"scope": { "entity_types": ["*"] },
"connections": [{ "connector": "procore", "status": "connected", "last_synced_at": "2026-07-08T09:30:00Z" }],
"connected_at": "2026-06-01T09:00:00Z",
"expires_at": null,
"revoked_at": null
}

List organizations

GET/v1/orgs

Lists every organization that has connected to you, in the order they first connected. Revoked and expired organizations are included so the list is complete for reconciliation; filter by status to narrow.

Parameters
statusstring

Narrow to one status: active, revoked, or expired.

external_idstring

Look up the organization carrying this external_id, exact match. The way to resolve one of your customer ids to its Valtrix organization.

limitnumber

1 to 200, defaults to 50.

cursorstring

The next_cursor from the previous page, sent back exactly as you received it.

Returns
orgsarray

Organization objects, in the order they first connected.

next_cursorstring or null

The bookmark for the next page. null means the last page.

$ curl https://api.valtrix.com/v1/orgs \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "status=active" -d "limit=50"
{
"orgs": [{ "id": "org_7f3k2m", "display_name": "Acme Inc", "external_id": "cus_123", "status": "active", "scope": { "entity_types": ["*"] }, "connections": [{ "connector": "procore", "status": "connected", "last_synced_at": "2026-07-08T09:30:00Z" }], "connected_at": "2026-06-01T09:00:00Z", "expires_at": null, "revoked_at": null }],
"next_cursor": null
}

Retrieve an organization

GET/v1/orgs/:id

Fetches one organization by id, whatever its status. The place to check where you stand after a grant.revoked or connection.broken webhook, or after any request returns org_not_granted.

Parameters
idpath

The organization id.

Returns

The organization object, or 404 org_not_found when no organization with that id has connected to you.

$ curl https://api.valtrix.com/v1/orgs/org_7f3k2m \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "id": "org_7f3k2m", "display_name": "Acme Inc", "external_id": "cus_123", "status": "revoked", "scope": { "entity_types": ["*"] }, "connections": [{ "connector": "procore", "status": "disconnected", "last_synced_at": "2026-07-08T09:30:00Z" }], "connected_at": "2026-06-01T09:00:00Z", "expires_at": null, "revoked_at": "2026-07-08T09:30:00Z" }

API keys

Mint and revoke organization-scoped API keys, so each app or environment that acts for one organization holds a credential that can only reach that organization's data. These endpoints require a platform-wide key with manage access, the same level that governs transformation deploys: scoped keys cannot mint, list, or revoke keys, and keys minted here are always scoped and carry read or write access, never manage. Read-only keys suit dashboards, analytics tools, and anything else that only consumes data. Platform-wide keys are minted only in the console, under Developers. Revoking the grant for an organization also cuts off its scoped keys, because every request re-checks the grant; revoking a single key retires one credential without touching the grant. Handing scoped keys to agent-built apps is covered in Embedded coding agents.

The API key object

An API key's metadata. The secret itself appears once, in the key field of the create response, and never again; only the prefix is kept for display.

Fields
idstring

The key id. Pass it to DELETE /v1/keys/:id to revoke the key.

namestring

The label the key was created with. Name keys after the app or environment that holds them.

prefixstring

The first characters of the secret, for display and log correlation. Organization-scoped secrets start with vlt_org_; platform-wide secrets start with vlt_; read-only secrets with vlt_read_ or vlt_org_read_.

orgstring or null

The organization the key is scoped to, or null for a platform-wide key.

accessstring

The key's access level: read, write, or manage. Read-only keys cannot write or delete records or create connect sessions. Organization-scoped keys are never manage.

manageboolean

Whether access is manage. Kept for backwards compatibility; read access instead.

created_atstring

When the key was created, as an ISO 8601 timestamp.

last_used_atstring or null

When the key last authenticated a request, or null if it never has.

revoked_atstring or null

When the key was revoked, or null while it is live. A revoked key fails authentication immediately.

{
"id": "key_8m2kf4",
"name": "Acme production app",
"prefix": "vlt_org_a1b2c3d4",
"org": "org_7f3k2m",
"access": "write",
"manage": false,
"created_at": "2026-07-10T08:00:00Z",
"last_used_at": null,
"revoked_at": null
}

Create an organization-scoped key

POST/v1/keys

Mints a new API key pinned to one organization. The organization must hold an active grant. The full secret is returned once, in the key field. Store it immediately.

Parameters
orgstring, required

The organization id to scope the key to, as listed by GET /v1/orgs and carried on every organization object. Required: keys created through the API are always organization-scoped.

namestring, required

A label for the key, non-empty and at most 100 characters. Name it after the app or environment that will hold it.

accessstring

read or write. Defaults to write. Read-only keys can read everything the grant covers but cannot write or delete records or create connect sessions.

Returns

The API key object plus a key field carrying the full secret, shown only in this response. 404 org_not_granted when the organization has no active grant.

$ curl https://api.valtrix.com/v1/keys \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "org": "org_7f3k2m", "name": "Acme production app" }'
{
"id": "key_8m2kf4",
"name": "Acme production app",
"prefix": "vlt_org_a1b2c3d4",
"org": "org_7f3k2m",
"access": "write",
"manage": false,
"created_at": "2026-07-10T08:00:00Z",
"last_used_at": null,
"revoked_at": null,
"key": "vlt_org_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}

List API keys

GET/v1/keys

Lists every API key, including revoked ones, newest first. Not paginated. Filter by org to see the keys scoped to one organization, for example when offboarding it.

Parameters
orgstring

Narrow to keys scoped to one organization, by organization id.

Returns
keysarray

API key objects, newest first.

$ curl https://api.valtrix.com/v1/keys \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "org=org_7f3k2m"
{
"keys": [{ "id": "key_8m2kf4", "name": "Acme production app", "prefix": "vlt_org_a1b2c3d4", "org": "org_7f3k2m", "access": "write", "manage": false, "created_at": "2026-07-10T08:00:00Z", "last_used_at": "2026-07-12T14:00:00Z", "revoked_at": null }]
}

Revoke an API key

DELETE/v1/keys/:id

Revokes a key immediately: any request made with it fails authentication from this point on. Revoking an already-revoked key is a no-op, so retries are safe.

Parameters
idpath

The key id.

Returns

The API key object with revoked_at set, or 404 key_not_found when no key with that id exists.

$ curl -X DELETE https://api.valtrix.com/v1/keys/key_8m2kf4 \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "id": "key_8m2kf4", "name": "Acme production app", "prefix": "vlt_org_a1b2c3d4", "org": "org_7f3k2m", "access": "write", "manage": false, "created_at": "2026-07-10T08:00:00Z", "last_used_at": "2026-07-12T14:00:00Z", "revoked_at": "2026-07-14T09:00:00Z" }

Transformations

The management surface for transformations as code: read every transformation as a canonical YAML definition and apply definitions through the same draft-then-publish pipeline the dashboard uses. This section is the contract: the endpoints, followed by the full definition file format (the file envelope, every step type, the expression functions, condition groups, and quality rules). The step-by-step workflow around them (repo layout, pull requests, CI wiring, ownership and adoption) lives in the Transformations as code guide, in the console under Developers. Reads and plans work with any API key; apply requires a key with manage access, the same level that governs API key management under Keys. To author a transformation from scratch, write a definition file, validate it with POST /v1/transformations/plan until the plan is clean, then publish it with POST /v1/transformations/apply.

The transformation object

A transformation and its canonical definition. The table name is the identity: definitions match remote transformations by the table they publish.

Fields
tablestring

The output table the transformation publishes. The same name you query at /v1/tables/:name, and the identity a definition file matches on.

namestring

The display name.

managed_bystring

ui while the dashboard editor owns the steps, code once a deploy has adopted it. Code-managed transformations are read-only in the dashboard editor.

statusstring

draft before the first publish, published after.

published_versionnumber or null

The current published version number, or null before the first publish.

has_draftboolean

Whether an unpublished dashboard draft exists.

definitionstring

The canonical YAML definition. Re-applying it unchanged plans as a noop.

{
"table": "vendors_enriched",
"name": "Vendors enriched",
"managed_by": "code",
"status": "published",
"published_version": 3,
"has_draft": false,
"definition": "version: 1\ntable: vendors_enriched\n..."
}

The definition file

The canonical YAML shape of a transformation, one file per output table. Steps and quality rules are the same validated shapes the dashboard editor produces; the file adds only this envelope. Step types, expression functions, condition groups, and quality rules are each documented below.

Fields
versionnumber, default 1

The definition format version. The only current version is 1.

tablestring

The output table name and the definition's identity: lowercase letters, digits, and underscores, starting with a letter, at most 63 characters. Renaming a table is a delete plus a create, not a rename.

namestring

The display name shown in the console.

source{ entity: string } or { table: string }

The input rows. { entity } reads the canonical records of an entity type (slugs listed by GET /v1/schema); { table } reads another published table, so transformations chain.

schedulenone | hourly | daily | weekly, default "none"

A timed rebuild cadence, on top of the rebuild that runs whenever new synced data arrives.

org_scopeall | selected, default "all"

all runs for every granted organization; selected runs only for organizations picked in the dashboard. Deploys never change which organizations are selected.

stepslist of steps

Applied in order; every step type is documented under Step types.

qualitylist of quality rules, optional

Documented under Quality rules.

version: 1
table: vendors_enriched
name: Vendors enriched
source:
entity: vendor
schedule: daily
org_scope: all
steps:
- type: standardize
column: email
format: lowercase
onUnparseable: keep
- type: filter_rows
where:
logic: and
conditions:
- { column: status, operator: eq, value: active }
- type: derived_column
column: region_label
expression:
fn: case
cases:
- when:
logic: and
conditions:
- { column: region, operator: not_null }
then: { column: region }
else: { literal: unknown }
quality:
- type: not_null
column: email
severity: warn
- type: accepted_values
column: status
severity: error
config:
values: [active]

List transformations

GET/v1/transformations

Lists every transformation with its canonical definition. The starting point for exporting existing transformations into files.

Returns
transformationsarray

Transformation objects, oldest first.

$ curl https://api.valtrix.com/v1/transformations \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"transformations": [{ "table": "vendors_enriched", "name": "Vendors enriched", "managed_by": "code", "status": "published", "published_version": 3, "has_draft": false, "definition": "version: 1\n..." }]
}

Retrieve a transformation

GET/v1/transformations/:table

Fetches one transformation by the table it publishes.

Parameters
tablepath

The output table name.

Returns

The transformation object, or 404 transformation_not_found when no transformation publishes that table.

$ curl https://api.valtrix.com/v1/transformations/vendors_enriched \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "table": "vendors_enriched", "name": "Vendors enriched", "managed_by": "code", "status": "published", "published_version": 3, "has_draft": false, "definition": "version: 1\n..." }

Plan a deploy

POST/v1/transformations/plan

Validates definitions server-side and returns the structured diff a deploy would apply, without writing anything. Works with any API key, so it runs in CI on pull requests; the CLI exits non-zero while changes are pending. Plan is also the way to iterate on a definition before publishing: fix what each item's error reports, re-plan, and apply once every item is clean.

Parameters
definitionsarray, required

An array of { "file", "content" } entries, one per definition file. At most 100 per call.

adoptboolean

When true, the plan treats dashboard-managed transformations as adoptable instead of reporting them blocked. Defaults to false.

Returns
itemsarray

One item per definition, in dependency order. Each carries the action (create, update, noop, missing_remote, or error), setting, step, and quality-rule changes, the schema impact (output columns added or removed), and warnings. missing_remote means the definition reads a source table that neither exists nor is defined in the same call. Items blocked by dashboard ownership carry blocked: "managed_by_ui"; invalid definitions carry the validation message in error.

$ curl https://api.valtrix.com/v1/transformations/plan \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "definitions": [{ "file": "vendors_enriched.yaml", "content": "version: 1\n..." }] }'
{
"items": [{ "file": "vendors_enriched.yaml", "table": "vendors_enriched", "action": "update", "managed_by": "code", "blocked": null, "settings": [], "steps": { "added": [], "removed": [], "modified": ["standardize (s1)"] }, "quality": null, "schema_impact": { "added": [], "removed": [], "dynamic": false }, "warnings": [], "error": null }]
}

Apply a deploy

POST/v1/transformations/apply

Writes definitions into drafts and publishes each changed transformation through the standard publish pipeline, stamping the publish with the API key's identity. Transformations apply in dependency order and the deploy stops at the first failure; later items report skipped. An unchanged definition is a noop and creates no new version. Preview the diff first with POST /v1/transformations/plan. Requires a key with manage access (the level that also governs API key management); applying marks each transformation managed_by code. The CLI has no bare apply command: valtrix deploy always plans first, shows the diff, and only applies with --yes.

Parameters
definitionsarray, required

An array of { "file", "content" } entries, one per definition file. At most 100 per call.

adoptboolean

When true, dashboard-managed transformations are adopted into code management instead of skipped. Defaults to false.

Returns
itemsarray

One item per definition, in dependency order. Each carries the action (create, update, noop, missing_remote, or error), setting, step, and quality-rule changes, the schema impact (output columns added or removed), a status (applied, noop, skipped, or failed), and the new version number and publish run_id for applied items. Items blocked by dashboard ownership carry blocked: "managed_by_ui".

$ curl https://api.valtrix.com/v1/transformations/apply \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "definitions": [{ "file": "vendors_enriched.yaml", "content": "version: 1\n..." }] }'
{
"items": [{ "file": "vendors_enriched.yaml", "table": "vendors_enriched", "action": "update", "status": "applied", "version": 4, "run_id": "run_9", "managed_by": "code", "blocked": null, "settings": [], "steps": { "added": [], "removed": [], "modified": ["standardize (s1)"] }, "quality": null, "schema_impact": { "added": [], "removed": [], "dynamic": false }, "warnings": [], "error": null }]
}

Step types

Steps run in order within each organization's rows; rows never mix across organizations. Every step also accepts an optional id (string), a stable identifier used in deploy diffs; ids like step_1 are minted when omitted. No step can produce the reserved row-metadata columns _record_id, _external_id, _org_id, _connector, _synced_at.

rename_columns

Rename columns; mapping is {"old_name": "new_name"}.

mappingobject

drop_columns

Remove the listed columns.

columnslist of strings

keep_columns

Keep only the listed columns and remove every other column.

columnslist of strings

cast

Convert a column to the given type; onError controls values that cannot be converted. "json" parses JSON text (an object or array) into structured data, so SQL step output built with json_object or json_group_array is served as real nested objects instead of a string.

columnstring

tostring | number | boolean | date | json

onErrorkeep | null

standardize

Normalize the format of a column. defaultRegion is a 2-letter country code used by phone_e164.

columnstring

formattrim | lowercase | uppercase | title_case | phone_e164 | date_iso | currency_code

defaultRegionstring, optional

onUnparseablekeep | null

map_values

Replace observed values with canonical ones; fallback controls values missing from the mapping.

columnstring

mappingobject

fallbackkeep | null | value

fallbackValuestring, optional

replace_text

Find and replace every occurrence inside a column's text; mode "regex" treats find as a regular expression.

columnstring

findstring

replaceWithstring

modeplain | regex

split_column

Split a column on a separator into the listed new columns, in order. The original column is removed unless keepOriginal is true.

columnstring

separatorstring

intolist of strings

keepOriginalboolean, optional

extract_json

Extract a nested value from a JSON object or array column into a new column. path uses dots and [n] indexes, e.g. "address.city" or "items[0].sku".

columnstring

pathstring

targetstring

bin

Label numeric ranges: the first bin whose exclusive upper bound "upTo" exceeds the value wins; a bin without upTo catches everything above. Non-numeric values get fallbackLabel or null.

columnstring

targetstring

binslist of objects

Each item: upTo (number, optional), label (string).

fallbackLabelstring, optional

filter_rows

Keep only rows matching the condition group.

wherecondition group

Documented under Conditions.

set_default

Fill empty values in a column with a default.

columnstring

valuestring

derived_column

Create a new column from an expression over existing columns.

columnstring

expressionexpression

One of the functions documented under Expression functions.

dedupe

Remove duplicate rows sharing the key columns. With orderBy, rows are ranked by that column and the first survives; otherwise keep decides ("latest" = most recently synced).

keyslist of strings

keeplatest | first

orderByobject, optional

Fields: column (string), direction (asc | desc).

lookup

Join columns from another relation by matching localKey to remoteKey. With source "table" (the default), tableName names a published table; with source "entity", tableName is the slug of another entity type and the join runs against its canonical records. Rows join within the same org only. Every relation and the input rows carry "_record_id", each record's own id; a reference column (like document_id or customer_id) holds the _record_id of the record it points at, so a child-to-parent join uses the reference column as localKey and "_record_id" as remoteKey.

sourcetable | entity, optional

tableNamestring

localKeystring

remoteKeystring

takelist of strings

prefixstring, optional

rollup

Aggregate the matching rows of another relation into one new column per row, e.g. the number of orders per customer. Matches localKey to remoteKey within the same org; fn runs over the matches' "column" ("count" may omit it). For per-parent child aggregates, use "_record_id" as localKey and the child's reference column (like document_id) as remoteKey.

sourcetable | entity, optional

tableNamestring

localKeystring

remoteKeystring

fncount | sum | avg | min | max

columnstring, optional

targetstring

aggregate

Group rows by the groupBy columns (within each org) and replace them with one row per group holding the groupBy columns plus the aggregation targets. An empty groupBy produces one row per org.

groupBylist of strings

aggregationslist of objects

One output column: fn over the group's rows. "count" without a column counts rows; every other fn needs a source column. Each item: target (string), fn (count | count_distinct | sum | avg | min | max | first), column (string, optional).

union

Append the rows of another relation (a published table, or an entity type with source "entity") below the current rows. mapping optionally renames source columns, {"source_column": "target_column"}; unmapped columns keep their names. Only rows from orgs present in the current rows are appended.

sourcetable | entity, optional

tableNamestring

mappingobject, optional

pivot

Reshape long to wide: group by the groupBy columns and create one column per listed headerValue, filled with agg over valueColumn of the rows whose headerColumn equals it. headerValues must list the expected values explicitly.

groupBylist of strings

headerColumnstring

valueColumnstring

aggcount | count_distinct | sum | avg | min | max | first

headerValueslist of strings

unpivot

Reshape wide to long: each row becomes one row per listed column, with the column name in keyColumn and its value in valueColumn. dropEmpty skips empty values.

columnslist of strings

keyColumnstring

valueColumnstring

dropEmptyboolean, optional

explode

Fan out a JSON-array column into one row per element. mode "value" puts each element in the column; mode "flatten" spreads object elements into columns (optionally prefixed). Rows without an array are dropped unless keepEmpty is true.

columnstring

modevalue | flatten

prefixstring, optional

keepEmptyboolean, optional

sql

Run a single SQLite SELECT (or WITH) over the current rows, separately per org. The table is named "input" and has the data columns plus _record_id, _org_id and _synced_at. Keep _record_id (select input.*) when the query preserves rows; a query that groups or reshapes may omit _record_id and row ids are generated. Published tables can be joined by their name, and entity types by their slug when no table shares that name; a joined relation has ONLY _org_id plus its data columns (no _record_id and no _synced_at) and already contains only rows from the same org, so no org filtering is needed. Use only when the other step types cannot express the logic.

querystring

Expression functions

The functions a derived_column step's expression field accepts, selected by fn. Column arguments name existing columns.

concat
partslist of { column: string } or { literal: string }

coalesce
columnslist of strings

arithmetic

Arithmetic over columns and numbers; left/right are a column name, a number, or a nested arithmetic expression.

leftcolumn name, number, or nested arithmetic expression

operator+ | - | * | /

rightcolumn name, number, or nested arithmetic expression

year_of
columnstring

case

If/else over conditions: the first matching case wins; "then"/"else" are a column reference or a literal. Without a matching case and no "else", the value is null.

caseslist of objects

Each item: when (condition group), then ({ column: string } or { literal: string or number }).

else{ column: string } or { literal: string or number }, optional

substring

Slice of the text; start is 1-based, length optional (to the end when omitted).

columnstring

startnumber

lengthnumber, optional

replace
columnstring

findstring

replaceWithstring

regexboolean, optional

regex_extract

First regex match; group defaults to capture group 1 when the pattern has one, else the whole match.

columnstring

patternstring

groupnumber, optional

split_part

Split the text on the separator and take the 1-based part.

columnstring

separatorstring

indexnumber

length
columnstring

date_part
columnstring

partyear | month | day

date_trunc
columnstring

unitday | week | month | year

date_diff

Difference "to" minus "from" in the unit; both are date columns, "to" defaults to now.

unitdays | months | years

fromstring

tostring, optional

date_add
columnstring

amountnumber

unitdays | months | years

round
columnstring

digitsnumber, optional

abs
columnstring

floor
columnstring

ceil
columnstring

Conditions

Condition groups appear in filter_rows steps, in case expressions, and in expression quality rules.

condition group

A group of column conditions combined with "and" or "or". Operators "in"/"not_in" read the "values" list; "not_null"/"is_null" need no value and treat blank strings, empty lists, and empty objects as empty; "contains"/"not_contains" check list elements when the column holds a list; "gt"/"gte"/"lt"/"lte" and "within_last_days" compare numerically; "before"/"after" compare as dates; every other operator compares against "value" as text.

logicand | or

conditionslist of conditions

condition

One column check inside a condition group.

columnstring

operatorenum

One of: eq (equals), neq (does not equal), contains (contains), not_contains (does not contain), starts_with (starts with), ends_with (ends with), regex (matches regex), in (is one of), not_in (is not one of), gt (greater than), gte (greater than or equal), lt (less than), lte (less than or equal), before (is before), after (is after), within_last_days (within last N days), not_null (is not empty), is_null (is empty).

valuestring, optional

The comparison value; unused by not_null, is_null, in, and not_in.

valueslist of strings, optional

The list read by in and not_in.

Quality rules

Rules evaluate separately within each organization's rows after every rebuild. With severity error, not_null, unique, accepted_values, regex, relationship, expression rules quarantine failing rows, removing them from the table until they pass; freshness and volume report without quarantining. With severity warn, every rule reports without quarantining.

not_null

Fails rows where the column is empty. Blank strings, empty lists, and empty objects count as empty.

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

unique

Fails every row whose column value appears more than once within the organization's rows.

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

accepted_values

Fails rows whose column value is not in the accepted list.

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.valueslist of strings

The accepted values, compared as text.

regex

Fails rows whose column value does not match the pattern.

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.patternstring

A JavaScript regular expression source, without slashes.

freshness

Fails rows whose last sync is older than the age limit. Takes no column.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.maxAgeHoursnumber, default 24

The maximum age in hours.

volume

Fails when an organization has fewer rows than the minimum. Takes no column.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.minRowsnumber, default 1

The minimum expected row count per organization.

relationship

Fails rows whose column value does not exist as a key in the related table or entity type, checked within the same organization.

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.relationobject

Fields: name (string, the related published table, or an entity type slug), remoteKey (string, the key column in the relation), source (table | entity, optional, resolved automatically when omitted).

expression

Fails rows that do not match the condition group.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.wherecondition group

Documented under Conditions.

Webhooks

Webhooks push notifications to your server so you never poll on a timer. Add an endpoint under Developers, Webhooks in the console; you get a signing secret shown once. Events carry ids, never row data: when table.changed arrives, drain the change feed from your own stored cursor. Deliveries retry with backoff and can repeat, so acknowledge with any 2xx within 5 seconds and deduplicate on the event id.

The event object

The envelope every webhook delivery posts to your endpoint. The same shape for every event type; only data varies.

Fields
idstring

Unique per event. Deliveries can repeat, so treat an id you have already seen as handled.

typestring

The event name, like table.changed. Event types lists every one.

occurred_atstring

When the event happened, as an ISO 8601 timestamp.

dataobject

The event payload. Carries ids and names, never row data; Event types lists the fields per event.

{
"id": "evt_01h9x...",
"type": "table.changed",
"occurred_at": "2026-07-08T09:30:00Z",
"data": { "table": "customers_clean", "latest_cursor": "eyJjIjoiODIzMiJ9" }
}

Event types

Every event the type field can name, with the fields each one puts in data.

table.changed

A table has new changes. Fetch GET /v1/tables/:name/changes from your own stored cursor. Coalesced, at most one delivery per table every 30 seconds.

data.tablestring

The name of the table that changed, as used in /v1/tables/:name paths.

data.latest_cursorstring

The bookmark of the newest change at send time. Use it only to compare against your own stored cursor: if they match you are already caught up and can skip the drain. Never fetch the change feed starting from this value; that starts at the end of the feed and skips everything between your last drain and now. Always drain from your own stored cursor.

org.connected

An organization granted you access, through a new connection or by extending an existing grant.

data.org_idstring

The organization that granted access. Use it with the org parameter across the API.

data.external_idstring or null

Your identifier for the organization, as sent on the Connect session. Null when you have never sent one.

data.connect_session_idstring or null

The id of the Connect session the organization completed, matching the id returned when you minted the link. The reliable way to attribute the event to the customer you minted the link for, even if the browser never reached your redirect_uri. Null when access was granted outside a Connect session.

org.initial_sync.completed

The first sync of a new connection finished and its data is ready to query.

data.org_idstring

The organization the connection belongs to.

data.external_idstring or null

Your identifier for the organization, or null when you have never sent one.

data.connectorstring

The connector slug of the system that synced.

connection.broken

An organization's connection stopped syncing. Rows already synced stay in your tables but stop updating until it recovers.

data.org_idstring

The organization the connection belongs to.

data.external_idstring or null

Your identifier for the organization, or null when you have never sent one.

data.connectorstring

The connector slug of the affected system.

data.reasonstring

reauthorization_required when the organization must reconnect through a new Connect session, sync_failed when syncing hit a persistent error.

grant.revoked

An organization revoked your access; its rows disappear from your tables.

data.org_idstring

The organization that revoked access.

data.external_idstring or null

Your identifier for the organization, or null when you have never sent one.

table.published

A table was published for the first time and now appears in GET /v1/schema.

data.tablestring

The name of the new table.

data.schema_versionstring

The version it published at, like "v1". The same form GET /v1/schema reports.

table.schema_updated

A published table's schema changed. Compare against GET /v1/schema and regenerate your SDK types.

data.tablestring

The name of the table whose schema changed.

data.schema_versionstring

The new schema version, like "v3". The same form GET /v1/schema reports.

endpoint.test

A test delivery triggered from the console, for verifying your handler end to end. data is empty.

Receive events

POST(your endpoint URL)

Every event is a JSON POST with the event object as the body. Verify the signature before trusting the payload: recompute HMAC-SHA256 over "<t>.<raw body>" with your signing secret and compare it to v1, rejecting timestamps older than 5 minutes. Respond with any 2xx within 5 seconds; anything else is retried with backoff, and an endpoint that keeps failing is marked failing in the console.

Parameters
Valtrix-Signatureheader

Formatted as t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" keyed with your signing secret>.

Valtrix-Event-Idheader

Unique per event. Deliveries may repeat, so treat events with a seen id as already handled.

Returns

This endpoint is yours, so the response is too: any 2xx within 5 seconds acknowledges the event, and the response body is ignored. The delivery itself carries the event object as its body.

POST https://yourapp.com/webhooks/valtrix
Valtrix-Signature: t=1783686600,v1=5f3a...
Valtrix-Event-Id: evt_01h9x...
{ "id": "evt_01h9x...", "type": "table.changed", "occurred_at": "2026-07-08T09:30:00Z", "data": { "table": "customers_clean", "latest_cursor": "eyJjIjoiODIzMiJ9" } }