dropspace
# 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):
```bash
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](https://www.dropspace.dev/llms.txt) | [OpenAPI](https://www.dropspace.dev/openapi.json)
## 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
```bash
# 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)
```bash
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
```bash
# 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
```bash
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
```bash
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
```bash
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
```bash
# 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:**
1. First page visit → middleware captures referrer + UTM params into `ds_attr` cookie (30-day expiry)
2. 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:**
```sql
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
```bash
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
标签
skill
ai