# Amplify Scheduler Developer API
This document describes the developer-facing API contracts for the Amplify Scheduler platform, a unified social media management API.
## Developer Platform Summary
Amplify Scheduler is designed to be used by external applications as a **proper developer platform**, not just an internal integration. Each customer receives profile-scoped API keys, paid access is enforced, and external apps can create, schedule, inspect, and manage posts through versioned endpoints.
## Overview
Amplify Scheduler provides a backend-first social media management platform with:
- **Profile-scoped resources** — all data is organized under user profiles
- **Unified platform abstraction** — consistent API across all social platforms
- **Backend-owned OAuth** — connections are managed server-side
- **Normalized post model** — single post can target multiple platforms
- **Scheduling** — schedule posts for future delivery
## Base URL
```
https://amplify-schedule.com/api/v1
```
Or your custom domain if configured.
## API Discovery
Use the root discovery endpoint to inspect the current platform contract:
```
GET /api/v1
```
**Response includes:**
- API version
- auth header name
- core endpoint list
- billing requirements
- platform capabilities
- docs link
## Authentication
All API requests require an API key passed in the `X-API-Key` header:
```
X-API-Key: your_api_key_here
```
API keys are scoped to a profile. Create/manage keys in the browser UI through `/api/api-keys` after subscription is active.
## Billing Requirement (Free Tier + No Free Trial on Paid Plans)
Amplify Scheduler supports a Free tier managed entirely in Supabase, plus paid Stripe-backed plans:
- Free tier includes 2 profiles, 25 posts per month, and analytics.
- Free tier does not include API keys or webhooks.
- Users must have an active paid subscription before creating API keys.
- API requests using `X-API-Key` return `402` when subscription is inactive.
- There is no free trial on paid plans.
Stripe environment variables required on the backend:
```
STRIPE_SECRET_KEY=...
STRIPE_PRICE_ID_STARTER=price_1TLLksAHVlcpHu3BPH1NoBSw
STRIPE_PRICE_ID_GROWTH=price_1TLLmCAHVlcpHu3BV2vmPNZY
STRIPE_PRICE_ID_AGENCY=price_1TLLn6AHVlcpHu3B34tzraTY
STRIPE_PRICE_ID_UNLIMITED=price_1TLLoZAHVlcpHu3BUyq1RnnY
STRIPE_WEBHOOK_SECRET=whsec_...
APP_URL=https://classy-chaja-99384d.netlify.app
```
Browser billing endpoints:
- `GET /api/billing/subscription-status`
- `POST /api/billing/create-checkout-session`
- `POST /api/billing/create-portal-session`
- `POST /api/billing/webhook`
## Webhooks
Amplify Scheduler supports customer-configured webhooks for post lifecycle events and delivery tracking.
Available management endpoints:
- `GET /api/webhook-endpoints`
- `POST /api/webhook-endpoints`
- `PATCH /api/webhook-endpoints/:webhookId`
- `DELETE /api/webhook-endpoints/:webhookId`
- `GET /api/webhook-endpoints/:webhookId/deliveries`
- `POST /api/webhook-deliveries/:deliveryId/retry`
Typical event types include:
- `post.published`
- `post.failed`
- delivery retry/status events
## Pricing (GBP)
All plans are billed monthly in GBP. The Free tier does not require Stripe.
### Core Plans
- **Free — £0/month**
- 2 profiles
- 25 posts per month
- Advanced analytics included
- No API keys
- No webhooks
- **Starter — £19/month**
- 10 profiles
- 150 scheduled posts per month
- 10,000 API requests per month
- Advanced analytics included
- **Growth — £49/month**
- 50 profiles
- Unlimited posts (fair use)
- 100,000 API requests per month
- Advanced analytics included
- **Agency — £99/month**
- 100 profiles
- Unlimited posts (fair use)
- 500,000 API requests per month
- Advanced analytics included
- **Unlimited — £299/month**
- Unlimited profiles
- Unlimited posts
- Unlimited API requests
- Advanced analytics included
## Free Tier Setup
Free users are seeded in Supabase by the `on_auth_user_created_billing` trigger, which inserts a `billing_subscriptions` row with `status = 'free'`. No Stripe checkout is required for the Free plan.
### Add-ons
- Extra profile pack: **£8/profile/month**
- White-label: **£49/workspace/month**
## API Key Management
### List API Keys
```
GET /api/v1/api-keys
```
**Response:**
```json
{
"apiKeys": [
{
"id": "key_uuid",
"name": "Production Key",
"key": "...abc12345",
"profile_id": "profile_uuid",
"status": "active",
"created_at": "2024-01-01T00:00:00.000Z",
"last_used_at": "2024-01-15T10:30:00.000Z"
}
]
}
```
### Create API Key
```
POST /api/v1/api-keys
```
**Request:**
```json
{
"name": "My Integration Key",
"profileId": "profile_uuid"
}
```
**Response:**
```json
{
"success": true,
"apiKey": {
"id": "key_uuid",
"name": "My Integration Key",
"key": "ak_abc123def456...",
"profileId": "profile_uuid",
"status": "active",
"createdAt": "2024-01-01T00:00:00.000Z"
},
"warning": "Save this key now. It will not be shown again."
}
```
> **Important:** The full API key is only returned once at creation. Store it securely.
## External Integration Flow
For third-party apps, the recommended flow is:
1. Create or select a profile
2. Connect the required social accounts
3. Create a paid subscription
4. Create an API key for the profile
5. Send `content`, `media`, and `scheduleDate` to `/api/v1/post`
6. Optionally poll `/api/v1/scheduled-posts` or subscribe to webhooks
Example post request:
```json
{
"platform": "facebook",
"content": "Final copy from the external app",
"scheduleDate": "2026-04-12T09:00:00Z",
"media": [
{
"public_url": "https://example.com/image.jpg",
"mime_type": "image/jpeg",
"file_name": "image.jpg"
}
]
}
```
### Revoke API Key
```
DELETE /api/v1/api-keys/{keyId}
```
**Response:**
```json
{
"success": true,
"message": "API key revoked"
}
```
## Platform Capabilities
### Platform Catalog
Retrieve the complete platform catalog with capability contracts:
```
GET /api/v1/platforms
```
**Response:**
```json
{
"platforms": [
{
"platform": "twitter",
"displayName": "Twitter / X",
"accountType": "social_profile",
"supportStatus": "partial",
"scopes": ["tweet.read", "tweet.write", "users.read", "offline.access"],
"publish": {
"supported": true,
"text": true,
"images": true,
"video": false,
"requiresMedia": false,
"supportedMediaTypes": ["image/*"],
"scheduling": true
},
"analytics": {
"supported": false
},
"notes": ["Current publishing path supports text and single-image posts."]
}
]
}
```
### Support Status Values
- `full` — Complete implementation with all claimed features working
- `partial` — Working implementation with some limitations
- `unsupported` — Not implemented or not feasible with current APIs
## Connections
### List Connections
Retrieve connected accounts for the API key's profile:
```
GET /api/v1/connections
```
**Response:**
```json
{
"connections": [
{
"id": "twitter_abc123",
"platform": "twitter",
"username": "@handle",
"externalAccountId": "123456789",
"externalAccountName": "@handle",
"connectionStatus": "connected",
"capabilities": {
"text": true,
"images": true,
"video": false,
"scheduling": true,
"analytics": false
},
"contract": {
"displayName": "Twitter / X",
"supportStatus": "partial",
"publish": {
"supported": true,
"scheduling": true
},
"notes": ["Current publishing path supports text and single-image posts."]
},
"readiness": {
"status": "ready",
"canPublish": true,
"canSchedule": true,
"missingRequirements": [],
"warnings": []
}
}
]
}
```
### Initiate OAuth Connection
Start the OAuth flow for a platform:
```
POST /api/v1/connect/init
```
**Request:**
```json
{
"platform": "twitter",
"returnUrl": "https://your-app.com/callback"
}
```
**Response:**
```json
{
"url": "https://twitter.com/i/oauth2/authorize?...",
"error": null
}
```
Redirect the user to this URL. After authorization, the user will be redirected to your `returnUrl`.
### Disconnect Account
Remove a connected account:
```
DELETE /api/v1/connections/{connectionId}
```
**Response:**
```json
{
"success": true
}
```
### Sync Connection
Refresh token and metadata for a connection:
```
POST /api/v1/connections/{connectionId}/sync
```
**Response:**
```json
{
"connection": {
"id": "twitter_abc123",
"platform": "twitter",
"connectionStatus": "connected",
"readiness": {
"status": "ready",
"canPublish": true
}
}
}
```
## Media
### Create Upload URL
Get a signed upload URL for media:
```
POST /api/v1/media/upload-url
```
**Request:**
```json
{
"profileId": "profile_uuid",
"fileName": "photo.jpg",
"contentType": "image/jpeg",
"fileSize": 1234567
}
```
**Response:**
```json
{
"upload": {
"bucket": "post-media",
"path": "user_uuid/profile_uuid/media_uuid-photo.jpg",
"token": "upload_token_here",
"signedUrl": "https://..."
},
"media": {
"id": "media_uuid",
"post_id": "",
"file_path": "user_uuid/profile_uuid/media_uuid-photo.jpg",
"public_url": "https://your-cdn.com/post-media/...",
"mime_type": "image/jpeg",
"file_size": 1234567,
"file_name": "photo.jpg",
"sort_order": 0,
"created_at": "2024-01-01T00:00:00.000Z"
}
}
```
### Upload Media
Upload the file to the signed URL:
```
PUT {signedUrl}
Content-Type: image/jpeg
[binary file data]
```
### Delete Media
Remove uploaded media:
```
DELETE /api/v1/media
```
**Request:**
```json
{
"profileId": "profile_uuid",
"filePath": "user_uuid/profile_uuid/media_uuid-photo.jpg"
}
```
## Posts
### Create Post (Immediate)
Publish immediately to connected platforms:
```
POST /api/v1/post
```
**Request:**
```json
{
"platforms": ["twitter", "linkedin"],
"content": "Hello world!",
"media": [
{
"id": "media_uuid",
"file_path": "user_uuid/profile_uuid/media_uuid-photo.jpg",
"public_url": "https://your-cdn.com/post-media/...",
"mime_type": "image/jpeg",
"file_size": 1234567,
"file_name": "photo.jpg"
}
]
}
```
**Response:**
```json
{
"success": true,
"postId": "post_uuid",
"targets": [
{
"platform": "twitter",
"status": "published",
"external_post_id": "123456789"
},
{
"platform": "linkedin",
"status": "published",
"external_post_id": "urn:li:share:123"
}
]
}
```
### Schedule Post
Schedule for future delivery:
```
POST /api/v1/post
```
**Request:**
```json
{
"platforms": ["twitter"],
"content": "Scheduled post",
"scheduleDate": "2024-01-15T10:00:00"
}
```
**Response:**
```json
{
"success": true,
"postId": "post_uuid",
"scheduled": true,
"scheduledAt": "2024-01-15T10:00:00.000Z"
}
```
### List Posts
Retrieve posts for a profile:
```
GET /api/v1/posts?profileId=profile_uuid
```
**Response:**
```json
{
"posts": [
{
"id": "post_uuid",
"content": "Hello world!",
"status": "published",
"scheduled_for": null,
"published_at": "2024-01-01T00:00:00.000Z",
"targets": [
{
"id": "target_uuid",
"platform": "twitter",
"status": "published",
"external_post_id": "123456789"
}
],
"media": []
}
]
}
```
### List Scheduled Posts
Retrieve scheduled posts:
```
GET /api/v1/scheduled-posts?profileId=profile_uuid
```
**Response:**
```json
{
"scheduledPosts": [
{
"id": "scheduled_uuid",
"post_id": "post_uuid",
"platform": "twitter",
"content": "Scheduled post",
"scheduled_at": "2024-01-15T10:00:00.000Z",
"status": "pending"
}
]
}
```
### Cancel Scheduled Post
Cancel a pending scheduled post:
```
DELETE /api/v1/scheduled-posts/{scheduledPostId}
```
**Response:**
```json
{
"success": true,
"postId": "post_uuid"
}
```
## Job Queue & Delivery Tracking
Amplify Scheduler includes a Zernio-grade job queue with automatic retry logic, delivery tracking, and rate limit handling.
### Job Logs
Retrieve delivery logs for scheduled and immediate posts:
```
GET /api/v1/job-logs?profileId=profile_uuid&limit=100&platform=twitter&status=failed
```
**Query Parameters:**
- `profileId` (required) — Profile UUID
- `limit` (optional) — Maximum results (default: 100, max: 500)
- `platform` (optional) — Filter by platform
- `status` (optional) — Filter by status: `success`, `failed`, `rate_limited`
**Response:**
```json
{
"logs": [
{
"id": "job_uuid",
"post_id": "post_uuid",
"scheduled_post_id": "sched_uuid",
"platform": "twitter",
"job_type": "publish",
"status": "success",
"attempt_number": 1,
"started_at": "2024-01-15T10:00:00.000Z",
"completed_at": "2024-01-15T10:00:02.500Z",
"duration_ms": 2500,
"error_message": null,
"error_category": null,
"was_rate_limited": false,
"will_retry": false,
"next_retry_at": null,
"metadata": { "durationMs": 2500, "isRetry": false },
"created_at": "2024-01-15T10:00:00.000Z"
}
]
}
```
### Delivery Metrics
Get aggregated delivery statistics by platform and date:
```
GET /api/v1/delivery-metrics?profileId=profile_uuid&days=30
```
**Query Parameters:**
- `profileId` (required) — Profile UUID
- `days` (optional) — Number of days (default: 30, max: 90)
**Response:**
```json
{
"metrics": [
{
"platform": "twitter",
"date": "2024-01-15",
"total_jobs": 25,
"successful_jobs": 23,
"failed_jobs": 1,
"retried_jobs": 2,
"rate_limited_jobs": 1,
"avg_duration_ms": 1840
}
]
}
```
### Retry Failed Post
Manually retry a failed scheduled post:
```
POST /api/v1/scheduled-posts/{scheduledPostId}/retry
```
**Request:**
```json
{
"profileId": "profile_uuid"
}
```
**Response:**
```json
{
"success": true,
"message": "Post queued for retry"
}
```
### Error Categories
The job queue automatically categorizes errors:
- `rate_limited` — Platform rate limit hit (auto-retry with exponential backoff)
- `auth_error` — Authentication/authorization failure (no retry)
- `network_error` — Network timeout/connection issues (auto-retry)
- `duplicate` — Duplicate post detected (no retry)
- `media_error` — Media upload/processing issue (auto-retry)
- `media_processing` — Instagram container not ready (auto-retry)
- `unknown` — Uncategorized error (auto-retry)
### Retry Behavior
- **Max Attempts**: 3 attempts by default
- **Backoff**: Exponential (1 min → 2 min → 4 min)
- **Rate Limits**: Automatically detected and backed off
- **Auth Errors**: Not retried (requires reconnection)
## Content Templates
Save and reuse content templates for faster posting.
### List Templates
Retrieve templates for a profile:
```
GET /api/v1/templates?profileId=profile_uuid&category=promotional&favorites=true
```
**Response:**
```json
{
"templates": [
{
"id": "tmpl_uuid",
"name": "Product Launch",
"description": "Standard product launch announcement",
"content": "🚀 Excited to announce our new product! Check it out at...",
"platforms": ["twitter", "linkedin", "instagram"],
"category": "promotional",
"is_favorite": true,
"usage_count": 15,
"created_at": "2024-01-01T00:00:00.000Z",
"updated_at": "2024-01-15T00:00:00.000Z"
}
]
}
```
### Create Template
Save a new content template:
```
POST /api/v1/templates
```
**Request:**
```json
{
"profileId": "profile_uuid",
"name": "Weekly Update",
"description": "Weekly team update template",
"content": "📊 This week's highlights:\n\n✅ {{achievement}}\n📈 {{metric}}\n🔜 {{next_week}}",
"platforms": ["twitter", "linkedin"],
"category": "internal",
"media": [
{
"file_path": "...",
"public_url": "...",
"mime_type": "image/jpeg"
}
]
}
```
**Response:**
```json
{
"success": true,
"template": {
"id": "tmpl_uuid",
"name": "Weekly Update",
"content": "📊 This week's highlights...",
"platforms": ["twitter", "linkedin"]
}
}
```
### Get Template
Retrieve a single template with media:
```
GET /api/v1/templates/{templateId}
```
**Response:**
```json
{
"template": {
"id": "tmpl_uuid",
"name": "Weekly Update",
"content": "...",
"platforms": ["twitter"],
"media": [
{
"id": "tmpl_media_uuid",
"file_path": "...",
"public_url": "...",
"mime_type": "image/jpeg"
}
]
}
}
```
### Update Template
Modify template content or mark as favorite:
```
PATCH /api/v1/templates/{templateId}
```
**Request:**
```json
{
"name": "Updated Name",
"content": "New content...",
"is_favorite": true,
"increment_usage": true
}
```
### Delete Template
Remove a template:
```
DELETE /api/v1/templates/{templateId}
```
**Response:**
```json
{
"success": true
}
```
## Team Management
Invite team members and manage roles for collaborative posting.
### List Profile Members
Get all members of a profile:
```
GET /api/v1/profiles/{profileId}/members
```
**Response:**
```json
{
"members": [
{
"id": "mem_uuid",
"user_id": "user_uuid",
"role": "owner",
"invited_by": null,
"invitation_status": "active",
"permissions": {},
"joined_at": "2024-01-01T00:00:00.000Z",
"user": {
"id": "user_uuid",
"name": "John Doe",
"email": "john@example.com",
"avatar_url": "https://..."
}
}
]
}
```
**Roles:**
- `owner` — Full control, can manage members and delete profile
- `admin` — Can post, schedule, and manage content
- `member` — Can post and schedule
### Invite Member
Send an invitation to join the profile:
```
POST /api/v1/profiles/{profileId}/invitations
```
**Request:**
```json
{
"email": "teammate@example.com",
"role": "admin"
}
```
**Response:**
```json
{
"success": true,
"invitation": {
"id": "inv_uuid",
"email": "teammate@example.com",
"role": "admin",
"expiresAt": "2024-01-15T00:00:00.000Z",
"inviteUrl": "https://app.com/invite/abc123..."
}
}
```
### Accept Invitation
Accept an invitation to join a profile:
```
POST /api/v1/invitations/{token}/accept
```
**Response:**
```json
{
"success": true,
"profileId": "profile_uuid",
"role": "admin"
}
```
### Update Member Role
Change a member's role (owner only):
```
PATCH /api/v1/profiles/{profileId}/members/{memberId}
```
**Request:**
```json
{
"role": "admin"
}
```
### Remove Member
Remove a member from the profile (owner only):
```
DELETE /api/v1/profiles/{profileId}/members/{memberId}
```
### Leave Profile
Remove yourself from a profile (cannot be owner):
```
DELETE /api/v1/profiles/{profileId}/leave
```
## Webhooks
Configure outbound webhooks to receive real-time events when posts are published, scheduled, or fail.
### List Webhook Endpoints
```
GET /api/v1/webhook-endpoints?profileId=profile_uuid
```
**Response:**
```json
{
"endpoints": [
{
"id": "wh_uuid",
"url": "https://your-app.com/webhook",
"events": ["post.published", "post.failed"],
"is_active": true,
"max_retries": 3,
"last_delivery_at": "2024-01-15T10:30:00.000Z",
"last_delivery_status": "success",
"created_at": "2024-01-01T00:00:00.000Z"
}
]
}
```
### Create Webhook Endpoint
```
POST /api/v1/webhook-endpoints
```
**Request:**
```json
{
"profileId": "profile_uuid",
"url": "https://your-app.com/webhook",
"secret": "your_webhook_signing_secret",
"events": ["post.published", "post.failed", "post.scheduled"],
"maxRetries": 5
}
```
**Events:**
- `post.published` — Fired when a post is successfully published
- `post.failed` — Fired when a post fails to publish
- `post.scheduled` — Fired when a post is scheduled
- `*` — Subscribe to all events
**Response:**
```json
{
"success": true,
"endpoint": {
"id": "wh_uuid",
"url": "https://your-app.com/webhook",
"events": ["post.published", "post.failed", "post.scheduled"],
"is_active": true,
"max_retries": 5
}
}
```
### Update Webhook Endpoint
```
PATCH /api/v1/webhook-endpoints/{webhookId}
```
**Request:**
```json
{
"is_active": false,
"events": ["post.published"],
"max_retries": 3
}
```
### Delete Webhook Endpoint
```
DELETE /api/v1/webhook-endpoints/{webhookId}
```
### List Delivery History
```
GET /api/v1/webhook-endpoints/{webhookId}/deliveries?limit=50
```
**Response:**
```json
{
"deliveries": [
{
"id": "whd_uuid",
"event_type": "post.published",
"status": "delivered",
"attempt_number": 1,
"response_status": 200,
"delivered_at": "2024-01-15T10:30:00.000Z",
"created_at": "2024-01-15T10:30:00.000Z"
}
]
}
```
### Retry Failed Delivery
```
POST /api/v1/webhook-deliveries/{deliveryId}/retry
```
**Response:**
```json
{
"success": true,
"message": "Delivery queued for retry"
}
```
### Webhook Payload Format
Webhook payloads are signed with HMAC-SHA256:
```json
{
"event_type": "post.published",
"timestamp": "2024-01-15T10:30:00.000Z",
"profile_id": "profile_uuid",
"data": {
"post_id": "post_uuid",
"platform": "twitter",
"content": "Hello World!",
"published_at": "2024-01-15T10:30:00.000Z",
"external_post_id": "1234567890"
}
}
```
### Signature Verification
Verify the webhook signature in your receiver:
```javascript
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(payload));
const expected = hmac.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express example
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifyWebhook(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
res.status(200).send('OK');
});
```
### Ready-to-run example
The repository includes copy-paste receiver examples at `webhook-receiver.example.js` and `webhook-receiver.example.ts`.
Run it with:
```bash
WEBHOOK_SECRET=your_webhook_secret node webhook-receiver.example.js
```
Then point an AmplifySchedule webhook endpoint at the public URL for `/webhook`.
The example:
- verifies `X-Webhook-Signature`
- accepts `post.published`, `post.failed`, and `post.scheduled`
- returns `200 OK` on success
- logs the event payload to the console
### Test locally
1. Start the receiver on your machine.
2. Expose it with a tunnel tool such as ngrok.
3. Create a webhook endpoint in AmplifySchedule pointing to the public tunnel URL.
4. Publish or schedule a post.
5. Confirm the event arrives and the receiver returns `200 OK`.
If you use the TypeScript example, run it with your preferred TS runtime or compile step and keep the same `WEBHOOK_SECRET` and `/webhook` path.
## Analytics
### Overview
Get posting analytics for a profile:
```
GET /api/v1/analytics/overview?profileId=profile_uuid
```
**Response:**
```json
{
"analytics": {
"totalPosts": 42,
"draftPosts": 5,
"scheduledPosts": 3,
"publishingPosts": 1,
"publishedPosts": 30,
"failedPosts": 3,
"totalTargets": 50,
"successfulTargets": 45,
"failedTargets": 5,
"platformBreakdown": [
{
"platform": "twitter",
"targets": 20,
"published": 18,
"failed": 2
}
]
}
}
```
### Real-time Dashboard
Retrieve unified dashboard totals, platform breakdown, daily trends, and top posts:
```
GET /api/analytics/realtime?profileId=profile_uuid&days=30
```
### Post Timeline
Get time-series post performance by day and platform:
```
GET /api/analytics/post-timeline?profileId=profile_uuid&days=30&platform=instagram
```
**Query Parameters:**
- `profileId` (required)
- `days` (optional, default `30`, max `180`)
- `platform` (optional, e.g. `instagram`)
### Content Decay
Measure engagement-rate decay from peak to latest snapshots:
```
GET /api/analytics/content-decay?profileId=profile_uuid&days=30&platform=linkedin
```
Returns per-platform decay summaries and top decayed posts.
### Posting Frequency
Get publishing cadence by day, hour, and platform:
```
GET /api/analytics/posting-frequency?profileId=profile_uuid&days=30&platform=facebook
```
### Best Time (Analytics-derived)
Get top posting time slots derived from historical performance:
```
GET /api/analytics/best-time?profileId=profile_uuid&days=30&platform=tiktok
```
### Analytics Capability Matrix
Get platform-level analytics support and connection status:
```
GET /api/analytics/capabilities?profileId=profile_uuid
```
### Analytics Sync Health
Get freshness and health diagnostics for analytics sync:
```
GET /api/analytics/sync-health?profileId=profile_uuid
```
**Response includes:**
- `summary` counts (`healthy`, `stale`, `needsAttention`, `noData`, `unsupported`, `disconnected`)
- `byAccount` status with `lastMetricAt`, `freshnessHours`, `reason`
## Advanced Scheduling
### Get Best Posting Times
Get AI-recommended optimal posting times based on historical engagement data:
```
GET /api/v1/scheduling/best-times?profileId=profile_uuid&platform=twitter
```
**Response (with historical data):**
```json
{
"bestTimes": [
{
"dayOfWeek": 2,
"hourOfDay": 9,
"avgEngagementScore": 85.5,
"postsCount": 12
},
{
"dayOfWeek": 3,
"hourOfDay": 14,
"avgEngagementScore": 72.3,
"postsCount": 8
}
],
"isDefault": false
}
```
**Response (no data yet - defaults):**
```json
{
"bestTimes": [
{ "day_of_week": 2, "hour_of_day": 9, "reason": "Weekday morning engagement" },
{ "day_of_week": 3, "hour_of_day": 12, "reason": "Midweek lunch break" }
],
"isDefault": true,
"message": "No historical data yet. Using recommended defaults."
}
```
**Day of week mapping:**
- `0` = Sunday
- `1` = Monday
- `2` = Tuesday
- `3` = Wednesday
- `4` = Thursday
- `5` = Friday
- `6` = Saturday
### List Recurring Schedules
Get all recurring post schedules:
```
GET /api/v1/recurring-schedules?profileId=profile_uuid
```
**Response:**
```json
{
"schedules": [
{
"id": "rs_uuid",
"name": "Daily Motivation",
"content": "Your daily dose of motivation! 💪",
"platforms": ["twitter", "linkedin"],
"recurrence_pattern": "daily",
"time_of_day": "09:00:00",
"timezone": "America/New_York",
"is_active": true,
"next_scheduled_at": "2024-01-16T09:00:00.000Z",
"last_posted_at": "2024-01-15T09:00:00.000Z"
}
]
}
```
### Create Recurring Schedule
Set up automated recurring posts:
```
POST /api/v1/recurring-schedules
```
**Request (Daily):**
```json
{
"profileId": "profile_uuid",
"name": "Morning Update",
"content": "Good morning! Here's today's update.",
"platforms": ["twitter", "linkedin"],
"recurrencePattern": "daily",
"timeOfDay": "08:00:00",
"timezone": "America/New_York"
}
```
**Request (Weekly):**
```json
{
"profileId": "profile_uuid",
"name": "Monday Motivation",
"content": "Happy Monday! Let's crush this week! 💪",
"platforms": ["instagram", "facebook"],
"recurrencePattern": "weekly",
"recurrenceConfig": { "day_of_week": 1 },
"timeOfDay": "09:00:00",
"timezone": "UTC"
}
```
**Request (Monthly):**
```json
{
"profileId": "profile_uuid",
"name": "Monthly Newsletter",
"content": "This month's highlights...",
"platforms": ["linkedin"],
"recurrencePattern": "monthly",
"recurrenceConfig": { "day_of_month": 1 },
"timeOfDay": "10:00:00",
"timezone": "Europe/London"
}
```
**Recurrence Patterns:**
- `daily` — Every day at specified time
- `weekly` — Weekly on specified day
- `monthly` — Monthly on specified date
- `custom` — Custom pattern (future use)
**Recurrence Config:**
- `day_of_week` — 0-6 (Sunday-Saturday) for weekly
- `day_of_month` — 1-31 for monthly
**Response:**
```json
{
"success": true,
"schedule": {
"id": "rs_uuid",
"name": "Morning Update",
"recurrencePattern": "daily",
"timeOfDay": "08:00:00",
"nextScheduledAt": "2024-01-16T08:00:00.000Z"
}
}
```
### Update Recurring Schedule
```
PATCH /api/v1/recurring-schedules/{scheduleId}
```
**Request:**
```json
{
"name": "Updated Name",
"isActive": false,
"timeOfDay": "10:00:00",
"recurrencePattern": "weekly",
"recurrenceConfig": { "day_of_week": 3 }
}
```
### Delete Recurring Schedule
```
DELETE /api/v1/recurring-schedules/{scheduleId}
```
## Platform-Specific Constraints
### Twitter / X
- Text: Yes (280 characters)
- Images: Yes (single image)
- Video: No
- Scheduling: Yes
### LinkedIn
- Text: Yes
- Images: Yes (single image, uses asset upload)
- Video: No
- Scheduling: Yes
### Instagram
- Text: Yes (caption)
- Images: Yes (single image)
- Video: Yes (single video, reels format)
- Scheduling: Yes
- Requires: Business account
### Facebook
- Text: Yes
- Images: Yes (single image)
- Video: Yes (single video)
- Scheduling: Yes
- Requires: Page with manage_posts permission
### Threads
- Text: Yes
- Images: Yes (single image)
- Video: Yes (single video)
- Scheduling: Yes
### TikTok
- Text: Yes (title only)
- Images: No
- Video: Yes (required)
- Scheduling: Yes
### YouTube
- Text: Yes (title/description)
- Images: No
- Video: Yes (required)
- Scheduling: Yes
### Pinterest
- Text: Yes (description)
- Images: Yes (required, single image)
- Video: No
- Scheduling: Yes
- Requires: Default board configured
### Reddit
- Text: Yes (self posts)
- Images: No
- Video: No
- Scheduling: Yes
### Snapchat
- Status: Unsupported (Marketing API only)
### Bluesky
- Status: Unsupported (AT Protocol implementation incomplete)
## Error Handling
All errors follow this format:
```json
{
"success": false,
"error": "Human readable error message"
}
```
Common HTTP status codes:
- `200` — Success
- `400` — Bad request (missing/invalid parameters)
- `401` — Unauthorized (invalid API key)
- `403` — Forbidden (profile access denied)
- `404` — Resource not found
- `500` — Server error
## Multi-Platform Posting Rules
When posting to multiple platforms simultaneously:
1. **Media compatibility** — All selected platforms must support the media type
2. **Single asset** — Only one media item per post (image or video)
3. **No mixed types** — Cannot combine images and videos in one post
4. **Scheduling compatibility** — All platforms must support scheduling
5. **Media requirements** — Platforms requiring media must receive it
The API validates these rules and returns errors for invalid combinations.
## Webhooks (Optional)
Configure webhooks to receive real-time events:
```
POST /api/v1/webhooks/configure
```
**Request:**
```json
{
"url": "https://your-app.com/webhook",
"events": ["post.published", "post.failed"],
"secret": "webhook_signing_secret"
}
```
Webhook payloads are signed with `X-Webhook-Signature` header using HMAC-SHA256.
## Rate Limits
- Posts: 100 per hour per profile
- Media uploads: 50 per hour per profile
- API requests: 1000 per hour per API key
Rate limit headers:
```
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1704067200
```
## Support
For integration support, please use the Contact Us form on the public site.