> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Amazon DynamoDB

> Client-mode REST API connector for Amazon DynamoDB: write device state with PutItem, update last-seen with UpdateItem, and query a device's history from MQTT over SigV4-signed JSON

Technical Preview

## Overview

A client-mode connector for the [Amazon DynamoDB API](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/) that keeps per-device state and history in a DynamoDB table straight from MQTT. DynamoDB uses the AWS JSON 1.0 protocol: every operation is `POST /` with `Content-Type: application/x-amz-json-1.0` and an `X-Amz-Target` header naming the operation.

| Connector event | X-Amz-Target | Notes |
| - | - | - |
| `PutItem` | `DynamoDB_20120810.PutItem` | Write or replace a device state item. [API\_PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) |
| `UpdateItem` | `DynamoDB_20120810.UpdateItem` | Set `lastSeen` or other attributes with an `UpdateExpression`. [API\_UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) |
| `QueryByDevice` | `DynamoDB_20120810.Query` | Items for one partition key (for example `deviceId`), newest first. [API\_Query](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) |
| `GetItem` | `DynamoDB_20120810.GetItem` | Read one item by primary key. |
| `BatchWriteItem` | `DynamoDB_20120810.BatchWriteItem` | Up to 25 puts or deletes per call. |
| `ListTables` | `DynamoDB_20120810.ListTables` | Credential smoke. |
| `DescribeTable` / `CreateTable` | `…DescribeTable`, `…CreateTable` | Table status, key schema, and one-time setup. |

Default `BASE_ADDRESS` is `https://dynamodb.eu-west-1.amazonaws.com`; change the region to match your table. Topics use `data/amazon-dynamodb/<object>/<verb>`, and replies arrive on `<topic>/response`.

## Auth

DynamoDB requires [AWS Signature Version 4](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv4.html) with service name `dynamodb`. It is not a Bearer token. The connector does not sign requests itself; an upstream signer (a Python action, edge service, or SDK) signs each request and publishes the signed headers with the body:

* `authorization`: the `AWS4-HMAC-SHA256 Credential=…, SignedHeaders=…, Signature=…` value
* `x_amz_date`: the `X-Amz-Date` timestamp used in the signature
* `x_amz_content_sha256`: hex SHA-256 of the body
* `body`: the DynamoDB request JSON. Only this field is sent to AWS.

Keep `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_REGION` in Hub secrets for the signer, and never commit keys. For temporary STS credentials, add an `x-amz-security-token` header to the events.

> [!WARNING]
>
> **The signature covers the exact body bytes.** Sign compact JSON (no spaces) and make sure the connector sends the same bytes. If they differ, AWS returns `400 InvalidSignatureException`. This has not been verified on the broker runtime yet.

The IAM policy needs `dynamodb:PutItem`, `UpdateItem`, `Query`, `GetItem`, `BatchWriteItem`, `ListTables` and `DescribeTable` on the table, plus `CreateTable` if the connector creates it.

## Basic Syntax

```lot wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
DEFINE ROUTE AmazonDynamodbRestApi WITH TYPE REST_API
    ADD REST_API_CONFIG
        WITH BASE_ADDRESS "https://dynamodb.eu-west-1.amazonaws.com"
        WITH ENABLE_CLIENT "true"
        WITH USE_SSL "true"
        WITH REQUEST_TIMEOUT '60'
        WITH DEFAULT_CONTENT_TYPE "application/x-amz-json-1.0"
        # Smoke: SigV4 POST / X-Amz-Target DynamoDB_20120810.ListTables body {} → 200 {"TableNames":[...]}
        # Errors come back as HTTP 400 JSON {"__type":"...#ValidationException","message":"..."}
    ADD EVENT PutItem
        WITH SOURCE_TOPIC "data/amazon-dynamodb/item/put"
        WITH DESTINATION_TOPIC "data/amazon-dynamodb/item/put/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/x-amz-json-1.0"
            ADD "X-Amz-Target" "DynamoDB_20120810.PutItem"
            ADD "Authorization" "{value.json.authorization}"
            ADD "x-amz-date" "{value.json.x_amz_date}"
            ADD "x-amz-content-sha256" "{value.json.x_amz_content_sha256}"
        WITH BODY "{value.json.body}"
    ADD EVENT UpdateItem
        WITH SOURCE_TOPIC "data/amazon-dynamodb/item/update"
        WITH DESTINATION_TOPIC "data/amazon-dynamodb/item/update/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/x-amz-json-1.0"
            ADD "X-Amz-Target" "DynamoDB_20120810.UpdateItem"
            ADD "Authorization" "{value.json.authorization}"
            ADD "x-amz-date" "{value.json.x_amz_date}"
            ADD "x-amz-content-sha256" "{value.json.x_amz_content_sha256}"
        WITH BODY "{value.json.body}"
    ADD EVENT QueryByDevice
        WITH SOURCE_TOPIC "data/amazon-dynamodb/items/query"
        WITH DESTINATION_TOPIC "data/amazon-dynamodb/items/query/response"
        WITH METHOD "POST"
        WITH ENDPOINT "/"
        WITH HEADERS
            ADD "Accept" "application/json"
            ADD "Content-Type" "application/x-amz-json-1.0"
            ADD "X-Amz-Target" "DynamoDB_20120810.Query"
            ADD "Authorization" "{value.json.authorization}"
            ADD "x-amz-date" "{value.json.x_amz_date}"
            ADD "x-amz-content-sha256" "{value.json.x_amz_content_sha256}"
        WITH BODY "{value.json.body}"
```

