MQTT API Key Management

Create, renew, and manage MQTT streaming credentials programmatically via the GraphQL Service API

Overview

To receive real-time sensor data via MQTT, you need an integration and an API key linked to it. This guide shows how to create and manage these credentials programmatically using GraphQL mutations.

Workflow Overview

%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#F6FAFA', 'primaryTextColor': '#143633', 'primaryBorderColor': '#143633', 'lineColor': '#143633', 'secondaryColor': '#C7FDE6', 'tertiaryColor': '#73F9C1', 'clusterBkg': '#ffffff', 'clusterBorder': '#143633', 'edgeLabelBackground': '#ffffff'}}}%%
flowchart TB
    A["fa:fa-sign-in-alt Step 1: Authenticate"] --> B["fa:fa-plug Step 2: Create Integration"]
    B --> C["fa:fa-key Step 3: Create API Key"]
    C --> D["fa:fa-user-shield Step 4: Assign MqttIntegrator Role"]
    D --> E["fa:fa-link Step 5: Link API Key to Integration"]
    E --> F["fa:fa-broadcast-tower Ready to connect via MQTT"]

    style A fill:#F6FAFA,stroke:#143633,stroke-width:2px,color:#143633
    style B fill:#F6FAFA,stroke:#143633,stroke-width:2px,color:#143633
    style C fill:#F6FAFA,stroke:#143633,stroke-width:2px,color:#143633
    style D fill:#F6FAFA,stroke:#143633,stroke-width:2px,color:#143633
    style E fill:#F6FAFA,stroke:#143633,stroke-width:2px,color:#143633
    style F fill:#73F9C1,stroke:#143633,stroke-width:2px,color:#143633

The setup involves five steps. Steps 2–5 only need to be done once per integration. After that, you only need to refresh the API key token before it expires.

Step 1: Authenticate

Log in with your user credentials to obtain a Bearer token for subsequent mutations.

mutation Login($email: String!, $organizationName: String!, $password: String!) {
  login(loginInput: {
    email: $email,
    organizationName: $organizationName,
    password: $password
  }) {
    accessToken
    expiresIn
    refreshExpiresIn
    refreshToken
    organizationId
  }
}
{
  "email": "your-email@example.com",
  "organizationName": "your-organization",
  "password": "your-password"
}

Use the returned accessToken in the Authorization header for all subsequent requests:

Authorization: Bearer eyJhbGciO...

See Service API Authentication for full details on token refresh and session management.

Step 2: Create MQTT Subscription Integration

Create an integration container. You typically need one integration per organization (or per logical data consumer).

mutation CreateMqttIntegration($description: String) {
  createMqttSubscriptionIntegration(object: {
    description: $description,
    mqttSubscriptionState: "active"
  }) {
    id
    description
    mqttSubscriptionState
    organizationId
    createdAt
    updatedAt
  }
}
{
  "description": "Production MQTT streaming"
}

Response

{
  "data": {
    "createMqttSubscriptionIntegration": {
      "id": "31efcae6-39ff-45f2-b732-2090e2ed54c4",
      "description": "Production MQTT streaming",
      "mqttSubscriptionState": "active",
      "organizationId": "2a749da0-5512-...",
      "createdAt": "2026-03-24T10:00:00Z",
      "updatedAt": "2026-03-24T10:00:00Z"
    }
  }
}

Save the id — this is your integration ID, needed in step 5 and in MQTT topic paths.

Step 3: Create API Key

Create an API key that will serve as MQTT credentials. The key name is for your reference (e.g., the customer or system it belongs to).

mutation CreateApiKey($name: String!, $expirationDays: Int) {
  createApiKey(createApiKeyInput: {
    name: $name
    expirationDays: $expirationDays
  }) {
    id
    name
    organizationId
    createdAt
    token {
      token
      issuedAt
      expiresAt
    }
  }
}
{
  "name": "Building Management System",
  "expirationDays": 365
}

Response

{
  "data": {
    "createApiKey": {
      "id": "c6f7b103-81df-4e73-8218-4f671056aee2",
      "name": "Building Management System",
      "organizationId": "2a749da0-5512-...",
      "createdAt": "2026-03-24T10:01:00Z",
      "token": {
        "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
        "issuedAt": "2026-03-24T10:01:00Z",
        "expiresAt": "2027-03-24T10:01:00Z"
      }
    }
  }
}

