Overview
In addition to the WebSocket-based SDK, Brook provides a REST API for publishing messages. This is useful for:
- Server-side applications
- One-way message publishing (no subscription needed)
- Integration with services that don’t support WebSockets
- Serverless functions and cron jobs
Endpoint
POST https://connect.aptly.cloud/realtime
Authentication
Aptly uses API keys for authentication. Include your API key in the x-api-key header.
Use Server Keys (sk_) for REST API requests. Public keys (pk_) cannot publish messages.
| Header | Value | Required | Notes |
x-api-key | Your server key | ✅ Yes | Must start with sk_ |
Content-Type | application/json | ✅ Yes | Request body format |
Origin | Your domain | ❌ No | Not required for server keys |
Server Keys vs Public Keys
Server keys are designed for backend services and have no origin restrictions.curl -X POST https://connect.aptly.cloud/realtime \
-H "x-api-key: sk_xyz789..." \
-H "Content-Type: application/json" \
-d '{"channel": "events", "message": {"type": "update"}}'
No Origin header needed. Perfect for serverless functions, APIs, and backend services.
Public keys are for client-side subscriptions only and cannot publish via REST API.Publishing with public keys is not supported via REST API. Use the JavaScript SDK for client-side publishing.
For client-side real-time subscriptions, see the JavaScript SDK or React SDK documentation.
Authentication Guide
Learn when to use public keys vs server keys
Body
{
"channel": "channel-name",
"message": {
// Your message data (any JSON-serializable object)
}
}
Examples
Using cURL
Simple Message
Complex Message
With Pretty Print
curl -X POST https://connect.aptly.cloud/realtime \
-H "Content-Type: application/json" \
-H "x-api-key: your-api-key" \
-H "Origin: https://your.allowed.website \
-d '{
"channel": "my-topic",
"message": {
"text": "Hello from cURL!"
}
}'
curl -X POST https://connect.aptly.cloud/realtime \
-H "Content-Type: application/json" \
-H "x-api-key: your-api-key" \
-H "Origin: https://your.allowed.website \
-d '{
"channel": "notifications",
"message": {
"type": "alert",
"severity": "high",
"title": "Server Down",
"description": "Production server is unreachable",
"timestamp": 1699564800000,
"metadata": {
"server": "prod-01",
"region": "us-east-1"
}
}
}'
curl -X POST https://connect.aptly.cloud/realtime \
-H "Content-Type: application/json" \
-H "x-api-key: your-api-key" \
-H "Origin: https://your.allowed.website \
-d '{
"channel": "my-topic",
"message": {"text": "Hello!"}
}' \
| jq
Using Fetch (Browser/Node.js)
Browser
Node.js
With Retry Logic
async function publishMessage(channel, message) {
const response = await fetch('https://connect.aptly.cloud/realtime', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key'
// Note: Origin header is automatically sent by the browser
},
body: JSON.stringify({
channel,
message
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Published:', data);
return data;
}
// Usage
publishMessage('my-topic', {
text: 'Hello from browser!',
timestamp: Date.now()
});
// Node.js 18+ (native fetch)
async function publishMessage(channel, message) {
try {
const response = await fetch('https://connect.aptly.cloud/realtime', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.BROOK_API_KEY,
'Origin': 'https://your.allowed.website'
},
body: JSON.stringify({ channel, message })
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to publish');
}
return data;
} catch (error) {
console.error('Publish error:', error);
throw error;
}
}
// Usage
publishMessage('notifications', {
type: 'email',
to: '[email protected]',
subject: 'Welcome!'
})
.then(result => console.log('Success:', result))
.catch(error => console.error('Error:', error));
async function publishWithRetry(channel, message, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch('https://connect.aptly.cloud/realtime', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
'Origin': 'https://your.allowed.website'
},
body: JSON.stringify({ channel, message })
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.log(`Attempt ${i + 1} failed:`, error);
if (i === maxRetries - 1) {
throw error;
}
// Wait before retrying (exponential backoff)
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, i) * 1000)
);
}
}
}
Using Axios
Basic
With Instance
With Interceptors
import axios from 'axios';
async function publishMessage(channel, message) {
try {
const response = await axios.post(
'https://connect.aptly.cloud/realtime',
{
channel,
message
},
{
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key',
'Origin': 'https://your.allowed.website'
}
}
);
console.log('Published:', response.data);
return response.data;
} catch (error) {
console.error('Error:', error.response?.data || error.message);
throw error;
}
}
// Usage
publishMessage('chat-room', {
user: 'alice',
text: 'Hello from axios!'
});
import axios from 'axios';
// Create a configured instance
const brookApi = axios.create({
baseURL: 'https://connect.aptly.cloud',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.BROOK_API_KEY,
'Origin': 'https://your.allowed.website'
}
});
async function publish(channel, message) {
const { data } = await brookApi.post('/realtime', {
channel,
message
});
return data;
}
// Usage
publish('notifications', {
type: 'info',
text: 'System update completed'
});
import axios from 'axios';
const brookApi = axios.create({
baseURL: 'https://connect.aptly.cloud',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.BROOK_API_KEY,
'Origin': 'https://your.allowed.website'
}
});
// Request interceptor
brookApi.interceptors.request.use(
(config) => {
console.log('Publishing to:', config.data.channel);
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor
brookApi.interceptors.response.use(
(response) => {
console.log('Published successfully:', response.data);
return response;
},
(error) => {
console.error('Publish failed:', error.response?.data);
return Promise.reject(error);
}
);
async function publish(channel, message) {
const { data } = await brookApi.post('/realtime', {
channel,
message
});
return data;
}
Success Response
{
"success": true,
"offset": 12345,
"channel": "my-topic",
"timestamp": "2024-01-15T10:30:00.000Z"
}
| Field | Type | Description |
success | boolean | Always true for successful publishes |
offset | number | Message offset/sequence number in the channel |
channel | string | The channel name |
timestamp | string | ISO timestamp when message was published |
Error Response
{
"error": "Error message description",
"success": false
}
| Status Code | Description |
200 | Success |
401 | Unauthorized (invalid API key) |
400 | Bad request (invalid payload) |
429 | Too many requests (rate limited) |
500 | Server error |
Use Cases
Serverless Functions
AWS Lambda
Vercel/Next.js
Cloudflare Workers
// AWS Lambda function
export const handler = async (event) => {
const response = await fetch('https://connect.aptly.cloud/realtime', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.BROOK_API_KEY,
'Origin': 'https://your.allowed.website'
},
body: JSON.stringify({
channel: 'lambda-events',
message: {
event: event.eventName,
time: event.time,
data: event.data
}
})
});
const data = await response.json();
return {
statusCode: 200,
body: JSON.stringify(data)
};
};
// pages/api/notify.js
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const response = await fetch('https://connect.aptly.cloud/realtime', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.BROOK_API_KEY,
'Origin': 'https://your.allowed.website'
},
body: JSON.stringify({
channel: 'notifications',
message: req.body
})
});
const data = await response.json();
res.status(200).json(data);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
export default {
async fetch(request, env) {
const response = await fetch('https://connect.aptly.cloud/realtime', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': env.BROOK_API_KEY,
'Origin': 'https://your.allowed.website'
},
body: JSON.stringify({
channel: 'worker-events',
message: {
timestamp: Date.now(),
url: request.url
}
})
});
return response;
}
};
Webhooks
// Express.js webhook endpoint
app.post("/webhook", async (req, res) => {
try {
// Forward webhook data to Brook
const response = await fetch("https://connect.aptly.cloud/realtime", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.BROOK_API_KEY,
Origin: "https://your.allowed.website",
},
body: JSON.stringify({
channel: "webhooks",
message: {
source: req.headers["x-webhook-source"],
event: req.body.event,
data: req.body,
receivedAt: new Date().toISOString(),
},
}),
});
const data = await response.json();
res.json({ success: true, brook: data });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Scheduled Tasks
// Cron job / scheduled task
import cron from "node-cron";
// Run every hour
cron.schedule("0 * * * *", async () => {
console.log("Publishing hourly update...");
await fetch("https://connect.aptly.cloud/realtime", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.BROOK_API_KEY,
Origin: "https://your.allowed.website",
},
body: JSON.stringify({
channel: "system-events",
message: {
type: "hourly-check",
timestamp: new Date().toISOString(),
status: "ok",
},
}),
});
});
Best Practices
Use environment variables for your API key to keep it secure.
Implement retry logic for critical messages to handle temporary network
issues.
Rate limits apply - avoid sending too many requests in a short period.
REST API is one-way - use the WebSocket SDK if you need to subscribe to
messages.
Error Handling
async function safePublish(channel, message) {
try {
const response = await fetch("https://connect.aptly.cloud/realtime", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.BROOK_API_KEY,
Origin: "https://your.allowed.website",
},
body: JSON.stringify({ channel, message }),
});
const data = await response.json();
if (!response.ok) {
// Handle specific errors
switch (response.status) {
case 401:
console.error("Invalid API key");
break;
case 429:
console.error("Rate limited - retry later");
break;
case 500:
console.error("Server error");
break;
default:
console.error("Unknown error:", data);
}
throw new Error(data.error || "Publish failed");
}
return data;
} catch (error) {
console.error("Network error:", error);
throw error;
}
}
Next Steps