API reference

https://api.valtrix.com/v1

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

Getting started

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

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

Overview

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

The Valtrix data lifecycle
1

Records

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

2

Transformations

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

3

Tables

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

4

Ready for consumption

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

Authentication

Authenticate every request with an API key in the Authorization header; requests without a valid key return 401. Keys come in two kinds. Platform-wide keys, minted in the console under Developers, reach every organization granted to you. Organization-scoped keys, minted through POST /v1/keys, are pinned to one organization: reads and writes default to it, the org parameter becomes optional, and naming any other organization returns 403 org_not_scoped. To mint your first key, sign in to the console, open Developers, and click Create API key. Name the key, leave the scope on Entire platform (all organizations), and pick an access level. The full key is shown once, on creation, so copy it there; a lost key cannot be recovered, only revoked and replaced. Every key also carries an access level: read, write, or manage. Read-only keys can call every read endpoint except key management (GET /v1/keys needs manage) but cannot write or delete records or create connect sessions (those return 403 key_access_denied); write is the default and covers records and connect sessions; manage exists only on platform-wide keys and additionally governs transformation deploys, key management, and webhook endpoints. 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. The SDK and the valtrix CLI read the key from VALTRIX_API_KEY and the API base URL from VALTRIX_API_URL (the production API when unset), from the shell or from a .env.local or .env file in the project directory, so neither value needs to appear in code.

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

Build with AI

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

MCP server

The MCP server gives Claude, ChatGPT, and any other MCP client live access to the same tables and records as the REST API, for writes as well as reads. It speaks the Model Context Protocol over streamable HTTP at /mcp and follows the same semantics as the REST endpoints: organization-scoped keys pin every call to their organization, platform-wide keys accept an org argument, and write access follows the key. Fifteen tools mirror the REST surface: get_schema, list_tables, query_table, query_records, upsert_record, get_record, delete_record, list_write_intents, get_write_intent, retry_write_intent, create_connect_link, request_connector (add a connector that is not in the catalog yet by provider name and homepage URL, mirroring POST /v1/connector-requests; write access), list_connector_requests (mirroring GET /v1/connector-requests), list_orgs (platform-wide keys only; resolve a connected organization by name and pass its id as the org argument), and list_connections. Organization-scoped keys do not get list_orgs; list_connections mirrors GET /v1/connections on both key kinds, narrowed to the organization on an organization-scoped key and taking an optional org on a platform-wide one, and carries each connection's writes and write_guidance. get_schema and list_tables take an optional org on platform-wide keys to narrow to what one organization granted, upsert_record and delete_record take an org argument on platform-wide keys (omitting it returns org_required) and the same propagate, connection, and idempotency_key arguments as POST /v1/records/:entityType, and delete_record removes only records written through the API, never connector-synced ones, and the write intent tools mirror GET /v1/write-intents, GET /v1/write-intents/:id, and POST /v1/write-intents/:id/retry. In claude.ai or chatgpt.com, add the /mcp URL as a custom MCP server, click Connect, and paste your API key on the Valtrix authorization page; Claude Code, Cursor, VS Code, and Codex authenticate through the same OAuth flow, and clients without OAuth support can send the key as a bearer header instead. The MCP setup guide has step-by-step instructions for every client. Where the Embedded coding agents path produces an app that queries the API on its own, with no model at runtime, the MCP server keeps the model itself in the conversation, reading live data and writing records back.

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

Embedded coding agents

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

Mint a key per appyour backend

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

Generate the clientyour app template

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

Copy in AGENTS.mdyour app template

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

Guard the schema in CIyour app template

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

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

Errors

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

invalid_source_fields400

source_fields must be an object and the connection the write propagates to must take native SDK fields; sending it without propagate: true is invalid_propagation.

connection_not_found404

No connection with that id belongs to an organization that has connected to you, or the key is scoped to a different organization.

run_not_found404

No sync run with that id exists on the connection.

invalid_history_from400

history_from is not an ISO 8601 date, is in the future, or would narrow the connection's existing history depth. History only widens.

invalid_until400

until is not an ISO 8601 timestamp in the future, or is more than a year away. Omit it to pause with no expiry.

invalid_mode400

mode is not "full". Incremental syncs run on the connection's own schedule and cannot be requested.

sync_in_progress409

A sync is already queued or running for the connection. Wait for it to finish, then request another.

connection_paused409

The connection is paused. Resume it before requesting a sync.

connection_not_syncable409

The connection's connector is still being built or does not sync, so no run can be queued.

connection_shared409

Another platform also reads this connection, so only the organization can pause it. The message names the other readers.

connection_not_pausable409

The connection is pending, disconnected, or already paused, so there is nothing to pause.

connection_not_paused409

The connection is not paused, so there is nothing to resume.

invalid_filter400

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

invalid_order400

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

invalid_limit400

limit is not a positive integer.

invalid_cursor400

The cursor is malformed. Always pass next_cursor back verbatim.

unknown_column400

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

unknown_connector400

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

connector_required400

An organization-scoped key mints a sign-in link for one system, so POST /v1/connect/sessions and create_connect_link need a connector on such keys.

org_required400

Record endpoints need an org parameter, POST /v1/keys needs an org in the body, and the MCP upsert_record and delete_record tools need an org argument on platform-wide keys.

invalid_name400

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

invalid_external_id400

external_id is missing or longer than 255 characters.

invalid_idempotency_key400

The Idempotency-Key header is longer than 255 characters.

invalid_fields400

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

invalid_attachments400

The attachments on a record write failed validation: not an array, more than 10 entries, a missing filename, a media type outside the supported set, content that is not base64, or more than 25 MB together. The response includes a fields array naming each issue.

invalid_scope400

scope is not a valid list of entity types.

invalid_credential_sections400

credential_sections must be "login" or "api".

invalid_status400

status must be active, revoked, or expired on GET /v1/orgs; pending, applied, or failed on GET /v1/write-intents; or pending, delivered, or dead on GET /v1/webhooks/:id/events.

invalid_url400

The webhook endpoint url is missing or not an https URL on a public host.

invalid_subscriptions400

The event_types or tables on a webhook endpoint are not valid: an unknown event type, no event types at all, a table that is not published, a column the table does not have, or table.row_changed without any table.

invalid_payload_template400

The payload_template on a webhook endpoint is not usable: not a JSON string, not valid JSON, over 16 KB, a malformed placeholder, or a placeholder naming an event field or table column the endpoint's subscriptions do not carry. Also returned when a subscription change would orphan a placeholder in the stored template.

invalid_access400

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

invalid_disposition400

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

invalid_definitions400

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

scope_not_granted403

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

org_not_scoped403

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

key_access_denied403

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

manage_required403

This endpoint needs a platform-wide key with manage access. Manage governs applying transformation deploys, minting, listing, or revoking organization-scoped API keys, managing webhook endpoints, and requesting connectors from a platform-wide key.

platform_not_granted403

The platform named on POST /v1/connector-requests does not have an active grant to the organization the key is scoped to. Omit platform to request through the organization's own Valtrix access.

invalid_request400

The request body is missing a required field or a field has the wrong shape. The error message names the problem.

product_choice_required409

The provider sells more than one product, so the request is ambiguous. Repeat it with product_choice set to one of the returned candidates.

table_not_found404

No published table with that name.

unknown_entity_type404

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

record_not_found404

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

org_not_granted404 or 403

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

org_not_found404

No organization with that id has connected to you.

key_not_found404

No API key with that id exists.

webhook_not_found404

No webhook endpoint with that id exists, or it was deleted.

attachment_not_found404

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

transformation_not_found404

No transformation publishes a table with that name.

external_id_conflict409

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

cursor_expired410

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

payload_too_large413

Record data must be under 100KB, and a request body with attachments under 40 MB.

poll_too_frequent429

This change feed read found no changes, and the same read with the same cursor found none less than 60 seconds ago; the feed only moves when a sync finishes or a record is written. Treat it as caught up and retry after the seconds in the Retry-After header, or read the feed when a webhook says it moved instead of on a timer.

rate_limited429

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

internal_error500

Something went wrong handling the request. Safe to retry after a short wait. Valtrix is alerted to persistent failures automatically.

storage_unavailable503

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

invalid_propagation400

connection was passed without propagate: true. The connection field only addresses a propagated write.

invalid_op400

A write intent op filter is not one of create, update, or delete.

unknown_connection404

The connection id passed for propagation does not belong to this organization.

write_not_supported409

propagate: true was requested but no connected system for the organization declares that create, update, or delete on that entity type.

write_ambiguous409

More than one connection can take the write. Pass connection to choose which one propagates.

write_fields_unsupported409

propagate: true was requested but the connection sends none of the fields in data on that operation (and the write carries no lines or document it takes), so nothing would reach the source. The message names the fields the operation sends; nothing was written.

write_intent_not_found404

No write intent with that id exists.

write_intent_not_retryable409

Only failed write intents can be retried.

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

Rate limits

Each API key can make 300 requests per minute, counted across all endpoints; a request that carries no valid key is limited the same way per client address. 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. The change feed has one more limit of its own. A change feed read that finds no changes, repeated with the same cursor less than 60 seconds after the first one that found none, returns 429 with code poll_too_frequent and a Retry-After header instead of an empty list, because the feed only moves when a sync finishes or a record is written. A read that finds changes is always served, and the SDK's changes() treats this 429 as caught up and ends without an error. Read the feed when a table.changed webhook fires rather than on a timer.

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

Filtering

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

eq, neqoperators

Equals and not equals.

gt, gte, lt, lteoperators

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

in.(a,b)operator

Matches any value in a comma separated list.

is.null, not.nulloperators

Whether the column has a value.

_record_idmeta column

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

_external_idmeta column

