MQTT API Key Management
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.
- A user account with Organization Manager role
- The GraphQL API endpoint:
https://haltian-iot-api.eu.haltian.io/v1/graphql - Familiarity with the Service API authentication flow
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:#143633The 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"
}
}
}
}
Save the token value immediately. The token is only returned at creation time and cannot be retrieved later. Set expirationDays to any value from 1 to 365 days. If you omit it, the token is valid for 90 days. Check the returned expiresAt value for the exact expiration time.
The returned values map to MQTT credentials:
| API Key Field | MQTT Usage |
|---|---|
id | MQTT username and {api-key-id} in topic path |
token.token | MQTT 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"
}
The role name MqttIntegrator is case-sensitive and must be spelled exactly as shown.
Step 5: Link API Key to Integration
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:
| Parameter | Value |
|---|---|
| Host | haltian-iot-mqtt.eu.haltian.io |
| Port | 18883 (TLS required) |
| Username | API Key ID (c6f7b103-81df-4e73-8218-4f671056aee2) |
| Password | API Key Token (eyJhbGciO...) |
| Topic | haltian-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
}
}
Before deleting an organization-wide keyword, remove it from every API key that uses it. Deleting the keyword removes it from devices but does not clear API key filters that reference it.
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"
}
}
}
}
After refreshing, update the MQTT client password with the new token. The previous token is immediately invalidated.
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
Related Pages
- Streaming & Integrations — Conceptual overview of the integration chain
- Stream API Topics — MQTT topic structure
- Stream API Examples — Connection code examples
- Service API Authentication — JWT token management
- Service API Mutations — Full mutation reference