CloudSignal Docs
GuidesAuth Providers

Token exchange API

API reference for exchanging identity provider tokens for MQTT credentials.

Exchange an identity provider token (Supabase, Firebase, Auth0, Clerk) for temporary CloudSignal MQTT credentials. Use this endpoint from your backend whenever an authenticated user needs to connect to the broker.

Endpoint

POST https://api.cloudsignal.io/v2/tokens/exchange

Authentication

Requires a CloudSignal API key (secret key recommended for server-side use):

Authorization: Bearer sk_live_your_secret_key

Request

Headers

HeaderRequiredDescription
AuthorizationYesBearer token with your CloudSignal API key
Content-TypeYesapplication/json

Body parameters

ParameterTypeRequiredDescription
providerstringYesIdentity provider name: supabase, firebase, or auth0
tokenstringYesJWT/access token from the identity provider
ttlintegerNoToken TTL in seconds (default: 3600, max: 86400)

Example request

curl -X POST https://api.cloudsignal.io/v2/tokens/exchange \
  -H "Authorization: Bearer sk_live_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "supabase",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "ttl": 3600
  }'

Response

Success response (200)

{
  "mqtt_username": "user_abc123@a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "mqtt_password": "temp_xyz789...",
  "expires_at": "2024-01-15T13:00:00Z",
  "expires_in": 3600,
  "provider": "supabase",
  "user_id": "abc123"
}
FieldTypeDescription
mqtt_usernamestringFull MQTT username including organization ID
mqtt_passwordstringTemporary MQTT password
expires_atstringISO 8601 timestamp when credentials expire
expires_inintegerSeconds until expiration
providerstringThe identity provider used
user_idstringUser identifier extracted from the token

Error responses

400 Bad Request

{
  "error": "invalid_request",
  "message": "Missing required parameter: provider"
}

401 Unauthorized

{
  "error": "unauthorized",
  "message": "Invalid or missing API key"
}

422 Unprocessable Entity

{
  "error": "invalid_token",
  "message": "Token validation failed: token expired"
}
Error codeDescription
invalid_requestMissing or invalid parameters
unauthorizedInvalid API key
invalid_tokenToken validation failed
provider_not_foundProvider not configured
provider_errorError communicating with provider

Code examples

JavaScript/TypeScript

interface TokenExchangeResponse {
  mqtt_username: string;
  mqtt_password: string;
  expires_at: string;
  expires_in: number;
  provider: string;
  user_id: string;
}

async function exchangeToken(
  provider: 'supabase' | 'firebase' | 'auth0',
  token: string,
  ttl?: number
): Promise<TokenExchangeResponse> {
  const response = await fetch('https://api.cloudsignal.io/v2/tokens/exchange', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.CLOUDSIGNAL_SK}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      provider,
      token,
      ...(ttl && { ttl })
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Token exchange failed: ${error.message}`);
  }

  return response.json();
}

// Usage
const credentials = await exchangeToken('supabase', supabaseAccessToken);
console.log(`Username: ${credentials.mqtt_username}`);
console.log(`Expires in: ${credentials.expires_in} seconds`);

Python

import os
import requests
from dataclasses import dataclass
from datetime import datetime

@dataclass
class MqttCredentials:
    mqtt_username: str
    mqtt_password: str
    expires_at: datetime
    expires_in: int
    provider: str
    user_id: str

def exchange_token(
    provider: str,
    token: str,
    ttl: int = None
) -> MqttCredentials:
    url = 'https://api.cloudsignal.io/v2/tokens/exchange'
    headers = {
        'Authorization': f'Bearer {os.environ["CLOUDSIGNAL_SK"]}',
        'Content-Type': 'application/json'
    }
    payload = {
        'provider': provider,
        'token': token
    }
    if ttl:
        payload['ttl'] = ttl

    response = requests.post(url, json=payload, headers=headers)
    response.raise_for_status()

    data = response.json()
    return MqttCredentials(
        mqtt_username=data['mqtt_username'],
        mqtt_password=data['mqtt_password'],
        expires_at=datetime.fromisoformat(data['expires_at'].replace('Z', '+00:00')),
        expires_in=data['expires_in'],
        provider=data['provider'],
        user_id=data['user_id']
    )

# Usage
credentials = exchange_token('firebase', firebase_id_token, ttl=7200)
print(f"Username: {credentials.mqtt_username}")
print(f"Expires at: {credentials.expires_at}")

Go

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "time"
)

type TokenExchangeRequest struct {
    Provider string `json:"provider"`
    Token    string `json:"token"`
    TTL      int    `json:"ttl,omitempty"`
}

type TokenExchangeResponse struct {
    MqttUsername string    `json:"mqtt_username"`
    MqttPassword string    `json:"mqtt_password"`
    ExpiresAt    time.Time `json:"expires_at"`
    ExpiresIn    int       `json:"expires_in"`
    Provider     string    `json:"provider"`
    UserID       string    `json:"user_id"`
}

func ExchangeToken(provider, token string, ttl int) (*TokenExchangeResponse, error) {
    reqBody := TokenExchangeRequest{
        Provider: provider,
        Token:    token,
        TTL:      ttl,
    }

    jsonBody, _ := json.Marshal(reqBody)
    req, _ := http.NewRequest("POST", "https://api.cloudsignal.io/v2/tokens/exchange", bytes.NewBuffer(jsonBody))
    req.Header.Set("Authorization", "Bearer "+os.Getenv("CLOUDSIGNAL_SK"))
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result TokenExchangeResponse
    json.NewDecoder(resp.Body).Decode(&result)
    return &result, nil
}

Best practices

Cache credentials

Don't call the API for every MQTT connection. Cache credentials and refresh before expiry:

class CredentialsManager {
  private credentials: TokenExchangeResponse | null = null;
  private refreshThreshold = 5 * 60 * 1000; // 5 minutes

  async getCredentials(provider: string, token: string): Promise<TokenExchangeResponse> {
    if (this.shouldRefresh()) {
      this.credentials = await exchangeToken(provider, token);
    }
    return this.credentials!;
  }

  private shouldRefresh(): boolean {
    if (!this.credentials) return true;
    const expiresAt = new Date(this.credentials.expires_at).getTime();
    return Date.now() > expiresAt - this.refreshThreshold;
  }
}

Handle errors gracefully

async function safeExchangeToken(provider: string, token: string) {
  try {
    return await exchangeToken(provider, token);
  } catch (error) {
    if (error.message.includes('token expired')) {
      // Trigger re-authentication with identity provider
      await refreshIdentityProviderToken();
      return await exchangeToken(provider, getNewToken());
    }
    throw error;
  }
}

Use appropriate TTL

Choose TTL based on your use case:

Use caseRecommended TTLNotes
Web session3600 (1 hour)Match browser session
Mobile app7200 (2 hours)Reduce API calls
Short task300 (5 min)Minimize exposure
Background job60 (1 min)Task-scoped credentials

Shorter TTLs are more secure but require more frequent token exchanges. Balance security with API usage.

Rate limits

PlanRequests/minuteRequests/day
Free101,000
Pro6010,000
EnterpriseCustomCustom

Exceeding rate limits returns 429 Too Many Requests. Implement exponential backoff for retries.


Next steps

On this page