On GET /v1/records/:entityType only: point lookup by the record's id in its source system with eq.<id> or in.(a,b); other operators are rejected. Every record carries it as _external_id: a connected system's own key for the row (an invoice's CFDI UUID, a vendor code) or the external_id an API write supplied. Rows do not carry _external_id; the records endpoint resolves a source id to its _record_id.

reference columnstyped

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

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

Pagination

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

limitnumber

Defaults to 50. Values above 200 are clamped to 200.

cursorstring

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

next_cursorresponse

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

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

Tables

The row object

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

Fields
<column>per schema

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

_record_idstring

The id of the row. Within a table an id matches at most one row, so store it to fetch the same row again with a _record_id filter. rec_ prefixed ids resolve to a record through GET /v1/records/:recordId; ids starting with syn_ belong to derived rows from aggregating or reshaping steps and resolve to no record. A syn_ id survives a republish of the transformation only when the SQL step that generates the row declares a key; otherwise the id is a hash of every value in the row, and a republish that changes the query replaces every row under a new id.

_org_idstring

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

_connectorstring or null

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

_synced_atstring

When the row's record was last re-read from its source, as an ISO 8601 timestamp. It moves on every sync whether or not anything changed, so it says how fresh the row is, not when it changed. Orderable, so order=_synced_at.desc reads the most recently confirmed rows first, and filterable by range, so _synced_at=lt.<timestamp> finds rows not confirmed since then.

_changed_atstring

When the row's record last actually changed, as an ISO 8601 timestamp: the last time a sync or a write found different data, not the last time it was looked at. A row whose _synced_at keeps moving while _changed_at stays put has been stable since then. Orderable, so order=_changed_at.desc reads the most recently changed rows first, and filterable by range, so _changed_at=gte.<timestamp> reads the rows that changed since a cutoff. Rows that aggregate many records carry the newest of their records' values. Counts from when Valtrix first synced the record, so it never predates the organization's connection.

_frozenboolean

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

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

The change object

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

Fields
typestring

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

cursorstring

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

occurred_atstring

When the change happened, as an ISO 8601 timestamp.

record_idstring

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

org_idstring

The organization the row belongs to.

changed_columnsarray or null

The columns whose values differ from the row's previous version, for an update. null when the row is new to the table, and null for deletes. Check it before reacting, so an edit to an unrelated column does not look like the change you care about.

previousobject or null

The values the changed columns held before this change, keyed by column, and only those columns. An empty object for an update whose changed_columns is empty, null whenever changed_columns is null.

rowobject or null

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

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

Retrieve the schema

GET/v1/schema

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

Parameters
orgstring

Narrow to the tables and entity types one organization granted, by id. Omit to see everything published for the platform.

Returns
tablesarray

One entry per published table, with its columns.

recordsarray

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

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

List tables

GET/v1/tables

Lists your published tables in the same shape as /v1/schema. GET /v1/tables/:name returns a single table, or 404 table_not_found. Each table reports row_identity: "record" when every row keeps its source record id, "synthetic" when the transformation aggregates or reshapes so row ids are generated, or "mixed" when both occur; null until the first build. A synthetic id is stable across republishes of the transformation only when its SQL step declares a key; without one the id is a hash of every value in the row, so a republish that adds a column replaces every row, and the change feed reports each as a delete of the old id followed by an insert of the new one.

Parameters
orgstring

Narrow to the tables and entity types one organization granted, by id. Omit to see everything published for the platform.

Returns
tablesarray

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

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

$ curl https://api.valtrix.com/v1/tables \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "tables": [{ "name": "customers_clean", "entity_type": "customer", "status": "live", "row_count": 1204, "row_identity": "record", "last_built_at": "2026-07-08T09:30:00Z", "schema_version": "v1", "columns": [...] }] }

List rows

GET/v1/tables/:name/rows

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

Parameters
namepath

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

<column>string, repeatable

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

_record_idstring

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

_changed_atstring, repeatable

Range filter on when the row last actually changed, as gt., gte., lt., or lte. followed by an ISO 8601 timestamp. Combine two for a window. A cutoff, not a change feed: rows deleted since the cutoff never appear here, so use the table's change feed to stay in sync and this filter for one-off reads such as a backfill.

_synced_atstring, repeatable

Range filter on when the row's record was last re-read from its source, with the same gt., gte., lt., and lte. forms as _changed_at.

orderstring

One column as <column>.<asc|desc>. _synced_at and _changed_at are also orderable, and filterable by range.

selectstring

Comma separated column keys to return. The row's underscore fields (_record_id, _org_id, _connector, _synced_at, _changed_at, _frozen) are always returned whatever you select, and naming one of them here is allowed.

limitnumber

Defaults to 50. Values above 200 are clamped to 200.

cursorstring

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

orgstring

Narrow to one organization by id.

Returns
rowsarray

Row objects matching the query.

next_cursorstring or null

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

schema_versionstring

The table's current schema version.

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

The change feed

GET/v1/tables/:name/changes

An ordered, resumable feed of row changes, deletes included. Each change carries a cursor, a bookmark for its position in the feed; it is not the record id. Read from the last cursor you processed until the list comes back empty, which means you are caught up. The feed only moves when a sync of the organization's connections finishes or a record is written, so read it when something has changed rather than on a tight timer. Subscribe a webhook endpoint to table.changed and drain the feed from your stored cursor each time it fires; then reconcile from the same cursor on a slow schedule, every 15 to 60 minutes, to cover a delivery you missed. Without webhooks, poll no more often than every 15 minutes. A read that finds no changes, repeated with the same cursor less than 60 seconds after the first one that found none, returns 429 poll_too_frequent with a Retry-After header instead of an empty list. A read that finds changes is always served, so a webhook that fires right after an empty read, or a backlog being drained, is never held back. A change appears in the feed only once its write has committed, so a cursor you store never skips a change that was still being written when you read. Changes are retained 30 days, after which a stale cursor returns 410 cursor_expired.

Parameters
namepath

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

cursorstring

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

limitnumber

Defaults to 50. Values above 200 are clamped to 200.

orgstring

Narrow to one organization by id.

Returns
changesarray

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

next_cursorstring or null

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

schema_versionstring

The table's current schema version.

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

Records

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

The record object

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

Fields
idstring

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

entity_typestring

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

external_idstring

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

org_idstring

The organization the record belongs to.

connectorstring

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

dataobject

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

connection_idstring or null

The id of the connection the record lives in, matching an entry in the organization object's connections. Pass it as connection on a propagated write to address that system explicitly. null for records that exist only in Valtrix.

source_idstring or null

The id the source system assigned to the record. For synced records it is the source's own id; for records you propagated it is filled once the write intent is applied, and null before then.

last_write_intentobject or null

The most recent write intent on this record, as { id, status }, or null when nothing has been propagated. Fetch it with GET /v1/write-intents/:id to see the outcome and any source error.

synced_atstring

When the record was last written or re-read from its source, as an ISO 8601 timestamp. It moves on every sync whether or not the data changed.

changed_atstring

When the record's data last actually changed, as an ISO 8601 timestamp: the last sync or write that found different values. Stays put while synced_at keeps moving on unchanged re-reads. Counts from when Valtrix first synced the record.

frozenboolean

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

{
"id": "rec_9d4f2c81a7b3e650d21c4f8a",
"entity_type": "customer",
"external_id": "customer_8f2",
"org_id": "org_7f3k2m",
"connector": "procore",
"connection_id": "conn_7f3k2m",
"source_id": "CR-88213",
"last_write_intent": null,
"data": { "name": "...", "email": "..." },
"synced_at": "2026-07-08T09:30:00Z",
"changed_at": "2026-07-06T15:12:00Z",
"frozen": false
}

The attachment object

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

Fields
idstring

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

filenamestring or null

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

media_typestring

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

bytesnumber

The stored size in bytes.

content_hashstring

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

created_atstring

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

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

List records

GET/v1/records/:entityType

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

Parameters
entityTypepath

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

<field>string, repeatable

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

_record_idstring

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

_external_idstring

Point lookup by the record's id in its source system, as eq.<id> or in.(a,b). Every record carries it as _external_id: a connected system's own key for the row (an invoice's CFDI UUID, a vendor code) or the external_id an API write supplied. Exact match. The way to find the record behind an id you hold from the source system, since GET /v1/records/:entityType/:externalId covers API-written records only.

_changed_atstring, repeatable

Range filter on when the record's data last actually changed, as gt., gte., lt., or lte. followed by an ISO 8601 timestamp. Combine two for a window. Deleted records never appear here, so this is a cutoff for one-off reads, not a way to stay in sync.

_synced_atstring, repeatable

Range filter on when the record was last re-read from its source, with the same gt., gte., lt., and lte. forms as _changed_at. _synced_at=gte.<timestamp> is the same window as synced_since.

orderstring

One field as <field>.<asc|desc>. _synced_at and _changed_at are also orderable, and filterable by range.

selectstring

Comma separated field keys to return. The record's underscore envelope (_record_id, _external_id, _org_id, _connector, _connection_id, _source_id, _last_write_intent, _synced_at, _changed_at, _frozen) is always returned whatever you select, and naming one of those fields here is allowed.

limitnumber

Defaults to 50. Values above 200 are clamped to 200.

cursorstring

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

orgstring

Narrow to one organization by id.

connectorstring

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

synced_sincestring

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

Returns
recordsarray

One object per record: the entity's fields, plus _record_id, _external_id, _org_id, _connector, _connection_id, _source_id, _last_write_intent, _synced_at, _changed_at, and _frozen. The underscore fields are the record object's envelope, prefixed so they never collide with entity fields.

next_cursorstring or null

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

schema_versionstring

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

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

Upsert a record

POST/v1/records/:entityType

Creates or updates a record, keyed by your external_id within the organization. Returns 201 on create, 200 on update, with the derived table rows. In tables that keep one row per record, the record id in the response is the same _record_id carried on that row. Aggregated tables report pending instead of a row: the write lands there through a rebuild, as changes to the bucket rows it feeds, under their own row ids. By default the write stays in Valtrix. Pass propagate: true to also push it into the organization's connected system: the record is still written immediately, the status becomes 202, and the response carries a write intent that tracks the push. The organization object's connections tell you which entity types and operations each connection can take. Each connection's write_guidance says what those writes take: the fields per operation with their meaning, the values a command field accepts and what each does, the documents an operation carries, and the connector's rules on refusals. Read it before the first propagated write to a system. How propagation works explains the lifecycle; send an Idempotency-Key on any write you might retry.

Parameters
entityTypepath

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

Idempotency-Keyheader, optional

Any unique string, up to 255 characters. A repeat of the same key on this API key within 24 hours replays the original status and body instead of writing again, so a retried request never creates a second record or a second write intent. After 24 hours the key is forgotten and the same call writes again.

orgstring, required

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

external_idstring, required

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

dataobject, required

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

linesarray of line_item objects, optional

The record's line items, written with it in the same call. Each entry is an object of line_item fields and becomes an ordinary line_item record, the same entity you read back from GET /v1/records/line_item; GET /v1/schema lists its writable fields. Accepted on contract, estimate, invoice, order, cost, and journal_entry records only (400 invalid_fields elsewhere, and for any unknown line field). The link to the parent record is stamped for you: document_entity the parent's entity (contract, cost, and so on), document_type its kind (purchase_order, bill, and so on), document_id its record id, position defaulting to the line's index, and external ids <external_id>/line/<position>. Sending lines replaces the record's previous lines. With propagate: true the header and its lines reach the source system in one write intent.

attachmentsarray of attachment inputs, optional

Files written with the record, at most 10, each { "filename", "media_type", "content" } with content the file as a base64 string. media_type is one of application/pdf, application/xml, text/xml, image/png, image/jpeg, image/gif, image/webp, and the files must decode to at most 25 MB together (400 invalid_attachments otherwise, with fields naming each issue; 503 storage_unavailable when attachment storage is down). Each file becomes a record attachment, listed by GET /v1/records/:recordId/attachments and downloadable like a synced document; the same content sent again is stored once. With propagate: true the files reach the source system in the same write intent when the connector's write takes them, the way an invoice's XML and PDF are uploaded into a supplier portal.

propagateboolean

Defaults to false. When true, the write is also propagated to the organization's connected system and the response is 202 with a write intent. An external_id that a connected system already synced (its own id for the row, the _external_id of that synced record) makes the propagated write an update of that row on its connection; any other external_id creates the row at the source. Returns 409 write_not_supported when no connected system declares create or update for this entity type, 409 write_ambiguous when more than one does and connection is not given, and 409 write_fields_unsupported when the connection sends none of the fields in data on that operation, so the write would reach the source as nothing; fields the connection does not send are stored in Valtrix and listed under warnings.

connectionstring

The id of the connection to propagate to, from the organization object's connections or a record's connection_id. Only valid with propagate: true (400 invalid_propagation otherwise); 404 unknown_connection when it does not belong to the organization.

source_fieldsobject

Connector-specific SDK fields and operations, up to 100KB. Requires propagate: true and an SDK write connector. Sage Estimating accepts fields, references, estimate_id, phase_code, item_code, catalog_item_id, copy_from, collections, and lines. Options are stored on the write intent and do not become canonical columns. Unsupported connectors return 400 invalid_source_fields.

Returns
createdboolean

true with status 201 on create, false with status 200 on update. With propagate: true the status is 202 either way.

recordobject

The record object as stored.

warningsarray

Present only when a field with a closed vocabulary received a value outside it, as [{ field, message }]. The write still lands, but filters written from the schema will not match that value, so switch to the canonical value the message names.

write_intentobject

The queued write intent, present only with propagate: true, as { id, status, connection_id } with status pending. Poll GET /v1/write-intents/:id or listen for write_intent.applied and write_intent.failed to learn the outcome.

linesarray of record objects

Present only when lines were sent: the line_item records written with this record, as full record objects in position order, each with document_entity, document_type, document_id, and position filled in. They are ordinary records from here on; read them back with GET /v1/records/line_item.

attachmentsarray of attachment objects

Present only when attachments were sent: the stored attachment objects, in the order sent, each with its id, filename, media_type, bytes, and content_hash. Download them like any synced document.

tablesarray

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

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

Retrieve a record

GET/v1/records/:recordId

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

Parameters
recordIdpath

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

Returns
recordobject

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

tablesarray

The current row it produces in each of your tables.

attachmentsarray

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

$ curl https://api.valtrix.com/v1/records/rec_9d4f2c81a7b3e650d21c4f8a \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "record": { "id": "rec_9d4f2c81a7b3e650d21c4f8a", "entity_type": "customer", "external_id": "customer_8f2", "org_id": "org_7f3k2m", "connector": "procore", "connection_id": "conn_7f3k2m", "source_id": "CR-88213", "last_write_intent": null, "data": { ... }, "synced_at": "2026-07-08T09:30:00Z", "changed_at": "2026-07-06T15:12:00Z", "frozen": false }, "tables": [{ "table": "customers_clean", "row": { ... } }], "attachments": [] }

Retrieve a record you wrote

GET/v1/records/:entityType/:externalId

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

Parameters
entityTypepath

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

externalIdpath

The external_id you supplied when writing the record.

orgstring, required

The organization the record belongs to.

Returns
recordobject

The record object.

tablesarray

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

attachmentsarray

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

$ curl "https://api.valtrix.com/v1/records/customer/customer_8f2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "record": { "id": "rec_9d4f2c81a7b3e650d21c4f8a", "entity_type": "customer", "external_id": "customer_8f2", "org_id": "org_7f3k2m", "connector": "valtrix-api", "connection_id": null, "source_id": null, "last_write_intent": null, "data": { ... }, "synced_at": "2026-07-08T09:30:00Z", "changed_at": "2026-07-08T09:30:00Z", "frozen": false }, "tables": [{ "table": "customers_clean", "row": { ... } }], "attachments": [] }

Delete a record

DELETE/v1/records/:entityType/:externalId

Deletes a record you wrote and removes its derived rows. The removals also appear in each change feed. Pass propagate=true as a query parameter to also delete it from the organization's connected system: the Valtrix record is removed immediately, the status becomes 202, and the response carries a write intent. How propagation works explains the lifecycle.

Parameters
entityTypepath

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

externalIdpath

The external_id you supplied when writing the record.

Idempotency-Keyheader, optional

Any unique string, up to 255 characters. A repeat of the same key on this API key within 24 hours replays the original status and body instead of deleting again. After 24 hours the key is forgotten and the same call deletes again.

orgstring, required

The organization the record belongs to.

propagatequery, optional

Defaults to false. When true, the delete is also propagated to the organization's connected system and the response is 202 with a write intent. Returns 409 write_not_supported when no connected system declares delete for this entity type, and 409 write_ambiguous when more than one does and connection is not given.

connectionquery, optional

The id of the connection to propagate to, from the organization object's connections or the record's connection_id. Only valid with propagate=true (400 invalid_propagation otherwise); 404 unknown_connection when it does not belong to the organization.

Returns
deletedboolean

true when the record and its derived rows were removed. With propagate=true the status is 202.

write_intentobject

The queued write intent, present only with propagate=true, as { id, status, connection_id } with status pending. Poll GET /v1/write-intents/:id or listen for write_intent.applied and write_intent.failed to learn the outcome.

tablesarray

The removal outcome per table.

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

List a record's attachments

GET/v1/records/:recordId/attachments

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

Parameters
recordIdpath

The rec_ prefixed record ID.

Returns
attachmentsarray

The record's attachment objects, oldest first.

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

Get a display link for an attachment

GET/v1/attachments/:attachmentId/url

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

Parameters
attachmentIdpath

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

dispositionstring

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

Returns
urlstring

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

expires_innumber

Seconds until the link stops working.

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

Download an attachment

GET/v1/attachments/:attachmentId/download

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

Parameters
attachmentIdpath

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

Returns

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

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

Record entity types

The entity types accepted by the record endpoints, with the fields each one takes. These fields make up a record's data object; every record additionally carries the envelope of the record object (id, entity_type, external_id, org_id, connector, synced_at, changed_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.

Activity

activity

A logged interaction or note in the sales process: calls, emails, meetings, notes, and stage moves all map here. What it was logged on is polymorphic (subject_id/subject_type: opportunity, customer, contact, or project; reviewer comments and status changes on a permit or job log against the project). Scheduled future work maps to schedule_task; time worked maps to time_entry.

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

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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

notestring

What happened

kindstring

One of: call, email, meeting, note, task, stage_change, other. Map the source activity types onto these; anything else is other with the raw label in source data

subject_idreference

Valtrix record ID of the record the activity was logged on

subject_typestring

Entity type of the linked subject record: opportunity, customer, contact, or project

customer_idreference

Valtrix record ID of the customer the entry concerns when the subject is something of theirs (a project, an opportunity, an admission); a clinical entry charted on an admission links the admission as subject and the patient here. Sources whose subject is the customer itself leave it empty

occurred_atdate

When the activity happened

created_atdate

When the activity was logged in the source

categorystring

The source's own type or form name for the entry (a note type, an assessment tool, a problem list, a vitals reading), as it labels it; source-defined, pass it through

codestring

A coded value the entry carries, as the source prints it (an ICD-10 diagnosis code, a procedure code), for clinical and coded sources

scorenumber

The numeric result the entry records, for scored assessments and measurements

outcomestring

The source's own status or interpretation of the entry (a problem status, an assessment interpretation, an allergy status), as it labels it; source-defined, pass it through

authorstring

Name of the person who recorded or signed the entry, for sources that keep one on it

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

Booking

booking

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

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

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

customer_namestring

Customer the booking is for

customer_idreference

Valtrix record ID of the customer the booking is for

event_idreference

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

resource_idreference

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

employee_idreference

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

item_idreference

Valtrix record ID of the catalog item for the booked service

location_idreference

Valtrix record ID of the location the booking takes place at

order_idreference

Valtrix record ID of the order that paid for the booking

entitlement_idreference

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

statusstring

One of: booked, confirmed, attended, completed, no_show, cancelled, waitlisted. Map the source booking states onto these (an unconfirmed booking is booked)

start_atdate

When the booking starts

end_atdate

When the booking ends

created_atdate

When the booking was made in the source

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

Budget change

budget_change

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

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

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

numberstring

Budget change number

titlestring

Budget change title

descriptionstring

What the budget change covers

statusstring

One of: draft, pending, approved, rejected, void. Map the source approval states onto these; the raw label stays in source data

amountnumber

Net amount of the change

project_idreference

Valtrix record ID of the project the budget change belongs to

created_atdate

When the budget change was created in the source

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

Budget detail

budget_detail

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

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

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

cost_codestring

Cost code the row is budgeted against

cost_code_idreference

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

categorystring

Cost category or division as the source names it; source-defined, pass it through

project_idreference

Valtrix record ID of the project the row is budgeted against

original_amountnumber

Original budgeted amount

budget_changesnumber

Approved budget changes

approved_cosnumber

Approved change orders

pending_cosnumber

Pending change orders

revised_amountnumber

Budget after approved changes

committed_costsnumber

Committed costs

direct_costsnumber

Direct costs to date

jtd_costsnumber

Job-to-date costs

projected_costsnumber

Projected total costs

estimated_finalnumber

Estimated cost at completion

over_undernumber

Projected over or under budget

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

Budget line

budget_line

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

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

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

project_namestring

Project or job the line is budgeted against

project_idreference

Valtrix record ID of the project the line is budgeted against

cost_codestring

Cost code the line is budgeted against

cost_code_idreference

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

descriptionstring

What the budget line covers

categorystring

Cost category or division as the source names it; source-defined, pass it through

cost_typestring

Cost type as the source names it (labor, material, subcontractor); source-defined, pass it through

quantitynumber

Budgeted quantity

unit_costnumber

Budgeted cost per unit

original_amountnumber

Original budgeted amount

revised_amountnumber

Revised budget after approved changes

document_typestring

One of: bill, credit, expense, payroll, card_transaction, invoice, order, estimate, bid_request, prime_contract, subcontract, purchase_order, work_order, requisition, change_order, journal_entry, claim, treatment_plan, other. The canonical kind of the document the line belongs to

document_statusstring

One of: draft, pending, approved, rejected, void. Approval status of the source document the line belongs to, mapped the same way as that document

created_atdate

When the budget line was created in the source

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

Change event

change_event

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

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

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

numberstring

Change event number

titlestring

Change event title

descriptionstring

What changed and why

scopestring

One of: in_scope, out_of_scope, tbd. Whether the change is in or out of the contracted scope

statusstring

One of: open, pending, closed, void. Map the source change event states onto these (awaiting pricing or sent to client is pending)

change_typestring

Kind of change as the source names it (owner change, design change, weather); source-defined, pass it through

change_reasonstring

Reason for the change as the source names it; source-defined, pass it through

change_order_idreference

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

change_order_typestring

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

project_idreference

Valtrix record ID of the project the change event belongs to

created_atdate

When the change event was created in the source

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

Change order

change_order

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

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

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

numberstring

Change order number

titlestring

Change order title

statusstring

One of: draft, pending, approved, rejected, void. Map the source approval states onto these (pricing and review states are pending); the raw label stays in source data

totalnumber

Total value of the change

contract_idreference

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

project_idreference

Valtrix record ID of the project the change order belongs to

executedboolean

Whether the change order is executed

created_atdate

When the change order was created in the source

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

Commitment change order

commitment_change_order

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

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

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

numberstring

Change order number

titlestring

Change order title

statusstring

One of: draft, pending, approved, rejected, void. Map the source approval states onto these; the raw label stays in source data

totalnumber

Total value of the change

contract_idreference

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

project_idreference

Valtrix record ID of the project the change order belongs to

executedboolean

Whether the change order is executed

created_atdate

When the change order was created in the source

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

Compliance document

compliance_document

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

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

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

titlestring

Name of the form or template the document was raised from

kindstring

One of: lien_waiver, insurance_certificate, tax_form, liability_waiver, consent_form, certification, other. Map the source document types onto these

statusstring

One of: pending, signed, declined, expired, released. Where the document sits in its signature lifecycle

subject_typestring

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

subject_idreference

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

counterpartystring

Vendor, customer, or employee required to sign

counterparty_idreference

Valtrix record ID of the party required to sign

counterparty_typestring

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

project_idreference

Valtrix record ID of the project the document belongs to

location_idreference

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

amountnumber

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

effective_atdate

Date the document takes effect from

expires_atdate

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

signed_atdate

When the counterparty signed, null while the document is unsigned

signed_by_namestring

Name of the individual who signed on the counterparty side

signed_by_titlestring

Role or title of the individual who signed

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

Contact

contact

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

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

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

namestring

Contact name

titlestring

Role or title

party_idreference

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

party_typestring

Entity type of the linked party record: customer or vendor

emailstring

Primary email address

phonestring

Primary phone number

created_atdate

When the contact was created in the source

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

Contract

contract

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

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

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

numberstring

Contract number

titlestring

Contract title

kindstring

One of: prime, subcontract, purchase_order, membership, subscription, lease, other. Map the source agreement types onto these

counterpartystring

Customer or vendor the agreement is with

counterparty_idreference

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

counterparty_typestring

Entity type of the linked counterparty record: customer or vendor

project_idreference

Valtrix record ID of the project the contract belongs to

statusstring

One of: draft, pending, sent, approved, rejected, fulfilled, completed, void. Map the source contract and purchase order states onto these (out for signature or awaiting approval is pending, issued to the counterparty is sent, executed is approved, received in full is fulfilled); the raw label stays in source data

totalnumber

Total contract value

retainage_percentnumber

Retainage percentage withheld

executedboolean

Whether the contract is executed

contract_atdate

Date of the agreement

starts_atdate

When the agreement takes effect (a membership start, a lease commencement, a subscription start)

ends_atdate

When the agreement ends or expires

originstring

One of: new, renewal, reactivation, upgrade, transfer, other. How the agreement came about, for recurring client agreements whose source labels a first sale, a renewal, or a win-back

categorystring

The plan, product line, or agreement category the source files it under; source-defined, pass it through

sold_bystring

Name of the staff member who made the sale

commission_agentstring

Name of the staff member credited with the commission when the source tracks one separately from the seller

resource_idreference

Valtrix record ID of the piece of equipment the agreement is for, such as the machine a parts order or rental agreement was raised against

maintenance_order_idreference

Valtrix record ID of the maintenance work order or request a parts order was raised under

needed_bydate

When the goods or work ordered are needed by

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

Cost

cost

A money-out document at the header level: vendor invoices, expenses, payroll, and other direct costs all collapse here, discriminated by kind. Its lines map to line_item; the transaction settling it maps to payment, and when one payment settles several costs each share is a payment_allocation. 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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

descriptionstring

What the cost covers

kindstring

One of: bill, credit, expense, payroll, card_transaction, other. A credit is a vendor credit or credit note that reduces what is owed (total stays positive); a vendor invoice is a bill

statusstring

One of: draft, pending, approved, rejected, paid, void. Map the source approval and payment states onto these (under review is pending, denied is rejected, cancelled is void, payment sent is paid); the raw label stays in source data

invoice_numberstring

Vendor invoice reference

payee_namestring

Vendor or employee paid

payee_idreference

Valtrix record ID of the vendor or employee paid

payee_typestring

Entity type of the linked payee record: vendor or employee

project_idreference

Valtrix record ID of the project the cost belongs to

location_idreference

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

totalnumber

Total amount, in currency

currencystring

ISO currency code the total is denominated in, null when the source books everything in one implicit currency

issued_atdate

The bill or invoice date stated by the vendor on the document

due_atdate

When payment is due

received_atdate

When the cost was received

paid_atdate

When the cost was paid

po_numberstring

Purchase order number the cost was raised against, as the source prints it, for sources that match bills to purchase orders

subtotalnumber

Amount before tax, in currency, for sources that print it separately from the total

taxnumber

Tax charged on the document, in currency, for sources that print it separately from the total

cost_centerstring

Cost center, business unit, or organization the cost is booked to, as the source names it; source-defined, pass it through

review_statusstring

The source's own review or audit state of the supporting document (audited, awaiting upload, rejected, cancelled), as it labels it; source-defined, pass it through. Distinct from status, which is the normalized lifecycle

match_statusstring

The source's purchase-order match result for the bill (matched, invalid, unmatched), as it labels it; source-defined, pass it through

resource_idreference

Valtrix record ID of the piece of equipment the cost was incurred on, for repair and service costs booked against a unit

maintenance_order_idreference

Valtrix record ID of the maintenance work order the cost was booked under

financial_account_idreference

Valtrix record ID of the card or bank instrument the cost was paid with at the moment it was incurred, for card transactions and other expenses settled on an instrument rather than through a later payment

employee_idreference

Valtrix record ID of the employee who incurred or submitted the cost: the cardholder of a card transaction, the submitter of an expense, for spend and expense sources that track who spent. Distinct from payee_id, which is who gets paid

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

Cost code

cost_code

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

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

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

codestring

Full cost code

namestring

Cost code name

statusstring

One of: active, inactive

$ 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"

Cost transaction

cost_transaction

One actual cost posted to a project's job cost ledger: the line a bill, payroll run, equipment allocation, inventory issue, or journal entry books against a cost code and cost type, with the amount and hours. This is the ledger every job cost report is built from. The documents that produce postings stay where they are (cost, journal_entry) and are linked via document_type and document_id when synced; budget_detail is the per-cost-code rollup of these postings, and budget_line is what they are measured against.

Writable through POST /v1/records/cost_transaction. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/cost_transaction/: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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

descriptionstring

What the posting covers, as the source shows it

numberstring

Source transaction or reference number the posting came from: the invoice, check, timecard, or journal number

source_kindstring

One of: bill, card_transaction, payroll, equipment, inventory, journal, other. What produced the posting; map the source screen or transaction type onto these and keep the raw label in source data

document_typestring

Entity type of the synced document that produced the posting: cost or journal_entry

document_idreference

Valtrix record ID of the cost or journal entry that produced the posting, typed by document_type; null when the producing document is not synced

project_namestring

Project or job the cost was posted to

project_idreference

Valtrix record ID of the project the cost was posted to

cost_codestring

Cost code the posting is coded against

cost_code_idreference

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

cost_typestring

Cost type as the source names it (material, labor, equipment, subcontract, other); source-defined, pass it through

vendor_namestring

Vendor the cost was incurred with, for postings that came from a bill, card charge, or inventory receipt

vendor_idreference

Valtrix record ID of the vendor the cost was incurred with

employee_namestring

Employee whose labor the posting records, for postings that came from payroll or a timecard

employee_idreference

Valtrix record ID of the employee whose labor the posting records

location_idreference

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

amountnumber

Cost amount posted, negative for a credit or reversal

hoursnumber

Labor or equipment hours the posting records, zero when the source posts none

quantitynumber

Units the posting records where the source counts them (pieces, equipment units); null otherwise

billableboolean

Whether the posting can be billed to the customer; false for non-billable postings and write-offs

currencystring

ISO currency code the amount is denominated in, null when the source books everything in one implicit currency

transaction_atdate

Accounting date of the posting as the source books it

created_atdate

When the posting was created in the source

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

Coverage

coverage

One insurance policy a customer holds with a payer: the member and group numbers, its rank among the customer's policies, and who holds it. The carrier itself maps to payer; a claim filed under the policy maps to invoice with its lines as line_item.

Writable through POST /v1/records/coverage. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/coverage/:externalId for records you wrote. The title key is payer_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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

customer_idreference

Valtrix record ID of the customer the policy covers

payer_namestring

Carrier or plan the policy is with

payer_idreference

Valtrix record ID of the carrier or plan the policy is with

rankstring

One of: primary, secondary, tertiary, other. The order the policy is billed in; map the source positions onto these

member_numberstring

Member, subscriber, or policy number

group_numberstring

Group or plan number

policy_holderstring

Who holds the policy, as the source records it: a name, or Self when the customer is the holder

policy_holder_birthdaydate

Date of birth of the policy holder, for sources that keep one on the policy

copaynumber

Copay due per visit under the policy, in currency

authorization_codestring

Authorization or pre-approval code on file for the policy

statusstring

One of: active, inactive. A terminated, replaced, or hidden policy is inactive

starts_atdate

When the policy takes effect

ends_atdate

When the policy ends

created_atdate

When the policy was recorded in the source

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

Customer

customer

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

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

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

namestring

Full customer name

emailstring

Primary email address

phonestring

Primary phone number

countrystring

Country code or name

statusstring

One of: prospect, active, inactive. Map the source lifecycle (archived, disabled, deleted, lead) onto these; the raw label stays in source data

created_atdate

When the customer was created in the source

birthdaydate

Date of birth, for sources that keep one on the customer (a member, a patient, a client)

genderstring

One of: female, male, other. Map the source labels onto these; the raw label stays in source data

addressstring

Street address as one line

citystring

City, town, or district of the address

regionstring

State, province, or region of the address, as the source names it

marital_statusstring

Marital or civil status as the source labels it; source-defined, pass it through

emergency_contactstring

Emergency contact as one line (name and phone), for sources that keep one on the customer

ownerstring

Name of the staff member who owns the relationship: the assigned account executive, sales representative, or coach

email_opt_inboolean

Whether the customer accepts marketing email, as the source records the consent

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

Employee

employee

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

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

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

namestring

Full name

emailstring

Work email address

phonestring

Primary phone number

job_titlestring

Role within the company

departmentstring

Department, team, or division the employee belongs to, as the source names it; source-defined, pass it through

annual_salarynumber

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

employee_idstring

Internal employee number

start_datedate

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

statusstring

One of: active, inactive. Map terminated, archived, or disabled employees to inactive

created_atdate

When the employee was created in the source

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

Entitlement

entitlement

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

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

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

item_namestring

Catalog item the entitlement was granted from

item_idreference

Valtrix record ID of the item the entitlement was granted from

customer_idreference

Valtrix record ID of the customer holding the entitlement

contract_idreference

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

order_idreference

Valtrix record ID of the order that purchased the entitlement

kindstring

One of: visits, credits, minutes, currency. What the balance counts

quantitynumber

Quantity granted

remainingnumber

Quantity remaining

statusstring

One of: active, expired, exhausted, inactive. Map the source entitlement states onto these

starts_atdate

When the entitlement becomes usable

expires_atdate

When the entitlement expires

created_atdate

When the entitlement was created in the source

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

Estimate

estimate

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

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

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

numberstring

Estimate or quote number

titlestring

Estimate title

customer_namestring

Customer the estimate was issued to

customer_idreference

Valtrix record ID of the customer the estimate was issued to

project_idreference

Valtrix record ID of the project the estimate belongs to

contract_idreference

Valtrix record ID of the contract the estimate became once accepted

statusstring

One of: draft, sent, pending, approved, rejected, expired, void. Map the source estimate and proposal states onto these; the raw label stays in source data

totalnumber

Total proposed amount

currencystring

ISO currency code

issued_atdate

When the estimate was issued

expires_atdate

When the estimate expires

created_atdate

When the estimate was created in the source

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

Event

event

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

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

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

namestring

Event name

kindstring

One of: class, course, workshop, other. Map the source occurrence types onto these

item_idreference

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

employee_idreference

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

resource_idreference

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

location_idreference

Valtrix record ID of the location the event takes place at

capacitynumber

Maximum number of bookings

booked_countnumber

Number of spots taken

statusstring

One of: scheduled, cancelled, completed. Map the source event states onto these

start_atdate

When the event starts

end_atdate

When the event ends

created_atdate

When the event was created in the source

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

Financial account

financial_account

A bank, card, or cash account the org moves money through, discriminated by kind: the payment instrument a payment is released from or received into, not the ledger account it posts to. gl_account_id, with gl_account_number and gl_account_name, carries the chart-of-accounts account the source maps the instrument to, so a payment can be traced from the account that released it to the ledger balance it reduces. Chart-of-accounts entries themselves map to gl_account.

Writable through POST /v1/records/financial_account. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/financial_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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Account name as the source labels it

kindstring

One of: bank, card, cash, other. Instrument classification

subtypestring

Finer-grained account type as the source names it (checking, savings, business); source-defined, pass it through

maskstring

Last digits of the account number, never the full number

statusstring

One of: active, inactive

location_idreference

Valtrix record ID of the location the account is set up for, for sources that bank per venue or site

gl_account_numberstring

Number of the chart-of-accounts account the source maps the instrument to, as the source records the mapping

gl_account_namestring

Name of the chart-of-accounts account the source maps the instrument to

gl_account_idreference

Valtrix record ID of the mapped chart-of-accounts account, when that account is synced

$ curl -X POST https://api.valtrix.com/v1/records/financial_account \
-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/financial_account/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

GL account

gl_account

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

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

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

numberstring

Account number in the chart of accounts

namestring

Account name

kindstring

One of: asset, liability, equity, income, expense. Account classification

subtypestring

Finer-grained account type as the source names it (accounts receivable, fixed asset, cost of goods sold); source-defined, pass it through

currencystring

ISO currency code

statusstring

One of: active, inactive

$ 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"

Inspection

inspection

A filed inspection of one piece of equipment against a checklist form, or one that was due and never filed: driver vehicle inspection reports, pre-use and daily walkarounds, and mechanic inspections all collapse here, discriminated by kind. The equipment maps to resource (linked via resource_id); a defect that becomes work maps to maintenance_order. A government building or permit inspection is not this: it stays on the project as schedule_task or activity.

Writable through POST /v1/records/inspection. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/inspection/: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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Name of the inspection form or checklist that was filed

kindstring

One of: dvir, inspection. A driver vehicle inspection report filed for regulatory compliance is dvir; every other checklist is inspection

statusstring

One of: completed, missed. A filed inspection is completed; one the source reports as due and not filed is missed

outcomestring

One of: pass, fail, other. The overall result of a filed inspection; a pass with noted defects is other. Null on a missed inspection

resource_namestring

Name of the piece of equipment inspected

resource_idreference

Valtrix record ID of the piece of equipment inspected

location_idreference

Valtrix record ID of the site or yard the equipment was at

inspector_namestring

Who filed the inspection, or who it was assigned to when missed

inspector_idreference

Valtrix record ID of the employee who filed the inspection or was assigned it

meter_hoursnumber

Engine or run hours recorded on the inspection

odometer_milesnumber

Odometer reading in miles recorded on the inspection; convert a source that reports kilometres

latitudenumber

Latitude where the inspection was filed

longitudenumber

Longitude where the inspection was filed

commentsstring

The inspector's overall comments

due_atdate

When the inspection was due, for sources that schedule them

started_atdate

When the inspector started the checklist

completed_atdate

When the inspection was filed

created_atdate

When the record was created in the source

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

Inventory level

inventory_level

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

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

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

item_namestring

Item the level is for

item_idreference

Valtrix record ID of the item the level is for

location_idreference

Valtrix record ID of the location holding the stock

quantitynumber

Quantity on hand

uomstring

Unit of measure as the source names it; source-defined, pass it through

unit_costnumber

Cost per unit used to value the stock

as_ofdate

When the level was measured or last updated

reorder_pointnumber

Quantity at or below which the org reorders the item at this location

min_quantitynumber

Lowest quantity the org wants on hand at this location

max_quantitynumber

Highest quantity the org wants on hand at this location

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

Inventory movement

inventory_movement

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

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

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

kindstring

One of: receipt, transfer, adjustment, count, waste, sale. Map the source movement types onto these

item_namestring

Item the movement is for

item_idreference

Valtrix record ID of the item the movement is for

from_location_idreference

Valtrix record ID of the location the stock moved out of

to_location_idreference

Valtrix record ID of the location the stock moved into

quantitynumber

Quantity moved, negative when stock decreases

uomstring

Unit of measure as the source names it; source-defined, pass it through

unit_costnumber

Cost per unit of the moved stock

occurred_atdate

When the movement happened

created_atdate

When the movement was recorded in the source

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

Invoice

invoice

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

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

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

numberstring

Invoice number

customer_namestring

Billed customer name

customer_idreference

Valtrix record ID of the billed customer

project_idreference

Valtrix record ID of the project the invoice bills against

location_idreference

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

amountnumber

Total amount due

currencystring

ISO currency code

statusstring

One of: draft, pending, approved, rejected, sent, partially_paid, paid, void. Map the source approval and payment states onto these; the raw label stays in source data

issued_atdate

Issue date

due_atdate

Due date

patient_balancenumber

Part of the open balance the customer owes personally, for sources that split a bill between the customer and an insurer

insurance_balancenumber

Part of the open balance billed to the customer's insurance, for sources that split a bill between the customer and an insurer

descriptionstring

The short title the source prints on the invoice (the job or service it bills), for sources that carry one beside the number

notesstring

Free-text notes on the invoice as the source keeps them: the summary of work performed, the memo printed for the customer

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

Item

item

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

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

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

namestring

Item name

skustring

SKU or item code in the source system

categorystring

Catalog category or family as the source names it; source-defined, pass it through

uomstring

Default unit of measure as the source names it; source-defined, pass it through

pricenumber

Standard selling price per unit

costnumber

Standard acquisition or production cost per unit

currencystring

ISO currency code

statusstring

One of: active, inactive. Map archived, draft, or discontinued items to inactive

created_atdate

When the item was created in the source

manufacturerstring

Who makes the item, for parts and materials catalogs

manufacturer_part_numberstring

The manufacturer's own part number, when it differs from the sku the org files the item under

vendor_idreference

Valtrix record ID of the vendor the org prefers to buy the item from

vendor_skustring

The preferred vendor's own number for the item

$ 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": "...", "vendor_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/item/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Journal entry

journal_entry

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

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

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

numberstring

Journal entry number

descriptionstring

What the entry records

statusstring

One of: draft, posted, adjusted, void. Map the source posting states onto these

location_idreference

Valtrix record ID of the location the entry books for, for sources that journal per venue or site

posted_atdate

When the entry was posted to the ledger

created_atdate

When the entry was created in the source

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

Line item

line_item

A single line on any financial document: contracts, purchase orders, invoices, orders, costs, estimates, and journal entries. The parent is identified by three columns: document_entity names the entity the parent record lives under (a bill line has document_entity cost), document_type carries the parent's kind (that same line has document_type bill), and document_id holds its record 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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

descriptionstring

What the line covers

document_entitystring

Entity type of the document the line belongs to: contract, cost, invoice, order, estimate, or journal_entry. Read document_id from this entity; filter on it to select the lines of every cost, every contract, and so on

document_typestring

One of: bill, credit, expense, payroll, card_transaction, invoice, order, estimate, bid_request, prime_contract, subcontract, purchase_order, work_order, requisition, change_order, journal_entry, claim, treatment_plan, other. The kind of the document the line belongs to, matching the parent record's kind rather than its entity (a line on a cost of kind bill has document_type bill and document_entity cost)

document_idreference

Valtrix record ID of the document the line belongs to, in the entity document_entity names

positionnumber

1-based order of the line on its document as the source shows it; sort by this to render lines the way the source does

project_idreference

Valtrix record ID of the project the line belongs to

item_idreference

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

cost_codestring

Cost code the line is coded against

cost_code_idreference

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

gl_account_idreference

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

cost_typestring

Cost type as the source names it (labor, materials, subcontract); source-defined, pass it through

classstring

Accounting class or tracking category the line is coded to (QuickBooks class, location or department class), as the source names it; source-defined, pass it through

departmentstring

Department, team, or division the line amount is allocated to, for payroll and labor costs, as the source names it; source-defined, pass it through

quantitynumber

Quantity on the line

uomstring

Unit of measure as the source names it; source-defined, pass it through

unit_costnumber

Cost per unit

amountnumber

Line amount

totalnumber

Extended line total

created_atdate

When the line was created in the source

discountnumber

Discount applied to the line, in currency, as a positive number

discount_codestring

The coupon or discount code the discount came from; source-defined, pass it through

taxnumber

Tax charged on the line, in currency

$ 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_entity": "...", "document_id": "rec_9d4f2c81a7b3e650d21c4f8a" } }'
$ curl "https://api.valtrix.com/v1/records/line_item/crm-9x2?org=org_7f3k2m" \
-H "Authorization: Bearer $VALTRIX_API_KEY"

Location

location

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

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

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

namestring

Location name

addressstring

Street address

created_atdate

When the location was created in the source

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

Maintenance order

maintenance_order

A unit of maintenance work on one piece of equipment: a maintenance or repair request, the work order that performs it, and a preventive service coming due all collapse here, discriminated by kind. The equipment maps to resource (linked via resource_id); labor booked to it maps to time_entry and outside service or parts costs to cost, both linked back via maintenance_order_id. A purchase order or subcontract with a vendor stays contract, and a project schedule row stays schedule_task.

Writable through POST /v1/records/maintenance_order. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/maintenance_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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

titlestring

What the work is, as the source titles it: the complaint, the work order title, or the service coming due

numberstring

Work order or request number as the source prints it

kindstring

One of: request, work_order, preventive. A reported problem or request for work is request; the job that carries out the work is work_order; a scheduled service coming due on a usage or time trigger, before any work order exists, is preventive

statusstring

One of: open, scheduled, in_progress, on_hold, completed, cancelled. Map the source states onto these: new, pending, requested, or due is open; approved, planned, or assigned with dates is scheduled; started is in_progress; waiting on parts or paused is on_hold; resolved, closed, or done is completed; denied, rejected, or voided is cancelled. The raw label stays in source data

prioritystring

One of: low, medium, high, urgent. Critical or emergency is urgent

maintenance_typestring

The org's own classification of the work (Repair, Preventative Maintenance, Damage, Warranty); source-defined, pass it through

descriptionstring

The problem reported or the work to perform, as plain text

resource_namestring

Name of the piece of equipment the work is on

resource_idreference

Valtrix record ID of the piece of equipment the work is on

location_idreference

Valtrix record ID of the site, yard, or shop the work was requested from or is performed at

work_order_idreference

Valtrix record ID of the work order a request or preventive service was rolled into; null on a work order itself

requested_by_namestring

Who asked for the work

requested_by_idreference

Valtrix record ID of the employee who asked for the work

assigned_to_namestring

The mechanic or technician the work is assigned to; several names joined with a comma when the source assigns more than one

assigned_to_idreference

Valtrix record ID of the first mechanic or technician assigned

out_of_serviceboolean

Whether the equipment is down until the work is done

meter_hoursnumber

Engine or run hours on the equipment when the work was raised

odometer_milesnumber

Odometer reading in miles when the work was raised; convert a source that reports kilometres

due_atdate

When the work is needed by, or when a time-triggered preventive service comes due

due_meternumber

The meter reading a usage-triggered preventive service comes due at, in meter_unit

meter_unitstring

Unit due_meter counts in, as the source names it (Hours, Miles, Kilometers); source-defined, pass it through

scheduled_start_atdate

When the work is planned to start

scheduled_end_atdate

When the work is planned to finish

completed_atdate

When the work was completed or the request resolved

labor_costnumber

Labor cost booked to the work, in currency

parts_costnumber

Parts cost booked to the work, in currency

total_costnumber

Total cost of the work including labor, parts, outside services, and markups, in currency

created_atdate

When the record was created in the source

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

Opportunity

opportunity

A potential sale the org is pursuing through the stages of a sales pipeline: deals, opportunities, and qualified pipeline leads all map here. The account being pursued maps to customer (linked via customer_id); a priced proposal issued along the way maps to estimate, and the agreement once won maps to contract.

Writable through POST /v1/records/opportunity. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/opportunity/: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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Opportunity or deal name, commonly the prospect account name

pipelinestring

Sales pipeline the opportunity is tracked in, as the source names it

stagestring

Pipeline stage as the source names it; this is a source-defined label, not a vocabulary, so pass it through

statusstring

One of: open, won, lost. Map closed-won and closed-lost stages onto won and lost; everything still in play is open

customer_namestring

Prospect account name

customer_idreference

Valtrix record ID of the prospect account

contact_namestring

Primary contact name on the deal

contact_idreference

Valtrix record ID of the primary contact on the deal

emailstring

Primary contact email address

websitestring

Prospect website URL

amountnumber

Expected deal value

currencystring

ISO currency code

notesstring

Free-form notes on the opportunity

closed_atdate

When the opportunity was won or lost

created_atdate

When the opportunity was created in the source

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

Order

order

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

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

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

numberstring

Order number

customer_namestring

Ordering customer name

customer_idreference

Valtrix record ID of the ordering customer

project_idreference

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

location_idreference

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

totalnumber

Order total

currencystring

ISO currency code

statusstring

One of: draft, pending, approved, rejected, unfulfilled, partially_fulfilled, fulfilled, cancelled. Map the source approval and fulfilment states onto these; the raw label stays in source data

items_countnumber

Number of line items

placed_atdate

When the order was placed

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

Payer

payer

An insurance carrier or plan the org bills on behalf of its customers: vision, medical, and dental insurers and the plans they offer all map here, discriminated by kind. A customer's own policy with the payer maps to coverage; the money the payer sends maps to payment.

Writable through POST /v1/records/payer. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/payer/: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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Carrier or plan name as the org lists it

kindstring

One of: medical, vision, dental, other. The line of insurance the payer covers; map the source's insurance types onto these

payer_codestring

Electronic payer id the org files claims under (the clearinghouse payer id), as the source prints it

statusstring

One of: active, inactive. Map hidden, archived, or disabled payers to inactive

created_atdate

When the payer was created in the source

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

Payment

payment

A payment transaction, issued to a vendor or received from a client, discriminated by kind, typically settling an invoice or cost. invoice_id and cost_id name the primary document; when one payment settles several documents, every share lives in payment_allocation and those rows are authoritative. 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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

numberstring

Payment number

kindstring

One of: issued, received. Issued to a vendor or received from a client

counterpartystring

Customer or vendor the payment settles with

counterparty_idreference

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

counterparty_typestring

Entity type of the linked counterparty record: customer or vendor

invoice_numberstring

Invoice the payment settles

invoice_idreference

Valtrix record ID of the invoice the payment settles

check_numberstring

Check or reference number

cost_idreference

Valtrix record ID of the primary cost the payment settles; a payment covering several costs lists each share in payment_allocation

project_idreference

Valtrix record ID of the project the payment belongs to

location_idreference

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

financial_account_idreference

Valtrix record ID of the bank, card, or cash account the payment was released from or received into, for sources that record the settling instrument

amountnumber

Amount paid

statusstring

One of: pending, sent, paid, failed, void. Map the source payment states onto these (authorized or in a check run is pending, cancelled is void); the raw label stays in source data

paid_atdate

Date of the payment

created_atdate

When the payment was recorded in the source

methodstring

One of: cash, credit_card, debit_card, card, transfer, check, other. How the payment was made; use card when the source does not say credit or debit

installmentsnumber

Number of card installments the payment was split into, for sources that record one

currencystring

ISO currency code the amount is denominated in, for sources that pay in more than one currency

$ 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"

Payment allocation

payment_allocation

One share of a payment applied to one document: the row that links a payment to each cost or invoice it settles, with the amount applied. A payment settling a single document still gets one allocation; a check covering several bills gets one per bill, and vendor credits applied against the payment carry negative amounts. The allocations of a payment sum to its amount.

Writable through POST /v1/records/payment_allocation. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/payment_allocation/:externalId for records you wrote. The title key is invoice_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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

payment_idreference

Valtrix record ID of the payment this share belongs to

document_typestring

Entity type of the settled document: cost or invoice

document_idreference

Valtrix record ID of the cost or invoice this share settles, typed by document_type

invoice_numberstring

Source document number of the settled cost or invoice

amountnumber

Amount of the payment applied to the document; negative for a credit applied against the payment

positionnumber

1-based order of the share within its payment as the source shows it

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

Prescription

prescription

A prescription written for a customer: spectacle and contact lens prescriptions with their per-eye values, and medication orders, discriminated by kind. The exam or visit it came from maps to activity or booking; the sale that fills it maps to invoice or order.

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

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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

summarystring

One line naming the prescription as the source lists it (its type and date, or the drug and strength)

kindstring

One of: glasses, contacts, medication, other. Map the source prescription types onto these

customer_idreference

Valtrix record ID of the customer the prescription is for

location_idreference

Valtrix record ID of the location the prescription was written or entered at

prescriberstring

Name of the doctor who wrote the prescription, as the source prints it

issued_atdate

Date of the prescription

expires_atdate

When the prescription expires

notesstring

Free-text notes on the prescription

created_atdate

When the prescription was recorded in the source

right_spherenumber

Sphere power of the right eye, in diopters

right_cylindernumber

Cylinder power of the right eye, in diopters

right_axisnumber

Cylinder axis of the right eye, in degrees

right_addnumber

Near addition of the right eye, in diopters

left_spherenumber

Sphere power of the left eye, in diopters

left_cylindernumber

Cylinder power of the left eye, in diopters

left_axisnumber

Cylinder axis of the left eye, in degrees

left_addnumber

Near addition of the left eye, in diopters

pupillary_distancenumber

Binocular distance pupillary distance, in millimetres

right_lensstring

Contact lens prescribed for the right eye, as the source names the product

right_base_curvenumber

Base curve of the right contact lens, in millimetres

right_diameternumber

Diameter of the right contact lens, in millimetres

right_lens_powernumber

Power of the right contact lens, in diopters

left_lensstring

Contact lens prescribed for the left eye, as the source names the product

left_base_curvenumber

Base curve of the left contact lens, in millimetres

left_diameternumber

Diameter of the left contact lens, in millimetres

left_lens_powernumber

Power of the left contact lens, in diopters

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

Project

project

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

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

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

namestring

Project name

numberstring

Job or project number

descriptionstring

Free-text description or summary of the work as the source keeps it: a technician's summary of work on a field-service job, a project brief. Filled by sources that carry one; others leave it empty

customer_namestring

Customer the project is for

customer_idreference

Valtrix record ID of the customer the project is for

stagestring

Lifecycle stage as the source names it (estimating, pre-construction, warranty); source-defined, pass it through

lifecycle_stagestring

One of: draft, applied, in_review, corrections, approved, issued, inspections, on_hold, finaled, denied, withdrawn, void, expired, other. Where a permit, plan review, or application sits in its lifecycle, mapped from the portal state so counts combine across portals: unsubmitted or saved is draft; submitted, received, or pending intake is applied; under review, in process, or routing is in_review; resubmit, corrections, or waiting on the applicant is corrections; approved or ready to issue is approved; issued or permitted is issued; a temporary certificate or inspections pending is inspections; hold or stop work is on_hold; finaled, closed, completed, or certificate of occupancy is finaled; denied or rejected is denied; withdrawn is withdrawn; void or cancelled is void; expired or archived is expired; a state that fits none is other. Only permitting and plan-review sources fill it; construction and job sources leave it null and keep their own stage

citystring

Site city

countrystring

Site country code or name

jurisdictionstring

Permitting authority the project is filed with: the city, county, or agency whose portal it lives in. Set per portal by the connector, not read off the record; sources that are not permit portals leave it empty

valuenumber

Contracted project value

statusstring

One of: active, closed, inactive. Completed or closed projects are closed; archived or disabled ones are inactive

start_atdate

Planned start date

completion_atdate

Planned completion date

created_atdate

When the project was created in the source

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

Project user

project_user

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

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

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

namestring

Full name

emailstring

Work email address

phonestring

Primary phone number

job_titlestring

Role within the project

person_idreference

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

person_typestring

Entity type of the linked person record: employee or contact

project_idreference

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

statusstring

One of: active, inactive

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

Project vendor

project_vendor

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

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

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

namestring

Vendor company name

tradestring

Trade or specialty the vendor operates in, as the source names it; source-defined, pass it through

citystring

City of the primary address

countrystring

Country code or name

vendor_idreference

Valtrix record ID of the company-level vendor record

project_idreference

Valtrix record ID of the project the vendor is assigned to

statusstring

One of: active, inactive

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

Resource

resource

A bookable or tracked asset at a location: courts, rooms, desks, chairs, operatories, lanes, and equipment all map here, discriminated by kind. A fleet unit from an equipment management source (a machine, vehicle, trailer, or attachment) is kind equipment and fills the make, model, serial, meter, and position columns. Reservations of it map to booking, work performed on it maps to maintenance_order, inspections of it map to inspection, and 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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Resource name

kindstring

One of: court, room, desk, chair, operatory, lane, equipment, other. Map the source resource types onto these

location_idreference

Valtrix record ID of the location the resource belongs to

capacitynumber

How many people the resource accommodates at once

statusstring

One of: active, inactive

created_atdate

When the resource was created in the source

makestring

Manufacturer of the equipment or vehicle (Caterpillar, Ford). Filled by equipment and fleet sources

modelstring

Manufacturer model designation (320 GC, F-550)

yearnumber

Model year

serial_numberstring

Manufacturer serial number or product identification number

vinstring

Vehicle identification number, for on-road vehicles

fleet_numberstring

The number the org itself knows the unit by: fleet, unit, or equipment number

license_platestring

Registration plate, for on-road vehicles

categorystring

Equipment category or class as the source names it (Excavator, Light Truck, Trenchbox); source-defined, pass it through

ownershipstring

One of: owned, rented, leased, other. How the org holds the unit; a rent-to-own unit still on rent is rented

operational_statusstring

What the unit is doing right now as the source names it (available, in use, down, in transit); source-defined, pass it through. Whether the record is still in service is status

meter_hoursnumber

Latest engine or run hours the source holds for the unit

odometer_milesnumber

Latest odometer reading in miles; convert a source that reports kilometres

purchase_pricenumber

What the org paid for the unit

purchase_datedate

When the org acquired the unit

latitudenumber

Latitude of the last known position, for tracked units

longitudenumber

Longitude of the last known position, for tracked units

located_atdate

When the last known position was reported

assigned_to_idreference

Valtrix record ID of the employee the unit is currently assigned to (its operator or driver)

engine_makestring

Manufacturer of the engine, which often differs from the maker of the machine

engine_modelstring

Engine model designation

engine_serial_numberstring

Serial number of the engine, the key parts suppliers look engine parts up by

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

Schedule task

schedule_task

A task or milestone on a project schedule, with WBS position, dates, and percent complete. Any Gantt or schedule row flavor maps here. Each plan-review discipline on a government permit or plan-review portal also maps here, as one non-milestone task per discipline keyed to the project (name is the discipline, description is the review outcome), so reviews read as one uniform set of rows across every portal.

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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Task name; for a plan-review discipline, the discipline (Electrical, Mechanical, Structural)

descriptionstring

What the task covers. For a plan-review discipline, carry the review outcome from the shared review vocabulary so counts combine across permit portals: Approved, Not Approved, In Progress, Not Started. Map the portal state onto these (corrections required and resubmit are Not Approved, under review and prescreen are In Progress); the raw portal label stays in source data

wbsstring

Work breakdown structure position

resource_namestring

Assigned resource or crew

project_idreference

Valtrix record ID of the project the task is scheduled on

percent_completenumber

Percentage complete

criticalboolean

Whether the task is on the critical path

milestoneboolean

Whether the task is a milestone

start_atdate

Planned start date

finish_atdate

Planned finish date

created_atdate

When the task was created in the source

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

Template

template

A reusable document or message template the org authors in a source and fills per customer: contract and agreement templates, email and message templates, and forms, discriminated by kind, with the template body as stored. This is the authored template itself, not a filled copy; a signed instrument produced from one maps to compliance_document, and a file attached to a record rides along as a record attachment.

Writable through POST /v1/records/template. Retrievable through GET /v1/records/:recordId with any record ID, or GET /v1/records/template/: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, changed_at); external_id is yours for records written through this API, the source system's id for synced records.

namestring

Template name as the source labels it

kindstring

One of: contract, email, message, form, other. Map the source template types onto these

codestring

The source's identifier for the template, when it has one apart from the name

subjectstring

Subject line, for email and message templates

bodystring

Template body as stored in the source, with its placeholders intact (HTML or text)

statusstring

One of: active, inactive. Map archived or disabled templates to inactive

updated_atdate

When the template was last edited in the source

created_atdate

When the template was created in the source

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

Time entry

time_entry

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

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

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

employee_namestring

Who logged the time

employee_idreference

Valtrix record ID of the employee who logged the time

project_namestring

Project or job the time was logged against

project_idreference

Valtrix record ID of the project the time was logged against

cost_codestring

Cost code the time was logged against

cost_code_idreference

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

location_idreference

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

hoursnumber

Hours worked

costnumber

Labor cost of the entry

started_atdate

When the entry started

ended_atdate

When the entry ended

created_atdate

When the entry was created in the source

resource_idreference

Valtrix record ID of the piece of equipment the time was spent on, for mechanic and shop time logged against a unit

maintenance_order_idreference

Valtrix record ID of the maintenance work order the time was logged against

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

Vendor

vendor

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

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

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

namestring

Vendor company name

tradestring

Trade or specialty the vendor operates in, as the source names it; source-defined, pass it through

emailstring

Primary contact email

phonestring

Primary phone number

citystring

City of the primary address

countrystring

Country code or name

statusstring

One of: active, inactive. Map archived, disabled, or deleted vendors to inactive

created_atdate

When the vendor was created in the source

tax_idstring

Tax identification number of the vendor (RFC, EIN, VAT number), as the source prints it, for sources that key suppliers by it

$ 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"

Write intents

How propagation works. A write intent is created when you upsert or delete a record with propagate: true and one of the organization's connected systems declares that operation for the entity type. What a write to that system takes is on the connection: write_guidance lists, per entity type, the fields each operation sends with their meaning, the values a command field accepts with the effect of each, the documents an operation carries, and the connector's own rules on refusals; read it before the first propagated write to a system. The record is written in Valtrix immediately and the response is 202 with the write intent (its op is create until a propagated write on that record has applied, and update after, so a record whose first propagation failed is created at the source on the next propagated write rather than updated); a worker then applies it to the source system and, where the connector can read the write back, re-reads it, recording the outcome as verification, and moves the write intent from pending to applied (source_external_id filled, and the record's source_id along with it) or failed (error says why in plain words, with a code, whether a retry can help, the source's own text, the step that failed, and whether anything reached the source). Write intents are never created directly: POST /v1/records/:entityType and DELETE /v1/records/:entityType/:externalId are the only ways in. Learn the outcome by polling GET /v1/write-intents/:id, reconciling with GET /v1/write-intents, or by listening for write_intent.applied and write_intent.failed; a failed write intent can be re-enqueued with POST /v1/write-intents/:id/retry once you have fixed the input. Before writing, the organization object's connections list which entity types and operations each connection can take, and GET /v1/connectors shows the same per connector with whether each operation has been verified by a real write yet. Propagation is explicit opt-in per call, so a write without propagate never leaves Valtrix. Send an Idempotency-Key on every propagated write you might retry, so a retry within 24 hours replays the original response instead of queuing a second write intent.

The write intent object

A propagation of one record write to the organization's source system, created by a write with propagate: true. The record it belongs to carries the same write intent as its last_write_intent, and the record's source_id is filled from source_external_id once the write intent is applied.

Fields
idstring

The wi_ prefixed write intent id.

record_idstring

The record this write intent propagates.

entity_typestring

The entity type of the record.

opstring

create, update, or delete.

statusstring

pending, applied, or failed.

connection_idstring

The connection the write propagates to.

source_external_idstring or null

The id the source system assigned once applied; null before then.

verificationstring or null

What the source showed after an applied write: confirmed (the connector read the write back and every field it sent came back as sent), unconfirmed (the operation has no read-back, such as a form submission, a document upload, or an API that does not echo the fields), or contradicted (the read-back showed a different value for at least one field). The status is applied in all three cases; contradicted means the source accepted the write and then showed something else, so read the record back before writing again. null while pending or failed.

errorobject or null

Why the write did not land, when the status is failed; null otherwise. code is one of refused (the source refused the write in its own words), invalid_input (the record lacks something the source needs, or carries nothing the operation sends), precondition_failed (a rule of the connector stopped the write before anything was sent), not_found (the source has no row for the record), exists (the source already holds the row), source_error (the source failed internally), session_refused (the connection's sign-in was refused), rate_limited, unreachable, reference_pending (a referenced record has no source id yet), grant_missing (the login the connection signs in with lacks a permission the operation needs at the source; the message names the objects and, for a bridge connection, the grant script that adds them), connector_error (the connector could not express the write; Valtrix is notified), or write_failed (unclassified). message says what went wrong in plain words and what to do; hint explains the code; retryable says whether retrying the same write can change the outcome; source_message is the source system's own text when message was translated from it, else null; step names the connector step that failed, else null; source_changed says whether anything had been posted to the source before the failure, null when the connector cannot tell.

attemptsnumber

How many times propagation has been attempted.

created_atstring

When the write intent was created, as an ISO 8601 timestamp.

applied_atstring or null

When the write landed in the source, or null.

{
"id": "wi_9k2f7c81a7b3e650",
"record_id": "rec_9d4f2c81a7b3e650d21c4f8a",
"entity_type": "vendor",
"op": "create",
"status": "applied",
"connection_id": "conn_7f3k2m",
"source_external_id": "CR-88213",
"verification": "confirmed",
"error": null,
"attempts": 1,
"created_at": "2026-07-08T09:30:00Z",
"applied_at": "2026-07-08T09:30:04Z"
}

List write intents

GET/v1/write-intents

Lists write intents across every organization this key can reach, newest first. Filter by record_id, entity_type, op, status, or connection to reconcile what has and has not landed.

Parameters
record_idstring

Only write intents for this record.

entity_typestring

Only write intents for this entity type.

opstring

create, update, or delete.

statusstring

pending, applied, or failed.

connectionstring

Only write intents propagating to this connection.

limitnumber

Defaults to 50. Values above 200 are clamped to 200.

cursorstring

The next_cursor from a previous page.

Returns
write_intentsarray

The write intent objects.

next_cursorstring or null

Pass as cursor to fetch the next page, or null when there are no more.

$ curl https://api.valtrix.com/v1/write-intents?status=failed \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"write_intents": [{ "id": "wi_9k2f7c81a7b3e650", "record_id": "rec_9d4f2c81a7b3e650d21c4f8a", "entity_type": "vendor", "op": "create", "status": "failed", "connection_id": "conn_7f3k2m", "source_external_id": null, "verification": null, "error": { "code": "exists", "message": "Vendor code ACME01 already exists with a different name.", "retryable": false, "hint": "The source already holds a row with this identity, so the create was not repeated. Write it as an update instead.", "source_message": "Duplicate key ACME01", "step": null, "source_changed": false }, "attempts": 1, "created_at": "2026-07-08T09:30:00Z", "applied_at": null }],
"next_cursor": null
}

Retrieve a write intent

GET/v1/write-intents/:id

Fetches one write intent by id, for polling a propagation to completion. The id comes from the write intent in a 202 write response or from a record's last_write_intent.

Parameters
idpath

The wi_ prefixed write intent id.

Returns

The write intent object itself, not wrapped, or 404 write_intent_not_found when no write intent with that id is visible to this key.

$ curl https://api.valtrix.com/v1/write-intents/wi_9k2f7c81a7b3e650 \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"id": "wi_9k2f7c81a7b3e650", "status": "applied", "source_external_id": "CR-88213", "applied_at": "2026-07-08T09:30:04Z"
}

