From 25a8771c85be0fb3c0315c301d146d7a35d80b1d Mon Sep 17 00:00:00 2001 From: thinkmakeshipiot Date: Thu, 5 Mar 2026 03:20:34 +0200 Subject: [PATCH 1/2] docs: comprehensive update to match Late API as of March 2026 - Add profiles.md: CRUD endpoints for profile management (POST, GET, PATCH, DELETE) - Update connect.md: fix redirectUrl/authUrl field names, add headless OAuth mode, Reddit subreddit selection flow, Telegram access code flow - Update media.md: fix fileName/fileType/publicUrl fields, add customMedia per-platform, auto-compression docs, correct 5GB upload limit - Update platforms.md: add complete Reddit platformSpecificData section (subreddit, title, flairId, url, forceSelf) - Update accounts.md: add reddit-flairs endpoint, Reddit-specific curl examples - Update posts.md: add unpublish endpoint, customContent overrides, tags, accountIds shorthand - Update analytics.md: add 5 new endpoints from Feb-Mar 2026 (daily-metrics, best-time-to-post, content-decay, posting-frequency, post-timeline) - Update tools.md: add 4 validation endpoints from Feb 2026 (post-length, post dry-run, media, subreddit) - Update errors.md: add 409 duplicate content detection (Jan 2026), platform target error fields with errorCategory/errorSource (Feb 2026) - Update SKILL.md: reference profiles.md, update descriptions for all rule files Co-Authored-By: Claude Opus 4.6 --- SKILL.md | 17 +++++----- rules/accounts.md | 20 +++++++++++ rules/analytics.md | 85 +++++++++++++++++++++++++++++++++++++++------- rules/connect.md | 73 ++++++++++++++++++++++++++++++++++----- rules/errors.md | 43 +++++++++++++++++++++++ rules/media.md | 49 +++++++++++++++++++++----- rules/platforms.md | 28 +++++++++++++++ rules/posts.md | 15 +++++++- rules/profiles.md | 69 +++++++++++++++++++++++++++++++++++++ rules/tools.md | 57 ++++++++++++++++++++++++++++++- 10 files changed, 416 insertions(+), 40 deletions(-) create mode 100644 rules/profiles.md diff --git a/SKILL.md b/SKILL.md index 6b81183..47d9985 100644 --- a/SKILL.md +++ b/SKILL.md @@ -34,16 +34,17 @@ curl -X POST https://getlate.dev/api/v1/posts \ Read individual rule files for detailed documentation: - [rules/authentication.md](rules/authentication.md) - API key format, usage examples, core concepts -- [rules/posts.md](rules/posts.md) - Create, schedule, retry posts, bulk upload -- [rules/accounts.md](rules/accounts.md) - List accounts, health checks, follower stats -- [rules/connect.md](rules/connect.md) - OAuth flows, Bluesky app password, Telegram bot token -- [rules/platforms.md](rules/platforms.md) - Platform-specific data for all 13 platforms +- [rules/profiles.md](rules/profiles.md) - Create, list, update, delete profiles (account containers) +- [rules/posts.md](rules/posts.md) - Create, schedule, retry, unpublish posts, bulk upload +- [rules/accounts.md](rules/accounts.md) - List accounts, health checks, follower stats, Reddit flairs +- [rules/connect.md](rules/connect.md) - OAuth flows, headless mode, Bluesky, Telegram, Reddit subreddits +- [rules/platforms.md](rules/platforms.md) - Platform-specific data for all 13 platforms including Reddit - [rules/webhooks.md](rules/webhooks.md) - Configure webhooks, verify signatures, events -- [rules/media.md](rules/media.md) - Presigned uploads, supported formats, platform limits +- [rules/media.md](rules/media.md) - Presigned uploads, custom media per platform, auto-compression - [rules/queue.md](rules/queue.md) - Queue management, slots configuration -- [rules/analytics.md](rules/analytics.md) - YouTube daily views, LinkedIn analytics -- [rules/tools.md](rules/tools.md) - Media download, hashtag checker, transcripts -- [rules/errors.md](rules/errors.md) - Error codes, rate limits, publishing logs +- [rules/analytics.md](rules/analytics.md) - Daily metrics, best times, content decay, YouTube, LinkedIn +- [rules/tools.md](rules/tools.md) - Media download, validation endpoints, hashtag checker +- [rules/errors.md](rules/errors.md) - Error codes, rate limits, duplicate detection, platform errors - [rules/sdks.md](rules/sdks.md) - Direct API usage examples ## Supported Platforms diff --git a/rules/accounts.md b/rules/accounts.md index 7e77dea..e43723c 100644 --- a/rules/accounts.md +++ b/rules/accounts.md @@ -26,6 +26,7 @@ | `PUT` | `/v1/accounts/{accountId}/pinterest-boards` | Set default Pinterest board | | `GET` | `/v1/accounts/{accountId}/reddit-subreddits` | List user's subreddits | | `PUT` | `/v1/accounts/{accountId}/reddit-subreddits` | Set default subreddit | +| `GET` | `/v1/accounts/{accountId}/reddit-flairs` | List available flairs for a subreddit | ## List Accounts @@ -53,3 +54,22 @@ Response indicates if tokens are valid or need reconnection. | `DELETE` | `/v1/account-groups/{groupId}` | Delete account group | Account groups let you organize accounts for bulk posting operations. + +## Reddit-Specific Account Endpoints + +```bash +# List subreddits the Reddit account can post to +curl "https://getlate.dev/api/v1/accounts/ACCOUNT_ID/reddit-subreddits" \ + -H "Authorization: Bearer YOUR_API_KEY" + +# Set default posting subreddit +curl -X PUT "https://getlate.dev/api/v1/accounts/ACCOUNT_ID/reddit-subreddits" \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"subreddit": "distrilicious"}' + +# List available flairs for a subreddit (many subreddits require flair) +curl "https://getlate.dev/api/v1/accounts/ACCOUNT_ID/reddit-flairs?subreddit=distrilicious" \ + -H "Authorization: Bearer YOUR_API_KEY" +# Returns: array of { id, text, ... } — use id as flairId in platformSpecificData +``` diff --git a/rules/analytics.md b/rules/analytics.md index 923bbef..6963472 100644 --- a/rules/analytics.md +++ b/rules/analytics.md @@ -5,15 +5,76 @@ | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | `/v1/analytics` | Get general analytics | -| `GET` | `/v1/analytics/youtube/daily-views` | Get YouTube video daily views | -| `GET` | `/v1/accounts/{accountId}/linkedin-aggregate-analytics` | LinkedIn account analytics | -| `GET` | `/v1/accounts/{accountId}/linkedin-post-analytics` | LinkedIn post analytics | -| `GET` | `/v1/accounts/follower-stats` | Follower count history | +| `GET` | `/v1/analytics/daily-metrics` | Daily aggregated metrics with platform breakdown | +| `GET` | `/v1/analytics/best-time-to-post` | Optimal posting times by day/hour | +| `GET` | `/v1/analytics/content-decay` | Engagement accumulation over time | +| `GET` | `/v1/analytics/posting-frequency` | Posting cadence vs engagement correlation | +| `GET` | `/v1/analytics/post-timeline` | Daily metric evolution for a specific post | +| `GET` | `/v1/analytics/youtube-daily-views` | YouTube video daily view counts | +| `GET` | `/v1/analytics/linkedin-aggregate` | LinkedIn account aggregate analytics | +| `GET` | `/v1/analytics/linkedin-post` | LinkedIn post analytics by URN | +| `GET` | `/v1/analytics/follower-stats` | Follower count history and growth | + +## General Analytics + +```bash +curl "https://getlate.dev/api/v1/analytics?profileId=PROFILE_ID" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Optional filter: `?postId=POST_ID` to get analytics for a specific post. + +Analytics responses include `latePostId` field for correlation with scheduled posts (added Jan 2026). + +## Daily Metrics (Feb 2026) + +```bash +curl "https://getlate.dev/api/v1/analytics/daily-metrics?profileId=PROFILE_ID&startDate=2026-02-01&endDate=2026-02-28" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Returns daily aggregated analytics metrics with platform breakdown. + +## Best Time to Post (Feb 2026) + +```bash +curl "https://getlate.dev/api/v1/analytics/best-time-to-post?profileId=PROFILE_ID" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Returns optimal posting slots by day/hour based on historical engagement data. + +## Content Decay (Feb 2026) + +```bash +curl "https://getlate.dev/api/v1/analytics/content-decay?postId=POST_ID" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Shows how engagement accumulates over time after publishing — useful for understanding content shelf life. + +## Posting Frequency (Feb 2026) + +```bash +curl "https://getlate.dev/api/v1/analytics/posting-frequency?profileId=PROFILE_ID" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Correlates posting cadence with engagement rates — helps find the optimal number of posts per day/week. + +## Post Timeline (Mar 2026) + +```bash +curl "https://getlate.dev/api/v1/analytics/post-timeline?postId=POST_ID" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Returns daily timeline showing metric evolution for a specific post, broken down by platform. ## YouTube Daily Views ```bash -curl "https://getlate.dev/api/v1/analytics/youtube/daily-views?accountId=ACCOUNT_ID&videoId=VIDEO_ID&startDate=2024-01-01&endDate=2024-01-31" \ +curl "https://getlate.dev/api/v1/analytics/youtube-daily-views?accountId=ACCOUNT_ID&videoId=VIDEO_ID&startDate=2026-01-01&endDate=2026-01-31" \ -H "Authorization: Bearer YOUR_API_KEY" ``` @@ -24,13 +85,13 @@ Response: "success": true, "videoId": "dQw4w9WgXcQ", "dateRange": { - "startDate": "2024-01-01", - "endDate": "2024-01-31" + "startDate": "2026-01-01", + "endDate": "2026-01-31" }, "totalViews": 15420, "dailyViews": [ { - "date": "2024-01-01", + "date": "2026-01-01", "views": 523, "estimatedMinutesWatched": 1045, "averageViewDuration": 120, @@ -50,19 +111,19 @@ Response: ```bash # Aggregate analytics for account -curl "https://getlate.dev/api/v1/accounts/ACCOUNT_ID/linkedin-aggregate-analytics" \ +curl "https://getlate.dev/api/v1/analytics/linkedin-aggregate?accountId=ACCOUNT_ID" \ -H "Authorization: Bearer YOUR_API_KEY" # Analytics for specific post -curl "https://getlate.dev/api/v1/accounts/ACCOUNT_ID/linkedin-post-analytics?postUrn=URN" \ +curl "https://getlate.dev/api/v1/analytics/linkedin-post?accountId=ACCOUNT_ID&postUrn=URN" \ -H "Authorization: Bearer YOUR_API_KEY" ``` ## Follower Stats ```bash -curl "https://getlate.dev/api/v1/accounts/follower-stats?profileId=PROFILE_ID" \ +curl "https://getlate.dev/api/v1/analytics/follower-stats?profileId=PROFILE_ID" \ -H "Authorization: Bearer YOUR_API_KEY" ``` -Returns historical follower counts across all connected accounts. +Returns historical follower counts and growth metrics across all connected accounts. diff --git a/rules/connect.md b/rules/connect.md index 79f9564..7367ba1 100644 --- a/rules/connect.md +++ b/rules/connect.md @@ -5,6 +5,9 @@ | Method | Endpoint | Description | |--------|----------|-------------| | `GET` | `/v1/connect/{platform}` | Start OAuth flow | +| `GET` | `/v1/connect/get-connect-url` | Start OAuth flow (generic, supports headless) | +| `POST` | `/v1/connect/callback` | Exchange OAuth code for tokens | +| `GET` | `/v1/connect/pending-data` | Get pending OAuth data (headless mode) | | `POST` | `/v1/connect/bluesky/credentials` | Connect Bluesky (app password) | | `GET` | `/v1/connect/telegram` | Generate Telegram access code | | `POST` | `/v1/connect/telegram` | Direct connect via chat ID | @@ -12,18 +15,20 @@ ## Platform Selection Endpoints -Some platforms require selecting a page/location after OAuth: +Some platforms require selecting a page/location/subreddit after OAuth: | Method | Endpoint | Description | |--------|----------|-------------| -| `GET` | `/v1/connect/facebook/select-page` | List Facebook pages | +| `GET` | `/v1/connect/facebook/pages` | List Facebook pages | | `POST` | `/v1/connect/facebook/select-page` | Select Facebook page | | `GET` | `/v1/connect/linkedin/organizations` | List available LinkedIn orgs | | `POST` | `/v1/connect/linkedin/select-organization` | Select LinkedIn org | -| `GET` | `/v1/connect/googlebusiness/locations` | List GMB locations | -| `POST` | `/v1/connect/googlebusiness/select-location` | Select GMB location | +| `GET` | `/v1/connect/google-business/locations` | List GMB locations | +| `POST` | `/v1/connect/google-business/select-location` | Select GMB location | | `GET` | `/v1/connect/pinterest/select-board` | List Pinterest boards | | `POST` | `/v1/connect/pinterest/select-board` | Select Pinterest board | +| `GET` | `/v1/connect/reddit/subreddits` | List Reddit subreddits (during connect) | +| `POST` | `/v1/connect/reddit/update-subreddits` | Set default subreddit (during connect) | | `GET` | `/v1/connect/snapchat/select-profile` | List Snapchat profiles | | `POST` | `/v1/connect/snapchat/select-profile` | Select Snapchat profile | @@ -33,11 +38,36 @@ Some platforms require selecting a page/location after OAuth: ```bash # Get OAuth URL -curl "https://getlate.dev/api/v1/connect/twitter?profileId=PROFILE_ID&callbackUrl=https://yourapp.com/callback" \ +curl "https://getlate.dev/api/v1/connect/twitter?profileId=PROFILE_ID&redirectUrl=https://yourapp.com/callback" \ -H "Authorization: Bearer YOUR_API_KEY" ``` -Returns `{ "url": "https://twitter.com/oauth/..." }` - redirect user there. +Returns `{ "authUrl": "https://twitter.com/oauth/..." }` — redirect user there. + +**Query parameters:** +- `profileId` (required) — the Late profile ID +- `redirectUrl` (required) — where to redirect after OAuth completes + +### Headless Mode (custom UI) + +For apps that want full control over the OAuth UI: + +```bash +# Get connect URL with headless mode +curl "https://getlate.dev/api/v1/connect/get-connect-url?profileId=PROFILE_ID&redirectUrl=https://yourapp.com/callback&headless=true" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Returns `{ "authUrl": "...", "connectToken": "tok_..." }`. + +Use `connectToken` in subsequent calls via `X-Connect-Token` header: + +```bash +# Get pending OAuth data after user completes consent +curl "https://getlate.dev/api/v1/connect/pending-data?token=PENDING_DATA_TOKEN" \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "X-Connect-Token: tok_..." +``` ### Bluesky (App Password) @@ -48,7 +78,7 @@ curl -X POST https://getlate.dev/api/v1/connect/bluesky/credentials \ -d '{ "profileId": "PROFILE_ID", "identifier": "user.bsky.social", - "appPassword": "xxxx-xxxx-xxxx-xxxx" + "password": "xxxx-xxxx-xxxx-xxxx" }' ``` @@ -85,6 +115,31 @@ curl -X POST https://getlate.dev/api/v1/connect/telegram \ The Late bot must already be added as admin in your channel/group. +## Reddit OAuth Flow + +Reddit requires subreddit selection after connecting: + +```bash +# 1. Start OAuth +curl "https://getlate.dev/api/v1/connect/reddit?profileId=PROFILE_ID&redirectUrl=https://yourapp.com/callback" \ + -H "Authorization: Bearer YOUR_API_KEY" +# Returns: { "authUrl": "https://www.reddit.com/api/v1/authorize?..." } + +# 2. After user completes OAuth, list available subreddits +curl "https://getlate.dev/api/v1/connect/reddit/subreddits?profileId=PROFILE_ID" \ + -H "Authorization: Bearer YOUR_API_KEY" + +# 3. Set default subreddit +curl -X POST "https://getlate.dev/api/v1/connect/reddit/update-subreddits" \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"profileId": "PROFILE_ID", "subreddit": "distrilicious"}' +``` + +You can also manage subreddits post-connect via the Accounts API: +- `GET /v1/accounts/{accountId}/reddit-subreddits` +- `PUT /v1/accounts/{accountId}/reddit-subreddits` + ## Supported Platforms | Platform | Auth Method | Notes | @@ -92,11 +147,11 @@ The Late bot must already be added as admin in your channel/group. | Twitter/X | OAuth 2.0 PKCE | Requires code verifier | | Instagram | OAuth 2.0 | 2-step token exchange | | Facebook | OAuth 2.0 | Requires page selection | -| LinkedIn | OAuth 2.0 | Optional org selection | +| LinkedIn | OAuth 2.0 | Optional org selection; headless restructured Jan 2026 | | TikTok | OAuth 2.0 | UX compliance required | | YouTube | Google OAuth | access_type=offline | | Pinterest | OAuth 2.0 | Requires board selection | -| Reddit | OAuth 2.0 | Strict user-agent | +| Reddit | OAuth 2.0 | Strict user-agent; requires subreddit selection | | Bluesky | App password | No OAuth, uses AT Protocol | | Threads | OAuth 2.0 | Similar to Instagram | | Google Business | Google OAuth | Requires location selection | diff --git a/rules/errors.md b/rules/errors.md index 567e1a4..3b04a2c 100644 --- a/rules/errors.md +++ b/rules/errors.md @@ -18,10 +18,53 @@ | 401 | `UNAUTHORIZED` | Invalid/missing API key | | 403 | `FORBIDDEN` | Insufficient permissions | | 404 | `NOT_FOUND` | Resource not found | +| 409 | `DUPLICATE_CONTENT` | Identical content already posted (see below) | | 422 | `VALIDATION_ERROR` | Validation failed | | 429 | `RATE_LIMITED` | Too many requests | | 500 | `INTERNAL_ERROR` | Server error | +## Duplicate Content Detection (Jan 2026) + +HTTP 409 is returned when identical content has already been posted to the same account: + +```json +{ + "error": "This exact content was already posted to this account", + "code": "DUPLICATE_CONTENT", + "details": { + "accountId": "acc_123", + "existingPostId": "post_456" + } +} +``` + +## Platform Target Errors (Feb 2026) + +When a post targets multiple platforms and some fail, the post status is `"partial"`. Each platform target includes three error fields: + +```json +{ + "status": "partial", + "platformTargets": [ + { + "platform": "twitter", + "status": "published" + }, + { + "platform": "linkedin", + "status": "failed", + "errorMessage": "Token expired, please reconnect", + "errorCategory": "auth_expired", + "errorSource": "platform" + } + ] +} +``` + +**Error categories:** `auth_expired`, `user_content`, `platform_rejected`, `rate_limited`, `media_error`, `system` + +**Error sources:** `user` (content issue), `platform` (platform-side rejection), `system` (Late internal error) + ## Rate Limits | Plan | Requests/Minute | diff --git a/rules/media.md b/rules/media.md index 853a5f3..8db8ca3 100644 --- a/rules/media.md +++ b/rules/media.md @@ -12,14 +12,18 @@ curl -X POST https://getlate.dev/api/v1/media/presign \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ - -d '{"filename": "image.jpg", "contentType": "image/jpeg"}' + -d '{"fileName": "image.jpg", "fileType": "image/jpeg"}' ``` +**Request body:** +- `fileName` (required) — filename with extension +- `fileType` (required) — MIME type (e.g., `image/jpeg`, `video/mp4`) + ## Upload Flow ```typescript // 1. Get presigned URL -const { uploadUrl, fileUrl } = await getPresignedUrl('image.jpg', 'image/jpeg'); +const { uploadUrl, publicUrl } = await getPresignedUrl('image.jpg', 'image/jpeg'); // 2. Upload to presigned URL await fetch(uploadUrl, { @@ -28,22 +32,49 @@ await fetch(uploadUrl, { headers: { 'Content-Type': 'image/jpeg' } }); -// 3. Use fileUrl in post +// 3. Use publicUrl in post await createPost({ content: 'Check this out!', - mediaItems: [{ type: 'image', url: fileUrl }], + mediaItems: [{ type: 'image', url: publicUrl }], platforms: [{ platform: 'twitter', accountId: 'acc_123' }] }); ``` +**Response fields:** +- `uploadUrl` — presigned URL for PUT upload +- `publicUrl` — permanent URL to use in posts +- `expires` — (optional) upload URL expiry + +## Custom Media Per Platform + +Use `customMedia` in each platform entry to send different media per platform: + +```json +{ + "platforms": [ + { + "platform": "instagram", + "accountId": "acc_123", + "customMedia": [{ "type": "image", "url": "https://..." }] + } + ] +} +``` + +## Auto-Compression + +Media is auto-compressed during publishing when files exceed platform limits (added Jan 2026). Bluesky images are auto-recompressed to ~1MB. + ## Supported Formats -| Type | Formats | Max Size | -|------|---------|----------| -| Images | JPG, PNG, WebP, GIF | 5MB | +| Type | Formats | Max Upload Size | +|------|---------|-----------------| +| Images | JPG, PNG, WebP, GIF | 5GB | | Videos | MP4, MOV, WebM | 5GB | | Documents | PDF (LinkedIn only) | 100MB | +Note: General upload limit is 5GB. Individual platforms impose smaller limits (see below). + ## Platform Media Limits | Platform | Max Images | Max Video | Special | @@ -56,8 +87,8 @@ await createPost({ | Facebook | 10MB | 4GB | 10 multi-image | | Threads | 8MB | 1GB, 5min | 10 carousel | | Pinterest | 32MB | 2GB | Requires cover image | -| Bluesky | 1MB | 50MB, 3min | 4 images max | +| Bluesky | 1MB (auto-compressed) | 50MB, 3min | 4 images max | | Snapchat | 20MB | 500MB | AES encryption required | | Google Business | 5MB | N/A | Images only | -| Reddit | 20MB | N/A | Via URL | +| Reddit | 20MB | N/A | Via URL only; no video upload via API | | Telegram | 10MB | 50MB | 4096 char limit | diff --git a/rules/platforms.md b/rules/platforms.md index 5f8a167..3661b35 100644 --- a/rules/platforms.md +++ b/rules/platforms.md @@ -213,6 +213,34 @@ Creates reply chains (Threads equivalent of Twitter threads). Supports up to 10 Parse modes: `HTML`, `Markdown`, `MarkdownV2`. Supports up to 10 images or videos in albums. Max 4096 chars for text-only, 1024 for media captions. +### Reddit + +```json +{ + "platformSpecificData": { + "subreddit": "distrilicious", + "title": "Your post title here", + "flairId": "flair_abc123", + "url": "https://example.com", + "forceSelf": false + } +} +``` + +- `subreddit` (effectively required) — target community name, no "r/" prefix. Falls back to default subreddit if omitted. Aliases: `subredditName`, `sr` +- `title` (required) — max 300 chars. **Cannot be edited after posting.** This is permanent. +- `flairId` — required by many subreddits. Use `GET /v1/accounts/{accountId}/reddit-flairs?subreddit=NAME` to list available flairs. Alias: `redditFlairId` +- `url` — creates a link post instead of text post +- `forceSelf` — forces text post even when media is attached + +**Post types supported:** text, link, image, gallery. Video uploads not supported via API. + +**Important notes:** +- Title is permanent — cannot be edited after posting +- Many subreddits require flair; posts without it will fail +- Analytics limited to upvotes and comments only +- Check subreddit rules before posting — Late provides `GET /v1/tools/validate/subreddit` to verify subreddit exists + ### Snapchat ```json diff --git a/rules/posts.md b/rules/posts.md index 22ca1e9..8537efe 100644 --- a/rules/posts.md +++ b/rules/posts.md @@ -11,6 +11,7 @@ | `DELETE` | `/v1/posts/{postId}` | Delete post | | `POST` | `/v1/posts/{postId}/retry` | Retry failed post | | `GET` | `/v1/posts/{postId}/logs` | Get publishing logs | +| `POST` | `/v1/posts/{postId}/unpublish` | Unpublish a published post | | `POST` | `/v1/posts/bulk-upload` | Bulk create posts | ## Create Post @@ -58,7 +59,19 @@ const post = await fetch('https://getlate.dev/api/v1/posts', { mediaItems: [ { type: 'image', url: 'https://...' }, { type: 'video', url: 'https://...' } - ] + ], + + // Optional: platform-specific content overrides + customContent: { + twitter: 'Short version for Twitter', + linkedin: 'Professional version for LinkedIn' + }, + + // Optional: tags for platforms that support them (e.g., YouTube) + tags: ['saas', 'buildinpublic'], + + // Optional: alternative to platforms array + // accountIds: ['acc_123', 'acc_456'] }) }); ``` diff --git a/rules/profiles.md b/rules/profiles.md new file mode 100644 index 0000000..d44fb01 --- /dev/null +++ b/rules/profiles.md @@ -0,0 +1,69 @@ +# Profiles API + +## Endpoints + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/v1/profiles` | Create profile | +| `GET` | `/v1/profiles` | List profiles | +| `GET` | `/v1/profiles/{id}` | Get profile | +| `PATCH` | `/v1/profiles/{id}` | Update profile | +| `DELETE` | `/v1/profiles/{id}` | Delete profile | + +## Create Profile + +```bash +curl -X POST https://getlate.dev/api/v1/profiles \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"name": "My Brand", "description": "Optional description", "color": "#3B82F6"}' +``` + +**Request body:** +- `name` (required) — profile name +- `description` (optional) — profile description +- `color` (optional) — hex color for UI display + +## List Profiles + +```bash +curl "https://getlate.dev/api/v1/profiles" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Returns profiles sorted by creation date. + +## Get Profile + +```bash +curl "https://getlate.dev/api/v1/profiles/PROFILE_ID" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +## Update Profile + +```bash +curl -X PATCH "https://getlate.dev/api/v1/profiles/PROFILE_ID" \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"name": "Updated Name", "description": "New description", "color": "#EF4444", "default": true}' +``` + +**Updatable fields:** `name`, `description`, `color`, `default` + +## Delete Profile + +```bash +curl -X DELETE "https://getlate.dev/api/v1/profiles/PROFILE_ID" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Permanently deletes the profile. Returns 200 on success, 404 if not found. + +## Concepts + +- Profiles are containers that group social accounts (think "brands" or "projects") +- Example: "Personal Brand" profile with Twitter + LinkedIn, "Company" profile with business accounts +- Each connected social account belongs to exactly one profile +- One profile can be set as `default` +- Profile `id` is used as `profileId` in connect and post endpoints diff --git a/rules/tools.md b/rules/tools.md index 578bee5..b6a5f1e 100644 --- a/rules/tools.md +++ b/rules/tools.md @@ -1,6 +1,6 @@ # Tools API -Media download and utility tools. Available to paid plans only. +Media download, validation, and utility tools. Available to paid plans only. **Rate limits:** Build (50/day), Accelerate (500/day), Unlimited (unlimited) @@ -32,6 +32,61 @@ Response: } ``` +## Validation Endpoints (Feb 2026) + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/v1/tools/validate/post-length` | Check character count per platform with limits | +| `POST` | `/v1/tools/validate/post` | Full pipeline dry-run across all platforms | +| `POST` | `/v1/tools/validate/media` | Validate media URL against platform size limits | +| `GET` | `/v1/tools/validate/subreddit` | Verify subreddit existence and fetch info | + +### Validate Post Length + +```bash +curl -X POST https://getlate.dev/api/v1/tools/validate/post-length \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"content": "Your post content here", "platforms": ["twitter", "linkedin"]}' +``` + +Returns character count per platform and whether content exceeds platform limits. + +### Validate Post (Dry Run) + +```bash +curl -X POST https://getlate.dev/api/v1/tools/validate/post \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "content": "Your post content", + "platforms": [{"platform": "twitter", "accountId": "acc_123"}], + "mediaItems": [{"type": "image", "url": "https://..."}] + }' +``` + +Runs the full publishing pipeline as a dry-run — validates content, media, and platform requirements without actually posting. + +### Validate Media + +```bash +curl -X POST https://getlate.dev/api/v1/tools/validate/media \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"url": "https://example.com/image.jpg", "platforms": ["instagram", "twitter"]}' +``` + +Validates a public media URL against platform size and format limits. + +### Validate Subreddit + +```bash +curl "https://getlate.dev/api/v1/tools/validate/subreddit?name=distrilicious" \ + -H "Authorization: Bearer YOUR_API_KEY" +``` + +Verifies subreddit exists and returns basic info (subscriber count, rules, etc.). + ## Utility Endpoints | Method | Endpoint | Description | From 4c3b3cbfca8077a683972219703437e208e56cfc Mon Sep 17 00:00:00 2001 From: thinkmakeshipiot Date: Thu, 5 Mar 2026 03:59:52 +0200 Subject: [PATCH 2/2] docs: add gotchas, integration guide, response schemas, pricing context, fix docs URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix docs URL: getlate.dev/docs → docs.getlate.dev - Improve skill description for better triggering - Add Common Gotchas table (9 most-common mistakes without reference docs) - Add end-to-end integration-guide.md: profile → connect → media → post → webhook - Add response schemas for POST /v1/posts, GET /v1/connect/{platform}, POST /v1/media/presign - Add post status values (scheduled/published/failed/partial) - Add pricing context table with SaaS guidance (Accelerate minimum) - Add redirectUrl requirement to Quick Start connect example Benchmark: skill achieves 100% accuracy (16/16 assertions) vs 8.3% baseline --- SKILL.md | 49 ++++++- rules/integration-guide.md | 284 +++++++++++++++++++++++++++++++++++++ rules/posts.md | 34 +++++ 3 files changed, 360 insertions(+), 7 deletions(-) create mode 100644 rules/integration-guide.md diff --git a/SKILL.md b/SKILL.md index 47d9985..6b4c78f 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,6 +1,6 @@ --- name: late-api -description: Official Late API reference for scheduling posts across 13 social media platforms. Covers authentication, endpoints, webhooks, and platform-specific features. Use when building with the Late Social Media Scheduling API. +description: Official Late API (getlate.dev) reference for scheduling, publishing, and managing social media posts across 13 platforms. Use this skill whenever the user is working with getlate.dev, the Late social media scheduling API, connecting social accounts via Late OAuth, uploading media to Late, debugging Late API errors, or integrating Late into their app. Covers authentication, all endpoints, webhooks, platform-specific data, media uploads, and error handling. Always use this skill rather than guessing Late API shapes — Claude frequently gets the endpoint paths and field names wrong without it. --- # Late API Reference @@ -9,7 +9,7 @@ Schedule posts across 13 social media platforms with a single API. **Base URL:** `https://getlate.dev/api/v1` -**Docs:** [getlate.dev/docs](https://getlate.dev/docs) +**Docs:** [docs.getlate.dev](https://docs.getlate.dev) ## Quick Start @@ -19,9 +19,10 @@ curl -X POST https://getlate.dev/api/v1/profiles \ -H "Authorization: Bearer YOUR_API_KEY" \ -d '{"name": "My Brand"}' -# 2. Connect account (opens OAuth) -curl "https://getlate.dev/api/v1/connect/twitter?profileId=PROFILE_ID" \ +# 2. Connect account (opens OAuth) — redirectUrl is required +curl "https://getlate.dev/api/v1/connect/twitter?profileId=PROFILE_ID&redirectUrl=https://yourapp.com/callback" \ -H "Authorization: Bearer YOUR_API_KEY" +# Returns { "authUrl": "https://twitter.com/oauth/..." } — redirect user there # 3. Create post curl -X POST https://getlate.dev/api/v1/posts \ @@ -29,23 +30,57 @@ curl -X POST https://getlate.dev/api/v1/posts \ -d '{"content": "Hello!", "platforms": [{"platform": "twitter", "accountId": "ACC_ID"}], "publishNow": true}' ``` +## Common Gotchas + +These are the most frequent mistakes when using the Late API without reference docs: + +| What Claude gets wrong | What's actually correct | +|------------------------|------------------------| +| `POST /profiles/{id}/connect` | `GET /v1/connect/{platform}?profileId=...&redirectUrl=...` | +| `POST /profiles/{id}/publish` | `POST /v1/posts` | +| `scheduledAt: "..."` | `scheduledFor: "..."` (ISO 8601) | +| `overrides: { twitter: {...} }` | `customContent: { twitter: "..." }` | +| `profiles: [{ profileKey: "..." }]` | `platforms: [{ platform: "twitter", accountId: "..." }]` | +| `platformSpecificData.postType: "story"` | `platformSpecificData.contentType: "story"` | +| Connect returns `{ url: "..." }` | Connect returns `{ authUrl: "..." }` | +| Media → use `uploadUrl` in post | Media → use `publicUrl` in post (not `uploadUrl`) | +| Upload media with FormData | Media uses presign flow: `POST /v1/media/presign` → PUT to `uploadUrl` | + +## End-to-End Integration Guide + +See [rules/integration-guide.md](rules/integration-guide.md) for a complete walkthrough: profile creation → OAuth connect → media upload → post with scheduling, custom content, and platform-specific data. + ## Rule Files Read individual rule files for detailed documentation: - [rules/authentication.md](rules/authentication.md) - API key format, usage examples, core concepts - [rules/profiles.md](rules/profiles.md) - Create, list, update, delete profiles (account containers) -- [rules/posts.md](rules/posts.md) - Create, schedule, retry, unpublish posts, bulk upload +- [rules/posts.md](rules/posts.md) - Create, schedule, retry, unpublish posts, bulk upload; response schema - [rules/accounts.md](rules/accounts.md) - List accounts, health checks, follower stats, Reddit flairs -- [rules/connect.md](rules/connect.md) - OAuth flows, headless mode, Bluesky, Telegram, Reddit subreddits +- [rules/connect.md](rules/connect.md) - OAuth flows, headless mode, Bluesky, Telegram, Reddit subreddits; response schemas - [rules/platforms.md](rules/platforms.md) - Platform-specific data for all 13 platforms including Reddit - [rules/webhooks.md](rules/webhooks.md) - Configure webhooks, verify signatures, events -- [rules/media.md](rules/media.md) - Presigned uploads, custom media per platform, auto-compression +- [rules/media.md](rules/media.md) - Presigned uploads, custom media per platform, auto-compression; response schemas - [rules/queue.md](rules/queue.md) - Queue management, slots configuration - [rules/analytics.md](rules/analytics.md) - Daily metrics, best times, content decay, YouTube, LinkedIn - [rules/tools.md](rules/tools.md) - Media download, validation endpoints, hashtag checker - [rules/errors.md](rules/errors.md) - Error codes, rate limits, duplicate detection, platform errors - [rules/sdks.md](rules/sdks.md) - Direct API usage examples +- [rules/integration-guide.md](rules/integration-guide.md) - Full end-to-end integration walkthrough + +## Pricing Context + +Late plans affect architecture decisions in multi-tenant apps: + +| Plan | Profiles | Posts/mo | Use case | +|------|----------|----------|----------| +| Free | 2 | 20 | Testing only | +| Build ($19/mo) | 10 | 120 | Personal tools only — 120 posts/mo is a shared pool | +| Accelerate ($49/mo) | 50 (stackable) | Unlimited | Minimum viable for multi-user SaaS | +| Unlimited ($999/mo) | Unlimited | Unlimited | Scale | + +**For multi-user SaaS**: each end-user = 1 profile. Build plan's 120 post/mo limit is account-level (shared across all users) — makes it unusable for SaaS. Use Accelerate minimum. ## Supported Platforms diff --git a/rules/integration-guide.md b/rules/integration-guide.md new file mode 100644 index 0000000..725b335 --- /dev/null +++ b/rules/integration-guide.md @@ -0,0 +1,284 @@ +# End-to-End Integration Guide + +Complete walkthrough of a Late API integration: onboard a user, connect their social accounts, upload media, and publish to multiple platforms. + +--- + +## Step 1: Create a Profile + +A profile is the container for a user's connected social accounts. Create one per user or brand. + +```typescript +const resp = await fetch('https://getlate.dev/api/v1/profiles', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ name: 'My Brand' }) +}); +const profile = await resp.json(); +// profile.id — save this as the user's profileId +``` + +**Response schema:** +```json +{ + "id": "prof_abc123", + "name": "My Brand", + "description": null, + "color": null, + "default": false, + "createdAt": "2026-01-15T10:00:00Z" +} +``` + +--- + +## Step 2: Connect a Social Account (OAuth) + +This is a redirect flow — you get an `authUrl` from Late, redirect the user there, and they land back on your `redirectUrl` after authorizing. + +```typescript +// GET — not POST, no body, params in query string +const connectResp = await fetch( + `https://getlate.dev/api/v1/connect/twitter?profileId=${profileId}&redirectUrl=${encodeURIComponent(redirectUrl)}`, + { headers: { 'Authorization': `Bearer ${apiKey}` } } +); +const { authUrl } = await connectResp.json(); +// Redirect the user to authUrl +``` + +**Response schema:** +```json +{ + "authUrl": "https://twitter.com/oauth/authorize?..." +} +``` + +**For Reddit — extra steps after OAuth:** + +```typescript +// After user returns from OAuth, list subreddits they moderate +const subredditsResp = await fetch( + `https://getlate.dev/api/v1/connect/reddit/subreddits?profileId=${profileId}`, + { headers: { 'Authorization': `Bearer ${apiKey}` } } +); +const { subreddits } = await subredditsResp.json(); +// subreddits: [{ name: "r/mysubreddit", members: 1200 }, ...] + +// Set default subreddit +await fetch('https://getlate.dev/api/v1/connect/reddit/update-subreddits', { + method: 'POST', + headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ profileId, subreddit: 'mysubreddit' }) +}); +``` + +**Platforms requiring post-OAuth selection:** + +| Platform | Extra step | +|----------|------------| +| Facebook | `GET /v1/connect/facebook/pages` → `POST /v1/connect/facebook/select-page` | +| LinkedIn | `GET /v1/connect/linkedin/organizations` → `POST /v1/connect/linkedin/select-organization` | +| Pinterest | `GET /v1/connect/pinterest/select-board` → `POST /v1/connect/pinterest/select-board` | +| Reddit | `GET /v1/connect/reddit/subreddits` → `POST /v1/connect/reddit/update-subreddits` | +| Google Business | `GET /v1/connect/google-business/locations` → `POST /v1/connect/google-business/select-location` | +| Snapchat | `GET /v1/connect/snapchat/select-profile` → `POST /v1/connect/snapchat/select-profile` | + +--- + +## Step 3: Get Account IDs + +After connecting, list accounts to get the `accountId` values needed for posting. + +```typescript +const accountsResp = await fetch( + `https://getlate.dev/api/v1/accounts?profileId=${profileId}`, + { headers: { 'Authorization': `Bearer ${apiKey}` } } +); +const accounts = await accountsResp.json(); +// accounts: [{ id: "acc_xyz", platform: "twitter", name: "@myhandle", ... }, ...] +``` + +**Response schema (single account):** +```json +{ + "id": "acc_xyz789", + "platform": "twitter", + "name": "@myhandle", + "profilePicture": "https://...", + "followers": 5200, + "status": "active" +} +``` + +--- + +## Step 4: Upload Media (Optional) + +Use the presign flow — do not send files directly in the post body. + +```typescript +// 1. Get presigned URL +const presignResp = await fetch('https://getlate.dev/api/v1/media/presign', { + method: 'POST', + headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ fileName: 'image.jpg', fileType: 'image/jpeg' }) +}); +const { uploadUrl, publicUrl } = await presignResp.json(); + +// 2. Upload file to presigned URL (direct PUT, no auth header) +await fetch(uploadUrl, { + method: 'PUT', + body: fileBuffer, + headers: { 'Content-Type': 'image/jpeg' } +}); + +// 3. Use publicUrl (not uploadUrl) in your post +``` + +**Response schema:** +```json +{ + "uploadUrl": "https://s3.amazonaws.com/...?X-Amz-Signature=...", + "publicUrl": "https://cdn.getlate.dev/media/abc123.jpg" +} +``` + +--- + +## Step 5: Create a Post + +```typescript +const postResp = await fetch('https://getlate.dev/api/v1/posts', { + method: 'POST', + headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ + content: 'Main post text (fallback for all platforms)', + platforms: [ + { platform: 'twitter', accountId: 'acc_tw123' }, + { platform: 'linkedin', accountId: 'acc_li456' }, + { + platform: 'instagram', + accountId: 'acc_ig789', + platformSpecificData: { + contentType: 'story', // 'story' only; feed/reel auto-detected from media + firstComment: 'First comment text' + } + } + ], + + // Optional: platform-specific text variants (key = platform name) + customContent: { + twitter: 'Short version for Twitter (280 chars)', + linkedin: 'Professional version for LinkedIn' + }, + + // Optional: shared media (use publicUrl from presign, not uploadUrl) + mediaItems: [ + { type: 'image', url: publicUrl } + ], + + // Scheduling: pick exactly one + publishNow: true, + // OR: scheduledFor: '2026-03-15T09:00:00Z', // ISO 8601, not "scheduledAt" + // OR: queuedFromProfile: profileId, + }) +}); +const post = await postResp.json(); +``` + +**Response schema:** +```json +{ + "id": "post_def456", + "content": "Main post text", + "status": "scheduled", + "scheduledFor": "2026-03-15T09:00:00Z", + "platforms": [ + { + "platform": "twitter", + "accountId": "acc_tw123", + "status": "scheduled", + "latePostId": "lp_001" + } + ], + "createdAt": "2026-03-05T10:00:00Z" +} +``` + +Note: if publishing to 3 platforms and 2 succeed but 1 fails, `status` = `"partial"`. Check each platform entry's `status` field. + +--- + +## Step 6: Handle Webhook Events (Recommended) + +Set up a webhook to know when posts publish or fail: + +```typescript +// Register webhook +await fetch('https://getlate.dev/api/v1/webhooks', { + method: 'POST', + headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ + url: 'https://yourapp.com/webhooks/late', + events: ['post.published', 'post.failed'] + }) +}); +``` + +See [webhooks.md](webhooks.md) for signature verification and event payload schemas. + +--- + +## Common Patterns + +### SaaS multi-tenant: one profile per user + +```typescript +// On user signup +const profile = await createProfile({ name: user.name }); +await db.users.update(user.id, { lateProfileId: profile.id }); + +// When user connects a platform +const { authUrl } = await getConnectUrl(profile.id, platform, callbackUrl); +redirect(authUrl); +``` + +### Post to all connected accounts + +```typescript +const accounts = await listAccounts(profileId); +const platforms = accounts.map(acc => ({ + platform: acc.platform, + accountId: acc.id +})); +await createPost({ content, platforms, publishNow: true }); +``` + +### Reddit-specific post with flair + +```typescript +// Get available flairs first +const flairsResp = await fetch( + `https://getlate.dev/api/v1/accounts/${accountId}/reddit-flairs?subreddit=mysubreddit`, + { headers: { 'Authorization': `Bearer ${apiKey}` } } +); +const { flairs } = await flairsResp.json(); +// flairs: [{ id: "flair_123", text: "Discussion" }, ...] + +await createPost({ + content: 'Post text', + platforms: [{ + platform: 'reddit', + accountId, + platformSpecificData: { + title: 'Required Reddit title', // required, permanent once posted + subreddit: 'mysubreddit', + flair: { id: 'flair_123', text: 'Discussion' } + } + }], + publishNow: true +}); +``` diff --git a/rules/posts.md b/rules/posts.md index 8537efe..0ea9224 100644 --- a/rules/posts.md +++ b/rules/posts.md @@ -114,6 +114,40 @@ await fetch('https://getlate.dev/api/v1/posts', { }); ``` +## Create Post — Response Schema + +```json +{ + "id": "post_def456", + "content": "Your post text", + "status": "scheduled", + "scheduledFor": "2026-03-15T09:00:00Z", + "platforms": [ + { + "platform": "twitter", + "accountId": "acc_tw123", + "status": "scheduled", + "latePostId": "lp_001" + }, + { + "platform": "linkedin", + "accountId": "acc_li456", + "status": "failed", + "error": "token_expired" + } + ], + "mediaItems": [{ "type": "image", "url": "https://..." }], + "createdAt": "2026-03-05T10:00:00Z", + "updatedAt": "2026-03-05T10:00:00Z" +} +``` + +**Status values:** +- `scheduled` — will publish at `scheduledFor` +- `published` — successfully published to all platforms +- `failed` — all platforms failed +- `partial` — some platforms succeeded, some failed (check each platform entry's `status`) + ## Retry Failed Posts ```bash