Pushing Data via the REST API
Omnismith exposes a REST API so scripts, agents, and automated pipelines can create and update entities without touching the UI.
Authentication
Section titled “Authentication”Generate a Personal Access Token in your account settings and pass it as a Bearer token in every request:
Authorization: Bearer omni_your_token_hereUpdate Entity Attributes
Section titled “Update Entity Attributes”PATCH https://api.omnismith.io/v1/entities/{id}Content-Type: application/jsonAuthorization: Bearer omni_your_token_here
{ "attributes": { "hostname": "prod-web-01", "status": "active" }}The attributes object maps attribute UUIDs or human-readable slugs to values:
| Value Shape | Example | Description |
|---|---|---|
| Scalar | "prod-web-01", 42, true | Updates the attribute to the new scalar value |
null | null | Clears/unsets the attribute value |
| Backfill Object | {"value": "active", "updated_at": "2026-02-23T10:00:00Z"} | Records value with explicit historical timestamp |
| Increment Object | {"op": "increment", "value": 1} | Adds to the stored number (number attributes and metrics); negative subtracts, never-set counts as 0 |
Keys may be slugs or UUIDs, mixed freely, but each attribute may appear only once per request and must belong to the entity’s template. Numbers and booleans are accepted as JSON values and serialized for you.
Use the increment object for counters — attendee counts, retries, stock levels — instead of reading the number and writing it back: the increment is applied to the stored value, so two writers bumping the same counter at the same time both land. It is accepted on update, replace, batch writes and action values, not on create (there is nothing to increment yet), and cannot be combined with updated_at.
Backfilling Historical Data
Section titled “Backfilling Historical Data”The optional updated_at timestamp in backfill objects lets you record a dimension value at a specific point in the past. This is useful when:
- A remote system buffered state changes during network downtime
- You are migrating historical audit data from another system
- You need to record an observation at an exact historical timestamp
When updated_at is supplied, Omnismith records the value at that timestamp in the entity audit trail. The History tab will reflect the correct timeline.
updated_at must be an RFC 3339 timestamp with an explicit offset — 2026-02-23T10:00:00Z or 2026-02-23T12:00:00+02:00. A timestamp without an offset (2026-02-23 10:00:00) is rejected with 422 rather than silently interpreted as UTC. The same rule applies to POST /v1/entities/{id}/metrics.
# Record historical dimension change with explicit timestampSERVER_ID="entity-uuid-here"TOKEN="omni_your_token_here"
curl -s -X PATCH "https://api.omnismith.io/v1/entities/$SERVER_ID" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "attributes": { "status": { "value": "maintenance", "updated_at": "2026-02-23T10:00:00Z" } } }'Replacing an Entity
Section titled “Replacing an Entity”PUT /v1/entities/{id} makes the entity look exactly like the map you send: every non-metric attribute of the template that is absent from the map is cleared (and the clear is recorded in history). An explicit "attributes": {} clears them all. Metric attributes cannot be replaced — send them with PATCH or the metrics endpoint. Prefer PATCH unless you really mean “overwrite the whole record”.
Batch Writes
Section titled “Batch Writes”POST /v1/entities/batch applies up to 100 mixed operations in order. Every operation carries exactly the fields for its op — id is always the entity id, template always the template slug or UUID:
{ "operations": [ { "op": "create", "template": "server_node", "attributes": { "hostname": "edge-fra-01" } }, { "op": "update", "id": "018b2f1b-8c1a-75b3-8000-7f0000010000", "attributes": { "status": "maintenance" } }, { "op": "replace", "id": "018b2f1b-8c1a-75b3-8000-7f0000010001", "attributes": { "hostname": "edge-fra-02" } }, { "op": "delete", "id": "018b2f1b-8c1a-75b3-8000-7f0000010002" } ]}replace behaves like PUT: attributes you leave out are cleared. Pass "atomic": true to run the whole batch in one transaction.
Two more operations identify records by your own ids: create accepts an optional external_key, and upsert (template, external_key, attributes) creates or updates the record holding the key — see below.
Upsert by External Key
Section titled “Upsert by External Key”If the system you integrate has its own ids for the records — customer ids, hostnames, order numbers — store them as each record’s External Key and write with upsert instead of searching first and then creating:
PUT https://api.omnismith.io/v1/entities/template/server/by-keyContent-Type: application/jsonAuthorization: Bearer omni_your_token_here
{ "external_key": "api-prod-01", "attributes": { "ip_address": "10.0.10.21", "cpu_usage": 41.2 }}The first call creates the record with the key (201, {"id": "...", "created": true}); every later call with the same key updates that record (200, "created": false) as a partial update. Metric values are appended on either branch. Sending the same key twice — a retry, a duplicated event, two workers racing — never creates two records. Read a record back with GET /v1/entities/template/{template}/by-key?key=api-prod-01.
Access Token or Inbound Endpoint?
Section titled “Access Token or Inbound Endpoint?”| Use | When |
|---|---|
| Access token + REST API | Your own code or agent writes to Omnismith: it can hold a token, choose the request shape, and call several endpoints (search, upsert, metrics, actions). |
| Inbound Endpoint | A third-party system posts its own fixed payload and signs it its own way — Stripe, GitHub, Shopify, form tools, Zapier, n8n, or a device that should not hold an API token. The endpoint verifies the signature, maps the payload to attributes and upserts by external key, with a delivery log and replay. |
An inbound endpoint only writes into its own template, so a leaked endpoint secret exposes far less than a leaked access token.
Aggregations
Section titled “Aggregations”POST /v1/entities/aggregate/{template} answers “how many”, “how much” and “broken down by” questions in one call, computed server-side. It takes the same filter_groups as search, up to three group_by attributes (list, reference, string, number, boolean or date), and one to ten aggregations:
{ "filter_groups": [[{ "field": "status", "operator": "eq", "value": "018b2f1b-8c1a-75b3-8000-7f0000010020" }]], "group_by": ["tier"], "aggregations": [{ "op": "count" }, { "op": "sum", "field": "mrr" }]}{ "data": [ { "key": [{ "field": "tier", "value": "018b2f1b-8c1a-75b3-8000-7f0000010031", "custom_value": "Team" }], "aggregates": [{ "op": "count", "field": null, "value": 12 }, { "op": "sum", "field": "mrr", "value": 3400.5 }] } ], "limit": 50, "truncated": false}count takes no field; sum and avg need a number attribute; min and max accept number, date and datetime attributes. Each group’s key and aggregates mirror the request order; custom_value carries the list label or the referenced record’s display value, and a null key groups the records that have no value. Groups come back ordered by the first aggregation descending, capped by limit (1-100, default 50) with truncated: true when more exist. Metric attributes are time series and are reduced with the chart endpoint instead.
Error Semantics
Section titled “Error Semantics”| Status | Meaning | What to do |
|---|---|---|
400 | The payload shape is wrong: missing attributes, a list instead of an object, an unknown field, a wrong type. | Fix the request structure; detail says what was wrong. |
422 | The shape is fine but the content is not: unknown attribute, attribute not on the template, the same attribute twice, a bad updated_at, or a value that fails its attribute type. | Read errors — it names the exact field, e.g. attributes.status — and fix that entry. |
404 | No such entity or template. | Check the id or slug. |
409 | The external_key is already held by another live record of the template, or a concurrent upsert took it. | Pick another key, or send the upsert again. |