Retry a write intent

POST/v1/write-intents/:id/retry

Re-enqueues a failed write intent, moving it back to pending. Use it after fixing your input with POST /v1/records/:entityType or once a connector is repaired. Returns 409 write_intent_not_retryable for a write intent that is not failed. Requires a key with write access.

Parameters
idpath

The wi_ prefixed write intent id.

Returns

The write intent object itself, not wrapped, now pending with its attempts count carried over.

$ curl -X POST https://api.valtrix.com/v1/write-intents/wi_9k2f7c81a7b3e650/retry \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"id": "wi_9k2f7c81a7b3e650", "status": "pending", "attempts": 3
}

Connect

The Connect session object

An invitation for an organization to connect one of its systems through the hosted Connect flow. Links stay valid for 45 days. On an organization-scoped key the same object describes the organization's own sign-in link for one system: whoever holds that system's login opens it, no Valtrix account is needed, it grants nothing, and it stays valid for 7 days.

Fields
idstring

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

org_idstring

The organization the link is bound to. When you omit org, Valtrix creates the organization up front (named from org_display_name) and it stays pending until the person completes Connect; it appears in GET /v1/orgs once access is granted. If the person signs into a different existing organization instead, that organization takes over and org.connected carries its id, so correlate on external_id or the connect_session_id rather than on this value alone.

