Real-time Updates (SSE)
Server-Sent Events (SSE) provide real-time progress updates during report generation. This is the recommended way to monitor report creation status.
Overview
SSE is a one-way communication channel from server to client, perfect for:
- Monitoring profile data loading progress
- Tracking AI summary generation
- Receiving completion notifications
- Keeping connections alive with heartbeat pings
Connecting to SSE
Endpoint
GET /sse/{reportId}
Authentication
Bearer token is optional but recommended for authenticated users:
curl -N https://dashboard.socialprofiler.com/api/v1/sse/68af01be012b843fd7bc2bf4 \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-H "Accept: text/event-stream"
Response Headers
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Event Types
Ping Events
The first ping arrives shortly after the connection opens with status: "inited"; subsequent
keepalive pings use status: "inprogress" and repeat every 15 seconds by default (server-configured):
event: ping
data: {"type":"ping","status":"inited"}
event: ping
data: {"type":"ping","status":"inprogress"}
Follows Events
Profile data loading progress:
event: follows
data: {"type":"follows","status":"inprogress","id":"123456789","source":"ig"}
event: follows
data: {"type":"follows","status":"done","id":"123456789","source":"ig"}
event: follows
data: {"type":"follows","status":"error","id":"123456789","source":"ig","error":"private"}
Summary Events
AI summary generation progress:
event: summary
data: {"type":"summary","status":"inprogress"}
event: summary
data: {"type":"summary","status":"done"}
Confirm Events
Profile confirmation status:
event: confirm
data: {"type":"confirm","status":"done","id":"123456789","source":"ig"}
Status Values
| Status | Description |
|---|---|
inited | Task initialized, waiting to start |
inprogress | Task is running |
done | Task completed successfully |
error | Task failed (check error field) |
Error Reasons
When status: "error", the error field contains:
| Error | Description |
|---|---|
private | Profile is private |
notFound | Profile doesn't exist |
zeroFollows | Profile has no follows |
optOut | User opted out of tracking |
unloaded | Data couldn't be loaded |
unmapped | Profile couldn't be mapped |
unexpected | Unexpected failure |
Client Implementation
JavaScript (Browser)
function connectSSE(reportId, token) {
const url = `https://dashboard.socialprofiler.com/api/v1/sse/${reportId}`;
const eventSource = new EventSource(url, {
headers: {
'Authorization': `Bearer ${token}`
}
});
// Handle ping events
eventSource.addEventListener('ping', (event) => {
const data = JSON.parse(event.data);
console.log('Ping:', data.status);
});
// Handle follows loading progress
eventSource.addEventListener('follows', (event) => {
const data = JSON.parse(event.data);
console.log(`Profile ${data.id} (${data.source}): ${data.status}`);
if (data.status === 'error') {
console.error(`Error: ${data.error}`);
}
});
// Handle summary generation
eventSource.addEventListener('summary', (event) => {
const data = JSON.parse(event.data);
console.log(`Summary: ${data.status}`);
if (data.status === 'done') {
console.log('Report ready!');
eventSource.close();
}
});
// Handle errors
eventSource.onerror = (error) => {
console.error('SSE Error:', error);
eventSource.close();
};
return eventSource;
}
// Usage
const sse = connectSSE('68af01be012b843fd7bc2bf4', 'your_token');
// Close when done
// sse.close();
JavaScript (Node.js)
Using the eventsource package:
const EventSource = require('eventsource');
function connectSSE(reportId, token) {
const url = `https://dashboard.socialprofiler.com/api/v1/sse/${reportId}`;
const eventSource = new EventSource(url, {
headers: {
'Authorization': `Bearer ${token}`
}
});
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Event:', data);
};
eventSource.onerror = (error) => {
console.error('Error:', error);
};
return eventSource;
}
Python
Using the sseclient-py package:
import sseclient
import requests
def connect_sse(report_id: str, token: str):
url = f'https://dashboard.socialprofiler.com/api/v1/sse/{report_id}'
headers = {
'Authorization': f'Bearer {token}',
'Accept': 'text/event-stream'
}
response = requests.get(url, headers=headers, stream=True)
client = sseclient.SSEClient(response)
for event in client.events():
data = json.loads(event.data)
event_type = event.event
print(f'{event_type}: {data}')
if event_type == 'summary' and data['status'] == 'done':
print('Report ready!')
break
# Usage
connect_sse('68af01be012b843fd7bc2bf4', 'your_token')
Go
package main
import (
"bufio"
"encoding/json"
"fmt"
"net/http"
"strings"
)
type SSEMessage struct {
Type string `json:"type"`
Status string `json:"status"`
ID string `json:"id,omitempty"`
Source string `json:"source,omitempty"`
Error string `json:"error,omitempty"`
}
func connectSSE(reportID, token string) error {
url := fmt.Sprintf("https://dashboard.socialprofiler.com/api/v1/sse/%s", reportID)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "text/event-stream")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
var eventType string
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "event:") {
eventType = strings.TrimSpace(strings.TrimPrefix(line, "event:"))
} else if strings.HasPrefix(line, "data:") {
data := strings.TrimPrefix(line, "data:")
var msg SSEMessage
json.Unmarshal([]byte(data), &msg)
fmt.Printf("%s: %+v\n", eventType, msg)
if eventType == "summary" && msg.Status == "done" {
fmt.Println("Report ready!")
return nil
}
}
}
return scanner.Err()
}
Best Practices
1. Handle Reconnection
SSE connections can drop. Implement automatic reconnection:
function connectWithRetry(reportId, token, maxRetries = 3) {
let retries = 0;
function connect() {
const sse = connectSSE(reportId, token);
sse.onerror = () => {
sse.close();
if (retries < maxRetries) {
retries++;
console.log(`Reconnecting... (attempt ${retries})`);
setTimeout(connect, 1000 * retries);
}
};
return sse;
}
return connect();
}
2. Set Timeouts
Don't wait forever for events:
const timeout = setTimeout(() => {
sse.close();
console.log('Timeout - checking status via API');
}, 120000); // 2 minutes
sse.addEventListener('summary', (event) => {
if (JSON.parse(event.data).status === 'done') {
clearTimeout(timeout);
}
});
3. Track Progress
Keep track of which profiles are loaded:
const profileStatus = {};
sse.addEventListener('follows', (event) => {
const data = JSON.parse(event.data);
profileStatus[`${data.source}:${data.id}`] = data.status;
const total = Object.keys(profileStatus).length;
const done = Object.values(profileStatus).filter(s => s === 'done').length;
console.log(`Progress: ${done}/${total}`);
});
4. Graceful Degradation
Fall back to polling if SSE fails:
async function waitForReport(reportId, token) {
try {
await new Promise((resolve, reject) => {
const sse = connectSSE(reportId, token);
sse.addEventListener('summary', (e) => {
if (JSON.parse(e.data).status === 'done') {
sse.close();
resolve();
}
});
sse.onerror = reject;
});
} catch (error) {
console.log('SSE failed, falling back to polling');
await pollForCompletion(reportId, token);
}
}
async function pollForCompletion(reportId, token) {
while (true) {
const response = await fetch(`/api/v1/check/${reportId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
if (data.generated && data.ready) break;
await new Promise(r => setTimeout(r, 5000));
}
}