Quick Start

Get from a board in your hand to its data in the dashboard in a few minutes, with nothing installed on your computer. No board yet? Send a message from the command line instead.

Working from Claude, Cursor, or VS Code?

tendrl-dev-mcp lets your AI assistant flash, provision, and upload code to the board for you. The steps below are the same flow, done by hand in the browser.

Prerequisites#

Step 1: Create an entity#

An entity is Contact's record of one device.

  1. Navigate to Entities in the sidebar
  2. Click Create Entity
  3. Fill in the details:

When you create the entity, Contact automatically issues an API key bound to it.

The Create Entity form with Name, Role, Service, Enabled, and Metadata fields Creating an entity from the dashboard.

Step 2: Copy the API key#

Contact shows the Connection Instructions dialog as soon as the entity is created. Reveal the API Key Secret with the eye icon, or use the copy button.

The Connection Instructions dialog showing the API Key ID and the masked API Key Secret Connection Instructions, shown once when the entity is created.

Caution

The secret is shown once. If you lose it, the provisioning form in Step 4 can Rotate it for you.

Step 3: Connect the board#

  1. Open the entity and switch to its Device tab
  2. Plug the board into USB and click Connect Device
  3. Pick the board in the browser's port picker

The tab reads the board's platform, free memory, and any SDK already installed, and recommends an install tier. See Connecting a Board.

A brand-new ESP32 usually isn't running MicroPython yet. Flash it from the same tab: Flashing MicroPython takes about a minute, still in the browser. A Pico W takes MicroPython as a UF2 drag-and-drop, and OpenMV boards are flashed with the OpenMV IDE.

Step 4: Provision#

In the provisioning form:

  1. Paste the entity's API key from Step 2
  2. Enter your Wi-Fi SSID and password. Most boards are 2.4 GHz only. The password goes down the USB cable; Contact never stores it.
  3. On ESP32, keep Tendrl firmware, the low-RAM install. If the board needs it, provisioning reflashes and asks you to unplug and replug once.
  4. Click Provision

Provisioning installs the SDK, writes /config.json, writes a starter main.py if the board has none, and resets the board. Open the Terminal to watch it join Wi-Fi and connect. Full details: Provisioning a Device.

Step 5: Send data#

The starter main.py connects the client and idles. Make it publish something: open main.py in the Device tab's Files view (/flash/main.py on OpenMV), replace it with this, save with ⌘S / Ctrl-S, and click ▶ Run.

python

from tendrl import Client

client = Client(debug=True)
client.start()

while True:
    gc.collect()
    client.publish({"free_heap": gc.mem_free()}, tags=["sensor"])
    time.sleep(10)

Swap gc.mem_free() for a real sensor reading when you have one wired up. tags is what routes a message to connectors and Strand workflows.

Step 6: See it in the dashboard#

  1. Navigate to Messages in the sidebar
  2. Your board's messages appear every ten seconds
  3. Click one to view its details

The entity also shows as online. If nothing arrives, Device troubleshooting covers the usual causes: Wi-Fi on a 5 GHz-only network, a wrong key, or a board out of memory.

No board? Send from the command line#

Do Steps 1 and 2 (the Uses MQTT switch doesn't matter here), then publish with the entity's key:

bash

curl -X POST https://app.tendrl.com/api/entities/message \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "msg_type": "publish",
    "data": {
      "temperature": 23.5,
      "humidity": 65
    },
    "context": {"tags": ["sensor"]}
  }'

A success returns 200 with the stored message id:

json

{"code": 200, "content": "01J8XR4M2K9P7QW3FDNVBZ0HYT"}

msg_type is required (publish, heartbeat, state_new, or state_update). Then check Messages as in Step 6.

If it didn't work#

Match the reason in the response body against this table before anything else — the status code alone doesn't distinguish a wrong key from a wrong kind of key.

Status reason What it means
401 Access Denied The key is wrong or revoked. The Bearer prefix is optional; a bare token is accepted.
401 Only entities can write messages You used an account-level API key. Messages must be sent with the key issued to the entity — the one under its Connection Instructions, not one from Access Control → API Keys.
403 Access Denied (in a message field, not reason) The entity's role lacks entity:WriteMessages. DefaultEntity has it; a custom role may not.
400 Message type is required msg_type is missing from the body.
400 Invalid message format The JSON is malformed, or data isn't an object.
413 Message size exceeds the 5KB per message limit. Trim the payload; the cap is per message and applies on every plan.
429 Monthly data limit exceeded. Your <plan> plan includes … You've hit the plan's data cap for the billing period. The rest of the sentence names what the plans above include, with price.
Tip

The most common first-run failure is the third row. Both key types are valid and both look identical, but only the entity's own key can publish on its behalf.

Next Steps#