connect_urlstring

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

expires_atstring

When the link stops working, as an ISO 8601 timestamp, 45 days after creation (7 days for a sign-in link minted on an organization-scoped key). Mint a new session after expiry.

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

Create a Connect session

POST/v1/connect/sessions

Mints a single use hosted Connect link bound to an organization. Pass org to reconnect or extend an existing organization; omit it and Valtrix creates the organization first (named from org_display_name) and binds the link to it. Send the organization through the link to grant access; your tables build from every connection the organization has. Connections belong to the organization, not to you: if it already syncs the connector you name, the link asks it to approve your access without signing in to that system again, and a link with no connector lets it approve access to everything it already syncs or connect another system first. On an organization-scoped key the same call mints the organization's own sign-in link instead: it names one system (connector is required, 400 connector_required), whoever opens it enters that system's sign-in details with no Valtrix account and no access to any data, it grants nothing, and it expires after 7 days; org_display_name, external_id, scope, and redirect_uri are ignored on such keys.

Parameters
org_display_namestring

The name for the organization Valtrix creates when org is omitted. The person completing Connect can override it, and the organization can rename itself later, so do not rely on it to recognize the organization afterwards; use external_id or the id from GET /v1/orgs.

external_idstring

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

orgstring

An existing organization id, to reconnect or extend access.

