Skip to content

Send your first sample

Data reaches OnTwins through an agent — a program you run near your equipment that reads values and pushes them in. OnTwins never reaches out to pull them.

In this step you play the part of that agent yourself, with two requests. Nothing is modelled yet, and that is fine: data can arrive before anything has been described.

Switch to Data Hub using the app switcher.

An agent needs an identity before it can send anything, and only a person can create one — an agent cannot register itself.

Register a new agent with a name and where it runs. In return you get a credential, in the form <id>.<secret>.

Copy it now. It is shown once and never again; OnTwins keeps only a hash of it. If you lose it, rotate the credential to get a new one.

For the two requests below, keep it in a variable:

Terminal window
ONTWINS=https://your-ontwins-address
CRED=svc_a1b2c3.sk_live_your_secret_here

An agent announces its data before sending it. The declaration names a dataset — one stream of related signals — and lists the signals it carries.

Terminal window
curl -X POST "$ONTWINS/api/v1/agent/datasets" \
-H "Authorization: Bearer $CRED" \
-H 'Content-Type: application/json' \
-d '{
"key": "room-204",
"name": "Room 204",
"kind": "stream",
"fields": [
{ "path": "temp", "schema": { "kind": "primitive", "type": "double" } }
]
}'

The response carries the dataset’s id — copy it, the next request needs it.

A few things are worth knowing about what you just sent.

  • key is yours, id is ours. You choose a key that means something in your system and keep using it; OnTwins assigns the id. Declaring the same key again updates the dataset instead of creating a second one, and anything a person has configured on it is preserved.
  • Declaring is not a one-time setup step. A real agent declares on every startup, which is how OnTwins always knows what the agent currently sends.
  • You may omit the unit. You can add "sourceUnit": "degC" to say what unit you are sending in, but you do not have to. Values are stored either way, and whoever configures the dataset later decides what it means. That is step five.
Terminal window
curl -X POST "$ONTWINS/api/v1/agent/ingest" \
-H "Authorization: Bearer $CRED" \
-H 'Content-Type: application/json' \
-d '{
"datasetId": "ds_paste_the_id_here",
"samples": [
{ "signal": "temp", "at": "2026-10-01T09:00:00Z", "value": 21.4 }
]
}'

Use a recent timestamp. The response reports what happened to every sample you sent:

{ "accepted": 1, "deadbanded": 0, "dropped": [], "fanout": 0 }
Field Meaning
accepted Stored. This is the number you want above zero.
fanout How many reached an entity’s state. Zero is correct right now — nothing is wired yet.
deadbanded Judged not to have moved enough to be worth storing.
dropped Rejected, each with a reason. Samples never disappear silently.

Note the timestamp travels with the sample rather than being the moment the request arrived. A reading is tied to when it was observed, so a delayed or retried batch still lands at the right point in time. Send the same signal, dataset and timestamp twice and the second is dropped as a duplicate — retrying is always safe.

Back in Data Hub, open the dataset you just declared. The sample you sent is there, with its timestamp.

You will also see the dataset flagged as incomplete — it has no confirmed unit yet. That is expected: you sent a number, but nothing has said what it measures. Leaving it unresolved is a deliberate feature of the contract, not an error. Collecting first and interpreting later means field work never waits on modelling decisions.


The value is stored but connected to nothing. Next, model your first entity to give it something to belong to.