SDK Endpoints
The SDK-facing API provides four endpoints used by the Spotlight frontend SDK to fetch configuration, retrieve content, record user events, and track progress. All endpoints are prefixed with /v1/sdk/.
Authentication
All SDK endpoints require:
X-API-Keyheader for tenant identification.Authorization: Bearer <jwt>header for user authentication.
See Authentication for details.
Base URL
https://api.{region}.spotlight.example.com/v1/sdk/Local development: http://localhost:8000/v1/sdk/
GET /v1/sdk/config
Fetches the SDK configuration for the authenticated tenant, including active tours, content, themes, and feature flags applicable to the current user and page context.
Request
curl -X GET "https://api.example.com/v1/sdk/config?url=/dashboard/analytics&locale=en" \
-H "X-API-Key: sk_live_..." \
-H "Authorization: Bearer <jwt>"Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Current page path (for URL pattern matching) |
locale | string | No | User's locale for i18n content (default: tenant default) |
Response 200 OK
{
"data": {
"tenant_id": "tenant_abc",
"user_id": "user_123",
"is_admin": false,
"theme": {
"theme_id": "theme_default",
"tokens": {
"colors": { "primary": "#4F46E5" },
"typography": { "font_family": "'Inter', sans-serif" },
"spacing": { "border_radius": "8px" }
}
},
"tours": [
{
"tour_id": "tour_456",
"title": "Analytics Dashboard Tour",
"version": 3,
"steps": [
{
"step_id": "step_1",
"title": "Welcome",
"body": "This is your analytics dashboard.",
"element_target": {
"data_attribute": "analytics-overview"
},
"positioning": {
"placement": "bottom",
"offset_x": 0,
"offset_y": 8,
"arrow": true
}
}
],
"trigger": {
"trigger_type": "page_load",
"delay_ms": 2000
}
}
],
"content": [
{
"content_id": "cnt_789",
"content_type": "tooltip",
"title": "New Feature",
"body": "Check out our new reporting tools.",
"element_target": {
"element_id": "reports-tab"
},
"dismiss_behavior": "once"
}
],
"user_progress": {
"tour_456": {
"status": "in_progress",
"current_step": "step_1",
"completed_steps": [],
"started_at": "2025-05-10T10:00:00Z"
}
},
"feature_flags": {
"show_analytics_tour": true,
"enable_checklist": false
}
}
}Response Fields
| Field | Description |
|---|---|
tenant_id | The resolved tenant identifier |
user_id | The authenticated user's ID (from JWT sub) |
is_admin | Whether the user has admin privileges |
theme | Active theme tokens for rendering UI components |
tours | Tours matching the current URL and audience criteria |
content | Non-tour content (tooltips, spotlights, banners) for the page |
user_progress | User's progress state for each returned tour |
feature_flags | Feature flag values relevant to returned content |
Filtering Logic
The config endpoint applies several filters server-side:
- URL matching -- only tours/content whose
url_patternsmatch theurlparameter. - Audience -- only items targeting the user's audience segment.
- Rollout -- percentage-based rollout check (deterministic per user ID).
- Schedule -- only items within their scheduled date range.
- Feature flags -- items gated behind a disabled flag are excluded.
- Status -- only
publisheditems are returned. - Dismiss state -- content with
dismiss_behavior: "once"is excluded if already dismissed.
POST /v1/sdk/content
Fetches content for a specific content ID. Used when the SDK needs to load content on demand (e.g., triggered by a custom event or element visibility).
Request
curl -X POST "https://api.example.com/v1/sdk/content" \
-H "X-API-Key: sk_live_..." \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"content_ids": ["cnt_789", "tour_456"],
"url": "/dashboard",
"locale": "en"
}'Request Body
| Field | Type | Required | Description |
|---|---|---|---|
content_ids | string[] | Yes | IDs of content to fetch |
url | string | No | Current page path for context |
locale | string | No | Locale for i18n content |
Response 200 OK
{
"data": {
"items": [
{
"content_id": "cnt_789",
"content_type": "tooltip",
"title": "New Feature",
"body": "Check out our new reporting tools."
}
],
"not_found": ["tour_456"]
}
}POST /v1/sdk/events
Records user interaction events. The SDK batches events and sends them periodically or on page unload.
Request
curl -X POST "https://api.example.com/v1/sdk/events" \
-H "X-API-Key: sk_live_..." \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"event_type": "TourStarted",
"content_id": "tour_456",
"timestamp": "2025-05-10T14:22:00Z",
"metadata": {
"page_url": "/dashboard/analytics",
"session_id": "sess_abc123"
}
},
{
"event_type": "StepViewed",
"content_id": "tour_456",
"step_id": "step_1",
"timestamp": "2025-05-10T14:22:02Z",
"metadata": {
"page_url": "/dashboard/analytics",
"session_id": "sess_abc123"
}
}
]
}'Request Body
| Field | Type | Required | Description |
|---|---|---|---|
events | Event[] | Yes | Array of events to record |
Event Object
| Field | Type | Required | Description |
|---|---|---|---|
event_type | string | Yes | Event type (see Analytics) |
content_id | string | Yes | Tour or content ID |
step_id | string | No | Step ID (for step-level events) |
timestamp | string | Yes | ISO 8601 timestamp |
metadata | object | No | Additional context (page URL, session ID, etc.) |
Response 202 Accepted
{
"data": {
"accepted": 2,
"rejected": 0
}
}Events are written to the Events.Outbox table transactionally. The outbox processor publishes them to EventBridge asynchronously.
Supported Event Types
TourStarted StepViewed StepCompleted
TourCompleted TourDismissed TourRestarted
ContentViewed ContentDismissed ContentCtaClicked
target_not_foundPOST /v1/sdk/progress
Updates the user's progress state for a tour or checklist. Called when the user advances steps, completes, or dismisses content.
Request
curl -X POST "https://api.example.com/v1/sdk/progress" \
-H "X-API-Key: sk_live_..." \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{
"content_id": "tour_456",
"action": "complete_step",
"step_id": "step_1",
"metadata": {
"time_on_step_ms": 4500
}
}'Request Body
| Field | Type | Required | Description |
|---|---|---|---|
content_id | string | Yes | Tour or content ID |
action | string | Yes | Progress action (see below) |
step_id | string | Conditional | Required for step-level actions |
metadata | object | No | Additional context |
Progress Actions
| Action | Description |
|---|---|
start | Mark the tour as started |
complete_step | Mark a specific step as completed |
complete | Mark the entire tour as completed |
dismiss | Mark the tour as dismissed |
restart | Reset progress and restart the tour |
Response 200 OK
{
"data": {
"content_id": "tour_456",
"status": "in_progress",
"current_step": "step_2",
"completed_steps": ["step_1"],
"started_at": "2025-05-10T14:22:00Z",
"updated_at": "2025-05-10T14:22:05Z"
}
}Progress Storage
Progress is stored in the Spotlight.Progress.UserState table:
PK: tenant_user_id = "tenant_abc#user_123"
SK: content_id = "tour_456"A GSI (gsi-content-users) enables querying all users' progress for a specific tour:
GSI PK: tenant_content_id = "tenant_abc#tour_456"
GSI SK: user_id = "user_123"Rate Limiting
SDK endpoints are rate limited at the API Gateway level:
| Setting | Default Value |
|---|---|
| Rate limit | 100 requests/second |
| Burst limit | 50 requests |
When the limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header.
The X-RateLimit-Remaining header is included in responses to help clients implement client-side throttling.
Response Envelope
All SDK endpoints return responses in a standard envelope:
{
"data": { },
"meta": {
"request_id": "req_abc123",
"timestamp": "2025-05-10T14:22:00Z"
}
}Error responses:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid API key",
"request_id": "req_abc123"
}
}