The returned values map to MQTT credentials:

API Key FieldMQTT Usage
idMQTT username and {api-key-id} in topic path
token.tokenMQTT password

Step 4: Assign MqttIntegrator Role

Grant the API key permission to access MQTT streams by assigning the MqttIntegrator role.

mutation AssignApiKeyRole($apiKeyId: uuid!) {
  assignApiKeyOrganizationRole(object: {
    roleName: "MqttIntegrator",
    apiKeyId: $apiKeyId
  }) {
    apiKeyId
    roleName
    organizationId
  }
}
{
  "apiKeyId": "c6f7b103-81df-4e73-8218-4f671056aee2"
}

Connect the API key to the MQTT subscription integration created in step 2.

mutation AssignApiKeyToMqttIntegration($mqttSubscriptionId: uuid!, $apiKeyId: uuid!) {
  assignMqttSubscriptionIntegrationApiKey(object: {
    mqttSubscriptionId: $mqttSubscriptionId,
    apiKeyId: $apiKeyId
  }) {
    mqttSubscriptionId
    apiKeyId
  }
}
{
  "mqttSubscriptionId": "31efcae6-39ff-45f2-b732-2090e2ed54c4",
  "apiKeyId": "c6f7b103-81df-4e73-8218-4f671056aee2"
}

After this step, the API key is fully configured. You can connect to the MQTT broker:

ParameterValue
Hosthaltian-iot-mqtt.eu.haltian.io
Port18883 (TLS required)
UsernameAPI Key ID (c6f7b103-81df-4e73-8218-4f671056aee2)
PasswordAPI Key Token (eyJhbGciO...)
Topichaltian-iot/events/{integration-id}/{api-key-id}/#

See Stream API Topics for full topic structure and Stream API Examples for connection code.

Optional: Filter MQTT Data by Keyword

Use keyword filtering when an MQTT consumer should receive events from only a specific group of devices, such as devices on one floor or devices used by one application.

Keyword filtering happens in Haltian IoT before an event is published to your API key’s MQTT stream. It does not change the MQTT topic or payload. Continue subscribing to the same topic:

haltian-iot/events/{integration-id}/{api-key-id}/#

How Matching Works

  • An API key with no assigned keywords receives events from all devices otherwise allowed by its access scope.
  • Assigning the first keyword to an API key enables keyword filtering for that key.
  • If an API key has multiple keywords, a device matches when it has at least one of them (OR logic).
  • Assigning a keyword to an API key does not assign that keyword to any devices. You must configure both assignments.
  • Keyword filtering applies to new events. MQTT does not replay events sent before a device matched the filter.

Step 1: Create the Keyword

Keywords are organization-wide text labels and do not have separate IDs. Skip this step if the keyword already exists.

mutation CreateKeyword {
  createKeyword(object: { keyword: "floor-2" }) {
    keyword
    createdAt
  }
}

Step 2: Assign the Keyword to Devices

Assign the keyword to every device whose events the MQTT consumer should receive:

mutation AssignDeviceKeyword {
  assignDeviceKeyword(object: {
    deviceId: "<device-uuid>"
    keyword: "floor-2"
  }) {
    deviceId
    keyword
  }
}

Repeat the mutation for other devices, or use assignDeviceKeywords to assign keywords in bulk.

Step 3: Assign the Keyword to the API Key

This assignment enables the filter. Use the API key ID that authenticates the MQTT client:

mutation AssignApiKeyKeyword {
  assignApiKeyKeyword(object: {
    apiKeyId: "<api-key-uuid>"
    keyword: "floor-2"
  }) {
    apiKeyId
    keyword
  }
}

The MQTT client now receives new events only from matching devices within its other access scopes. Test the filter by generating an event from one matching device and one device without the keyword.

Remove Keyword Filtering

Remove one keyword from an API key with deleteApiKeyKeyword:

mutation DeleteApiKeyKeyword {
  deleteApiKeyKeyword(where: {
    apiKeyId: { _eq: "<api-key-uuid>" }
    keyword: { _eq: "floor-2" }
  }) {
    affectedRows
  }
}