connectorstring

A connector slug to preselect. GET /v1/connectors lists the valid ones. Required on an organization-scoped key, which mints a sign-in link for that one system.

credential_sectionsstring

Either "login" or "api". Some connectors capture both a product sign-in and API credentials on one Connect page; this restricts the link to one of the two sections. Omit it to request everything the connector captures, which is the recommended default. Ignored by connectors that only capture one kind of credential.

redirect_uristring

Where the organization lands after finishing.

scopeobject

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

history_fromstring

An ISO 8601 date. A one-time backfill for migrations: the first sync loads date-bounded history (invoices, bills, transactions) from this date, and every sync after it fetches only the connector's default trailing window, so the backfilled records stay in the organization's tables but are not refreshed. Master data such as customers, vendors, and items always loads in full. Set it when a customer needs more history than the connector fetches by default, for example an organization changing systems that needs its full fiscal year in its tables. It widens an existing connection to the same connector and never narrows it, and the connection object shows the value as history_from. A source with its own retention ceiling clamps the start and the sync run report says provider_limit. Must not be in the future: 400 invalid_history_from.

Returns

The Connect session object, with status 201.

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

Connectors

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

The connector object

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

Fields
slugstring

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

display_namestring

The connector's name, for display.

categorystring

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

entity_typesarray

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

writesobject

The entity types this connector can take propagated writes for, each mapped to { create, update, delete, verified }. The three booleans say which operations the connector declares; verified becomes true once a real propagated write has landed and been re-read from the source. An empty object means writes to this connector stay in Valtrix. The organization object's connections carry the same capability per connection.

write_guidanceobject

What to send when writing each entity type into this connector, keyed like writes: fields lists the keys each operation sends under data with their meaning, whether they are required, and, for a field that acts as a command, the values it takes with the effect each has at the source; documents lists the files an operation carries with their media types; notes are the connector's own rules on preconditions, side effects, and refusals. Read it before a propagated write instead of inferring from column names; an entity type with nothing declared is absent.

{
"slug": "procore",
"display_name": "Procore",
"category": "construction",
"entity_types": ["project", "customer", "invoice"],
"writes": { "vendor": { "create": true, "update": true, "delete": false, "verified": true } },
"write_guidance": { "vendor": { "fields": [{ "op": "create", "column": "name", "description": "Display name", "required": true, "values": [] }], "documents": [], "notes": ["A vendor code the system already holds is reused, never duplicated."] } }
}

The connector request object

Tracks a connector you added for a provider Valtrix had not built support for yet, and where the build stands. The provider is the third-party system your customers use; once support is built, it appears in the catalog as a connector and is identified by its slug everywhere else in the API.

Fields
idstring

The request id.

provider_namestring

The provider name as you submitted it.

product_namestring or null

The specific product Valtrix resolved the request to, when the provider sells more than one.

homepage_urlstring

The provider homepage you submitted.

statusstring

requested when the request is queued, building while Valtrix prepares the connector, live once customers' data syncs, or not_available when the provider cannot be supported. The two terminal transitions also fire the connector_request.live and connector_request.not_available webhooks.

connectorstring or null

The live connector's slug once there is one, usable everywhere the API takes a connector.

created_atstring

When you added it, as an ISO 8601 timestamp.

updated_atstring

When the status last changed, as an ISO 8601 timestamp.

{
"id": "creq_9f2k7d",
"provider_name": "Aspire",
"product_name": "Aspire",
"homepage_url": "https://www.youraspire.com",
"status": "building",
"connector": null,
"created_at": "2026-08-13T09:00:00Z",
"updated_at": "2026-08-13T09:00:00Z"
}

List connectors

GET/v1/connectors

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

Returns
connectorsarray

Connector objects, in display name order. Not paginated.

$ curl https://api.valtrix.com/v1/connectors \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"connectors": [{ "slug": "procore", "display_name": "Procore", "category": "construction", "entity_types": ["project", "customer", "invoice"], "writes": { "vendor": { "create": true, "update": true, "delete": false, "verified": true } }, "write_guidance": {} }]
}

Add a connector

POST/v1/connector-requests

Adds a connector for a provider your customers use that is not in the catalog yet. The provider is the third-party system itself; what Valtrix builds and ships is the connector. The add is tracked as a connector request: Valtrix resolves the product, prepares it for connection, and builds the connector; the request's status tracks progress, and the terminal transitions push webhooks: connector_request.live when customers' data starts syncing, connector_request.not_available when the provider cannot be supported. Console members are also emailed on live. As soon as the connector is added it appears in your catalog, so you can include it in connect links and send customers through Connect right away. If the provider is already available, it is added to your catalog and the response returns the existing connector; if another platform already added it, your platform is attached to the open request. Requires a write key. Organization-scoped keys minted through POST /v1/keys file the request for your platform on that organization's behalf.

Parameters
provider_namestring

The provider or product name, as your customer says it.

homepage_urlstring

The provider's homepage URL.

product_choicestring

When a previous attempt returned product_choice_required, the product_name you picked from its candidates.

Returns

An outcome field: created or attached with the connector request object, already_available with the slug of the existing connector that was added to your catalog, or a 409 product_choice_required with candidate products to choose between (the SDK returns this as an outcome instead of throwing).

$ curl https://api.valtrix.com/v1/connector-requests \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "provider_name": "Aspire", "homepage_url": "https://www.youraspire.com" }'
{
"outcome": "created",
"request": { "id": "creq_9f2k7d", "provider_name": "Aspire", "product_name": "Aspire", "homepage_url": "https://www.youraspire.com", "status": "building", "connector": null, "created_at": "2026-08-13T09:00:00Z", "updated_at": "2026-08-13T09:00:00Z" }
}

List connector requests

GET/v1/connector-requests

Lists your connector requests (one for each connector you added that was not already in the catalog), newest first, including requests where you joined an existing build for the same provider. Status is one of requested, building, live, or not_available; connector carries the live connector's slug once there is one. Poll this for the intermediate stages, or subscribe to the connector_request.live and connector_request.not_available webhooks to be pushed the terminal ones. Organization-scoped keys see the requests filed for their organization.

Returns
requestsarray

Connector request objects, newest first. Not paginated.

$ curl https://api.valtrix.com/v1/connector-requests \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"requests": [{ "id": "creq_9f2k7d", "provider_name": "Aspire", "product_name": "Aspire", "homepage_url": "https://www.youraspire.com", "status": "live", "connector": "aspire", "created_at": "2026-08-13T09:00:00Z", "updated_at": "2026-08-14T07:30:00Z" }]
}

Organizations

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

The organization object

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

Fields
idstring

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

display_namestring

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

external_idstring or null

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

statusstring

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

scopeobject

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

connectionsarray

The organization's connected systems, in the same shape as the connection object without org_id. Each carries an id (the value to pass as connection on a propagated write, and the value records expose as connection_id), the connector slug, a status: pending, connected, error, expired, disconnected, or paused, history_from, the date its date-bounded history was backfilled from once or null for the connector's default window, last_synced_at, when data last synced from it, null before the first sync, initial_sync_completed_at, when its first full load finished, paused_at and paused_until while it is paused, and writes: the entity types this connection can take propagated writes for, mapped to the operations among create, update, and delete. An empty writes object means writes to this connection stay in Valtrix. Use it to decide whether to send propagate: true and which connection to address; GET /v1/connectors shows the same per connector, with verification status per operation. write_guidance, keyed the same way, says what to send: the fields each operation takes with their meaning, the values a command field accepts with the effect of each, the documents an operation carries, and the connector's notes on refusals.

