@wahlu/cli
v0.1.1
Published
Wahlu CLI — manage your social media from the terminal
Maintainers
Readme
Wahlu CLI
Manage your social media from the terminal. Works for humans, AI agents, and CI/CD pipelines.
Install
npm install -g @wahlu/cliAuthentication
Generate an API key at wahlu.com under Settings > API Keys.
# Option 1: Save to config
wahlu auth login wahlu_live_abc123...
# Option 2: Environment variable
export WAHLU_API_KEY=wahlu_live_abc123...Authentication priority:
WAHLU_API_KEYenvironment variable (highest)- Saved key in
~/.config/wahlu/config.json
Quick start
# List your brands
wahlu brand list
# Set a default brand so you don't need --brand every time
wahlu brand switch <brand-id>
# List content items (via post alias)
wahlu post list
# Create a content item
wahlu post create --name "Monday motivation" \
--copy-mode single \
--single-copy '{"caption":"Rise and grind","hashtags":[]}' \
--instagram '{"post_type":"GRID_POST"}'
# Schedule it
wahlu schedule create <content-item-id> \
--at 2026-03-15T14:00:00Z \
--integrations <integration-id>
# Upload media
wahlu media upload ./photo.jpg
# List what's been published
wahlu publication listCommands
Auth
| Command | Description |
|---------|-------------|
| wahlu auth login <key> | Save API key to ~/.config/wahlu/config.json |
| wahlu auth logout | Remove saved API key |
| wahlu auth status | Show current auth method, masked key, and config |
Brands
Brands represent social media profiles. All content items, media, publish runs, and queues belong to a brand.
| Command | Description |
|---------|-------------|
| wahlu brand list | List all brands |
| wahlu brand get <id> | Get full brand details |
| wahlu brand switch <id> | Set default brand for all commands |
Response fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Brand ID |
| name | string | Brand name |
| description | string|null | Brand description |
| logo_url | string|null | Logo URL |
| timezone | string|null | IANA timezone (e.g. Australia/Sydney) |
| website | string|null | Brand website URL |
| business_category | string|null | Business category |
| brand_kit | object|null | Brand kit (fonts, colours, voice) |
| content_preferences | object|null | CTA, logo frequency settings |
| image_posting | object|null | Image posting preferences |
| video_posting | object|null | Video posting preferences |
| created_at | string | ISO 8601 timestamp |
| updated_at | string | ISO 8601 timestamp |
Content items (post command)
Content items are the core content unit. Captions/hashtags are canonical via copy_mode + single_copy/platform_copy, while platform settings control media/post options.
| Command | Description |
|---------|-------------|
| wahlu post list | List content items (paginated) |
| wahlu post get <id> | Get full content item details |
| wahlu post create [options] | Create a new content item |
| wahlu post update <id> [options] | Update a content item (partial update) |
| wahlu post delete <id> | Permanently delete a content item |
Create/update options:
| Option | Description |
|--------|-------------|
| --name <name> | Content item name (max 500 chars) |
| --copy-mode <mode> | Canonical caption mode: single or per_platform |
| --single-copy <json> | Canonical shared copy JSON: {"caption":"...","hashtags":["..."],"title":"..."} |
| --platform-copy <json> | Canonical per-platform copy JSON map |
| --instagram <json> | Instagram settings as JSON string |
| --tiktok <json> | TikTok settings as JSON string |
| --facebook <json> | Facebook settings as JSON string |
| --youtube <json> | YouTube settings as JSON string |
| --linkedin <json> | LinkedIn settings as JSON string |
| --labels <ids...> | Label IDs to attach (max 50) |
Response fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Content item ID |
| name | string|null | Content item name |
| brand_id | string | Brand ID |
| label_ids | string[] | Attached label IDs |
| created_by | string|null | Creator user ID |
| thumbnail_timestamp | number | Thumbnail timestamp (seconds) |
| copy_mode | string|null | single | per_platform |
| single_copy | object|null | Canonical shared caption + hashtags |
| platform_copy | object|null | Canonical per-platform caption map |
| instagram_settings | object|null | Instagram configuration |
| tiktok_settings | object|null | TikTok configuration |
| facebook_settings | object|null | Facebook configuration |
| youtube_settings | object|null | YouTube configuration |
| linkedin_settings | object|null | LinkedIn configuration |
| created_at | string | ISO 8601 timestamp |
| updated_at | string | ISO 8601 timestamp |
Platform settings reference
Captions and hashtags come from canonical copy fields:
--copy-mode single --single-copy '{"caption":"...","hashtags":["..."]}'--copy-mode per_platform --platform-copy '{"instagram":{"caption":"...","hashtags":[]}}'
Instagram (--instagram):
| Field | Type | Values |
|-------|------|--------|
| post_type | string | GRID_POST | REEL | STORY |
| media_ids | string[] | Media IDs to attach |
| trial_reel | boolean | Post as trial reel (non-followers first) |
| graduation_strategy | string | MANUAL | SS_PERFORMANCE |
TikTok (--tiktok):
| Field | Type | Values |
|-------|------|--------|
| post_type | string | VIDEO | IMAGE | CAROUSEL |
| media_ids | string[] | Media IDs to attach |
| privacy_level | string | PUBLIC_TO_EVERYONE | MUTUAL_FOLLOW_FRIENDS | FOLLOWER_OF_CREATOR | SELF_ONLY |
| allow_comment | boolean | Allow comments (default: true) |
| allow_duet | boolean | Allow duets (default: true, video only) |
| allow_stitch | boolean | Allow stitches (default: true, video only) |
| auto_add_music | boolean | Auto-add music (photo/carousel only) |
| is_aigc | boolean | Disclose as AI-generated content |
| is_commercial_content | boolean | Mark as commercial/branded content |
Facebook (--facebook):
| Field | Type | Values |
|-------|------|--------|
| post_type | string | FB_POST | FB_STORY | FB_REEL | FB_TEXT |
| media_ids | string[] | Media IDs to attach |
YouTube (--youtube):
| Field | Type | Values |
|-------|------|--------|
| title | string | Video title |
| description | string | Video description |
| post_type | string | YT_SHORT | YT_VIDEO |
| media_ids | string[] | Media IDs to attach |
| privacy_level | string | PUBLIC | UNLISTED | PRIVATE |
| notify_subscribers | boolean | Notify subscribers on publish |
LinkedIn (--linkedin):
| Field | Type | Values |
|-------|------|--------|
| post_type | string | LI_TEXT | LI_IMAGE | LI_VIDEO | LI_ARTICLE |
| media_ids | string[] | Media IDs to attach |
| visibility | string | PUBLIC | CONNECTIONS |
| title | string | Article title (LI_ARTICLE only) |
| original_url | string | Article URL (LI_ARTICLE only) |
Examples:
# Instagram grid post
wahlu post create --name "Photo post" \
--copy-mode single \
--single-copy '{"caption":"Hello!","hashtags":[]}' \
--instagram '{"post_type":"GRID_POST","media_ids":["mid-123"]}'
# Cross-platform video
wahlu post create --name "Video" \
--copy-mode per_platform \
--platform-copy '{"tiktok":{"caption":"Check this out","hashtags":["video"]},"instagram":{"caption":"Check this out","hashtags":[]}}' \
--tiktok '{"post_type":"VIDEO","media_ids":["mid-123"]}' \
--instagram '{"post_type":"REEL","media_ids":["mid-123"]}'
# LinkedIn article
wahlu post create --name "Article share" \
--copy-mode single \
--single-copy '{"caption":"Read our latest","hashtags":[]}' \
--linkedin '{"post_type":"LI_ARTICLE","original_url":"https://example.com","title":"Our Post"}'Publish runs (schedule command)
Schedule content items for future publishing to specific integrations.
| Command | Description |
|---------|-------------|
| wahlu schedule list | List publish runs (paginated) |
| wahlu schedule create <content-item-id> | Schedule a content item |
| wahlu schedule update <publish-run-id> | Update a publish run (reschedule/retarget) |
| wahlu schedule delete <id> | Remove a publish run (does not delete the content item) |
Create options:
| Option | Required | Description |
|--------|----------|-------------|
| --at <datetime> | Yes | ISO 8601 datetime (e.g. 2026-03-15T14:00:00Z) |
| --integrations <ids...> | Yes | Integration IDs to publish to (max 20) |
Response fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Publish run ID |
| content_item_id | string | Referenced content item ID |
| scheduled_at | string | ISO 8601 datetime |
| integration_ids | string[] | Integration IDs |
| status | string | e.g. ready_for_processing, published, failed |
| approval_status | string|null | Approval status |
| source | string|null | api for API-created entries |
| failure_reason | string|null | Failure reason |
| created_at | string | ISO 8601 timestamp |
| updated_at | string | ISO 8601 timestamp |
Example:
wahlu schedule create content-abc \
--at 2026-03-15T14:00:00Z \
--integrations int-123 int-456Queues
Queues define recurring time slots for automatic publishing.
| Command | Description |
|---------|-------------|
| wahlu queue list | List all queues |
| wahlu queue add <queue-id> <content-item-id> | Append a content item to a queue |
Response fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Queue ID |
| name | string | Queue name |
| active | boolean | Whether the queue is active |
| mode | string | Queue mode |
| times_of_day | string[] | Scheduled times (e.g. ["09:00","17:00"]) |
| timezone | string|null | IANA timezone |
| next_run_at | string|null | Next scheduled publishing time |
| loop | boolean | Whether to loop through posts |
| content_item_ids | string[] | Ordered content item IDs in the queue |
| integration_ids | string[] | Integration IDs |
| created_at | string | ISO 8601 timestamp |
| updated_at | string | ISO 8601 timestamp |
Media
Upload images and videos to your media library.
| Command | Description |
|---------|-------------|
| wahlu media list | List media files (paginated) |
| wahlu media upload <file> | Upload a local file |
| wahlu media delete <id> | Permanently delete a media file |
Supported formats: .png, .jpg, .jpeg, .gif, .webp, .mp4, .mov, .webm
Response fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Media ID (use in media_ids arrays) |
| file_name | string | Original filename |
| content_type | string | MIME type (e.g. image/jpeg) |
| size | number | File size in bytes |
| duration | number|null | Duration in seconds (video only) |
| status | string | ready_for_processing | processing | completed | failed |
| download_url | string|null | Signed download URL |
| thumbnail_large_url | string|null | Large thumbnail |
| thumbnail_small_url | string|null | Small thumbnail |
| source | string|null | upload | generated | stock | scan |
| description | string|null | Media description |
| created_at | string | ISO 8601 timestamp |
| updated_at | string | ISO 8601 timestamp |
Upload workflow:
# Upload returns a media ID
wahlu media upload ./photo.jpg
# Uploaded photo.jpg — media ID: mid-abc123
# Use the media ID in a content item
wahlu post create --name "Photo post" \
--copy-mode single \
--single-copy '{"caption":"Nice!","hashtags":[]}' \
--instagram '{"post_type":"GRID_POST","media_ids":["mid-abc123"]}'Ideas
Save content ideas for later development into full content items.
| Command | Description |
|---------|-------------|
| wahlu idea list | List ideas (paginated) |
| wahlu idea create <name> | Save a new idea |
| wahlu idea delete <id> | Delete an idea |
Create options:
| Option | Description |
|--------|-------------|
| --description <text> | Detailed description (max 10000 chars) |
| --type <type> | Idea type (max 50 chars) |
Response fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Idea ID |
| name | string|null | Idea name/title |
| description | string|null | Detailed description |
| type | string|null | Idea type |
| status | string | Status |
| labels | string[] | Text labels |
| created_at | string | ISO 8601 timestamp |
| updated_at | string | ISO 8601 timestamp |
Labels
Labels categorise and organise content items and media.
| Command | Description |
|---------|-------------|
| wahlu label list | List all labels |
| wahlu label create <name> | Create a label |
| wahlu label delete <id> | Delete a label |
Create options:
| Option | Description |
|--------|-------------|
| --color <hex> | Colour hex code (e.g. #ff5500) |
Response fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Label ID |
| name | string | Label name |
| color | string|null | Colour hex code |
| created_at | string | ISO 8601 timestamp |
| updated_at | string | ISO 8601 timestamp |
Integrations (read-only)
Connected social media accounts. You need integration IDs when scheduling content items.
| Command | Description |
|---------|-------------|
| wahlu integration list | List connected integrations |
Response fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Integration ID (use in --integrations when scheduling) |
| platform | string | instagram | tiktok | facebook | youtube | linkedin |
| status | string | Connection status |
| display_name | string|null | Display name on the platform |
| username | string|null | Platform username/handle |
| avatar_url | string|null | Profile avatar URL |
| permissions | object|null | Granted permissions |
| created_at | string | ISO 8601 timestamp |
| updated_at | string | ISO 8601 timestamp |
Publications (read-only)
Records of content items published to social media platforms.
| Command | Description |
|---------|-------------|
| wahlu publication list | List published content items (paginated) |
Response fields:
| Field | Type | Description |
|-------|------|-------------|
| id | string | Publication ID |
| platform | string | instagram | tiktok | facebook | youtube | linkedin |
| post_id | string | Source content item ID |
| post_name | string|null | Content item name |
| post_type | string|null | Post type |
| status | string | processing | published | failed |
| source | string|null | calendar (from schedule) or queue (from queue) |
| failure_reason | string|null | Failure reason |
| integration_id | string | Integration used |
| publish_id | string|null | Platform content ID |
| published_at | string | When published (ISO 8601) |
| created_at | string | ISO 8601 timestamp |
| updated_at | string | ISO 8601 timestamp |
Global options
| Flag | Description |
|------|-------------|
| --brand <id> | Use a specific brand (overrides default) |
| --json | Output as JSON (available on all list/get/create/update commands) |
| --help | Show help for any command |
| --version | Show CLI version |
Pagination
All list commands support pagination:
| Flag | Default | Max | Description |
|------|---------|-----|-------------|
| --page <n> | 1 | - | Page number |
| --limit <n> | 50 | 100 | Items per page |
Configuration
Config is stored at ~/.config/wahlu/config.json:
{
"api_key": "wahlu_live_...",
"api_url": "https://api.wahlu.com",
"default_brand_id": "abc123"
}Environment variables take priority over config file:
| Variable | Description |
|----------|-------------|
| WAHLU_API_KEY | API key |
| WAHLU_API_URL | API base URL (default: https://api.wahlu.com) |
| WAHLU_BRAND_ID | Default brand ID |
For AI agents
Every command supports --json for structured output:
# Get content item IDs
wahlu post list --json | jq '.[].id'
# Get integration IDs for scheduling
wahlu integration list --json | jq '.[] | {id, platform, username}'
# Find failed publications
wahlu publication list --json | jq '.[] | select(.status == "failed")'
# Create and capture the ID
CONTENT_ITEM_ID=$(wahlu post create --name "Auto post" --json | jq -r '.id')
wahlu schedule create $CONTENT_ITEM_ID --at 2026-03-15T14:00:00Z --integrations int-123Documentation
Full documentation: wahlu.com/docs
License
MIT