Check that affectedRows is 1. When you remove the final keyword from an API key, the key returns to receiving events from all devices otherwise allowed by its access scope.

To remove the keyword from a device without changing the API key filter:

mutation DeleteDeviceKeyword {
  deleteDeviceKeywords(where: {
    deviceId: { _eq: "<device-uuid>" }
    keyword: { _eq: "floor-2" }
  }) {
    affectedRows
  }
}

Step 6: Refresh API Key Token

API key tokens are valid for up to 365 days, or 90 days if you do not set expirationDays. Refresh before the returned expiresAt time to maintain uninterrupted MQTT access. The API key ID remains the same, only the token is regenerated.

mutation RefreshApiKey($apiKeyId: uuid!, $expirationDays: Int) {
  refreshApiKeyToken(refreshApiKeyTokenInput: {
    apikeyId: $apiKeyId
    expirationDays: $expirationDays
  }) {
    id
    organizationId
    token {
      token
      issuedAt
      expiresAt
    }
  }
}
{
  "apiKeyId": "c6f7b103-81df-4e73-8218-4f671056aee2",
  "expirationDays": 365
}

Response

{
  "data": {
    "refreshApiKeyToken": {
      "id": "c6f7b103-81df-4e73-8218-4f671056aee2",
      "organizationId": "2a749da0-5512-...",
      "token": {
        "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.new-token...",
        "issuedAt": "2026-03-24T12:00:00Z",
        "expiresAt": "2027-03-24T12:00:00Z"
      }
    }
  }
}

Listing and Deleting Keys

List All API Keys

Check existing keys and their expiration status:

query ListApiKeys {
  apiKeys {
    id
    name
    createdAt
    updatedAt
    organization {
      id
      name
    }
    token {
      issuedAt
      expiresAt
      revokedAt
    }
    organizationRoles {
      roleName
      organizationId
    }
  }
}

List MQTT Integrations

View integrations and their attached API keys:

query ListMqttIntegrations {
  mqttSubscriptionIntegration {
    id
    description
    mqttSubscriptionState
    organizationId
    createdAt
    updatedAt
    apiKeys {
      apiKeyId
    }
  }
}

Delete API Key

Permanently revoke an API key. The MQTT client using this key will be disconnected immediately.

mutation DeleteApiKey($apiKeyId: uuid!) {
  deleteApiKey(deleteApiKeyInput: {
    apikeyId: $apiKeyId
  }) {
    message
  }
}

Complete Python Example

This script demonstrates the full setup workflow — from login to a working MQTT connection.

import requests
import json
import paho.mqtt.client as mqtt
import ssl

GRAPHQL_ENDPOINT = "https://haltian-iot-api.eu.haltian.io/v1/graphql"
MQTT_HOST = "haltian-iot-mqtt.eu.haltian.io"
MQTT_PORT = 18883


def graphql_request(query, variables=None, token=None):
    """Execute a GraphQL request."""
    headers = {"Content-Type": "application/json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    response = requests.post(
        GRAPHQL_ENDPOINT,
        json={"query": query, "variables": variables or {}},
        headers=headers,
    )
    response.raise_for_status()
    result = response.json()
    if "errors" in result:
        raise Exception(f"GraphQL error: {result['errors']}")
    return result["data"]


# --- Step 1: Authenticate ---
login_data = graphql_request(
    """
    mutation Login($email: String!, $organizationName: String!, $password: String!) {
      login(loginInput: {email: $email, organizationName: $organizationName, password: $password}) {
        accessToken
        organizationId
      }
    }
    """,
    {
        "email": "your-email@example.com",
        "organizationName": "your-organization",
        "password": "your-password",
    },
)
token = login_data["login"]["accessToken"]
print(f"Authenticated. Organization: {login_data['login']['organizationId']}")

# --- Step 2: Create MQTT Integration ---
integration = graphql_request(
    """
    mutation CreateMqttIntegration($description: String) {
      createMqttSubscriptionIntegration(object: {
        description: $description,
        mqttSubscriptionState: "active"
      }) { id description }
    }
    """,
    {"description": "My MQTT Integration"},
    token,
)
integration_id = integration["createMqttSubscriptionIntegration"]["id"]
print(f"Integration created: {integration_id}")