connected_atstring

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

expires_atstring or null

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

revoked_atstring or null

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

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

List organizations

GET/v1/orgs

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

Parameters
statusstring

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

external_idstring

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

limitnumber

Defaults to 50. Values above 200 are clamped to 200.

cursorstring

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

Returns
orgsarray

Organization objects, in the order they first connected.

next_cursorstring or null

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

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

Retrieve an organization

GET/v1/orgs/:id

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

Parameters
idpath

The organization id.

Returns

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

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

Connections

Every connected system of every organization that has connected to you, as a resource of its own. The organization object nests the same connections; these endpoints are where you act on one: decide how far back its history loads, run a full sync on demand and read the run's report, and pause it while its data needs to stand still. They also cover an organization changing systems. Connect the outgoing system with history_from covering the period the organization needs and keep reading it live while you reconcile. At the switch, start a full sync, wait for its run to settle, replay GET /v1/tables/:name/changes from the cursors it returns for anything that changed after the run, and pause the connection with no expiry: it stays a source of the organization's tables for as long as the organization keeps it, so the queries you already run keep serving that era. Connect the new system alongside it, and where its connector takes propagated writes, carry records across with propagate: true so the new system's row and the record you wrote stay one record in Valtrix. Read the organization through Valtrix before, during, and after the switch rather than copying its data into a store of your own; the cursors are a bookmark for following changes, not a replication log. Called with an organization-scoped key, these endpoints see only that key's organization.

The connection object

One connected system of one organization. Connections belong to the organization, not to you; a connection stays listed while the organization keeps it, whatever your grant's state.

Fields
idstring

The connection id. The value records expose as connection_id and the value to pass as connection on a propagated write.

org_idstring

The organization the connection belongs to.

connectorstring

The connector slug of the connected system, as listed by GET /v1/connectors.

statusstring

pending before the organization finishes connecting, connected while it syncs, error after a persistent sync failure, expired when the stored credentials stopped working, disconnected after the organization removed it, or paused while syncing is stopped on purpose. A paused connection's data stays readable through every read endpoint; only ingestion stops.

history_fromstring or null

The date its date-bounded history was backfilled from, or null when only the connector's default trailing window applies. The backfill runs once, on the first sync after the date is set; later syncs fetch the default window and keep the backfilled records without refreshing them. Set at connect time or widened later with PATCH; never narrowed.

last_synced_atstring or null

When data last synced from it, null before the first sync.

initial_sync_completed_atstring or null

When the first full load finished, null until then. The moment the organization's data is complete enough to build on.

paused_atstring or null

When the connection was paused, null while it is not.

paused_untilstring or null

When a pause lifts on its own, null for a pause with no expiry or while not paused.

writesobject

The entity types this connection can take propagated writes for, mapped to the operations among create, update, and delete. Empty when writes to this connection stay in Valtrix.

write_guidanceobject

The connector's write guidance for this connection, keyed by entity type like writes: the fields each operation sends with their meaning and whether they are required, the values a command field takes with the effect of each, the documents an operation carries, and the connector's notes on preconditions, side effects, and refusals. The same object as write_guidance on the connector object, here so a caller holding the organization knows what to send before propagating a write.

{
"id": "conn_7f3k2m",
"org_id": "org_7f3k2m",
"connector": "sage-100-contractor",
"status": "paused",
"history_from": "2022-01-01T00:00:00Z",
"last_synced_at": "2026-09-08T09:30:00Z",
"initial_sync_completed_at": "2026-08-01T10:12:00Z",
"paused_at": "2026-09-08T09:31:00Z",
"paused_until": "2026-09-11T09:31:00Z",
"writes": { "vendor": ["create", "update"] },
"write_guidance": { "vendor": { "fields": [{ "op": "create", "column": "name", "description": "Display name", "required": true, "values": [] }], "documents": [], "notes": [] } }
}

The sync run object

One sync of a connection, returned when you start a sync and when you read a run back. Once it finishes, the report says what was fetched per resource and how far back, and once the organization's tables have rebuilt from it, tables gives the change-feed position to replay later deltas from.

Fields
idstring

The run id.

connection_idstring

The connection the run belongs to.

triggerstring

What started it: initial for the first load, scheduled, manual for a requested sync, webhook when the source pushed a change, or field_selection after a field change.

modestring

full or incremental.

statusstring

queued, running, succeeded, partial when some resources failed, failed, or cancelled.

started_atstring or null

When the run started, null while queued.

finished_atstring or null

When the run finished, null until then.

errorstring or null

The failure summary of a partial or failed run.

reportobject or null

The completeness report, null until the run finishes. resources maps each resource type to fetched, created, updated, attachments, failed_scopes, skipped_scopes, denied_scopes, error, error_code, effective_window_start (the date the fetch started from) and window_reason: template_window when the connector's default window applied, history_from when the connection's history date widened it, provider_limit when the source's own ceiling clamped it, or watermark on an incremental run that resumed from the last sync. deleted_by_entity_type counts records the run swept as deleted per entity type. bounded is true when any resource fetch was bounded by a date, which means items dated before that start that are still open in the source, such as an old unpaid invoice, are not guaranteed to be present; treat them as opening balances. error and error_code are null when the resource synced. Otherwise error says in plain words what stopped the fetch and error_code is the value to branch on: auth_rejected (the source rejected the stored credentials), permission_denied (the credentials work but this resource is not permitted), shape_drift (the source answered in a shape the connector no longer recognizes, or a full fetch came back empty where records were expected), session_ended (the source ended the session twice in one run, before and after a fresh login), provider_error (the source answered with a server error), timeout (the source did not answer in time), not_found (the source has no such resource), and scopes_failed (some scopes of a fan-out could not be fetched and are retried next run). Every code except auth_rejected and permission_denied is retried on the next run without action on your side.

derivationstring or null

pending while the organization's tables are still rebuilding from this run, settled once they have, null while the run itself has not finished.

tablesarray or null

One { table, cursor } per published table once derivation is settled, null before. cursor is the current position of that table's change feed for this organization, the same opaque string GET /v1/tables/:name/changes accepts, or null for a table with no changes yet. Read the tables, then replay the change feed from these cursors to pick up everything that changed after this run.

{
"id": "run_3c2b1a",
"connection_id": "conn_7f3k2m",
"trigger": "manual",
"mode": "full",
"status": "succeeded",
"started_at": "2026-09-08T09:00:00Z",
"finished_at": "2026-09-08T09:24:00Z",
"error": null,
"report": {
"resources": { "invoices": { "fetched": 4120, "created": 12, "updated": 40, "attachments": 0, "failed_scopes": 0, "skipped_scopes": 0, "denied_scopes": 0, "error": null, "error_code": null, "effective_window_start": "2022-01-01T00:00:00.000Z", "window_reason": "history_from" } },
"deleted_by_entity_type": {},
"bounded": true
},
"derivation": "settled",
"tables": [{ "table": "invoices_clean", "cursor": "eyJjIjoiODIzMiJ9" }]
}

List connections

GET/v1/connections

Lists every connection of every organization that has connected to you, oldest first. Filter by org, connector, or status to narrow.

Parameters
orgstring

Only this organization's connections. 404 org_not_granted when it has not connected to you.

connectorstring

Only connections of this connector slug.

statusstring

Only connections in this status: pending, connected, error, expired, disconnected, or paused.

limitnumber

Defaults to 50. Values above 200 are clamped to 200.

cursorstring

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

Returns
connectionsarray

Connection objects.

next_cursorstring or null

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

$ curl https://api.valtrix.com/v1/connections \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-G -d "status=connected" -d "limit=50"
{
"connections": [{ "id": "conn_7f3k2m", "org_id": "org_7f3k2m", "connector": "sage-100-contractor", "status": "connected", "history_from": "2022-01-01T00:00:00Z", "last_synced_at": "2026-09-08T09:30:00Z", "initial_sync_completed_at": "2026-08-01T10:12:00Z", "paused_at": null, "paused_until": null, "writes": { "vendor": ["create", "update"] }, "write_guidance": {} }],
"next_cursor": null
}

Retrieve a connection

GET/v1/connections/:id

Fetches one connection by id.

Parameters
idpath

The connection id.

Returns

The connection object, or 404 connection_not_found when no organization that connected to you owns it.

$ curl https://api.valtrix.com/v1/connections/conn_7f3k2m \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "id": "conn_7f3k2m", "org_id": "org_7f3k2m", "connector": "sage-100-contractor", "status": "connected", "history_from": null, "last_synced_at": "2026-09-08T09:30:00Z", "initial_sync_completed_at": "2026-08-01T10:12:00Z", "paused_at": null, "paused_until": null, "writes": {}, "write_guidance": {} }

Update a connection

PATCH/v1/connections/:id

Updates the settings of a connection. history_from is the one updatable field today: setting it to an earlier date queues one full sync that backfills the added history. That backfill runs once per resource; every sync after it is back on the connector's default trailing window, so the added records stay put but are not refreshed. The date can only move earlier; a later date returns 400 invalid_history_from, because narrowing would sweep records the organization already relies on. Pausing and resuming are state changes and have their own endpoints.

Parameters
idpath

The connection id.

history_fromstring

An ISO 8601 date, earlier than the current history_from and not in the future.

Returns

The updated connection object.

$ curl -X PATCH https://api.valtrix.com/v1/connections/conn_7f3k2m \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "history_from": "2022-01-01" }'
{ "id": "conn_7f3k2m", "history_from": "2022-01-01T00:00:00.000Z", ... }

Start a sync

POST/v1/connections/:id/runs

Starts a full sync of the connection and returns the run with status 202. Poll GET /v1/connections/:id/runs/:runId, or subscribe to the connection.sync_completed webhook event, for the report and the per-table cursors. 409 sync_in_progress while a sync is already queued or running, 409 connection_paused while the connection is paused.

Parameters
idpath

The connection id.

modestring

Only "full" is accepted; omit it. Incremental syncs run on the connection's own schedule.

Returns

The sync run object, with status 202.

$ curl -X POST https://api.valtrix.com/v1/connections/conn_7f3k2m/runs \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
{ "id": "run_3c2b1a", "connection_id": "conn_7f3k2m", "trigger": "manual", "mode": "full", "status": "queued", "started_at": null, "finished_at": null, "error": null, "report": null, "derivation": null, "tables": null }

Retrieve a sync run

GET/v1/connections/:id/runs/:runId

Fetches one sync run of the connection. While it runs, report and tables are null. Once it finishes, report is filled; once the organization's tables have rebuilt from it, derivation is settled and tables carries the cursor per table. Note the cursors, read the tables, then replay the change feed from the cursors for anything that changed afterwards.

Parameters
idpath

The connection id.

runIdpath

The run id, as returned by POST /v1/connections/:id/runs or the connection.sync_completed event.

Returns

The sync run object, or 404 run_not_found.

$ curl https://api.valtrix.com/v1/connections/conn_7f3k2m/runs/run_3c2b1a \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "id": "run_3c2b1a", "status": "succeeded", "report": { "resources": { "invoices": { "fetched": 4120, ... } }, "deleted_by_entity_type": {}, "bounded": true }, "derivation": "settled", "tables": [{ "table": "invoices_clean", "cursor": "eyJjIjoiODIzMiJ9" }], ... }

Pause a connection

POST/v1/connections/:id/pause

Stops syncing the connection while keeping every record, table row, change, and attachment readable and the organization's credentials stored. Queued and running syncs are cancelled, nothing is scheduled, and source webhooks are ignored. The connection stays billable, since its data is still served. Use it to hold the organization's data still while you reconcile a point-in-time read, or for a source the organization has switched off, whose data stays part of its tables for as long as the organization keeps the connection. A platform may pause only a connection it is the sole reader of; when another platform also reads it, 409 connection_shared names them and the organization pauses it from its console. Resume with POST /v1/connections/:id/resume, or let a pause with until lift on its own.

Parameters
idpath

The connection id.

untilstring

An ISO 8601 timestamp at which the pause lifts and syncing resumes from where it stopped, at most a year away. Omit it to pause with no expiry.

Returns

The paused connection object. 409 connection_not_pausable when the connection is pending, disconnected, or already paused.

$ curl -X POST https://api.valtrix.com/v1/connections/conn_7f3k2m/pause \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "until": "2026-09-11T09:31:00Z" }'
{ "id": "conn_7f3k2m", "status": "paused", "paused_at": "2026-09-08T09:31:00Z", "paused_until": "2026-09-11T09:31:00Z", ... }

Resume a connection

POST/v1/connections/:id/resume

Lifts a pause. The connection returns to connected and its next sync runs at once, incrementally from the watermark it stopped at, so nothing that changed in the source during the pause is lost.

Parameters
idpath

The connection id.

Returns

The connection object. 409 connection_not_paused when it is not paused.

$ curl -X POST https://api.valtrix.com/v1/connections/conn_7f3k2m/resume \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
{ "id": "conn_7f3k2m", "status": "connected", "paused_at": null, "paused_until": null, ... }

API keys

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

The API key object

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

Fields
idstring

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

namestring

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

prefixstring

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

orgstring or null

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

accessstring

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

manageboolean

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

created_atstring

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

last_used_atstring or null

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

revoked_atstring or null

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

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

Create an organization-scoped key

POST/v1/keys

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

Parameters
orgstring, required

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

namestring, required

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

accessstring

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

Returns

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

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

List API keys

GET/v1/keys

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

Parameters
orgstring

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

Returns
keysarray

API key objects, newest first.

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

Revoke an API key

DELETE/v1/keys/:id

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

Parameters
idpath

The key id.

Returns

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

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

Transformations

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

The transformation object

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

Fields
tablestring

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

namestring

The display name.

managed_bystring

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

statusstring

draft before the first publish, published after.

published_versionnumber or null

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

has_draftboolean

Whether an unpublished dashboard draft exists.

definitionstring

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

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

The definition file

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

Fields
versionnumber, default 1

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

tablestring

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

namestring

The display name shown in the console.

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

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

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

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

org_scopeall | selected, default "all"

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

stepslist of steps

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

qualitylist of quality rules, optional

Documented under Quality rules.

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

List transformations

GET/v1/transformations

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

Returns
transformationsarray

Transformation objects, oldest first.

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

Retrieve a transformation

GET/v1/transformations/:table

Fetches one transformation by the table it publishes.

Parameters
tablepath

The output table name.

Returns

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

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

Plan a deploy

POST/v1/transformations/plan

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

Parameters
definitionsarray, required

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

adoptboolean

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

Returns
itemsarray

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

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

Apply a deploy

POST/v1/transformations/apply

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

Parameters
definitionsarray, required

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

adoptboolean

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

Returns
itemsarray

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

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

Step types

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

rename_columns

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

mappingobject

drop_columns

Remove the listed columns.

columnslist of strings

keep_columns

Keep only the listed columns and remove every other column.

columnslist of strings

cast

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

columnstring

tostring | number | boolean | date | json

onErrorkeep | null

standardize

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

columnstring

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

defaultRegionstring, optional

onUnparseablekeep | null

map_values

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

columnstring

mappingobject

fallbackkeep | null | value

fallbackValuestring, optional

replace_text

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

columnstring

findstring

replaceWithstring

modeplain | regex

split_column

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

columnstring

separatorstring

intolist of strings

keepOriginalboolean, optional

extract_json

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

columnstring

pathstring

targetstring

bin

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

columnstring

targetstring

binslist of objects

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

fallbackLabelstring, optional

filter_rows

Keep only rows matching the condition group.

wherecondition group

Documented under Conditions.

set_default

Fill empty values in a column with a default.

columnstring

valuestring

derived_column

Create a new column from an expression over existing columns.

columnstring

expressionexpression

One of the functions documented under Expression functions.

dedupe

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

keyslist of strings

keeplatest | first

orderByobject, optional

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

lookup

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

sourcetable | entity, optional

tableNamestring

localKeystring

remoteKeystring

takelist of strings

prefixstring, optional

rollup

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

sourcetable | entity, optional

tableNamestring

localKeystring

remoteKeystring

fncount | sum | avg | min | max

columnstring, optional

targetstring

aggregate

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

groupBylist of strings

aggregationslist of objects

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

union

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

sourcetable | entity, optional

tableNamestring

mappingobject, optional

pivot

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

groupBylist of strings

headerColumnstring

valueColumnstring

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

headerValueslist of strings

unpivot

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

columnslist of strings

keyColumnstring

valueColumnstring

dropEmptyboolean, optional

explode

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

columnstring

modevalue | flatten

prefixstring, optional

keepEmptyboolean, optional

sql

Run a single SQLite SELECT (or WITH) over the current rows, separately per org. The table is named "input" and has the data columns plus _record_id, _external_id, _org_id, _connector, _synced_at (when the record was last re-read from its source), _changed_at (when its data last actually differed; use it for "last updated" columns), _attachment_id (the id of the stored source document behind the record, the first when there are several, null when none) and _attachment_ids (a JSON array of all of them). 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. Set key to the output columns that identify such a row (an invoice id plus a payment id, a status plus a month) so the generated id stays the same when the other columns change; without key the id is derived from every value in the row, so any edit to the query or to a value replaces the row with a new one and consumers of the change feed see a delete and an insert. 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 _record_id and _org_id plus its data columns (no _synced_at or _changed_at) and already contains only rows from the same org, so no org filtering is needed. Use only when the other step types cannot express the logic.

