https://api.valtrix.com/v1
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" }
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.
Organizations connect their third-party systems through Connect. Valtrix syncs their data continuously and stores it as Records.
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.
Publishing a Transformation produces typed, versioned Tables where data from every organization shares one shape.
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.
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"
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.
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 URLhttps://api.valtrix.com/mcpAuthorization headerAuthorization: Bearer YOUR_API_KEY
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 backendWhen 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 templateAdd @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 templateThe 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 templateRun 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 .
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_filter400A row filter is not in <op>.<value> form, or its value does not match the column type.
invalid_order400order is not in <column>.<asc|desc> form.
invalid_limit400limit is not a positive integer.
invalid_cursor400The cursor is malformed. Always pass next_cursor back verbatim.
unknown_column400A filter, select, or order names a column that is not in the table or entity schema.
unknown_connector400No connector with that slug. GET /v1/connectors lists the valid ones.
org_required400Record endpoints need an org parameter, and POST /v1/keys needs an org in the body.
invalid_name400An API key name is empty or longer than 100 characters.
invalid_external_id400external_id is missing or longer than 255 characters.
invalid_fields400Record data failed validation. The response includes a fields array naming each issue.
invalid_scope400scope is not a valid list of entity types.
invalid_status400status must be active, revoked, or expired.
invalid_access400access must be read or write. Keys minted through POST /v1/keys never carry manage.
invalid_disposition400disposition on GET /v1/attachments/:attachmentId/url must be inline or attachment.
invalid_definitions400The definitions payload on POST /v1/transformations/plan or /v1/transformations/apply failed validation. The response includes the failing items.
scope_not_granted403The grant for this organization does not cover that entity type.
org_not_scoped403The request names an organization other than the one this organization-scoped key is pinned to.
key_access_denied403The 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_required403This 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_found404No published table with that name.
unknown_entity_type404No entity type with that slug. GET /v1/schema lists the valid ones.
record_not_found404No 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 403No 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_found404No organization with that id has connected to you.
key_not_found404No API key with that id exists.
attachment_not_found404No attachment with that id exists, or its record is outside your granted organizations.
transformation_not_found404No transformation publishes a table with that name.
external_id_conflict409The external_id on a connect session already identifies a different organization. Omit org to reuse it, or send the matching org id.
cursor_expired410The changes cursor is older than the 30 day retention window. Restart from a full rows read.
payload_too_large413Record data must be under 100KB.
rate_limited429Over 300 requests per minute on this API key. Retry after the seconds in the Retry-After header.
storage_unavailable503Attachment 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"}
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 429Retry-After: 12{"error": "Rate limit exceeded.","code": "rate_limited"}
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, neqoperatorsEquals and not equals.
gt, gte, lt, lteoperatorsRange comparisons, typed per column: number columns compare numerically, date columns compare in ISO order.
in.(a,b)operatorMatches any value in a comma separated list.
is.null, not.nulloperatorsWhether the column has a value.
_record_idmeta columnPoint 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 columnstypedColumns 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"
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.
limitnumber1 to 200, defaults to 50.
cursorstringThe next_cursor from the previous page, sent back exactly as you received it. Omit on the first call.
next_cursorresponseSend 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..."
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.
<column>per schemaOne 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_idstringThe 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_idstringThe 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 nullThe 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_atstringWhen 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.
_frozenbooleantrue 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}
One entry in a table's change feed, describing a row that was created, updated, or removed.
typestringupsert when the row was created or updated, delete when it was removed.
cursorstringA bookmark for this change's position in the feed. Store the last one you processed and resume from it.
occurred_atstringWhen the change happened, as an ISO 8601 timestamp.
record_idstringThe id of the affected row, the same value the row carries as _record_id. For deletes it identifies which row to drop.
org_idstringThe organization the row belongs to.
rowobject or nullThe 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": { ... }}
/v1/schemaYour full API contract: every published table with its columns, and every writable entity type. Compare schema_version between reads to detect published changes.
tablesarrayOne entry per published table, with its columns.
recordsarrayOne 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": [...] }]}
/v1/tablesLists 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.
tablesarrayTable 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": [...] }] }
/v1/tables/:name/rowsReads rows across every organization you hold an active grant for. Filtering, ordering, projection, and pagination run server side.
namepathThe table name, as listed by /v1/tables.
<column>string, repeatableFilter 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_idstringPoint 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.
orderstringOne column as <column>.<asc|desc>. _synced_at is also orderable.
selectstringComma separated column keys to return.
limitnumber1 to 200, defaults to 50.
cursorstringThe next_cursor from the previous page, sent back exactly as you received it.
orgstringNarrow to one organization by id.
rowsarrayRow objects matching the query.
next_cursorstring or nullThe bookmark for the next page. null means the last page.
schema_versionstringThe 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"}
/v1/tables/:name/changesAn 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.
namepathThe table name, as listed by /v1/tables.
cursorstringThe cursor of the last change you processed, or the last next_cursor. Omit on the first call to read from the start.
limitnumber1 to 200, defaults to 50.
orgstringNarrow to one organization by id.
changesarrayChange objects in feed order. Empty means you are caught up.
next_cursorstring or nullThe 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_versionstringThe 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 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.
A record as Valtrix stores it, before transformation. Records synced from connected systems and records you write through this API share this shape.
idstringThe 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_typestringThe entity type slug. GET /v1/schema lists them.
external_idstringThe identifier the record was written under: yours for records written through this API, the source system's for synced records.
org_idstringThe organization the record belongs to.
connectorstringThe 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.
dataobjectThe 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_atstringWhen the record was last written or synced, as an ISO 8601 timestamp.
frozenbooleantrue 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}
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.
idstringThe 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 nullThe 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_typestringThe 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.
bytesnumberThe stored size in bytes.
content_hashstringSHA-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_atstringWhen 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"}
/v1/records/:entityTypeQueries 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.
entityTypepathThe entity type slug. GET /v1/schema lists the valid ones.
<field>string, repeatableFilter 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_idstringPoint 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.
orderstringOne field as <field>.<asc|desc>. _synced_at is also orderable.
selectstringComma separated field keys to return.
limitnumber1 to 200, defaults to 50.
cursorstringThe next_cursor from the previous page, sent back exactly as you received it.
orgstringNarrow to one organization by id.
connectorstringNarrow 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_sincestringOnly 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.
recordsarrayOne object per record: the entity's fields, plus _record_id, _external_id, _org_id, _connector, _synced_at, and _frozen.
next_cursorstring or nullThe bookmark for the next page. null means the last page.
schema_versionstringThe 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"}
/v1/records/:entityTypeCreates 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.
entityTypepathThe entity type slug. GET /v1/schema lists the valid ones.
orgstring, requiredThe organization the record belongs to. The grant must cover this entity type.
external_idstring, requiredYour identifier, up to 255 characters. Same external_id updates the same record.
dataobject, requiredAn 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.
createdbooleantrue with status 201 on create, false with status 200 on update.
recordobjectThe record object as stored.
tablesarrayThe 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": [] }]}
/v1/records/:recordIdFetches 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.
recordIdpathThe rec_ prefixed record ID, from a row's _record_id, a reference field, or a record object's id.
recordobjectThe 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.
tablesarrayThe current row it produces in each of your tables.
attachmentsarrayThe 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": [] }
/v1/records/:entityType/:externalIdFetches 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.
entityTypepathThe entity type slug. GET /v1/schema lists the valid ones.
externalIdpathThe external_id you supplied when writing the record.
orgstring, requiredThe organization the record belongs to.
recordobjectThe record object.
tablesarrayThe current row the record produces in each table. A null row means it was filtered out or quarantined.
attachmentsarrayThe 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": [] }
/v1/records/:entityType/:externalIdDeletes a record you wrote and removes its derived rows. The removals also appear in each change feed.
entityTypepathThe entity type slug. GET /v1/schema lists the valid ones.
externalIdpathThe external_id you supplied when writing the record.
orgstring, requiredThe organization the record belongs to.
deletedbooleantrue when the record and its derived rows were removed.
tablesarrayThe 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": [] }] }
/v1/records/:recordId/attachmentsLists 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.
recordIdpathThe rec_ prefixed record ID.
attachmentsarrayThe 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" }] }
/v1/attachments/:attachmentId/urlReturns 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.
attachmentIdpathThe attachment ID, from the record's attachment list.
dispositionstringinline (default) serves the document for viewing in the browser; attachment serves it as a file download with the original filename.
urlstringThe 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_innumberSeconds 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 }
/v1/attachments/:attachmentId/downloadFetches 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.
attachmentIdpathThe attachment ID, from the record's attachment list.
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
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.
bookingA 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_namestringCustomer the booking is for
customer_idreferenceValtrix record ID of the customer the booking is for
event_idreferenceValtrix record ID of the event the booking reserves a spot in, for group occurrences
resource_idreferenceValtrix record ID of the resource the booking reserves, such as a court, room, or desk
employee_idreferenceValtrix record ID of the staff member the booking is with, such as the practitioner or stylist
item_idreferenceValtrix record ID of the catalog item for the booked service
location_idreferenceValtrix record ID of the location the booking takes place at
order_idreferenceValtrix record ID of the order that paid for the booking
entitlement_idreferenceValtrix record ID of the entitlement the booking consumed a credit from
statusstringBooked, attended, completed, no show, cancelled, or waitlisted
start_atdateWhen the booking starts
end_atdateWhen the booking ends
created_atdateWhen 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_changeAn 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.
numberstringBudget change number
titlestringBudget change title
descriptionstringWhat the budget change covers
statusstringApproval status in the source system
amountnumberNet amount of the change
project_idreferenceValtrix record ID of the project the budget change belongs to
created_atdateWhen 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_detailA 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_codestringCost code the row is budgeted against
cost_code_idreferenceValtrix record ID of the cost code the row is budgeted against
categorystringCost category or division
project_idreferenceValtrix record ID of the project the row is budgeted against
original_amountnumberOriginal budgeted amount
budget_changesnumberApproved budget changes
approved_cosnumberApproved change orders
pending_cosnumberPending change orders
revised_amountnumberBudget after approved changes
committed_costsnumberCommitted costs
direct_costsnumberDirect costs to date
jtd_costsnumberJob-to-date costs
projected_costsnumberProjected total costs
estimated_finalnumberEstimated cost at completion
over_undernumberProjected 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_lineA 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_namestringProject or job the line is budgeted against
project_idreferenceValtrix record ID of the project the line is budgeted against
cost_codestringCost code the line is budgeted against
cost_code_idreferenceValtrix record ID of the cost code the line is budgeted against
descriptionstringWhat the budget line covers
categorystringCost category or division
cost_typestringCost type (labor, material, subcontractor, ...)
quantitynumberBudgeted quantity
unit_costnumberBudgeted cost per unit
original_amountnumberOriginal budgeted amount
revised_amountnumberRevised budget after approved changes
document_typestringKind of source document the line belongs to
document_statusstringApproval status of the source document
created_atdateWhen 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_eventAn 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.
numberstringChange event number
titlestringChange event title
descriptionstringWhat changed and why
scopestringWhether the change is in or out of scope
statusstringLifecycle status in the source system
change_typestringKind of change
change_reasonstringReason for the change
change_order_idreferenceValtrix record ID of the change order the event became once priced
change_order_typestringEntity type of the linked change order record, change_order or commitment_change_order
project_idreferenceValtrix record ID of the project the change event belongs to
created_atdateWhen 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_orderA 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.
numberstringChange order number
titlestringChange order title
statusstringApproval status in the source system
totalnumberTotal value of the change
contract_idreferenceValtrix record ID of the client-facing contract the change order belongs to
project_idreferenceValtrix record ID of the project the change order belongs to
executedbooleanWhether the change order is executed
created_atdateWhen 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_orderA 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.
numberstringChange order number
titlestringChange order title
statusstringApproval status in the source system
totalnumberTotal value of the change
contract_idreferenceValtrix record ID of the commitment contract the change order belongs to
project_idreferenceValtrix record ID of the project the change order belongs to
executedbooleanWhether the change order is executed
created_atdateWhen 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_documentA 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.
titlestringName of the form or template the document was raised from
kindstringLien waiver, insurance certificate, tax form, liability waiver, consent form, or certification
statusstringWhere the document sits in its signature lifecycle: pending, signed, declined, expired, or released
subject_typestringEntity type of the record the document gates, cost, contract, or booking
subject_idreferenceValtrix record ID of the record this signature gates, null for a standing credential held against the counterparty itself
counterpartystringVendor, customer, or employee required to sign
counterparty_idreferenceValtrix record ID of the party required to sign
counterparty_typestringEntity type of the linked counterparty record, vendor, customer, or employee
project_idreferenceValtrix record ID of the project the document belongs to
location_idreferenceValtrix record ID of the location the document belongs to, for sources that hold compliance against a venue or site rather than a project
amountnumberAmount the document covers, which may be a partial release against the gated record total
effective_atdateDate the document takes effect from
expires_atdateDate the document lapses and must be renewed, for coverage and certifications that expire
signed_atdateWhen the counterparty signed, null while the document is unsigned
signed_by_namestringName of the individual who signed on the counterparty side
signed_by_titlestringRole 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"
contactAn 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.
namestringContact name
titlestringRole or title
party_idreferenceValtrix record ID of the customer or vendor the contact belongs to
party_typestringEntity type of the linked party record, customer or vendor
emailstringPrimary email address
phonestringPrimary phone number
created_atdateWhen 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"
contractAn 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.
numberstringContract number
titlestringContract title
kindstringPrime, subcontract, purchase order, membership, subscription, or lease
counterpartystringCustomer or vendor the agreement is with
counterparty_idreferenceValtrix record ID of the customer or vendor the agreement is with
counterparty_typestringEntity type of the linked counterparty record, customer or vendor
project_idreferenceValtrix record ID of the project the contract belongs to
statusstringContract status in the source system
totalnumberTotal contract value
retainage_percentnumberRetainage percentage withheld
executedbooleanWhether the contract is executed
contract_atdateDate 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"
costA 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.
descriptionstringWhat the cost covers
kindstringInvoice, expense, payroll, or other cost type
statusstringApproval status in the source system
invoice_numberstringVendor invoice reference
payee_namestringVendor or employee paid
payee_idreferenceValtrix record ID of the vendor or employee paid
payee_typestringEntity type of the linked payee record, vendor or employee
project_idreferenceValtrix record ID of the project the cost belongs to
location_idreferenceValtrix record ID of the location the cost belongs to, for sources that book costs against a venue or site rather than a project
totalnumberTotal amount
received_atdateWhen the cost was received
paid_atdateWhen 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_codeAn 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.
codestringFull cost code
namestringCost code name
statusstringActive 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"
customerA 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.
namestringFull customer name
emailstringPrimary email address
phonestringPrimary phone number
countrystringCountry code or name
statusstringLifecycle status in the source system
created_atdateWhen 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"
employeeA 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.
namestringFull name
emailstringWork email address
phonestringPrimary phone number
job_titlestringRole within the company
departmentstringDepartment, team, or division the employee belongs to
annual_salarynumberAnnual base salary or salary-equivalent compensation, in the source currency
employee_idstringInternal employee number
start_datedateWhen the employee started at the company, the hire date in the source system
statusstringLifecycle status in the source system
created_atdateWhen 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"
entitlementA 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_namestringCatalog item the entitlement was granted from
item_idreferenceValtrix record ID of the item the entitlement was granted from
customer_idreferenceValtrix record ID of the customer holding the entitlement
contract_idreferenceValtrix record ID of the membership or subscription agreement granting the entitlement
order_idreferenceValtrix record ID of the order that purchased the entitlement
kindstringWhat the balance counts: visits, credits, minutes, or currency
quantitynumberQuantity granted
remainingnumberQuantity remaining
statusstringActive, expired, or exhausted in the source system
starts_atdateWhen the entitlement becomes usable
expires_atdateWhen the entitlement expires
created_atdateWhen 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"
estimateA 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.
numberstringEstimate or quote number
titlestringEstimate title
customer_namestringCustomer the estimate was issued to
customer_idreferenceValtrix record ID of the customer the estimate was issued to
project_idreferenceValtrix record ID of the project the estimate belongs to
contract_idreferenceValtrix record ID of the contract the estimate became once accepted
statusstringLifecycle status in the source system
totalnumberTotal proposed amount
currencystringISO currency code
issued_atdateWhen the estimate was issued
expires_atdateWhen the estimate expires
created_atdateWhen 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"
eventA 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.
namestringEvent name
kindstringClass, course, workshop, or other occurrence type
item_idreferenceValtrix record ID of the catalog item for the service the event delivers
employee_idreferenceValtrix record ID of the employee leading the event, such as the instructor or teacher
resource_idreferenceValtrix record ID of the resource the event occupies, such as a room or court
location_idreferenceValtrix record ID of the location the event takes place at
capacitynumberMaximum number of bookings
booked_countnumberNumber of spots taken
statusstringScheduled, cancelled, or completed in the source system
start_atdateWhen the event starts
end_atdateWhen the event ends
created_atdateWhen 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_accountAn 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.
numberstringAccount number in the chart of accounts
namestringAccount name
kindstringAccount classification: asset, liability, equity, income, or expense
subtypestringFiner-grained account type in the source system
currencystringISO currency code
statusstringActive 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_levelThe 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_namestringItem the level is for
item_idreferenceValtrix record ID of the item the level is for
location_idreferenceValtrix record ID of the location holding the stock
quantitynumberQuantity on hand
uomstringUnit of measure
unit_costnumberCost per unit used to value the stock
as_ofdateWhen 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_movementA 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.
kindstringReceipt, transfer, adjustment, count, waste, or sale
item_namestringItem the movement is for
item_idreferenceValtrix record ID of the item the movement is for
from_location_idreferenceValtrix record ID of the location the stock moved out of
to_location_idreferenceValtrix record ID of the location the stock moved into
quantitynumberQuantity moved, negative when stock decreases
uomstringUnit of measure
unit_costnumberCost per unit of the moved stock
occurred_atdateWhen the movement happened
created_atdateWhen 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"
invoiceA 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.
numberstringInvoice number
customer_namestringBilled customer name
customer_idreferenceValtrix record ID of the billed customer
project_idreferenceValtrix record ID of the project the invoice bills against
location_idreferenceValtrix record ID of the location the invoice belongs to, for sources that bill per venue or site rather than a project
amountnumberTotal amount due
currencystringISO currency code
statusstringPayment status in the source system
issued_atdateIssue date
due_atdateDue 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"
itemA 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.
namestringItem name
skustringSKU or item code in the source system
categorystringCatalog category or family
uomstringDefault unit of measure
pricenumberStandard selling price per unit
costnumberStandard acquisition or production cost per unit
currencystringISO currency code
statusstringLifecycle status in the source system
created_atdateWhen 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_entryA 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.
numberstringJournal entry number
descriptionstringWhat the entry records
statusstringPosting status in the source system
posted_atdateWhen the entry was posted to the ledger
created_atdateWhen 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_itemA 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.
descriptionstringWhat the line covers
document_typestringKind of document the line belongs to (prime contract, purchase order, invoice, order, direct cost, ...)
document_idreferenceValtrix record ID of the document the line belongs to, typed by document_type
project_idreferenceValtrix record ID of the project the line belongs to
item_idreferenceValtrix record ID of the catalog item the line sells or consumes
cost_codestringCost code the line is coded against
cost_code_idreferenceValtrix record ID of the cost code the line is coded against
gl_account_idreferenceValtrix record ID of the general ledger account the line is coded to, for accounting sources
cost_typestringCost type (labor, materials, subcontract, ...)
departmentstringDepartment, team, or division the line amount is allocated to, for payroll and labor costs
quantitynumberQuantity on the line
uomstringUnit of measure
unit_costnumberCost per unit
amountnumberLine amount
totalnumberExtended line total
created_atdateWhen 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"
locationA 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.
namestringLocation name
addressstringStreet address
created_atdateWhen 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"
orderA 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.
numberstringOrder number
customer_namestringOrdering customer name
customer_idreferenceValtrix record ID of the ordering customer
project_idreferenceValtrix record ID of the project the order belongs to or kicked off
location_idreferenceValtrix record ID of the location the order was placed at, for sources that scope orders to a store or venue
totalnumberOrder total
currencystringISO currency code
statusstringFulfilment status in the source system
items_countnumberNumber of line items
placed_atdateWhen 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"
paymentA 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.
numberstringPayment number
kindstringIssued to a vendor or received from a client
counterpartystringCustomer or vendor the payment settles with
counterparty_idreferenceValtrix record ID of the customer or vendor the payment settles with
counterparty_typestringEntity type of the linked counterparty record, customer or vendor
invoice_numberstringInvoice the payment settles
invoice_idreferenceValtrix record ID of the invoice the payment settles
check_numberstringCheck or reference number
cost_idreferenceValtrix record ID of the cost the payment settles
project_idreferenceValtrix record ID of the project the payment belongs to
location_idreferenceValtrix record ID of the location the payment belongs to, for sources that settle money per venue or site rather than a project
amountnumberAmount paid
statusstringPayment status in the source system
paid_atdateDate of the payment
created_atdateWhen 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"
projectA 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.
namestringProject name
numberstringJob or project number
customer_namestringCustomer the project is for
customer_idreferenceValtrix record ID of the customer the project is for
stagestringLifecycle stage in the source system
citystringSite city
countrystringSite country code or name
valuenumberContracted project value
statusstringActive or inactive in the source system
start_atdatePlanned start date
completion_atdatePlanned completion date
created_atdateWhen 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_userA 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.
namestringFull name
emailstringWork email address
phonestringPrimary phone number
job_titlestringRole within the project
person_idreferenceValtrix record ID of the company-level record for this person, an employee for internal staff or a contact for external members
person_typestringEntity type of the linked person record, employee or contact
project_idreferenceValtrix record ID of the project the person is a member of
statusstringActive 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_vendorA 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.
namestringVendor company name
tradestringTrade or specialty the vendor operates in
citystringCity of the primary address
countrystringCountry code or name
vendor_idreferenceValtrix record ID of the company-level vendor record
project_idreferenceValtrix record ID of the project the vendor is assigned to
statusstringActive 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"
resourceA 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.
namestringResource name
kindstringCourt, room, desk, chair, operatory, lane, or equipment
location_idreferenceValtrix record ID of the location the resource belongs to
capacitynumberHow many people the resource accommodates at once
statusstringActive or inactive in the source system
created_atdateWhen 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_taskA 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.
namestringTask name
descriptionstringWhat the task covers
wbsstringWork breakdown structure position
resource_namestringAssigned resource or crew
project_idreferenceValtrix record ID of the project the task is scheduled on
percent_completenumberPercentage complete
criticalbooleanWhether the task is on the critical path
milestonebooleanWhether the task is a milestone
start_atdatePlanned start date
finish_atdatePlanned finish date
created_atdateWhen 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_entryA 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_namestringWho logged the time
employee_idreferenceValtrix record ID of the employee who logged the time
project_namestringProject or job the time was logged against
project_idreferenceValtrix record ID of the project the time was logged against
cost_codestringCost code the time was logged against
cost_code_idreferenceValtrix record ID of the cost code the time was logged against
location_idreferenceValtrix record ID of the location the time was worked at, for sources that schedule labor per venue or site rather than a project
hoursnumberHours worked
costnumberLabor cost of the entry
started_atdateWhen the entry started
ended_atdateWhen the entry ended
created_atdateWhen 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"
vendorA 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.
namestringVendor company name
tradestringTrade or specialty the vendor operates in
emailstringPrimary contact email
phonestringPrimary phone number
citystringCity of the primary address
countrystringCountry code or name
statusstringLifecycle status in the source system
created_atdateWhen 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"
An invitation for an organization to connect one of its systems through the hosted Connect flow. Links stay valid for 45 days.
idstringThe 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_urlstringThe 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_atstringWhen 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"}
/v1/connect/sessionsMints 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.
org_display_namestringA 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_idstringYour 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.
orgstringAn existing organization id, to reconnect or extend access.
connectorstringA connector slug to preselect. GET /v1/connectors lists the valid ones.
redirect_uristringWhere the organization lands after finishing.
scopeobjectLimits the requested grant, as { "entityTypes": ["..."] }. Defaults to full access.
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" }
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.
A system Valtrix syncs data from, as available to you.
slugstringThe connector's identifier everywhere in this API. Stable, lowercase, safe to store.
display_namestringThe connector's name, for display.
categorystringThe kind of system the connector covers, such as construction or accounts_payable.
entity_typesarrayThe 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"]}
/v1/connectorsLists 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.
connectorsarrayConnector 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"] }]}
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.
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.
idstringThe 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_namestringThe 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 nullYour 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.
statusstringactive 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.
scopeobjectThe entity types the grant covers, as { "entity_types": ["..."] }. A single "*" means full access.
connectionsarrayThe 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_atstringWhen the organization granted you access, as an ISO 8601 timestamp.
expires_atstring or nullWhen the grant expires, or null when it has no expiry.
revoked_atstring or nullWhen 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}
/v1/orgsLists 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.
statusstringNarrow to one status: active, revoked, or expired.
external_idstringLook up the organization carrying this external_id, exact match. The way to resolve one of your customer ids to its Valtrix organization.
limitnumber1 to 200, defaults to 50.
cursorstringThe next_cursor from the previous page, sent back exactly as you received it.
orgsarrayOrganization objects, in the order they first connected.
next_cursorstring or nullThe 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}
/v1/orgs/:idFetches 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.
idpathThe organization id.
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" }
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.
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.
idstringThe key id. Pass it to DELETE /v1/keys/:id to revoke the key.
namestringThe label the key was created with. Name keys after the app or environment that holds them.
prefixstringThe 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 nullThe organization the key is scoped to, or null for a platform-wide key.
accessstringThe 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.
managebooleanWhether access is manage. Kept for backwards compatibility; read access instead.
created_atstringWhen the key was created, as an ISO 8601 timestamp.
last_used_atstring or nullWhen the key last authenticated a request, or null if it never has.
revoked_atstring or nullWhen 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}
/v1/keysMints 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.
orgstring, requiredThe 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, requiredA label for the key, non-empty and at most 100 characters. Name it after the app or environment that will hold it.
accessstringread or write. Defaults to write. Read-only keys can read everything the grant covers but cannot write or delete records or create connect sessions.
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"}
/v1/keysLists 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.
orgstringNarrow to keys scoped to one organization, by organization id.
keysarrayAPI 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 }]}
/v1/keys/:idRevokes 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.
idpathThe key id.
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" }
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.
A transformation and its canonical definition. The table name is the identity: definitions match remote transformations by the table they publish.
tablestringThe output table the transformation publishes. The same name you query at /v1/tables/:name, and the identity a definition file matches on.
namestringThe display name.
managed_bystringui while the dashboard editor owns the steps, code once a deploy has adopted it. Code-managed transformations are read-only in the dashboard editor.
statusstringdraft before the first publish, published after.
published_versionnumber or nullThe current published version number, or null before the first publish.
has_draftbooleanWhether an unpublished dashboard draft exists.
definitionstringThe 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 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.
versionnumber, default 1The definition format version. The only current version is 1.
tablestringThe 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.
namestringThe 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 stepsApplied in order; every step type is documented under Step types.
qualitylist of quality rules, optionalDocumented under Quality rules.
version: 1table: vendors_enrichedname: Vendors enrichedsource:entity: vendorschedule: dailyorg_scope: allsteps:- type: standardizecolumn: emailformat: lowercaseonUnparseable: keep- type: filter_rowswhere:logic: andconditions:- { column: status, operator: eq, value: active }- type: derived_columncolumn: region_labelexpression:fn: casecases:- when:logic: andconditions:- { column: region, operator: not_null }then: { column: region }else: { literal: unknown }quality:- type: not_nullcolumn: emailseverity: warn- type: accepted_valuescolumn: statusseverity: errorconfig:values: [active]
/v1/transformationsLists every transformation with its canonical definition. The starting point for exporting existing transformations into files.
transformationsarrayTransformation 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..." }]}
/v1/transformations/:tableFetches one transformation by the table it publishes.
tablepathThe output table name.
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..." }
/v1/transformations/planValidates 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.
definitionsarray, requiredAn array of { "file", "content" } entries, one per definition file. At most 100 per call.
adoptbooleanWhen true, the plan treats dashboard-managed transformations as adoptable instead of reporting them blocked. Defaults to false.
itemsarrayOne 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 }]}
/v1/transformations/applyWrites 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.
definitionsarray, requiredAn array of { "file", "content" } entries, one per definition file. At most 100 per call.
adoptbooleanWhen true, dashboard-managed transformations are adopted into code management instead of skipped. Defaults to false.
itemsarrayOne 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 }]}
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_columnsRename columns; mapping is {"old_name": "new_name"}.
mappingobjectdrop_columnsRemove the listed columns.
columnslist of stringskeep_columnsKeep only the listed columns and remove every other column.
columnslist of stringscastConvert 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.
columnstringtostring | number | boolean | date | jsononErrorkeep | nullstandardizeNormalize the format of a column. defaultRegion is a 2-letter country code used by phone_e164.
columnstringformattrim | lowercase | uppercase | title_case | phone_e164 | date_iso | currency_codedefaultRegionstring, optionalonUnparseablekeep | nullmap_valuesReplace observed values with canonical ones; fallback controls values missing from the mapping.
columnstringmappingobjectfallbackkeep | null | valuefallbackValuestring, optionalreplace_textFind and replace every occurrence inside a column's text; mode "regex" treats find as a regular expression.
columnstringfindstringreplaceWithstringmodeplain | regexsplit_columnSplit a column on a separator into the listed new columns, in order. The original column is removed unless keepOriginal is true.
columnstringseparatorstringintolist of stringskeepOriginalboolean, optionalextract_jsonExtract 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".
columnstringpathstringtargetstringbinLabel 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.
columnstringtargetstringbinslist of objectsEach item: upTo (number, optional), label (string).
fallbackLabelstring, optionalfilter_rowsKeep only rows matching the condition group.
wherecondition groupDocumented under Conditions.
set_defaultFill empty values in a column with a default.
columnstringvaluestringderived_columnCreate a new column from an expression over existing columns.
columnstringexpressionexpressionOne of the functions documented under Expression functions.
dedupeRemove 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 stringskeeplatest | firstorderByobject, optionalFields: column (string), direction (asc | desc).
lookupJoin 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, optionaltableNamestringlocalKeystringremoteKeystringtakelist of stringsprefixstring, optionalrollupAggregate 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, optionaltableNamestringlocalKeystringremoteKeystringfncount | sum | avg | min | maxcolumnstring, optionaltargetstringaggregateGroup 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 stringsaggregationslist of objectsOne 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).
unionAppend 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, optionaltableNamestringmappingobject, optionalpivotReshape 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 stringsheaderColumnstringvalueColumnstringaggcount | count_distinct | sum | avg | min | max | firstheaderValueslist of stringsunpivotReshape 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 stringskeyColumnstringvalueColumnstringdropEmptyboolean, optionalexplodeFan 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.
columnstringmodevalue | flattenprefixstring, optionalkeepEmptyboolean, optionalsqlRun 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.
querystringThe functions a derived_column step's expression field accepts, selected by fn. Column arguments name existing columns.
concatpartslist of { column: string } or { literal: string }coalescecolumnslist of stringsarithmeticArithmetic over columns and numbers; left/right are a column name, a number, or a nested arithmetic expression.
leftcolumn name, number, or nested arithmetic expressionoperator+ | - | * | /rightcolumn name, number, or nested arithmetic expressionyear_ofcolumnstringcaseIf/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 objectsEach item: when (condition group), then ({ column: string } or { literal: string or number }).
else{ column: string } or { literal: string or number }, optionalsubstringSlice of the text; start is 1-based, length optional (to the end when omitted).
columnstringstartnumberlengthnumber, optionalreplacecolumnstringfindstringreplaceWithstringregexboolean, optionalregex_extractFirst regex match; group defaults to capture group 1 when the pattern has one, else the whole match.
columnstringpatternstringgroupnumber, optionalsplit_partSplit the text on the separator and take the 1-based part.
columnstringseparatorstringindexnumberlengthcolumnstringdate_partcolumnstringpartyear | month | daydate_trunccolumnstringunitday | week | month | yeardate_diffDifference "to" minus "from" in the unit; both are date columns, "to" defaults to now.
unitdays | months | yearsfromstringtostring, optionaldate_addcolumnstringamountnumberunitdays | months | yearsroundcolumnstringdigitsnumber, optionalabscolumnstringfloorcolumnstringceilcolumnstringCondition groups appear in filter_rows steps, in case expressions, and in expression quality rules.
condition groupA 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 | orconditionslist of conditionsconditionOne column check inside a condition group.
columnstringoperatorenumOne 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, optionalThe comparison value; unused by not_null, is_null, in, and not_in.
valueslist of strings, optionalThe list read by in and not_in.
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_nullFails rows where the column is empty. Blank strings, empty lists, and empty objects count as empty.
columnstringThe column the rule checks.
severityerror | warn, default "error"namestring, optionalThe display name shown in the console.
uniqueFails every row whose column value appears more than once within the organization's rows.
columnstringThe column the rule checks.
severityerror | warn, default "error"namestring, optionalThe display name shown in the console.
accepted_valuesFails rows whose column value is not in the accepted list.
columnstringThe column the rule checks.
severityerror | warn, default "error"namestring, optionalThe display name shown in the console.
config.valueslist of stringsThe accepted values, compared as text.
regexFails rows whose column value does not match the pattern.
columnstringThe column the rule checks.
severityerror | warn, default "error"namestring, optionalThe display name shown in the console.
config.patternstringA JavaScript regular expression source, without slashes.
freshnessFails rows whose last sync is older than the age limit. Takes no column.
severityerror | warn, default "error"namestring, optionalThe display name shown in the console.
config.maxAgeHoursnumber, default 24The maximum age in hours.
volumeFails when an organization has fewer rows than the minimum. Takes no column.
severityerror | warn, default "error"namestring, optionalThe display name shown in the console.
config.minRowsnumber, default 1The minimum expected row count per organization.
relationshipFails rows whose column value does not exist as a key in the related table or entity type, checked within the same organization.
columnstringThe column the rule checks.
severityerror | warn, default "error"namestring, optionalThe display name shown in the console.
config.relationobjectFields: 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).
expressionFails rows that do not match the condition group.
severityerror | warn, default "error"namestring, optionalThe display name shown in the console.
config.wherecondition groupDocumented under Conditions.
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 envelope every webhook delivery posts to your endpoint. The same shape for every event type; only data varies.
idstringUnique per event. Deliveries can repeat, so treat an id you have already seen as handled.
typestringThe event name, like table.changed. Event types lists every one.
occurred_atstringWhen the event happened, as an ISO 8601 timestamp.
dataobjectThe 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" }}
Every event the type field can name, with the fields each one puts in data.
table.changedA 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.tablestringThe name of the table that changed, as used in /v1/tables/:name paths.
data.latest_cursorstringThe 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.connectedAn organization granted you access, through a new connection or by extending an existing grant.
data.org_idstringThe organization that granted access. Use it with the org parameter across the API.
data.external_idstring or nullYour identifier for the organization, as sent on the Connect session. Null when you have never sent one.
data.connect_session_idstring or nullThe 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.completedThe first sync of a new connection finished and its data is ready to query.
data.org_idstringThe organization the connection belongs to.
data.external_idstring or nullYour identifier for the organization, or null when you have never sent one.
data.connectorstringThe connector slug of the system that synced.
connection.brokenAn organization's connection stopped syncing. Rows already synced stay in your tables but stop updating until it recovers.
data.org_idstringThe organization the connection belongs to.
data.external_idstring or nullYour identifier for the organization, or null when you have never sent one.
data.connectorstringThe connector slug of the affected system.
data.reasonstringreauthorization_required when the organization must reconnect through a new Connect session, sync_failed when syncing hit a persistent error.
grant.revokedAn organization revoked your access; its rows disappear from your tables.
data.org_idstringThe organization that revoked access.
data.external_idstring or nullYour identifier for the organization, or null when you have never sent one.
table.publishedA table was published for the first time and now appears in GET /v1/schema.
data.tablestringThe name of the new table.
data.schema_versionstringThe version it published at, like "v1". The same form GET /v1/schema reports.
table.schema_updatedA published table's schema changed. Compare against GET /v1/schema and regenerate your SDK types.
data.tablestringThe name of the table whose schema changed.
data.schema_versionstringThe new schema version, like "v3". The same form GET /v1/schema reports.
endpoint.testA test delivery triggered from the console, for verifying your handler end to end. data is empty.
(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.
Valtrix-SignatureheaderFormatted as t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" keyed with your signing secret>.
Valtrix-Event-IdheaderUnique per event. Deliveries may repeat, so treat events with a seen id as already handled.
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/valtrixValtrix-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" } }