The full template also includes `GetItem`, `BatchWriteItem`, `ListTables`, `DescribeTable` and `CreateTable`, which use the same headers with their own `X-Amz-Target`.

## Payload examples

DynamoDB values use attribute-value JSON: strings are `{"S": "…"}`, and numbers are `{"N": "…"}` written as strings.

**Write device state (topic `data/amazon-dynamodb/item/put`):**

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "authorization": "AWS4-HMAC-SHA256 Credential=AKIA…/20261004/eu-west-1/dynamodb/aws4_request, SignedHeaders=…, Signature=…",
  "x_amz_date": "20261004T220500Z",
  "x_amz_content_sha256": "<hex sha256 of body>",
  "body": {
    "TableName": "cf_device_state",
    "Item": {
      "deviceId": {"S": "press-01"},
      "ts": {"N": "1759615200"},
      "temp": {"N": "21.5"},
      "state": {"S": "RUN"}
    }
  }
}
```

**Update last-seen (topic `data/amazon-dynamodb/item/update`), body only:**

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "TableName": "cf_device_state",
  "Key": {"deviceId": {"S": "press-01"}, "ts": {"N": "1759615200"}},
  "UpdateExpression": "SET lastSeen = :t, #s = :s",
  "ExpressionAttributeNames": {"#s": "state"},
  "ExpressionAttributeValues": {":t": {"S": "2026-10-04T22:05:00Z"}, ":s": {"S": "IDLE"}},
  "ReturnValues": "ALL_NEW"
}
```

**Query a device, newest first (topic `data/amazon-dynamodb/items/query`), body only:**

```json theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "TableName": "cf_device_state",
  "KeyConditionExpression": "deviceId = :d",
  "ExpressionAttributeValues": {":d": {"S": "press-01"}},
  "ScanIndexForward": false,
  "Limit": 10
}
```

> [!TIP]
>
> `state`, `name`, `status` and many other common words are DynamoDB reserved words. Alias them with `ExpressionAttributeNames` (`#s`) in update and query expressions.

## Errors and limits

* Errors are `HTTP 400` JSON with `__type` and `message`, for example `ValidationException`, `ResourceNotFoundException`, `ConditionalCheckFailedException`, or `ProvisionedThroughputExceededException` (back off and retry).
* Items are limited to 400 KB. `BatchWriteItem` accepts up to 25 requests (16 MB) and may return `UnprocessedItems` with `HTTP 200`; resend those.
* `Query` returns at most 1 MB per call; page with `LastEvaluatedKey` → `ExclusiveStartKey`.
* Scan, deletes, PartiQL, transactions and DynamoDB Streams (a separate `streams.dynamodb` endpoint) are not included.

## Testing

**Status: blocked on credentials.** The factory box has no AWS keys, so the connector has not been smoke-tested against AWS. Wire format was checked against a local [moto](https://github.com/getmoto/moto) 5.2.3 DynamoDB emulator, which does not verify signatures: SigV4-signed requests for all eight events returned `200` (CreateTable, ListTables, DescribeTable, PutItem, UpdateItem with `ALL_NEW`, GetItem read-back, BatchWriteItem with no unprocessed items, and Query newest first). Against `dynamodb.eu-west-1.amazonaws.com`, an unsigned `ListTables` returned `400 MissingAuthenticationTokenException` and a dummy-key signed one returned `400 UnrecognizedClientException`, as expected.

To clear the gate, sign `ListTables` with real lab keys (expect `200`), then `PutItem` and `Query` on a lab table (expect `200`).

## Related

* [REST API Connectors](/latest/lot-language/routes/rest-api-routes)
* [REST API Apps](/latest/lot-language/routes/rest-api-apps/index)
* [Amazon S3](/latest/lot-language/routes/rest-api-apps/amazon-s3)
* [Amazon CloudWatch](/latest/lot-language/routes/rest-api-apps/amazon-cloudwatch)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.