# --- Step 3: Create API Key ---
api_key = graphql_request(
    """
    mutation CreateApiKey($name: String!, $expirationDays: Int!) {
      createApiKey(createApiKeyInput: {
        name: $name, expirationDays: $expirationDays
      }) {
        id
        token { token issuedAt expiresAt }
      }
    }
    """,
    {"name": "My MQTT Key", "expirationDays": 365},
    token,
)
api_key_id = api_key["createApiKey"]["id"]
api_key_token = api_key["createApiKey"]["token"]["token"]
expires_at = api_key["createApiKey"]["token"]["expiresAt"]
print(f"API Key created: {api_key_id}")
print(f"Token expires: {expires_at}")
# IMPORTANT: Save api_key_token securely — it cannot be retrieved again

# --- Step 4: Assign MqttIntegrator Role ---
graphql_request(
    """
    mutation AssignRole($apiKeyId: uuid!) {
      assignApiKeyOrganizationRole(object: {
        roleName: "MqttIntegrator", apiKeyId: $apiKeyId
      }) { apiKeyId roleName }
    }
    """,
    {"apiKeyId": api_key_id},
    token,
)
print("MqttIntegrator role assigned")

# --- Step 5: Link API Key to Integration ---
graphql_request(
    """
    mutation LinkKey($mqttSubscriptionId: uuid!, $apiKeyId: uuid!) {
      assignMqttSubscriptionIntegrationApiKey(object: {
        mqttSubscriptionId: $mqttSubscriptionId, apiKeyId: $apiKeyId
      }) { mqttSubscriptionId apiKeyId }
    }
    """,
    {"mqttSubscriptionId": integration_id, "apiKeyId": api_key_id},
    token,
)
print("API Key linked to integration")

# --- Connect to MQTT ---
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        topic = f"haltian-iot/events/{integration_id}/{api_key_id}/#"
        client.subscribe(topic, qos=1)
        print(f"Connected and subscribed to: {topic}")
    else:
        print(f"Connection failed: {rc}")

def on_message(client, userdata, msg):
    payload = json.loads(msg.payload.decode())
    print(f"{msg.topic}: {json.dumps(payload, indent=2)}")

client = mqtt.Client()
client.username_pw_set(api_key_id, api_key_token)
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS)
client.on_connect = on_connect
client.on_message = on_message

print(f"Connecting to {MQTT_HOST}:{MQTT_PORT}...")
client.connect(MQTT_HOST, MQTT_PORT)
client.loop_forever()

Key Renewal Script

Standalone script for refreshing an expiring API key token:

import requests

GRAPHQL_ENDPOINT = "https://haltian-iot-api.eu.haltian.io/v1/graphql"


def graphql_request(query, variables=None, token=None):
    headers = {"Content-Type": "application/json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    response = requests.post(
        GRAPHQL_ENDPOINT,
        json={"query": query, "variables": variables or {}},
        headers=headers,
    )
    response.raise_for_status()
    result = response.json()
    if "errors" in result:
        raise Exception(f"GraphQL error: {result['errors']}")
    return result["data"]


# Authenticate
login_data = graphql_request(
    """
    mutation Login($email: String!, $organizationName: String!, $password: String!) {
      login(loginInput: {email: $email, organizationName: $organizationName, password: $password}) {
        accessToken
      }
    }
    """,
    {
        "email": "your-email@example.com",
        "organizationName": "your-organization",
        "password": "your-password",
    },
)
token = login_data["login"]["accessToken"]

# Refresh the API key token
API_KEY_ID = "c6f7b103-81df-4e73-8218-4f671056aee2"  # your API key ID
result = graphql_request(
    """
    mutation RefreshApiKey($apiKeyId: uuid!, $expirationDays: Int!) {
      refreshApiKeyToken(refreshApiKeyTokenInput: {
        apikeyId: $apiKeyId, expirationDays: $expirationDays
      }) {
        id
        token { token issuedAt expiresAt }
      }
    }
    """,
    {"apiKeyId": API_KEY_ID, "expirationDays": 365},
    token,
)

new_token = result["refreshApiKeyToken"]["token"]["token"]
expires_at = result["refreshApiKeyToken"]["token"]["expiresAt"]
print(f"Token refreshed. New expiration: {expires_at}")
# IMPORTANT: Update your MQTT client password with new_token