SDK for TESAIoT Dev Kit
API reference & tutorials (ModusToolbox)
Loading...
Searching...
No Matches
C5 — TESAIoT cloud: HTTPS REST, the Device API Key, and mTLS

What this chapter is for

The board can reach TESAIoT two ways. C3 covers MQTT; this covers the other.

Four things to know:

  1. HTTPS is a one-shot request — no session, no task, no queue. Nothing like MQTT.
  2. There are two kinds of key, in two different config fields. Putting one in the wrong field produces a 401 that reads the same and means something else.
  3. The payload must carry a data object, or the API answers 400 VALIDATION_ERROR.
  4. The trust anchor is pinned in firmware. When the server's certificate authority changes, the board hangs up before it says anything — and that has already happened once.

Which one to use

MQTT (C3) HTTPS (here)
Shape hold a connection, publish repeatedly one request, then closed
Authentication certificate in the chip (mTLS) Device API Key
Can receive commands yes, by subscribing no
Memory cost holds a TLS session throughout all of it comes back
Suits frequent sends, command handling occasional sends, mostly-asleep devices

Do not run both at once. Measured on a Dev Kit 2026-08-31: with MQTT connected the heap had 13,128 bytes free and malloc(32768) failed, so a second TLS session never reached its handshake. Without MQTT there were 71,712 bytes and it passed.

Two kinds of key, two fields

This is where the most time was lost, and the names do not help.

config field HTTP header sent key shape intended for
apisix_api_key apikey: tesa_ak_… the APISIX gateway
api_key whatever api_key_header says, X-API-KEY by default tesa_dak_… the platform backend

ipc_tesaiot_defs.h:40 labels apisix_api_key "RESTful API key (tesa_ak_...)", which invites exactly the wrong choice.

On api.tesaiot.dev, use api_key. Measured with four curl variants: sending only the apikey header produces the same answer as sending no header at all — AUTH_MISSING in both cases — so there is no APISIX in front of that host.

import tesaiot
tesaiot.config_set("api_host", "api.tesaiot.dev")
tesaiot.config_set("api_key", "tesa_dak_<device>_<hex>")
tesaiot.config_set("api_key_header", "X-API-KEY")

The Device API Key comes from the platform's Device Credentials page and is shown once.

The payload

{"device_id": "905f31fa-92cb-4555-a8ae-f68a65e142fb",
"data": {"temperature": 31.4, "humidity": 60}}

data must be an object. Flat values at the top level get:

400 {"code":"VALIDATION_ERROR","error":"data field is required and must be an object"}

On success:

200 {"success":true,"message":"Telemetry data stored successfully",
"metrics_stored":["temperature"],"telemetry_id":"…"}

The trust anchor, and how it broke

tesaiot_https.c sets three socket options and no more — the CA to trust, the SNI name, and the verification mode. The CA is TESAIOT_HTTPS_ROOT_CA from tesaiot_https_root_ca.h.

It used to pin Let's Encrypt E7. The endpoint moved to a certificate under YE2, and every request then died at:

[TLS-DIAG] err=-0x2700 vf=0x00000008 BADCERT_NOT_TRUSTED
[HTTPS] Connect failed: 0x082A000E

The board hung up before exchanging a byte of HTTP, which reads like a network fault and is in fact TLS working correctly.

It now pins ISRG Root YE, which issues YE2.

Why the root and not the intermediate. Intermediates rotate. Pinning one is what caused the outage above. Root YE runs to 2032-09-02.

Why not ISRG Root X1. It is RSA-4096, and verifying a certificate that size exhausts the heap on this part. Every certificate in the chain the server sends is ECDSA P-384, so that path is never taken.

How to check when you suspect the CA changed:

openssl s_client -connect api.tesaiot.dev:443 -servername api.tesaiot.dev \
-showcerts </dev/null 2>/dev/null | openssl x509 -noout -issuer

If the issuer is not the one pinned, the header has to change and the firmware has to be rebuilt.

mTLS on the HTTPS path

When tls_mode is TESAIOT_MODE_MTLS, the HTTPS path calls mqtt_mtls_setup_optiga() to raise the OPTIGA identity before connecting. The name is misleading: nothing in that function is about MQTT. It reads the certificate out of the chip and hands it, with the key, to two globals in cy_tls.c that cy_tls_connect() consults on every connection.

It is declared weak and NULL-checked, so a build without that code still links and falls back to key authentication.

When it works:

[mTLS] device pair verified — using TESAIoT identity
[PSA-SE] Setting signing key OID to 0xE0F1
[mTLS] Certificate read: 730 bytes PEM
[TLS-DIAG] optiga_key=2147483616 cert_ready=1
[TLS-DIAG] mbedtls_pk_setup_opaque = 0
[TLS-DIAG] mbedtls_ssl_conf_own_cert = 0

It cannot yet authenticate anything. The server does not ask for a client certificate:

openssl s_client -connect api.tesaiot.dev:443 -servername api.tesaiot.dev </dev/null 2>&1 \
| grep -i 'client certificate'
# No client certificate CA names sent

With no CertificateRequest, TLS sends no certificate, however ready the board is. The API's 401 text — "or use mTLS certificate" — is boilerplate, not a statement that this host has the mode enabled. It has to be turned on server-side.

Traps

  • Read the two 401s. AUTH_MISSING means the server saw no authentication header at all; API_KEY_INVALID means it saw one and rejected the value. They point in different directions.
  • Do not leave MQTT connected and then call HTTPS. There is not enough heap.
  • api.tesaiot.com and api.tesaiot.dev are different machines with different CAs. .com is fronted by Amazon, .dev by Let's Encrypt. The firmware pins the .dev one.
  • Reproduce with curl from a workstation first. If curl gets 401 too, the board is not the problem.