Dropspace API Skill
Multi-platform social media publishing with AI-generated content, per-post analytics, and persona-based writing styles.
Setup
Set your API key with least-privilege scopes (only grant what you need):
export DROPSPACE_API_KEY="ds_live_..." # from dropspace.dev/settings/api
Recommended: Create a key with only read, write, generate scopes. Do not grant publish, admin, or delete unless you explicitly need autonomous publishing or account management. You can always create a separate key with broader scopes later.
Base URL: https://api.dropspace.dev
Auth header: -H "Authorization: Bearer $DROPSPACE_API_KEY"
Also available as MCP server: npx @jclvsh/dropspace-mcp
Machine-readable docs: llms.txt | OpenAPI
API Key Scopes
Keys can be scoped to limit what they're allowed to do. Use least-privilege — only grant scopes your workflow requires.
| Scope | What it allows |
|---|
read | View launches, personas, connections, analytics, usage |
write | Create and update launches and personas |
generate | AI content generation (text from descriptions) |
publish | Publish launches to connected social accounts |
delete | Delete launches and personas |
admin | Manage API keys and webhooks |
| Use Case | Recommended Scopes |
|---|
| AI agents / MCP (draft + review) | read, write, generate |
| AI agents / MCP (auto-publish) | read, write, generate, publish |
| CI/CD publishing | read, publish |
| Monitoring / dashboards | read |
| Full access (admin tools) | all scopes |
Missing scope returns 403 with AUTH_002.
Audit Logging
Destructive/sensitive API operations are automatically logged: delete launch/persona, publish, create/revoke key, create/delete/rotate webhook.
Supported Platforms
twitter, linkedin, reddit, instagram, tiktok, facebook, producthunt, hackernews, substack
Per-Platform Character Limits
Twitter 280/tweet (25,000 for X Premium) × 6 tweets (use thread array or content string), LinkedIn 3,000, Instagram 2,200, Reddit 3,000, Facebook 3,000, TikTok 4,000, Product Hunt 500, Hacker News 2,000, Substack 3,000
Core Workflow: Create & Publish
1. Check connected platforms
# User's OAuth connections
curl -s https://api.dropspace.dev/connections \
-H "Authorization: Bearer $DROPSPACE_API_KEY"
# Official Dropspace accounts available for posting
curl -s https://api.dropspace.dev/dropspace/status \
-H "Authorization: Bearer $DROPSPACE_API_KEY"
Connections: OAuth-connected accounts with platform, username, account_type, is_active, expires_at. Managed via dashboard.
Dropspace status: Shows which official Dropspace accounts (facebook, linkedin, twitter, reddit, instagram, tiktok) are connected and available for the dropspace_platforms field.
2. Create a launch (AI generates platform-specific content)
curl -s -X POST https://api.dropspace.dev/launches \
-H "Authorization: Bearer $DROPSPACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "launch title",
"product_description": "what to post about (1-2000 chars)",
"platforms": ["twitter", "linkedin", "reddit"],
"product_url": "https://optional-url.com",
"persona_id": "optional-uuid",
"scheduled_date": "optional ISO 8601 (≥15 min future)",
"publish": "optional boolean — immediately publish after creation (returns 202, mutually exclusive with scheduled_date, requires write+publish scopes)",
"wait": "optional boolean — wait for publishing to complete and return post URLs inline (requires publish: true, returns 200 with posting_status)",
"dropspace_platforms": ["twitter"],
"user_platform_accounts": {"twitter": "account-id"},
"media": [
{"source": "url", "url": "https://example.com/image.png"},
{"source": "base64", "data": "iVBOR...", "filename": "slide.png", "mime_type": "image/png"}
],
"media_attach_platforms": ["twitter", "linkedin"],
"media_mode": "images|video",
"custom_content": "single text for all platforms (validated against lowest char limit)",
"custom_content_reddit_title": "reddit title when using custom_content (max 300 chars)",
"platform_contents": {
"twitter": {"thread": ["tweet 1", "tweet 2", "tweet 3"]},
"reddit": {"title": "...", "content": "..."},
"linkedin": {"content": "custom post"},
"tiktok": {
"content": "caption text",
"tiktok_settings": {
"privacy_level": "PUBLIC_TO_EVERYONE",
"auto_add_music": true,
"allow_comments": true,
"allow_duet": true,
"allow_stitch": true
}
}
}
}'
Content auto-generated via Claude from description + scraped URL + persona.
Content options (mutually exclusive):
- Omit both → full AI generation for all platforms
platform_contents → per-platform custom content. Platforms without content get AI-generated. Reddit requires title. Product Hunt/HN support optional title (max 60/80 chars). Twitter supports thread (string[], each ≤280 chars or ≤25,000 for X Premium, max 6 tweets) instead of content — mutually exclusive with content.
custom_content → string distributed to all platforms (validated against most restrictive char limit), or string[] to create a Twitter thread (each ≤280 chars or ≤25,000 for X Premium, max 6) that joins with double newlines for other platforms. Array form requires Twitter in platforms. Use custom_content_reddit_title if Reddit is included.
user_platform_accounts key format:
- Simple keys:
"twitter", "reddit", "instagram", "tiktok"
- LinkedIn personal:
"linkedin:personal" → personal profile
- LinkedIn org:
"linkedin:organization:<org_id>" → company page
- Facebook page:
"facebook:page:<page_id>"
- Multiple keys allowed (e.g. post to personal + org simultaneously)
TikTok settings (platform_contents.tiktok.tiktok_settings):
privacy_level (REQUIRED before publishing): "PUBLIC_TO_EVERYONE", "FOLLOWER_OF_CREATOR", "MUTUAL_FOLLOW_FRIENDS", or "SELF_ONLY"
- Optional booleans:
allow_comments, allow_duet, allow_stitch, is_commercial, is_your_brand, is_branded_content, auto_add_music
- Branded content (
is_branded_content: true) cannot use SELF_ONLY privacy
- Can be set at creation or updated via PATCH (deep-merged)
Inline media upload (media field): Upload images/videos inline via URL or base64 — server handles storage upload and populates media_assets in the response. Max 10 items (9 images + 1 video). Images: jpeg/png/webp/gif (5MB). Videos: mp4/mov (512MB via URL, 4MB via base64). media_attach_platforms and media_mode auto-inferred if not set. Mutually exclusive with media_assets.
Pre-uploaded media (media_assets field): For files already in storage — requires id, url, type, filename, size, mime_type. Mutually exclusive with media.
Media limits: Instagram/Facebook 10, Reddit 20, TikTok 35.
Inline publish (publish + wait fields):
publish: true → immediately publish after creation (returns 202). Mutually exclusive with scheduled_date. Requires both write and publish scopes.
wait: true (requires publish: true) → wait for publishing to complete synchronously and return 200 with posting_status containing per-platform results and post URLs. Typical wait: 10-30s for text platforms, up to 60s+ with Instagram/TikTok.
- If publish validation fails after creation, returns 201 with
publish_error: { code, message } — retry via POST /launches/:id/publish.
Publish validation (LAUNCH_007): When publishing fails validation, the response includes a details array listing all blocking issues. Checks include: char limits (all platforms), Reddit title ≤300 chars, Reddit video needs video+thumbnail, Reddit images need images, Instagram reel needs video, Instagram carousel needs ≥2 images, TikTok requires tiktok_settings.privacy_level, TikTok video needs video, TikTok photo needs images.
Instagram requires media. TikTok requires video or photos.
media_mode: "images" or "video" (auto-inferred when using media).
Upload errors: UPLOAD_001 (unsupported type), UPLOAD_002 (too large), UPLOAD_003 (storage upload failed).
3. Review & edit content
# Edit specific platform content:
curl -s -X PATCH https://api.dropspace.dev/launches/LAUNCH_ID \
-H "Authorization: Bearer $DROPSPACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"platform_contents": {"twitter": {"thread": ["tweet 1", "tweet 2", "tweet 3"]}}}'
# Or regenerate for specific platforms:
curl -s -X POST https://api.dropspace.dev/launches/LAUNCH_ID/generate-content \
-H "Authorization: Bearer $DROPSPACE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"platforms": ["twitter"]}'
4. Publish
curl -s -X POST https://api.dropspace.dev/launches/LAUNCH_ID/publish \
-H "Authorization: Bearer $DROPSPACE_API_KEY"
Returns 202. Async — poll /status or use webhooks (launch.completed, launch.failed, launch.partial).
5. Check posting status
curl -s https://api.dropspace.dev/launches/LAUNCH_ID/status \
-H "Authorization: Bearer $DROPSPACE_API_KEY"
Returns per-platform posting logs with status, post_url, post_id, error_message, error_code, attempt_count, posted_at.
6. Get per-post analytics with engagement metrics
curl -s https://api.dropspace.dev/launches/LAUNCH_ID/analytics \
-H "Authorization: Bearer $DROPSPACE_API_KEY"
Returns per-platform metrics (live refresh — fetches from platform APIs when stale >5 min):
- Twitter: likes, retweets, replies, quotes, bookmarks, impressions, urlClicks, profileClicks
- LinkedIn: impressions, uniqueImpressions, likes, comments, shares, clicks, engagement
- Facebook: reactions, comments, shares
- Instagram: views, engagement, saved, likes, comments, shares
- Reddit: score, upvotes, upvoteRatio, comments
- TikTok: views, likes, comments, shares
Response includes fetched_at, next_refresh_at (fetched_at + 5 min), and per-platform cache_status (fresh | refreshed | stale). Calling the endpoint triggers a refresh automatically when data is stale.
Batch Analytics
# Fetch analytics for up to 100 launches in one request
curl -s "https://api.dropspace.dev/launches/analytics?ids=uuid1,uuid2,uuid3" \
-H "Authorization: Bearer $DROPSPACE_API_KEY"
Returns { data: [...], errors: [...] } with partial success support. Each item includes launch_id, platforms (with per-platform metrics + deletion info), and status. No fetched_at/next_refresh_at (those are single-launch only). Uses cached metrics — does not trigger live refresh from platforms.
Post Deletion Detection
Analytics responses include deletion detection fields per platform:
is_deleted (boolean) — true if the post was detected as deleted/removed
deleted_detected_at (ISO 8601) — when deletion was first detected
deletion_reason — one of: not_found (404), gone (410), creator_deleted, moderation_removed, account_deleted, spam_filtered
Platform-specific detection:
- LinkedIn: Detects
REMOVED_BY_LINKEDIN_TAKEDOWN
- Reddit: Checks
removed_by_category
- Twitter: Catches authorization errors as possible account deletion
- TikTok/Instagram/Facebook: Detects 404/410 responses
Deletion is detected during the analytics cron refresh cycle (runs every 5 minutes for recently active posts).
First-Touch Attribution (Built-in)
Dropspace has built-in signup attribution via Next.js middleware → httpOnly cookie → profiles table.
How it works:
- First page visit → middleware captures referrer + UTM params into
ds_attr cookie (30-day expiry)
- On signup → auth callback writes to
profiles.signup_referrer, signup_utm_source, signup_utm_medium, signup_utm_campaign
Add UTM params to links:
https://www.dropspace.dev?utm_source=tiktok&utm_medium=social&utm_campaign=slideshow_feb
Query attribution data via Supabase:
SELECT signup_referrer, signup_utm_source, signup_utm_medium, signup_utm_campaign, created_at
FROM profiles
WHERE created_at >= now() - interval '7 days'
AND signup_utm_source IS NOT NULL;
Launch Management
| Method | Endpoint | Description |
|---|
| GET | /launches | List all (paginated: page, page_size 1-100) |
| POST | /launches | Create with AI-generated content |
| GET | /launches/:id | Get single with posting_status |
| PATCH | /launches/:id | Update draft/scheduled/cancelled (name, content, schedule, status, media, dropspace_platforms, user_platform_accounts) |
| DELETE | /launches/:id | Delete (not running) |
| POST | /launches/:id/publish | Publish (async, 202) |
| POST | /launches/:id/retry | Retry failed platforms only |
| POST | /launches/:id/retry-content | Retry content generation for failed platforms (optional platforms filter) |
| POST | /launches/:id/generate-content | Regenerate AI content (optional platforms) |
| GET | /launches/:id/status | Detailed posting logs |
| GET | /launches/:id/analytics | Per-post engagement metrics (live refresh, 5-min cache) |
| DELETE | /launches/:id/posts/:logId | Delete a single published post from platform (logId from /status) |
| DELETE | /launches/:id/posts | Delete all published posts for a launch |
Launch Status Lifecycle
Statuses in lifecycle order: draft → manual → trigger → scheduled → running → completed / partial / failed / cancelled
| Status | Cron auto-publishes? | Dashboard action buttons? | Can publish via API? | Use case |
|---|
draft | ❌ | ❌ | ✅ | Initial creation, not ready |
manual | ❌ | ✅ | ✅ | Ready, waiting for manual publish |
trigger | ❌ | ✅ | ✅ | Ready, waiting for explicit trigger |
scheduled | ✅ (when scheduled_date ≤ now) | ✅ | ✅ | Will auto-publish at scheduled time |
running | — | — | — | Currently publishing |
completed | — | — | — | All platforms succeeded |
partial | — | — | ✅ (retry) | Some platforms failed |
failed | — | — | ✅ (retry) | All platforms failed |
cancelled | — | — | ✅ (retry) | Manually cancelled |
Key rules:
- The publish cron only picks up
scheduled launches with scheduled_date ≤ now. All other statuses require manual action.
- Status is auto-derived on PATCH for editable launches (draft/manual/trigger/scheduled). The server overrides your requested status based on configuration:
- Has
user_platform_accounts or dropspace_platforms + scheduled_date → scheduled
- Has
user_platform_accounts or dropspace_platforms + no scheduled_date → trigger
- Has content + no posting accounts →
manual
- This means you cannot force
manual on a launch that has posting accounts. It will auto-transition to trigger.
- To unschedule without losing settings: PATCH with
{"scheduled_date": null}. The server will auto-set status to trigger (since posting accounts exist). Do NOT set to draft (loses dashboard action buttons) or DELETE (soft-deletes, hard to recover).
- To pause a scheduled launch: PATCH
{"scheduled_date": null}. Status becomes trigger automatically.
POST /launches/:id/publish triggers immediate publishing regardless of scheduled_date. Never call this on scheduled launches unless you want them published now.
manual = no posting accounts configured (publish manually). trigger = has posting accounts but no schedule (publish on demand).
PATCH fields: name (1-200 chars), scheduled_date (ISO 8601 | null to unschedule), status, platforms, product_description (max 10,000 chars), product_url (empty string to clear), persona_id (null to clear), platform_contents (deep-merged per platform — existing platforms preserved, within a platform included fields replace old values), user_platform_accounts, dropspace_platforms, media/media_assets (replaces entirely), media_attach_platforms, media_mode. All optional but ≥1 required. Running launch can only be cancelled. TikTok tiktok_settings is deep-merged (can update individual fields without overwriting others).
Note: Media generation (AI image/video via fal.ai) is not available through the public API. Media upload (attaching existing images/videos) is supported.
Deleting Published Posts
- Requires
delete scope on API key (enabled)
- Works on: Twitter, Facebook, LinkedIn, Reddit
- Does NOT work on: Instagram, TikTok (returns DELETE_NOT_SUPPORTED — manual deletion required)
logId is the posting_log UUID from the /status endpoint
- If post was already deleted on platform (404), treated as successful deletion
- On success, posting log status updated to
deleted
- Use Dropspace API:
DELETE /launches/:id/posts/:logId or DELETE /launches/:id/posts
Personas (Writing Styles)
| Method | Endpoint | Description |
|---|
| GET | /personas | List all (paginated) |
| POST | /personas | Create {"name": "..."} |
| GET | /personas/:id | Get with all writing samples + analysis |
| PATCH | /personas/:id | Update name/samples (custom, twitter, reddit, facebook, instagram, tiktok, linkedin — max 50 each) |
| DELETE | /personas/:id | Delete (not if used by launches) |
| POST | /personas/:id/analyze | Trigger AI analysis (async, 202). Optional: platforms, include_custom_samples |
Build statuses: idle, building, complete, error
Connections
| Method | Endpoint | Description |
|---|
| GET | /connections | List OAuth platform connections (read-only, paginated) |
Returns: id, platform, entity_id, account_info (username, display_name), account_type, is_active, expires_at.
Dropspace Official Accounts
| Method | Endpoint | Description |
|---|
| GET | /dropspace/status | Check which official Dropspace accounts are connected |
Returns all 6 auto-post platforms (facebook, linkedin, twitter, reddit, instagram, tiktok) with connected status and account_name. Use connected_platforms to know valid values for dropspace_platforms field.
API Keys Management
| Method | Endpoint | Description |
|---|
| GET | /keys | List all API keys |
| GET | /keys/me | Get current API key info (id, name, scopes) — no scope required |
| POST | /keys | Create new key (max 10). Body: {"name": "...", "scopes": ["read", "write", ...]} |
| PATCH | /keys/:id | Rename key. Body: {"name": "..."} |
| DELETE | /keys/:id | Revoke key permanently |
Key shown only once on creation. Keys begin with ds_live_.
Default scopes: read, write, publish, generate. Available: read, write, delete, publish, generate, admin.
Webhooks
| Method | Endpoint | Description |
|---|
| GET | /webhooks | List webhook endpoints |
| POST | /webhooks | Create endpoint (max 10). Body: {"url": "https://...", "events": [...]} |
| GET | /webhooks/:id | Get endpoint |
| PATCH | /webhooks/:id | Update url, events, or active status |
| DELETE | /webhooks/:id | Delete endpoint |
| POST | /webhooks/:id/rotate-secret | Rotate signing secret (new secret shown once) |
| GET | /webhooks/:id/deliveries | List delivery attempts (paginated) |
Webhook Events
| Event | Fired when |
|---|
| launch.completed | All platforms posted successfully |
| launch.failed | All platforms failed |
| launch.partial | Some succeeded, some failed |
| post.deleted | Post detected as deleted from platform (deletion_reason: not_found, gone, creator_deleted, moderation_removed, account_deleted, spam_filtered) |
| persona.analyzed | AI persona analysis finished |
Webhook Delivery
Payload envelope: { "id": "evt_...", "event": "...", "created_at": "...", "data": { ... } }
Headers: Content-Type, X-Dropspace-Signature (HMAC-SHA256), X-Dropspace-Event, X-Dropspace-Delivery (idempotency key).
Signature format: sha256=<hex-digest> of JSON body using webhook secret.
Retries: up to 3 with exponential backoff (QStash), 30s timeout. Return 2xx to acknowledge.
Usage / Plan Limits
curl -s https://api.dropspace.dev/usage \
-H "Authorization: Bearer $DROPSPACE_API_KEY"
Returns: plan name, billing period, limits (launches/month, personas, analyses/persona, regenerations/launch), and features (can_connect_own_accounts, can_post_to_official_accounts, allowed_platforms). Limit/remaining can be "unlimited" (string) for higher-tier plans. Personas is a lifetime limit.
Agent Payments (x402 + MPP)
Autonomous agents can use the API without accounts or subscriptions via two payment protocols:
x402 (crypto): USDC on Base via X-PAYMENT header (base64-encoded proof)
MPP (Stripe): Card charge via Authorization: Payment <base64url> header
- Only supported on launch endpoints (create, list, get, update, delete, publish, status)
- 5 free launches/month using official Dropspace accounts
- Beyond free tier: $0.50 per launch creation (paid at creation, publishing is always free)
- Agent users can only post to official Dropspace accounts (not connected user accounts)
- Rate limit: 30 req/min per agent identity (wallet address for x402, payer credential for MPP)
- On 402 response, body contains both
x402 and mpp challenge objects
Rate Limits
| Limit | Value |
|---|
| General API | 60 req/min per API key |
| Content generation | 30 req/min per user |
On 429: check Retry-After header (seconds). Rate limit headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix timestamp in milliseconds, not seconds).
Error Codes
| Code | HTTP | Meaning |
|---|
| AUTH_001 | 401 | Invalid/revoked key |
| AUTH_002 | 403 | Plan restriction |
| AUTH_003 | 403 | API key missing required scope |
| LAUNCH_001 | 404 | Launch not found |
| LAUNCH_002 | 429 | Launch/regen limit exceeded |
| LAUNCH_003 | 409 | Invalid status for operation |
| LAUNCH_004 | 409 | Not publishable / already publishing |
| LAUNCH_007 | 400 | Platform requirements not met |
| PERSONA_001 | 429 | Persona creation limit |
| PERSONA_002 | 404 | Persona not found |
| PERSONA_003 | 429 | Persona build limit |
| UPLOAD_001 | 400 | Unsupported media type (allowed: jpeg, png, webp, gif, mp4, mov) |
| UPLOAD_002 | 400 | Media file too large (images: 5MB, videos: 512MB URL / 4MB base64) |
| UPLOAD_003 | 400 | Media storage upload failed |
| PAYMENT_001 | 402 | Payment required (free tier exceeded) |
| PAYMENT_002 | 402 | Payment verification failed |
| PAYMENT_003 | 402 | MPP credential verification failed |
| RATE_001 | 429 | Rate limited |
| SERVER_001 | 500 | Internal server error |
| SERVER_002 | 400 | Validation error |
| SERVER_003 | 404 | Resource not found |
| SERVER_004 | 405 | Method not allowed |
| SERVER_005 | 400 | Invalid input / business rule violation |
| SERVER_008 | 500 | Database error |
| SERVER_009 | 409 | Conflict (duplicate name, race condition, already building) |
Open Source Pipeline
For the full automation pipeline (self-improving content engine, DJ clipper, photo slideshows), see the companion skills and repo:
- dropspace-content-engine — autonomous content generation + scheduling
- dropspace-dj-clipper — long recordings → short-form clips
- dropspace-photo-slideshows — event photos → daily TikTok slideshows
- Repo: https://github.com/joshchoi4881/dropspace-agents