querystring

keylist of strings, optional

Expression functions

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

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

coalesce
columnslist of strings

arithmetic

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

leftcolumn name, number, or nested arithmetic expression

operator+ | - | * | /

rightcolumn name, number, or nested arithmetic expression

year_of
columnstring

case

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

caseslist of objects

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

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

substring

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

columnstring

startnumber

lengthnumber, optional

replace
columnstring

findstring

replaceWithstring

regexboolean, optional

regex_extract

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

columnstring

patternstring

groupnumber, optional

split_part

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

columnstring

separatorstring

indexnumber

length
columnstring

date_part
columnstring

partyear | month | day

date_trunc
columnstring

unitday | week | month | year

date_diff

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

unitdays | months | years

fromstring

tostring, optional

date_add
columnstring

amountnumber

unitdays | months | years

round
columnstring

digitsnumber, optional

abs
columnstring

floor
columnstring

ceil
columnstring

Conditions

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

condition group

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

logicand | or

conditionslist of conditions

condition

One column check inside a condition group.

columnstring

operatorenum

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

valuestring, optional

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

valueslist of strings, optional

The list read by in and not_in.

Quality rules

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

not_null

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

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

unique

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

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

accepted_values

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

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.valueslist of strings

The accepted values, compared as text.

regex

Fails rows whose column value does not match the pattern.

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.patternstring

A JavaScript regular expression source, without slashes.

freshness

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

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.maxAgeHoursnumber, default 24

The maximum age in hours.

volume

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

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.minRowsnumber, default 1

The minimum expected row count per organization.

relationship

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

columnstring

The column the rule checks.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.relationobject

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

expression

Fails rows that do not match the condition group.

severityerror | warn, default "error"

namestring, optional

The display name shown in the console.

config.wherecondition group

Documented under Conditions.

Webhooks

Webhooks push notifications to your server so you never poll on a timer. Register an endpoint with POST /v1/webhooks, or under Developers, Webhooks in the console, pick the event types it receives, and you get a signing secret shown once. Two paths cover data changes. table.changed is the thin one: it carries ids, never row data, and you drain the change feed from your own stored cursor. table.row_changed is the fat one, opt-in per endpoint and per table: one delivery per changed row with the row, the columns that changed, and their previous values, optionally narrowed to changes touching the columns you name. Deliveries retry with backoff for about 3 days and can repeat, so acknowledge with any 2xx within 5 seconds and deduplicate on the event id. Events arrive as fast as the data changes, and a sync often surfaces a whole day of changes at once, so expect bursts of hundreds of deliveries within seconds and process them asynchronously rather than inline in the request. An event still undelivered after that is marked dead and kept for 14 days; GET /v1/webhooks/:id/events lists the queue and POST /v1/webhooks/:id/replay re-queues the dead ones. Webhooks are the wake-up and the change feed is the record: subscribe to table.changed, drain GET /v1/tables/:name/changes from your stored cursor each time it fires, and reconcile from the same cursor every 15 to 60 minutes to cover a delivery you missed, never on a tight timer. Every delivery posts the event object unless the endpoint has a payload_template, in which case the receiver gets your JSON with placeholders filled in from the event: write the exact body the receiver expects, such as a chat tool's incoming-webhook message, and reference event fields as {{data.row.status}} or {{data.previous.status | default 'none'}}. A string that is exactly one placeholder takes the value's own JSON type, so a number stays a number and an object stays an object; placeholders inside text are interpolated, with objects and arrays as compact JSON. A delivery whose every placeholder resolves to nothing is skipped instead of sent. POST /v1/webhooks/preview renders a template against a sample event before you save it, and the placeholders it returns are the paths you can use.

The webhook endpoint object

A URL you registered to receive events, with the event types it subscribes to and its delivery health. Managing endpoints needs a platform-wide key with manage access; the signing secret is returned only by the create and rotate-secret calls.

Fields
idstring

The endpoint id.

urlstring

Where events are posted. https on a public host in production. Fixed for the life of the endpoint: register a new endpoint to change it.

descriptionstring or null

A label for your own reference.

statusstring

active while deliveries succeed, failing once 20 deliveries in a row have failed (deliveries continue), disabled when you turned it off; a disabled endpoint receives nothing, and events keep queuing for it until you re-enable it or delete it, for up to 90 days; an event still waiting after that is dropped.

event_typesarray

The event types the endpoint receives, sorted. Any of the names under Event types except endpoint.test, which every endpoint accepts.

payload_templatestring or null

null when deliveries post the event object as is. Otherwise a JSON document, as a string, that is rendered per event and posted instead: every {{path}} in its strings is replaced with the value at that path of the event object (id, type, occurred_at, or data and its fields, down to data.row.column and data.previous.column for table.row_changed), {{path | default 'text'}} supplies a value when the path is missing, {{path | escape}} writes the value with &, <, and > replaced by &amp;, &lt;, and &gt; (the escaping a chat tool such as Slack asks for, so a value is never read as its markup; it combines with default in either order), and a string that is exactly one placeholder is replaced by the raw value rather than text. A Slack incoming webhook URL takes a template of the form { "text": "..." } and nothing else: Slack requires text and answers 200 with the body ok. Deliveries whose placeholders all resolve to nothing are recorded as skipped and not sent. The signature covers the rendered body.

stale_after_secondsnumber or null

The age past which a queued event is discarded instead of delivered, in seconds, or null to retry every event for the full three-day window. Set it on an endpoint that drives alerts so a receiver that comes back after a day gets the last day of changes, not everything that queued while it was down. Discarded events are listed with status discarded and can be replayed.

tablesarray

The table.row_changed subscriptions as { table, columns, change_types, resync_required_at }: the published table by name, when columns is non-empty the columns an update must touch to be delivered, and when change_types is non-empty the kinds of change delivered, any of insert (a new row), update, and delete. An empty change_types delivers all three, and columns never holds back a new row or a delete, so an endpoint that should hear about one column changing and nothing else sets both. resync_required_at is null unless the subscription fell so far behind that changes it had not received were pruned (an endpoint left disabled for over 90 days); it then marks when that happened, and the gap is closed by a full read of the table, after which sending the table again in PATCH /v1/webhooks/:id clears it. Empty unless event_types includes table.row_changed.

consecutive_failuresnumber

Failed deliveries since the last success. Resets to zero on a successful delivery or when you re-enable the endpoint.

last_success_atstring or null

When a delivery last got a 2xx, as an ISO 8601 timestamp.

last_failure_atstring or null

When a delivery last failed, as an ISO 8601 timestamp.

pending_eventsnumber

Events queued for the endpoint and not yet delivered, including those waiting out a retry backoff.

dead_eventsnumber

Events that exhausted their retries and can be re-queued with POST /v1/webhooks/:id/replay. Kept for 14 days.

discarded_eventsnumber

Events dropped without delivery, by queued_events: discard on PATCH /v1/webhooks/:id or by the endpoint's stale_after_seconds. Replayable like dead events, kept for 14 days.

oldest_pending_atstring or null

When the oldest event still waiting was queued, as an ISO 8601 timestamp; null when nothing is pending. Together with pending_events it says how far behind a receiver is.

created_atstring

When the endpoint was registered, as an ISO 8601 timestamp.

{
"id": "whe_4k2m9p",
"url": "https://yourapp.com/webhooks/valtrix",
"description": "Production app",
"status": "active",
"event_types": ["connection.broken", "table.row_changed", "write_intent.applied", "write_intent.failed"],
"payload_template": null,
"stale_after_seconds": null,
"tables": [{ "table": "customers_clean", "columns": ["email"], "change_types": ["update"], "resync_required_at": null }],
"consecutive_failures": 0,
"last_success_at": "2026-07-10T08:05:00Z",
"last_failure_at": null,
"pending_events": 0,
"dead_events": 0,
"discarded_events": 0,
"oldest_pending_at": null,
"created_at": "2026-07-10T08:00:00Z"
}

The queued event object

An event in an endpoint's queue: waiting to be delivered, delivered, dead after exhausting its retries, or discarded without delivery. Read them from GET /v1/webhooks/:id/events to see what an endpoint has not received yet, and re-queue dead or discarded ones with POST /v1/webhooks/:id/replay. Delivered, dead, and discarded events are kept for 14 days.

Fields
idstring

The event id, the same value your endpoint receives in the Valtrix-Event-Id header and the envelope.

typestring

The event type, like table.row_changed. Event types lists every one.

statusstring

pending while delivery is still being attempted, delivered once your endpoint answered 2xx, dead once every retry failed, discarded when it was dropped without delivery, either by queued_events: discard or because it aged past the endpoint's stale_after_seconds (the delivery log records which).

attemptsnumber

How many deliveries have been tried. A replay resets it to zero.

next_attempt_atstring or null

When the next delivery is due for a pending event, as an ISO 8601 timestamp; null once delivered, dead, or discarded.

delivered_atstring or null

When your endpoint accepted the event, as an ISO 8601 timestamp; null until then.

created_atstring

When the event was queued, as an ISO 8601 timestamp; the envelope's occurred_at.

dataobject

The event payload exactly as the envelope carries it. Event types lists the fields per event.

{
"id": "evt_01h9x...",
"type": "table.row_changed",
"status": "dead",
"attempts": 19,
"next_attempt_at": null,
"delivered_at": null,
"created_at": "2026-07-10T08:05:00Z",
"data": { "table": "customers_clean", "record_id": "rec_9d4f2c81a7b3e650d21c4f8a", "change_type": "upsert" }
}

The delivery object

One attempt to post an event to an endpoint, kept for 14 days, with the body that was sent and the start of the receiver's reply. Read them from GET /v1/webhooks/:id/deliveries to see why an endpoint is failing.

Fields
idstring

The delivery id.

event_idstring or null

The queued event this delivery attempted, as listed by GET /v1/webhooks/:id/events; null for table.changed pings and test events, which are not queued.

event_typestring

The event type that was posted, like table.row_changed or endpoint.test.

statusstring

succeeded when your endpoint answered 2xx within 5 seconds, failed otherwise, skipped when the endpoint's payload_template resolved no placeholder for this event so nothing was sent. A 429 is recorded as failed but retried after the Retry-After header without counting toward the endpoint's failure count or the event's attempts, so a rate-limited event waits as long as the endpoint keeps asking.

http_statusnumber or null

The HTTP status your endpoint returned, or null when the request never completed.

errorstring or null

What went wrong on a failed delivery: the non-2xx status, a timeout, or a connection error.

request_bodystring or null

The exact body that was posted: the event object, or the rendered payload_template when the endpoint has one. Truncated past 128 KB. Null when nothing was sent, as on a skipped delivery.

response_bodystring or null

The first 2 KB of what your endpoint answered, or null when it sent nothing or never answered. The place to read a receiver's own error message. Always null on a test delivery, whose reply is not stored.

duration_msnumber or null

How long the request took.

created_atstring

When the attempt was made, as an ISO 8601 timestamp.

{
"id": "whd_9s2ph1",
"event_id": "evt_01h9x...",
"event_type": "table.row_changed",
"status": "failed",
"http_status": 500,
"error": "Endpoint responded 500",
"request_body": "{\"id\":\"evt_01h9x...\",\"type\":\"table.row_changed\",\"occurred_at\":\"2026-07-10T08:05:00Z\",\"data\":{...}}",
"response_body": "internal error",
"duration_ms": 212,
"created_at": "2026-07-10T08:05:00Z"
}

The event object

The envelope every webhook delivery posts to your endpoint. The same shape for every event type; only data varies. An endpoint with a payload_template posts the rendered template instead, with this object as the source of its placeholder values.

Fields
idstring

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

typestring

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

occurred_atstring

When the event happened, as an ISO 8601 timestamp.

dataobject

The event payload. Carries ids and names; only table.row_changed carries row data. Event types lists the fields per event.

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

Event types

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

connection.broken

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

data.org_idstring

The organization the connection belongs to.

data.external_idstring or null

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

data.connectorstring

The connector slug of the affected system.

data.reasonstring

reauthorization_required when the organization must reconnect through a new Connect session, sync_failed when syncing hit a persistent error, bridge_offline when the Valtrix Bridge on the organization's machine stopped reporting in, bridge_revoked when the organization's bridge pairing was revoked.

connection.paused

A connection was paused, by you, by the organization, or by an admin. Its data stays readable but stops updating until it resumes. Not in an endpoint's default subscriptions; opt in.

data.org_idstring

The organization the connection belongs to.

data.external_idstring or null

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

data.connection_idstring

The paused connection.

data.connectorstring

The connector slug of the paused system.

data.paused_untilstring or null

When the pause lifts on its own, or null for a pause with no expiry.

connection.resumed

A paused connection resumed, by request or because its pause expired. Its next sync runs at once. Not in an endpoint's default subscriptions; opt in.

data.org_idstring

The organization the connection belongs to.

data.external_idstring or null

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

data.connection_idstring

The resumed connection.

data.connectorstring

The connector slug of the resumed system.

connection.sync_completed

A sync run of a connection finished with data: succeeded, or partial when some resources failed. Carries the same report GET /v1/connections/:id/runs/:runId returns: on a partial run each failed resource carries error, a sentence for a person, and error_code, the value to branch on. Read the run for the per-table cursors once the organization's tables have rebuilt. Fires for every run, scheduled ones included, so filter on trigger and mode. Not in an endpoint's default subscriptions; opt in.

data.org_idstring

The organization the connection belongs to.

data.external_idstring or null

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

data.connection_idstring

The connection that synced.

data.connectorstring

The connector slug of the synced system.

data.run_idstring

The run id, to read back with GET /v1/connections/:id/runs/:runId.

data.triggerstring

initial, scheduled, manual, webhook, or field_selection.

data.modestring

full or incremental.

data.statusstring

succeeded or partial.

data.reportobject

The completeness report of the sync run object.

connector_request.live

A connector you added through POST /v1/connector-requests went live: the connector is in the catalog and requesters' customer data is syncing. Sent to every platform on the request, including ones that joined an existing build.

data.request_idstring

The connector request id, matching the id in GET /v1/connector-requests.

data.provider_namestring

The provider name as the request submitted it.

data.product_namestring or null

The specific product the request resolved to, when the provider sells more than one.

data.statusstring

Always "live" for this event.

data.connectorstring

The new connector's slug, valid everywhere the API takes a connector, so you can start minting Connect sessions with it.

connector_request.not_available

A connector request ended without a connector: the provider cannot be supported. The request stays visible in GET /v1/connector-requests with status not_available.

data.request_idstring

The connector request id, matching the id in GET /v1/connector-requests.

data.provider_namestring

The provider name as the request submitted it.

data.product_namestring or null

The specific product the request resolved to, when the provider sells more than one.

data.statusstring

Always "not_available" for this event.

data.connectornull

Always null for this event.

endpoint.test

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

grant.revoked

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

data.org_idstring

The organization that revoked access.

data.external_idstring or null

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

org.connected

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

data.org_idstring

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

data.external_idstring or null

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

data.connect_session_idstring or null

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

org.initial_sync.completed

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

data.org_idstring

The organization the connection belongs to.

data.external_idstring or null

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

data.connectorstring

The connector slug of the system that synced.

table.changed

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

data.tablestring

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

data.latest_cursorstring

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

table.published

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

data.tablestring

The name of the new table.

data.schema_versionstring

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

table.row_changed

One row of a subscribed table changed. Opt in per endpoint with the tables field on POST /v1/webhooks or under Developers, Webhooks, choosing the tables and optionally the columns and the change types: with columns set, an update is delivered only when one of them changed, and with change_types set, only those kinds of change (insert, update, delete) are delivered. Columns alone never hold back a new row or a delete; leave insert and delete out of change_types for that. An insert arrives as change_type upsert with changed_columns null, an update as upsert with the columns that changed. Delivered in feed order per table, one event per change, starting from when the subscription was created; a table's initial load after a new connection is skipped, so you only hear about changes to data that was already there. Republishing the table's transformation is not skipped: every row the new version changes is delivered, and a row whose id changed arrives as a delete of the old id followed by an insert of the new one, deletes first. Rows from an aggregating or reshaping SQL step keep their syn_ id across republishes only when the step declares a key, so a step without one re-emits the whole table when a column is added. Rows larger than 64 KB arrive without the row, with row_omitted set, so fetch by record_id in that case.

data.tablestring

The name of the table the row belongs to, as used in /v1/tables/:name paths.

data.org_idstring

The organization the row belongs to.

data.external_idstring or null

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

data.record_idstring

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

data.change_typestring

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

data.cursorstring

This change's position in the table's change feed, the same value GET /v1/tables/:name/changes returns for it. Store it if you also drain the feed, so the two paths never double-process a change.

data.changed_columnsarray or null

The columns whose values differ from the row's previous version. null when the row is new to the table, and null for deletes.

data.previousobject or null

The values the changed columns held before this change, keyed by column. null whenever changed_columns is null.

data.rowobject or null

The full row after the change, in the row object shape. null for deletes, and null when the row was too large to deliver.

data.row_omittedboolean

