Skip to main content
See the Authentication API reference for the interactive playground.

Principal types

DynamoSQL recognizes two principal types:
TypeDescriptionHow to authenticate
Portal userHuman user who logged in via the DynamoSQL portalPasskey or password + TOTP; portal issues a session
API clientMachine-to-machine service account created in the portalPOST /v1/auth/token with clientId + clientSecret
The /v1/query endpoint is intended for API clients. Portal users typically interact through the portal’s SQL editor.

Getting a token

API clients authenticate by posting their credentials to the token endpoint:
curl -X POST https://api.dynamosql.com/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "YOUR_CLIENT_ID",
    "clientSecret": "YOUR_CLIENT_SECRET"
  }'
The response contains:
{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJSUzI1NiIs...",
    "refreshToken": "eyJjdHkiOiJKV1QiLCJl...",
    "expiresIn": 3600,
    "tokenType": "Bearer"
  }
}
  • accessToken — JWT valid for one hour. Pass as Authorization: Bearer <accessToken>.
  • refreshToken — use to obtain a new access token without re-sending credentials.
  • expiresIn — token lifetime in seconds.

Scopes

Scopes control which endpoints an API client can access. They are assigned when the client is created in the portal.
ScopeRequired for
queryPOST /v1/query (both execute and plan modes)
schemas:readGET /v1/schemas, GET /v1/schemas/{name}
schemas:writePOST /v1/schemas, PATCH /v1/schemas/{name}, DELETE /v1/schemas/{name}, POST /v1/schemas/{name}/refresh-metadata, POST /v1/schemas/{name}/validate-role
usage:readGET /v1/usage/summary
New API clients receive query and schemas:read by default.

Passing the bearer token

Include the token in the Authorization header on every request:
Authorization: Bearer YOUR_ACCESS_TOKEN

Token refresh

When your access token expires, exchange the refresh token for a new access token instead of re-authenticating with credentials:
curl -X POST https://api.dynamosql.com/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refreshToken": "YOUR_REFRESH_TOKEN"
  }'
The refresh response contains a new accessToken and expiresIn. No new refresh token is issued.

Token caching example

import requests
import time

class TokenCache:
    def __init__(self, client_id, client_secret):
        self.client_id = client_id
        self.client_secret = client_secret
        self._access_token = None
        self._refresh_token = None
        self._expires_at = 0

    def get(self):
        if time.time() < self._expires_at - 30:  # 30s buffer
            return self._access_token

        # Try refresh first if we have a refresh token
        if self._refresh_token:
            try:
                return self._refresh()
            except Exception:
                pass  # Fall through to full auth

        return self._authenticate()

    def _authenticate(self):
        resp = requests.post(
            "https://api.dynamosql.com/v1/auth/token",
            json={
                "clientId": self.client_id,
                "clientSecret": self.client_secret,
            },
        )
        resp.raise_for_status()
        data = resp.json()["data"]
        self._access_token = data["accessToken"]
        self._refresh_token = data.get("refreshToken")
        self._expires_at = time.time() + data["expiresIn"]
        return self._access_token

    def _refresh(self):
        resp = requests.post(
            "https://api.dynamosql.com/v1/auth/refresh",
            json={"refreshToken": self._refresh_token},
        )
        resp.raise_for_status()
        data = resp.json()["data"]
        self._access_token = data["accessToken"]
        self._expires_at = time.time() + data["expiresIn"]
        return self._access_token

Tenant scoping

Every JWT carries a tenantId claim. The server reads this claim and scopes all DynamoDB access to that tenant’s tables. You cannot query another tenant’s data. The tenantId field in the request body is optional. If provided, it must match the tenantId in the JWT — a mismatch returns 403.