true when row is null because the row exceeded 64 KB. Read it with GET /v1/records/:recordId or the table's rows endpoint.

data.schema_versionstring or null

The table's schema version at the time of the change, like "v3". Compare it with the version your client was generated from before reading columns.

table.schema_updated

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

data.tablestring

The name of the table whose schema changed.

data.schema_versionstring

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

write_intent.applied

A propagated write landed in the organization's connected system. Where the connector can read the write back, verification says whether the source showed the values as sent. Fetch GET /v1/write-intents/:id for the full write intent, or the record for its updated source_id.

data.org_idstring

The organization the write belongs to.

data.external_idstring or null

Your identifier for the organization, as sent when minting its Connect session.

data.write_intentobject

The write intent summary: id, status (always applied), op, entity_type, record_id, source_external_id, the id the source system assigned, and verification (confirmed, unconfirmed, or contradicted).

write_intent.failed

A propagated write could not be applied after its retries. Inspect error, fix the input with POST /v1/records/:entityType if needed, then POST /v1/write-intents/:id/retry.

data.org_idstring

The organization the write belongs to.

data.external_idstring or null

Your identifier for the organization, as sent when minting its Connect session.

data.write_intentobject

The write intent summary: id, status (always failed), op, entity_type, record_id, and error, why the write did not land, as { code, message, retryable, hint, source_message, step, source_changed }; the same error the write intent object carries.

Create a webhook endpoint

POST/v1/webhooks

Registers a URL to receive events and returns the signing secret once, in the secret field. Store it immediately; it is never shown again, only rotated. With no event_types, the endpoint receives every event type except table.row_changed; pass tables to receive table.row_changed for those tables (it is added to event_types for you). Requires a platform-wide key with manage access.

Parameters
urlstring, required

Where to post events. Must be https on a public host in production; localhost and private addresses are rejected.

descriptionstring

A label for your own reference.

event_typesarray

The event types to receive, from the names under Event types. Omit for every type except table.row_changed. At least one is required when sent.

tablesarray

table.row_changed subscriptions as { table, columns, change_types }: the published table by name, as listed by GET /v1/tables, optionally the columns an update must touch to be delivered (omit or send [] for every update), and optionally the kinds of change to deliver, any of insert, update, and delete (omit or send [] for all three). columns narrows updates only, so new rows and deletes still arrive unless change_types leaves them out: { columns: ["status"], change_types: ["update"] } delivers a row only when its status changes. Each subscription starts at the table's current position, so only changes after this call are delivered.

payload_templatestring

A JSON document, as a string, to post instead of the event object, with {{path}} placeholders filled in from each event; see payload_template on the webhook endpoint object for the rules. Placeholders are checked against the event types and tables subscribed here, so a template naming a column no subscribed table has is rejected. Omit or send null to post the event object.

stale_after_secondsnumber

How old a queued event may get before it is discarded instead of delivered, in seconds (60 to 2592000, thirty days). Set it on an endpoint that drives alerts so a receiver that was down for a while catches up on recent changes only, not on everything since it went down. Omit or send null to keep retrying every event for the full retry window.

Returns

The webhook endpoint object plus a secret field carrying the signing secret, shown only in this response. 400 invalid_url when the URL is rejected, 400 invalid_subscriptions when an event type, table, column, or change type is unknown, 400 invalid_payload_template when the template does not parse or names a field the subscriptions do not carry, 400 invalid_request when stale_after_seconds is out of range.

$ curl https://api.valtrix.com/v1/webhooks \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://yourapp.com/webhooks/valtrix", "event_types": ["table.row_changed", "connection.broken"], "tables": [{ "table": "customers_clean", "columns": ["email"], "change_types": ["update"] }] }'
{
"id": "whe_4k2m9p",
"url": "https://yourapp.com/webhooks/valtrix",
"description": null,
"status": "active",
"event_types": ["connection.broken", "table.row_changed"],
"tables": [{ "table": "customers_clean", "columns": ["email"], "change_types": ["update"] }],
"consecutive_failures": 0,
"last_success_at": null,
"last_failure_at": null,
"created_at": "2026-07-10T08:00:00Z",
"secret": "whsec_7c1f0b2e9a4d5f6a8b9c0d1e2f3a4b5c"
}

List webhook endpoints

GET/v1/webhooks

Lists every endpoint that has not been deleted, newest first, with its subscriptions and delivery health. Not paginated. Requires a platform-wide key with manage access.

Returns
webhooksarray

Webhook endpoint objects, newest first.

$ curl https://api.valtrix.com/v1/webhooks \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"webhooks": [{ "id": "whe_4k2m9p", "url": "https://yourapp.com/webhooks/valtrix", "description": null, "status": "active", "event_types": ["connection.broken", "table.row_changed"], "tables": [{ "table": "customers_clean", "columns": ["email"], "change_types": ["update"] }], "consecutive_failures": 0, "last_success_at": "2026-07-10T08:05:00Z", "last_failure_at": null, "created_at": "2026-07-10T08:00:00Z" }]
}

Get a webhook endpoint

GET/v1/webhooks/:id

Fetches one endpoint by id. Requires a platform-wide key with manage access.

Parameters
idpath

The endpoint id.

Returns

The webhook endpoint object, or 404 webhook_not_found.

$ curl https://api.valtrix.com/v1/webhooks/whe_4k2m9p \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "id": "whe_4k2m9p", "url": "https://yourapp.com/webhooks/valtrix", "description": null, "status": "active", "event_types": ["connection.broken", "table.row_changed"], "tables": [{ "table": "customers_clean", "columns": ["email"], "change_types": ["update"] }], "consecutive_failures": 0, "last_success_at": "2026-07-10T08:05:00Z", "last_failure_at": null, "created_at": "2026-07-10T08:00:00Z" }

Update a webhook endpoint

PATCH/v1/webhooks/:id

Changes where an endpoint posts, what it receives, what it posts, how long it keeps trying, or turns it off and on. Send only the fields to change: url moves the endpoint (its queue, secret, and subscriptions stay, its failure count resets, and every waiting event is sent to the new URL straight away), description relabels it, event_types replaces the event list, tables replaces the table.row_changed subscriptions (a table already subscribed keeps its position and has its resync_required_at cleared, a new one starts from now), payload_template replaces or clears the custom payload, stale_after_seconds sets or clears the age past which a queued event is discarded instead of delivered, enabled pauses or resumes delivery, and queued_events sends or discards what is waiting. Sending tables without event_types adds table.row_changed to the current event list. Requires a platform-wide key with manage access.

Parameters
idpath

The endpoint id.

urlstring

The new https URL to post to. Fixing a wrong URL this way keeps everything the endpoint has queued: pending events go to the new URL at once instead of waiting out their backoff, and dead events stay replayable.

descriptionstring or null

A new label for the endpoint, or null to clear it.

event_typesarray

The full list of event types to receive from now on. At least one.

tablesarray

The full list of table.row_changed subscriptions as { table, columns, change_types }, with the same meaning as on POST /v1/webhooks: change_types is any of insert, update, and delete, and an entry sent without it goes back to all three. Send [] to drop them all, together with an event_types list that omits table.row_changed.

enabledboolean

false pauses the endpoint: nothing is sent, but events keep queuing for it. true resumes it, clears its failure count, and sends the queue straight away unless queued_events says discard.

queued_eventsstring

What to do with every pending event on the endpoint. send clears the failure count and delivers them all straight away instead of waiting out their backoff, the move after a receiver that was down is back; discard marks them discarded so they are never sent, the move when the receiver should start from now, for example an alerting endpoint that would otherwise post days of stale notifications. Discarded events stay listed and can be re-queued with POST /v1/webhooks/:id/replay for 14 days. Combine with enabled: true to choose what a resumed endpoint does with its queue; on its own it acts on an active or failing endpoint, and send is refused on a disabled one.

payload_templatestring or null

Replaces the payload template: a JSON string with {{path}} placeholders to render per event, or null to go back to posting the event object. Checked against the subscriptions as they stand after this call. Queued events not yet delivered are rendered with the new template.

stale_after_secondsnumber

The age past which a queued event is discarded instead of delivered, in seconds (60 to 2592000), or null to retry every event for the full retry window. Applies to events already waiting as well as new ones.

Returns

The updated webhook endpoint object. 404 webhook_not_found, 400 invalid_url when the URL is rejected, 400 invalid_subscriptions when an event type, table, or column is unknown, 400 invalid_payload_template when the template does not fit, or when new subscriptions would orphan a placeholder in the stored template (change the template in the same call, or first), 400 invalid_request when stale_after_seconds is out of range or queued_events is send on a disabled endpoint.

$ curl -X PATCH https://api.valtrix.com/v1/webhooks/whe_4k2m9p \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "tables": [{ "table": "customers_clean", "columns": [], "change_types": [] }] }'
{ "id": "whe_4k2m9p", "url": "https://yourapp.com/webhooks/valtrix", "description": null, "status": "active", "event_types": ["connection.broken", "table.row_changed"], "tables": [{ "table": "customers_clean", "columns": [], "change_types": [] }], "consecutive_failures": 0, "last_success_at": "2026-07-10T08:05:00Z", "last_failure_at": null, "created_at": "2026-07-10T08:00:00Z" }

Preview a payload template

POST/v1/webhooks/preview

Renders a payload template against a sample event and returns what an endpoint with that template would post, without registering or changing anything. The sample is the most recent change on a subscribed table when there is one, so the preview shows real values; otherwise a synthetic event with plausible values. Pass the event_types and tables the endpoint will have so placeholders are checked against the same subscriptions the endpoint will be saved with; with neither, the template is checked against every event type and every published table. Requires a platform-wide key with manage access.

Parameters
payload_templatestring, required

The template to render, a JSON document as a string with {{path}} placeholders.

event_typesarray

The event types the endpoint will receive. Omit for every type.

tablesarray

The table.row_changed subscriptions the endpoint will have, as { table, columns, change_types } (change_types any of insert, update, and delete). Omit for every published table.

Returns
eventobject

The sample event object the template was rendered against.

bodyobject or null

The rendered payload, exactly what would be posted; null when the delivery would be skipped.

skippedboolean

true when every placeholder resolved to nothing against the sample, so this event would not be sent.

samplestring

recent_change when the sample is a real change from a subscribed table, synthetic when it was made up.

placeholdersarray

Every path the template may reference for these subscriptions: id, type, occurred_at, data.<field> for each field the subscribed event types carry, and data.row.<column> and data.previous.<column> for each column of the subscribed tables.

400 invalid_payload_template when the template does not parse or names a path the subscriptions do not carry, 400 invalid_subscriptions when event_types or tables are not valid.

$ curl https://api.valtrix.com/v1/webhooks/preview \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "payload_template": "{ \"text\": \"{{data.table}} row {{data.record_id}} changed {{data.changed_columns}}\" }", "tables": [{ "table": "customers_clean", "columns": ["email"] }] }'
{
"event": { "id": "evt_01h9x...", "type": "table.row_changed", "occurred_at": "2026-07-10T08:05:00Z", "data": { "table": "customers_clean", "record_id": "rec_9d4f2c81a7b3e650d21c4f8a", "changed_columns": ["email"], "...": "..." } },
"body": { "text": "customers_clean row rec_9d4f2c81a7b3e650d21c4f8a changed [\"email\"]" },
"skipped": false,
"sample": "recent_change",
"placeholders": ["id", "type", "occurred_at", "data.table", "..."]
}

Rotate the signing secret

POST/v1/webhooks/:id/rotate-secret

Issues a new signing secret and returns it once. Deliveries are signed with the new secret from this moment, so update your server before calling it or accept a brief window of failed verifications. Requires a platform-wide key with manage access.

Parameters
idpath

The endpoint id.

Returns

The webhook endpoint object plus a secret field carrying the new signing secret, or 404 webhook_not_found.

$ curl -X POST https://api.valtrix.com/v1/webhooks/whe_4k2m9p/rotate-secret \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "id": "whe_4k2m9p", "url": "https://yourapp.com/webhooks/valtrix", "description": null, "status": "active", "event_types": ["connection.broken", "table.row_changed"], "tables": [{ "table": "customers_clean", "columns": [], "change_types": [] }], "consecutive_failures": 0, "last_success_at": "2026-07-10T08:05:00Z", "last_failure_at": null, "created_at": "2026-07-10T08:00:00Z", "secret": "whsec_0d3e5f7a9b1c2d4e6f8a0b1c2d3e4f5a" }

Send a test event

POST/v1/webhooks/:id/test

Posts an endpoint.test event to the endpoint right away, signed like any other delivery, and reports how your server answered. Use it to prove the receiver and the stored secret before real events flow. An endpoint with a payload_template instead posts the template rendered against the same sample event POST /v1/webhooks/preview uses, so the receiver sees a realistic message; the delivery is still logged as endpoint.test. The attempt is recorded in the delivery log and counts toward the endpoint's failure count. Requires a platform-wide key with manage access.

Parameters
idpath

The endpoint id.

Returns
deliveredboolean

true when your endpoint answered 2xx within 5 seconds, or when the delivery was skipped.

skippedboolean

true when the endpoint's payload_template resolved no placeholder against the sample event, so nothing was posted.

http_statusnumber or null

The status your endpoint returned, or null when the request never completed.

duration_msnumber or null

How long the request took.

errorstring or null

Why the delivery failed, or null when it was delivered.

200 with the outcome either way; the endpoint's own failure is reported in the body, not as an API error. 404 webhook_not_found when the endpoint does not exist.

$ curl -X POST https://api.valtrix.com/v1/webhooks/whe_4k2m9p/test \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "delivered": true, "skipped": false, "http_status": 200, "duration_ms": 143, "error": null }

List recent deliveries

GET/v1/webhooks/:id/deliveries

The endpoint's delivery log, newest first, kept for 14 days. Page with limit and offset. Requires a platform-wide key with manage access.

Parameters
idpath

The endpoint id.

limitnumber

Deliveries per page, 1 to 200. Defaults to 50.

offsetnumber

How many deliveries to skip. Defaults to 0.

Returns
deliveriesarray

Delivery objects, newest first.

totalnumber

How many deliveries the log holds for this endpoint.

404 webhook_not_found when the endpoint does not exist.

$ curl "https://api.valtrix.com/v1/webhooks/whe_4k2m9p/deliveries?limit=20" \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"deliveries": [{ "id": "whd_9s2ph1", "event_id": "evt_01h9x...", "event_type": "table.row_changed", "status": "failed", "http_status": 500, "error": "Endpoint responded 500", "request_body": "{...}", "response_body": "internal error", "duration_ms": 212, "created_at": "2026-07-10T08:05:00Z" }],
"total": 1
}

List queued events

GET/v1/webhooks/:id/events

The endpoint's event queue, newest first: what is waiting to be delivered, what was delivered, what died after exhausting its retries, and what was discarded, by you or by the endpoint's stale limit. Filter with status to see just the dead or discarded ones before a replay. Page with limit and offset. Requires a platform-wide key with manage access.

Parameters
idpath

The endpoint id.

statusstring

pending, delivered, dead, or discarded. Omit for every event.

limitnumber

Events per page, 1 to 200. Defaults to 50.

offsetnumber

How many events to skip. Defaults to 0.

Returns
eventsarray

Queued event objects, newest first.

totalnumber

How many events match, across pages.

404 webhook_not_found when the endpoint does not exist, 400 invalid_status for a status outside the four.

$ curl "https://api.valtrix.com/v1/webhooks/whe_4k2m9p/events?status=dead" \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{
"events": [{ "id": "evt_01h9x...", "type": "table.row_changed", "status": "dead", "attempts": 19, "next_attempt_at": null, "delivered_at": null, "created_at": "2026-07-10T08:05:00Z", "data": { "table": "customers_clean", "record_id": "rec_9d4f2c81a7b3e650d21c4f8a", "change_type": "upsert" } }],
"total": 1
}

Replay dead or discarded events

POST/v1/webhooks/:id/replay

Re-queues events that died after exhausting their retries or were discarded, so they are delivered again from the next tick with a fresh attempt budget. Send event_ids to replay specific ones, or an empty body to replay every dead and discarded event on the endpoint. Delivered and pending events are never touched. Requires a platform-wide key with manage access.

Parameters
idpath

The endpoint id.

event_idsarray

The dead or discarded events to replay, by id. Omit to replay all of them.

Returns
replayednumber

How many events were re-queued.

404 webhook_not_found when the endpoint does not exist, 400 invalid_request when event_ids is sent empty.

$ curl -X POST https://api.valtrix.com/v1/webhooks/whe_4k2m9p/replay \
-H "Authorization: Bearer $VALTRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
{ "replayed": 3 }

Delete a webhook endpoint

DELETE/v1/webhooks/:id

Removes the endpoint. Queued events are dropped and nothing more is sent to it; its delivery log stays readable in the console until it expires. Deleting again returns 404, so treat webhook_not_found as already done when retrying. Requires a platform-wide key with manage access.

Parameters
idpath

The endpoint id.

Returns
deletedboolean

Always true.

404 webhook_not_found when no endpoint with that id exists.

$ curl -X DELETE https://api.valtrix.com/v1/webhooks/whe_4k2m9p \
-H "Authorization: Bearer $VALTRIX_API_KEY"
{ "deleted": true }

Receive events

POST(your endpoint URL)

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

Parameters
Valtrix-Signatureheader

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

Valtrix-Event-Idheader

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

Returns

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

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