diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index 8d9f6c1..3f697d2 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -159,7 +159,7 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} - skip_existing: true + skip-existing: true - name: Swap package name to late-sdk if: steps.changes.outputs.has_changes == 'true' @@ -185,4 +185,4 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} - skip_existing: true + skip-existing: true diff --git a/README.md b/README.md index e941e49..11bb689 100644 --- a/README.md +++ b/README.md @@ -275,19 +275,6 @@ Both `from zernio import ...` and `from late import ...` work identically. The ` | `media.upload_large_bytes()` | Upload large file from bytes | | `media.upload_multiple()` | Upload multiple files | -### Tools -| Method | Description | -|--------|-------------| -| `tools.get_you_tube_transcript()` | Get YouTube transcript | -| `tools.check_instagram_hashtags()` | Check IG hashtag bans | -| `tools.download_bluesky_media()` | Download Bluesky media | -| `tools.download_facebook_video()` | Download Facebook video | -| `tools.download_instagram_media()` | Download Instagram media | -| `tools.download_linked_in_video()` | Download LinkedIn video | -| `tools.download_tik_tok_video()` | Download TikTok video | -| `tools.download_twitter_media()` | Download Twitter/X media | -| `tools.download_you_tube_video()` | Download YouTube video | - ### Users | Method | Description | |--------|-------------| @@ -323,11 +310,13 @@ Both `from zernio import ...` and `from late import ...` work identically. The ` | `connect.get_reddit_flairs()` | List subreddit flairs | | `connect.get_reddit_subreddits()` | List Reddit subreddits | | `connect.get_telegram_connect_status()` | Generate Telegram code | +| `connect.get_youtube_playlists()` | List YouTube playlists | | `connect.update_facebook_page()` | Update Facebook page | | `connect.update_gmb_location()` | Update GBP location | | `connect.update_linked_in_organization()` | Switch LinkedIn account type | | `connect.update_pinterest_boards()` | Set default Pinterest board | | `connect.update_reddit_subreddits()` | Set default subreddit | +| `connect.update_youtube_default_playlist()` | Set default YouTube playlist | | `connect.complete_telegram_connect()` | Check Telegram status | | `connect.connect_bluesky_credentials()` | Connect Bluesky account | | `connect.connect_whats_app_credentials()` | Connect WhatsApp via credentials | @@ -358,6 +347,36 @@ Both `from zernio import ...` and `from late import ...` work identically. The ` | `account_settings.set_messenger_menu()` | Set FB persistent menu | | `account_settings.set_telegram_commands()` | Set TG bot commands | +### Ad Audiences +| Method | Description | +|--------|-------------| +| `ad_audiences.list_ad_audiences()` | List custom audiences | +| `ad_audiences.create_ad_audience()` | Create a custom audience (Meta only) | +| `ad_audiences.get_ad_audience()` | Get audience details | +| `ad_audiences.delete_ad_audience()` | Delete a custom audience | +| `ad_audiences.add_users_to_ad_audience()` | Add users to a customer list audience | + +### Ad Campaigns +| Method | Description | +|--------|-------------| +| `ad_campaigns.list_ad_campaigns()` | List campaigns with aggregate metrics | +| `ad_campaigns.get_ad_tree()` | Get nested campaign/ad-set/ad tree | +| `ad_campaigns.update_ad_campaign_status()` | Pause or resume a campaign | + +### Ads +| Method | Description | +|--------|-------------| +| `ads.list_ad_accounts()` | List ad accounts for a social account | +| `ads.list_ads()` | List ads | +| `ads.create_standalone_ad()` | Create a standalone ad with custom creative | +| `ads.get_ad()` | Get ad details | +| `ads.get_ad_analytics()` | Get ad analytics with daily breakdown | +| `ads.update_ad()` | Update ad (pause/resume, budget, targeting, name) | +| `ads.delete_ad()` | Cancel an ad | +| `ads.boost_post()` | Boost an existing post as a paid ad | +| `ads.search_ad_interests()` | Search targeting interests | +| `ads.sync_external_ads()` | Sync external ads from platform ad managers | + ### Broadcasts | Method | Description | |--------|-------------| @@ -455,8 +474,13 @@ Both `from zernio import ...` and `from late import ...` work identically. The ` | `messages.get_inbox_conversation()` | Get conversation | | `messages.get_inbox_conversation_messages()` | List messages | | `messages.update_inbox_conversation()` | Update conversation status | +| `messages.delete_inbox_message()` | Delete message | +| `messages.add_message_reaction()` | Add reaction | | `messages.edit_inbox_message()` | Edit message | +| `messages.remove_message_reaction()` | Remove reaction | | `messages.send_inbox_message()` | Send message | +| `messages.send_typing_indicator()` | Send typing indicator | +| `messages.upload_media_direct()` | Upload media file | ### Reviews (Inbox) | Method | Description | diff --git a/openapi.yaml b/openapi.yaml index e5f1d4c..7ceab20 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -126,15 +126,17 @@ x-documentation: Receive real-time notifications for post status changes, account events, and incoming messages: - - `post.scheduled` - Post successfully scheduled + - `post.scheduled` - Post created and scheduled for publishing - `post.published` - Post successfully published - `post.failed` - Post failed on all platforms - `post.partial` - Post published to some platforms, failed on others - - `post.recycled` - A new scheduled copy was created from a recycling post + - `post.cancelled` - Post publishing was cancelled + - `post.recycled` - Post recycled (cloned and re-scheduled) - `account.connected` - Social account connected - `account.disconnected` - Social account disconnected (token expired) - `message.received` - New DM received - `comment.received` - New comment received on a post + - `webhook.test` - Test event sent when verifying a webhook endpoint Webhook payloads are signed with HMAC-SHA256 via the `X-Zernio-Signature` header. @@ -162,7 +164,6 @@ tags: - name: Invites - name: Connect - name: Media - # - name: Video Clips # AI Clipping feature temporarily disabled - name: Reddit Search - name: Facebook - name: GMB Reviews @@ -199,11 +200,6 @@ tags: description: | X/Twitter-specific engagement endpoints for retweeting, bookmarking, and following. Rate limits: 50 requests per 15-min window per user. Retweets share the 300/3hr creation limit with tweet creation. - - name: Tools - description: | - Media download and utility tools. Available to paid plans only. - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - All responses include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers. - name: Validate description: | Pre-flight validation endpoints. Check post content, character limits, media URLs, and subreddit existence before publishing. @@ -231,10 +227,29 @@ tags: Comment-to-DM growth automations. Set up keyword triggers on Instagram/Facebook posts so commenters automatically receive a DM. Supports dedup, optional public comment reply, and auto-creates contacts. + - name: Ads + description: | + Paid advertising management across Meta (Facebook/Instagram), Google, TikTok, LinkedIn, Pinterest, and X/Twitter. + Create, boost, pause/resume ads and campaigns, view metrics, manage audiences, and sync external ads. + Requires the Ads add-on. + - name: Ad Campaigns + description: | + Campaign-level operations. Campaigns are virtual aggregations of ads grouped by their platform campaign ID. + List campaigns with aggregate metrics, or pause/resume all ads in a campaign at once. + Requires the Ads add-on. + - name: Ad Audiences + description: | + Custom audience management for ad targeting. Create customer lists, website retargeting audiences, + and lookalike audiences. Upload user data (hashed server-side). Currently Meta-only for creation, + read-only for other platforms. + Requires the Ads add-on. - name: Webhooks description: | - Configure webhooks for real-time notifications. Events: post.scheduled, post.published, post.failed, post.partial, post.recycled, account.connected, account.disconnected, message.received, comment.received. + Configure webhooks for real-time notifications. Events: post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled, account.connected, account.disconnected, message.received, comment.received, webhook.test. Security: optional HMAC-SHA256 signature in X-Zernio-Signature header. Configure a secret key to enable verification. Custom headers supported. + - name: Webhook Events + description: | + Incoming webhook deliveries sent by Zernio to your configured endpoint URL. - name: Logs description: | Publishing logs for transparency and debugging. Each log includes the platform API endpoint, HTTP status code, request/response bodies, duration, and retry attempts. Logs are automatically deleted after 7 days. @@ -304,6 +319,135 @@ components: details: type: object additionalProperties: true + WhatsAppTemplateButton: + type: object + required: [type, text] + properties: + type: + type: string + enum: [quick_reply, url, phone_number, otp, flow, mpm, catalog] + text: + type: string + url: + type: string + format: uri + description: Required when type is URL + example: + type: array + items: { type: string } + description: Example values for URL suffix variables + phone_number: + type: string + description: Required when type is phone_number + otp_type: + type: string + enum: [copy_code, one_tap, zero_tap] + description: Required when type is otp + autofill_text: + type: string + package_name: + type: string + signature_hash: + type: string + flow_id: + type: string + flow_name: + type: string + flow_json: + type: string + flow_action: + type: string + navigate_screen: + type: string + WhatsAppTemplateComponent: + oneOf: + - $ref: '#/components/schemas/WhatsAppHeaderComponent' + - $ref: '#/components/schemas/WhatsAppBodyComponent' + - $ref: '#/components/schemas/WhatsAppFooterComponent' + - $ref: '#/components/schemas/WhatsAppButtonsComponent' + discriminator: + propertyName: type + mapping: + header: '#/components/schemas/WhatsAppHeaderComponent' + body: '#/components/schemas/WhatsAppBodyComponent' + footer: '#/components/schemas/WhatsAppFooterComponent' + buttons: '#/components/schemas/WhatsAppButtonsComponent' + WhatsAppHeaderComponent: + type: object + required: [type, format] + properties: + type: + type: string + enum: [header] + format: + type: string + enum: [text, image, video, gif, document, location] + text: + type: string + description: Header text (may include {{1}} variable). Used when format is TEXT. + example: + type: object + properties: + header_text: + type: array + items: { type: string } + description: Sample values for header text variables + header_handle: + type: array + minItems: 1 + maxItems: 1 + items: + type: string + format: uri + description: When the header format is a media type (image, video, gif, document), provide a public URL here. Zernio will download and upload it to WhatsApp on your behalf, replacing it with the internal file handle before creating the template. + WhatsAppBodyComponent: + type: object + required: [type, text] + properties: + type: + type: string + enum: [body] + text: + type: string + description: Body text with optional {{n}} variables + add_security_recommendation: + type: boolean + description: Add security recommendation text (authentication templates only) + example: + type: object + properties: + body_text: + type: array + items: + type: array + items: { type: string } + description: Sample values for body variables (array of arrays) + WhatsAppFooterComponent: + type: object + required: [type] + properties: + type: + type: string + enum: [footer] + text: + type: string + description: Static footer text + code_expiration_minutes: + type: integer + minimum: 1 + description: OTP code expiry in minutes (authentication templates only) + WhatsAppButtonsComponent: + type: object + required: [type, buttons] + properties: + type: + type: string + enum: [buttons] + buttons: + type: array + minItems: 1 + items: + $ref: '#/components/schemas/WhatsAppTemplateButton' FoodMenuLabel: type: object required: [displayName] @@ -586,7 +730,7 @@ components: type: array items: type: string - enum: [post.scheduled, post.published, post.failed, post.partial, post.recycled, account.connected, account.disconnected, message.received, comment.received] + enum: [post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled, account.connected, account.disconnected, message.received, comment.received] description: Events subscribed to isActive: type: boolean @@ -617,7 +761,7 @@ components: description: Name of the webhook that was triggered event: type: string - enum: [post.scheduled, post.published, post.failed, post.partial, post.recycled, account.connected, account.disconnected, message.received, comment.received, webhook.test] + enum: [post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled, account.connected, account.disconnected, message.received, comment.received, webhook.test] url: type: string format: uri @@ -648,12 +792,17 @@ components: WebhookPayloadPost: type: object description: Webhook payload for post events + required: [id, event, post, timestamp] properties: + id: + type: string + description: Stable webhook event ID event: type: string - enum: [post.scheduled, post.published, post.failed, post.partial, post.recycled] + enum: [post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled] post: type: object + required: [id, content, status, scheduledFor, platforms] properties: id: type: string @@ -671,11 +820,14 @@ components: type: array items: type: object + required: [platform, status] properties: platform: type: string status: type: string + platformPostId: + type: string publishedUrl: type: string error: @@ -686,12 +838,17 @@ components: WebhookPayloadAccountConnected: type: object description: Webhook payload for account connected events + required: [id, event, account, timestamp] properties: + id: + type: string + description: Stable webhook event ID event: type: string enum: [account.connected] account: type: object + required: [accountId, profileId, platform, username] properties: accountId: type: string @@ -711,12 +868,17 @@ components: WebhookPayloadAccountDisconnected: type: object description: Webhook payload for account disconnected events + required: [id, event, account, timestamp] properties: + id: + type: string + description: Stable webhook event ID event: type: string enum: [account.disconnected] account: type: object + required: [accountId, profileId, platform, username, disconnectionType, reason] properties: accountId: type: string @@ -743,12 +905,17 @@ components: WebhookPayloadComment: type: object description: Webhook payload for comment received events (Instagram, Facebook, Twitter/X, YouTube, LinkedIn, Bluesky, Reddit) + required: [id, event, comment, post, account, timestamp] properties: + id: + type: string + description: Stable webhook event ID event: type: string enum: [comment.received] comment: type: object + required: [id, postId, platformPostId, platform, text, author, createdAt, isReply, parentCommentId] properties: id: type: string @@ -767,6 +934,7 @@ components: description: Comment text content author: type: object + required: [id] properties: id: type: string @@ -790,6 +958,7 @@ components: description: Parent comment ID if this is a reply post: type: object + required: [id, platformPostId] properties: id: type: string @@ -799,6 +968,7 @@ components: description: Platform's post ID account: type: object + required: [id, platform, username] properties: id: type: string @@ -812,13 +982,18 @@ components: format: date-time WebhookPayloadMessage: type: object - description: Webhook payload for message received events (DMs from Instagram, Facebook, Telegram, Bluesky, Reddit) + description: Webhook payload for message received events + required: [id, event, message, conversation, account, timestamp] properties: + id: + type: string + description: Stable webhook event ID event: type: string enum: [message.received] message: type: object + required: [id, conversationId, platform, platformMessageId, direction, text, attachments, sender, sentAt, isRead] properties: id: type: string @@ -828,13 +1003,13 @@ components: description: Internal conversation ID platform: type: string - enum: [instagram, facebook, telegram, bluesky, reddit] + enum: [instagram, facebook, telegram, whatsapp] platformMessageId: type: string description: Platform's message ID direction: type: string - enum: [incoming] + enum: [incoming, outgoing] text: type: string nullable: true @@ -843,6 +1018,7 @@ components: type: array items: type: object + required: [type, url] properties: type: type: string @@ -855,6 +1031,7 @@ components: description: Additional attachment metadata sender: type: object + required: [id] properties: id: type: string @@ -866,7 +1043,6 @@ components: type: string instagramProfile: type: object - nullable: true description: Instagram profile data for the sender. Only present for Instagram conversations. properties: isFollower: @@ -892,6 +1068,7 @@ components: type: boolean conversation: type: object + required: [id, platformConversationId, status] properties: id: type: string @@ -905,16 +1082,12 @@ components: type: string participantPicture: type: string - participantVerifiedType: - type: string - nullable: true - enum: [blue, government, business, none] - description: X/Twitter verified badge type. Only present for Twitter/X conversations. status: type: string enum: [active, archived] account: type: object + required: [id, platform, username] properties: id: type: string @@ -945,6 +1118,23 @@ components: timestamp: type: string format: date-time + WebhookPayloadTest: + type: object + description: Webhook payload for test deliveries + required: [id, event, message, timestamp] + properties: + id: + type: string + description: Stable webhook event ID + event: + type: string + enum: [webhook.test] + message: + type: string + description: Human-readable test message + timestamp: + type: string + format: date-time PostLog: type: object description: Publishing log entry showing details of a post publishing attempt @@ -1150,7 +1340,7 @@ components: instagramThumbnail: type: string format: uri - description: Optional custom cover image URL for Instagram Reels + description: "Custom cover image URL for Instagram Reels. Can also be set via platformSpecificData.instagramThumbnail or platformSpecificData.reelCover. Resolution order: this field > platformSpecificData.instagramThumbnail > platformSpecificData.reelCover > platformSpecificData.thumbnailUrl (legacy)." tiktokProcessed: type: boolean description: Internal flag indicating the image was resized for TikTok @@ -1499,8 +1689,16 @@ components: thumbOffset: type: integer minimum: 0 - description: Millisecond offset from video start for the Reel thumbnail. Ignored if a custom thumbnail URL is provided. Defaults to 0. + description: Millisecond offset from video start for the Reel cover frame. Ignored when instagramThumbnail or reelCover is provided. Defaults to 0. example: 5000 + instagramThumbnail: + type: string + format: uri + description: Custom cover image URL for Instagram Reels (JPG or PNG, publicly accessible). Overrides thumbOffset when provided. Also accepted as reelCover (alias). + reelCover: + type: string + format: uri + description: Alias for instagramThumbnail. If both are provided, instagramThumbnail takes priority. description: Feed aspect ratio 0.8-1.91, carousels up to 10 items, stories require media (no captions). User tag coordinates 0.0-1.0 from top-left. Images over 8 MB and videos over platform limits are auto-compressed. LinkedInPlatformData: @@ -1570,6 +1768,9 @@ components: type: string default: '22' description: "YouTube video category ID. Defaults to 22 (People & Blogs). Common: 1 (Film), 2 (Autos), 10 (Music), 15 (Pets), 17 (Sports), 20 (Gaming), 23 (Comedy), 24 (Entertainment), 25 (News), 26 (Howto), 27 (Education), 28 (Science & Tech)." + playlistId: + type: string + description: "Optional YouTube playlist ID to add the video to after upload (e.g. 'PLxxxxxxxxxxxxx'). Use GET /v1/accounts/{id}/youtube-playlists to list available playlists. Works for both immediate and scheduled uploads. Quota cost: 50 YouTube API units per call." description: Videos under 3 min auto-detected as Shorts. Custom thumbnails for regular videos only. Scheduled videos are uploaded immediately with the specified visibility. GoogleBusinessPlatformData: @@ -1814,6 +2015,17 @@ components: type: string format: date-time description: Last time follower count was updated (only included if user has analytics add-on) + metadata: + type: object + description: | + Platform-specific metadata. Fields vary by platform. For WhatsApp accounts, includes: + - `qualityRating`: Phone number quality rating from Meta (`GREEN`, `YELLOW`, `RED`, or `UNKNOWN`) + - `nameStatus`: Display name review status (`APPROVED`, `PENDING_REVIEW`, `DECLINED`, or `NONE`). Messages cannot be sent until the display name is approved by Meta. + - `messagingLimitTier`: Maximum unique business-initiated conversations per 24h rolling window (`TIER_250`, `TIER_1K`, `TIER_10K`, `TIER_100K`, or `TIER_UNLIMITED`). Scales automatically as quality rating improves. + - `verifiedName`: Meta-verified business display name + - `displayPhoneNumber`: Formatted phone number (e.g., "+1 555-123-4567") + - `wabaId`: WhatsApp Business Account ID + - `phoneNumberId`: Meta phone number ID AccountWithFollowerStats: allOf: - $ref: '#/components/schemas/SocialAccount' @@ -1890,8 +2102,6 @@ components: uploads: { type: integer } profiles: { type: integer } lastReset: { type: string, format: date-time } - # VideoClipJob, VideoClip, VideoClipJobProcessing, VideoClipJobCompleted, VideoClipJobFailed, VideoClipUsageStats - # schemas removed - AI Clipping feature temporarily disabled PostAnalytics: type: object properties: @@ -2313,79 +2523,6 @@ components: format: date-time timezone: type: string - # Tools Responses - DownloadFormat: - type: object - properties: - formatId: - type: string - ext: - type: string - resolution: - type: string - filesize: - type: integer - quality: - type: string - DownloadResponse: - type: object - properties: - url: - type: string - format: uri - title: - type: string - thumbnail: - type: string - format: uri - duration: - type: integer - formats: - type: array - items: - $ref: '#/components/schemas/DownloadFormat' - TranscriptSegment: - type: object - properties: - text: - type: string - start: - type: number - duration: - type: number - TranscriptResponse: - type: object - properties: - transcript: - type: string - segments: - type: array - items: - $ref: '#/components/schemas/TranscriptSegment' - language: - type: string - HashtagInfo: - type: object - properties: - hashtag: - type: string - status: - type: string - enum: [safe, banned, restricted, unknown] - postCount: - type: integer - HashtagCheckResponse: - type: object - properties: - hashtags: - type: array - items: - $ref: '#/components/schemas/HashtagInfo' - CaptionResponse: - type: object - properties: - caption: - type: string # Users Responses User: type: object @@ -2413,412 +2550,304 @@ components: properties: user: $ref: '#/components/schemas/User' -security: - - bearerAuth: [] -paths: - # ============================================ - # Tools API - Media Download & Utilities - # ============================================ - /v1/tools/youtube/download: - get: - operationId: downloadYouTubeVideo - tags: [Tools] - summary: Download YouTube video - description: | - Download YouTube videos or audio. Returns available formats or direct download URL. - - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - security: - - bearerAuth: [] - parameters: - - name: url - in: query - required: true - description: YouTube video URL or video ID - schema: - type: string - example: "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - - name: action - in: query - description: "Action to perform: 'download' returns download URL, 'formats' lists available formats" - schema: - type: string - enum: [download, formats] - default: download - - name: format - in: query - description: Desired format (when action=download) - schema: - type: string - enum: [video, audio] - default: video - - name: quality - in: query - description: Desired quality (when action=download) - schema: - type: string - enum: [hd, sd] - default: hd - - name: formatId - in: query - description: Specific format ID from formats list - schema: - type: string + AdMetrics: + type: object + properties: + spend: { type: number } + impressions: { type: integer } + reach: { type: integer } + clicks: { type: integer } + ctr: { type: number, description: Click-through rate (%) } + cpc: { type: number, description: Cost per click } + cpm: { type: number, description: Cost per 1000 impressions } + engagement: { type: integer } + lastSyncedAt: { type: string, format: date-time, description: "Present on individual ads only, not on campaign aggregations" } + Ad: + type: object + properties: + _id: { type: string } + name: { type: string } + platform: { type: string, enum: [facebook, instagram, tiktok, linkedin, pinterest, google, twitter] } + status: { type: string, enum: [active, paused, pending_review, rejected, completed, cancelled, error] } + adType: { type: string, enum: [boost, standalone] } + goal: { type: string, enum: [engagement, traffic, awareness, video_views] } + isExternal: { type: boolean, description: True for ads synced from platform ad managers } + budget: + type: object + nullable: true + properties: + amount: { type: number } + type: { type: string, enum: [daily, lifetime] } + metrics: + allOf: + - { $ref: '#/components/schemas/AdMetrics' } + nullable: true + platformAdId: { type: string } + platformAdAccountId: { type: string } + platformCampaignId: { type: string } + platformAdSetId: { type: string } + campaignName: { type: string } + adSetName: { type: string } + creative: + type: object + nullable: true + description: Platform-specific creative data. Fields vary by platform. + properties: + thumbnailUrl: { type: string, description: Primary thumbnail/image URL } + imageUrl: { type: string, description: Alternative image URL } + mediaUrls: + type: array + items: { type: string } + description: All media URLs for this ad (carousel images, multiple assets). Populated for Meta (carousel child_attachments), Google Ads (responsive display marketing_images), and LinkedIn (multi-image posts). + body: { type: string, description: Ad copy/text } + googleHeadline: { type: string, description: Google Ads headline } + googleDescription: { type: string, description: Google Ads description } + linkUrl: { type: string, description: Destination URL } + pinterestImageUrl: { type: string } + pinterestTitle: { type: string } + pinterestDescription: { type: string } + targeting: { type: object } + schedule: + type: object + nullable: true + properties: + startDate: { type: string, format: date-time } + endDate: { type: string, format: date-time } + rejectionReason: { type: string } + createdAt: { type: string, format: date-time } + updatedAt: { type: string, format: date-time } + AdTreeAdSet: + type: object + description: Ad set (or ad group/line item depending on platform) with rolled-up metrics and child ads + properties: + platformAdSetId: { type: string } + adSetName: { type: string } + status: { type: string, enum: [active, paused, pending_review, rejected, completed, cancelled, error], description: Derived from child ad statuses } + adCount: { type: integer } + budget: + type: object + nullable: true + properties: + amount: { type: number } + type: { type: string, enum: [daily, lifetime] } + metrics: { $ref: '#/components/schemas/AdMetrics' } + ads: + type: array + items: { $ref: '#/components/schemas/Ad' } + description: Individual ads within this ad set (capped at 100). Returns a subset of Ad fields from the aggregation (core fields like _id, name, platform, status, budget, metrics, creative, goal are included; targeting and schedule may be absent). + AdTreeCampaign: + type: object + description: Campaign with nested ad sets and rolled-up metrics + properties: + platformCampaignId: { type: string } + platform: { type: string, enum: [facebook, instagram, tiktok, linkedin, pinterest, google, twitter] } + campaignName: { type: string } + status: { type: string, enum: [active, paused, pending_review, rejected, completed, cancelled, error], description: Derived from child ad statuses } + adCount: { type: integer, description: Total ads across all ad sets } + adSetCount: { type: integer } + budget: + type: object + nullable: true + properties: + amount: { type: number } + type: { type: string, enum: [daily, lifetime] } + metrics: { $ref: '#/components/schemas/AdMetrics' } + platformAdAccountId: { type: string } + accountId: { type: string } + profileId: { type: string } + adSets: + type: array + items: { $ref: '#/components/schemas/AdTreeAdSet' } + AdCampaign: + type: object + properties: + platformCampaignId: { type: string } + platform: { type: string, enum: [facebook, instagram, tiktok, linkedin, pinterest, google, twitter] } + campaignName: { type: string } + status: { type: string, enum: [active, paused, pending_review, rejected, completed, cancelled, error], description: Derived from child ad statuses } + adCount: { type: integer } + budget: + type: object + nullable: true + properties: + amount: { type: number } + type: { type: string, enum: [daily, lifetime] } + metrics: { $ref: '#/components/schemas/AdMetrics' } + platformAdAccountId: { type: string } + accountId: { type: string } + profileId: { type: string } + earliestAd: { type: string, format: date-time } + latestAd: { type: string, format: date-time } +webhooks: + post.scheduled: + post: + operationId: onPostScheduled + summary: Post scheduled event + description: Fired when a post is created and scheduled for publishing. + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadPost' responses: - "200": - description: Success - headers: - X-RateLimit-Limit: - schema: { type: string } - description: Daily rate limit - X-RateLimit-Remaining: - schema: { type: string } - description: Remaining calls today - X-RateLimit-Reset: - schema: { type: string } - description: Unix timestamp when limit resets - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - title: { type: string } - downloadUrl: { type: string, format: uri } - formats: - type: array - items: - type: object - properties: - id: { type: string } - label: { type: string } - ext: { type: string } - type: { type: string } - height: { type: integer } - width: { type: integer } - "401": - $ref: '#/components/responses/Unauthorized' - "403": - description: Tools API not available on free plan - "429": - description: Daily rate limit exceeded - - /v1/tools/youtube/transcript: - get: - operationId: getYouTubeTranscript - tags: [Tools] - summary: Get YouTube transcript - description: | - Extract transcript/captions from a YouTube video. - - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - security: - - bearerAuth: [] - parameters: - - name: url - in: query - required: true - description: YouTube video URL or video ID - schema: - type: string - - name: lang - in: query - description: Language code for transcript - schema: - type: string - default: en + '200': + description: Webhook received successfully + post.published: + post: + operationId: onPostPublished + summary: Post published event + description: Fired when a post is successfully published. + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadPost' responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - videoId: { type: string } - language: { type: string } - fullText: { type: string } - segments: - type: array - items: - type: object - properties: - text: { type: string } - start: { type: number } - duration: { type: number } - "404": - description: No transcript available - "503": - description: Transcript service temporarily unavailable - - /v1/tools/instagram/download: - get: - operationId: downloadInstagramMedia - tags: [Tools] - summary: Download Instagram media - description: | - Download Instagram reels, posts, or photos. - - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - security: - - bearerAuth: [] - parameters: - - name: url - in: query - required: true - description: Instagram reel or post URL - schema: - type: string - example: "https://www.instagram.com/reel/ABC123/" + '200': + description: Webhook received successfully + post.failed: + post: + operationId: onPostFailed + summary: Post failed event + description: Fired when a post fails to publish on all target platforms. + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadPost' responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - title: { type: string } - downloadUrl: { type: string, format: uri } - - /v1/tools/instagram/hashtag-checker: + '200': + description: Webhook received successfully + post.partial: post: - operationId: checkInstagramHashtags - tags: [Tools] - summary: Check IG hashtag bans - description: | - Check if Instagram hashtags are banned, restricted, or safe to use. - - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - security: - - bearerAuth: [] + operationId: onPostPartial + summary: Post partial event + description: Fired when a post publishes on some platforms and fails on others. + tags: [Webhook Events] requestBody: required: true content: application/json: schema: - type: object - required: [hashtags] - properties: - hashtags: - type: array - maxItems: 20 - items: - type: string - example: ["travel", "followforfollow", "fitness"] + $ref: '#/components/schemas/WebhookPayloadPost' responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - results: - type: array - items: - type: object - properties: - hashtag: { type: string } - status: - type: string - enum: [banned, restricted, safe, unknown] - reason: { type: string } - confidence: { type: number } - summary: - type: object - properties: - banned: { type: integer } - restricted: { type: integer } - safe: { type: integer } - - /v1/tools/tiktok/download: - get: - operationId: downloadTikTokVideo - tags: [Tools] - summary: Download TikTok video - description: | - Download TikTok videos with or without watermark. - - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - security: - - bearerAuth: [] - parameters: - - name: url - in: query - required: true - description: TikTok video URL or ID - schema: - type: string - - name: action - in: query - description: "'formats' to list available formats" - schema: - type: string - enum: [download, formats] - default: download - - name: formatId - in: query - description: Specific format ID (0 = no watermark, etc.) - schema: - type: string + '200': + description: Webhook received successfully + post.cancelled: + post: + operationId: onPostCancelled + summary: Post cancelled event + description: Fired when a post publishing job is cancelled. + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadPost' responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - title: { type: string } - downloadUrl: { type: string, format: uri } - formats: - type: array - items: - type: object - properties: - id: { type: string } - label: { type: string } - ext: { type: string } - - /v1/tools/twitter/download: - get: - operationId: downloadTwitterMedia - tags: [Tools] - summary: Download Twitter/X media - description: | - Download videos from Twitter/X posts. - - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - security: - - bearerAuth: [] - parameters: - - name: url - in: query - required: true - description: Twitter/X post URL - schema: - type: string - example: "https://x.com/user/status/123456789" - - name: action - in: query - schema: - type: string - enum: [download, formats] - default: download - - name: formatId - in: query - schema: - type: string + '200': + description: Webhook received successfully + post.recycled: + post: + operationId: onPostRecycled + summary: Post recycled event + description: Fired when a post is recycled (cloned and re-scheduled for publishing). + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadPost' responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - title: { type: string } - downloadUrl: { type: string, format: uri } - - /v1/tools/facebook/download: - get: - operationId: downloadFacebookVideo - tags: [Tools] - summary: Download Facebook video - description: | - Download videos and reels from Facebook. - - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - security: - - bearerAuth: [] - parameters: - - name: url - in: query - required: true - description: Facebook video or reel URL - schema: - type: string + '200': + description: Webhook received successfully + account.connected: + post: + operationId: onAccountConnected + summary: Account connected event + description: Fired when a social account is successfully connected. + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadAccountConnected' responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - title: { type: string } - downloadUrl: { type: string, format: uri } - thumbnail: { type: string, format: uri } - - /v1/tools/linkedin/download: - get: - operationId: downloadLinkedInVideo - tags: [Tools] - summary: Download LinkedIn video - description: | - Download videos from LinkedIn posts. - - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - security: - - bearerAuth: [] - parameters: - - name: url - in: query - required: true - description: LinkedIn post URL - schema: - type: string + '200': + description: Webhook received successfully + account.disconnected: + post: + operationId: onAccountDisconnected + summary: Account disconnected event + description: Fired when a connected social account becomes disconnected. + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadAccountDisconnected' responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - title: { type: string } - downloadUrl: { type: string, format: uri } - - /v1/tools/bluesky/download: - get: - operationId: downloadBlueskyMedia - tags: [Tools] - summary: Download Bluesky media - description: | - Download videos from Bluesky posts. - - Rate limits: Build (50/day), Accelerate (500/day), Unlimited (unlimited). - security: - - bearerAuth: [] - parameters: - - name: url - in: query - required: true - description: Bluesky post URL - schema: - type: string - example: "https://bsky.app/profile/user.bsky.social/post/abc123" + '200': + description: Webhook received successfully + message.received: + post: + operationId: onMessageReceived + summary: Message received event + description: Fired when a new inbox message is received. + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadMessage' responses: - "200": - description: Success - content: - application/json: - schema: - type: object - properties: - success: { type: boolean } - title: { type: string } - text: { type: string } - downloadUrl: { type: string, format: uri } - thumbnail: { type: string, format: uri } + '200': + description: Webhook received successfully + comment.received: + post: + operationId: onCommentReceived + summary: Comment received event + description: Fired when a new comment is received on a tracked post. + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadComment' + responses: + '200': + description: Webhook received successfully + webhook.test: + post: + operationId: onWebhookTest + summary: Webhook test event + description: Fired when sending a test webhook to verify the endpoint configuration. + tags: [Webhook Events] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookPayloadTest' + responses: + '200': + description: Webhook received successfully +security: + - bearerAuth: [] +paths: + # NOTE: Tools download endpoints (/v1/tools/{platform}/download, /transcript, /hashtag-checker) removed from docs but still functional for existing customers # ============================================ # Validate @@ -4399,9 +4428,6 @@ paths: file_too_large: value: { error: "File too large. Maximum size is 5GB." } '401': { $ref: '#/components/responses/Unauthorized' } - # Video Clips API paths removed - AI Clipping feature temporarily disabled - # /v1/video-clips, /v1/video-clips-replicate, /v1/video-clips/process, - # /v1/video-clips/jobs, /v1/video-clips/status/{jobId}, /v1/video-clips/monthly-stats /v1/reddit/search: get: operationId: searchReddit @@ -5366,14 +5392,21 @@ paths: tags: [Posts] summary: Update post metadata description: | - Updates metadata of an already-published post on the specified platform without re-uploading the media. - Currently only supported for YouTube videos (title, description, tags, category, privacy status). - The post must have "published" status on the target platform. At least one updatable field is required. + Updates metadata of a published video on the specified platform without re-uploading. + Currently only supported for YouTube. At least one updatable field is required. + + **Two modes:** + + 1. **Post-based** (video published through Zernio): pass the Zernio postId in the URL and `platform` in the body. + 2. **Direct video ID** (video uploaded outside Zernio, e.g. directly to YouTube): use `_` as the postId, + and pass `videoId` + `accountId` + `platform` in the body. The accountId is the Zernio social account ID + for the connected YouTube channel. parameters: - name: postId in: path required: true schema: { type: string } + description: Zernio post ID, or "_" when using direct video ID mode requestBody: required: true content: @@ -5387,6 +5420,12 @@ paths: description: The platform to update metadata on enum: - youtube + videoId: + type: string + description: YouTube video ID (required for direct mode, ignored for post-based mode) + accountId: + type: string + description: Zernio social account ID (required for direct mode, ignored for post-based mode) title: type: string maxLength: 100 @@ -5407,11 +5446,42 @@ paths: type: string enum: [public, private, unlisted] description: Video privacy setting - example: - platform: "youtube" - title: "Updated Video Title" - description: "New SEO-optimized description" - tags: ["seo", "marketing", "tutorial"] + thumbnailUrl: + type: string + format: uri + description: "Public URL of a custom thumbnail image (JPEG, PNG, or GIF, max 2 MB, recommended 1280x720). Works on any video you own, including existing videos not published through Zernio. The channel must be verified (phone verification) to set custom thumbnails." + madeForKids: + type: boolean + description: "COPPA compliance flag. Set true for child-directed content (restricts comments, notifications, ad targeting)." + containsSyntheticMedia: + type: boolean + description: "AI-generated content disclosure. Set true if the video contains synthetic content that could be mistaken for real. YouTube may add a label." + playlistId: + type: string + description: "YouTube playlist ID to add the video to (e.g. 'PLxxxxxxxxxxxxx'). Use GET /v1/accounts/{id}/youtube-playlists to list available playlists. Only playlists owned by the channel are supported." + examples: + post-based: + summary: Update a video published through Zernio + value: + platform: "youtube" + title: "Updated Video Title" + description: "New SEO-optimized description" + tags: ["seo", "marketing", "tutorial"] + direct-video-id: + summary: Update a video uploaded directly to YouTube + value: + platform: "youtube" + videoId: "dQw4w9WgXcQ" + accountId: "68fb37418bbca9c10cbfef26" + title: "Updated Title with SEO Keywords" + tags: ["seo", "youtube", "optimization"] + update-thumbnail: + summary: Update thumbnail on an existing video + value: + platform: "youtube" + videoId: "dQw4w9WgXcQ" + accountId: "68fb37418bbca9c10cbfef26" + thumbnailUrl: "https://example.com/my-thumbnail.jpg" responses: '200': description: Metadata updated successfully @@ -5422,12 +5492,13 @@ paths: properties: success: { type: boolean } message: { type: string } + videoId: { type: string, description: Only present in direct video ID mode } updatedFields: type: array items: { type: string } example: success: true - message: "Video metadata updated on youtube successfully" + message: "YouTube video metadata updated successfully" updatedFields: ["title", "description", "tags"] '400': description: "Invalid request: unsupported platform, post not published, missing fields, or validation error." @@ -5706,7 +5777,9 @@ paths: operationId: listAccounts tags: [Accounts] summary: List accounts - description: Returns connected social accounts. Only includes accounts within the plan limit by default. Follower data requires analytics add-on. + description: | + Returns connected social accounts. Only includes accounts within the plan limit by default. Follower data requires analytics add-on. + Supports optional server-side pagination via page/limit params. When omitted, returns all accounts (backward-compatible). parameters: - name: profileId in: query @@ -5723,9 +5796,17 @@ paths: type: boolean default: false description: When true, includes accounts from over-limit profiles. + - name: page + in: query + schema: { type: integer, minimum: 1 } + description: Page number (1-based). When provided with limit, enables server-side pagination. Omit for all accounts. + - name: limit + in: query + schema: { type: integer, minimum: 1, maximum: 100 } + description: Page size. Required alongside page for pagination. responses: '200': - description: Accounts + description: Accounts (with optional pagination) content: application/json: schema: @@ -5737,6 +5818,9 @@ paths: hasAnalyticsAccess: type: boolean description: Whether user has analytics add-on access + pagination: + description: Only present when page/limit params are provided + $ref: '#/components/schemas/Pagination' examples: example: value: @@ -8609,9 +8693,7 @@ paths: username: { type: string, description: Display phone number } displayName: { type: string, description: Meta-verified business name } isActive: { type: boolean } - phoneNumber: { type: string } - verifiedName: { type: string } - qualityRating: { type: string, description: "GREEN, YELLOW, or RED" } + selectedPhoneNumber: { type: string, description: The connected phone number } example: message: "WhatsApp connected successfully" account: @@ -8620,9 +8702,7 @@ paths: username: "+1 555-123-4567" displayName: "Acme Corp" isActive: true - phoneNumber: "+1 555-123-4567" - verifiedName: "Acme Corp" - qualityRating: "GREEN" + selectedPhoneNumber: "+1 555-123-4567" '400': description: | Invalid request. Either missing fields or the phoneNumberId was not found @@ -9647,12 +9727,12 @@ paths: '401': { $ref: '#/components/responses/Unauthorized' } '404': { description: Account not found } - /v1/accounts/{accountId}/gmb-locations: + /v1/accounts/{accountId}/youtube-playlists: get: - operationId: getGmbLocations + operationId: getYoutubePlaylists tags: [Connect] - summary: List GBP locations - description: Returns all Google Business Profile locations the connected account has access to, including the currently selected location. + summary: List YouTube playlists + description: Returns the playlists available for a connected YouTube account. Use this to get a playlist ID when creating a YouTube post with the `playlistId` field. parameters: - name: accountId in: path @@ -9660,20 +9740,109 @@ paths: schema: { type: string } responses: '200': - description: Locations list + description: Playlists list content: application/json: schema: type: object properties: - locations: + playlists: type: array items: type: object properties: id: { type: string } - name: { type: string } - accountId: { type: string } + title: { type: string } + description: { type: string } + privacy: { type: string, enum: [public, private, unlisted] } + itemCount: { type: integer } + thumbnailUrl: { type: string } + defaultPlaylistId: + type: string + nullable: true + example: + playlists: + - id: "PLxxxxxxxxxxxxx" + title: "Tutorials" + description: "Step-by-step video tutorials" + privacy: "public" + itemCount: 24 + thumbnailUrl: "https://i.ytimg.com/vi/xxx/mqdefault.jpg" + - id: "PLyyyyyyyyyyyyy" + title: "Vlogs" + description: "Weekly vlogs" + privacy: "public" + itemCount: 52 + thumbnailUrl: "https://i.ytimg.com/vi/yyy/mqdefault.jpg" + defaultPlaylistId: null + '400': { description: Not a YouTube account } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': { description: Account not found } + put: + operationId: updateYoutubeDefaultPlaylist + tags: [Connect] + summary: Set default YouTube playlist + description: Sets the default playlist used when publishing videos for this account. When a post does not specify a `playlistId`, the default playlist is not automatically used (it is stored for client-side convenience). + parameters: + - name: accountId + in: path + required: true + schema: { type: string } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [defaultPlaylistId] + properties: + defaultPlaylistId: { type: string } + defaultPlaylistName: { type: string } + example: + defaultPlaylistId: "PLxxxxxxxxxxxxx" + defaultPlaylistName: "Tutorials" + responses: + '200': + description: Default playlist set + content: + application/json: + schema: + type: object + properties: + success: { type: boolean } + example: + success: true + '400': { description: Invalid request } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': { description: Account not found } + + /v1/accounts/{accountId}/gmb-locations: + get: + operationId: getGmbLocations + tags: [Connect] + summary: List GBP locations + description: Returns all Google Business Profile locations the connected account has access to, including the currently selected location. + parameters: + - name: accountId + in: path + required: true + schema: { type: string } + responses: + '200': + description: Locations list + content: + application/json: + schema: + type: object + properties: + locations: + type: array + items: + type: object + properties: + id: { type: string } + name: { type: string } + accountId: { type: string } accountName: { type: string } address: { type: string } category: { type: string } @@ -10280,7 +10449,7 @@ paths: type: array items: type: string - enum: [post.scheduled, post.published, post.failed, post.partial, post.recycled, account.connected, account.disconnected, message.received, comment.received] + enum: [post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled, account.connected, account.disconnected, message.received, comment.received] description: Events to subscribe to isActive: type: boolean @@ -10297,7 +10466,7 @@ paths: name: "My Production Webhook" url: "https://example.com/webhook" secret: "your-secret-key" - events: ["post.scheduled", "post.published", "post.failed", "post.partial", "account.connected", "account.disconnected", "message.received", "comment.received"] + events: ["post.scheduled", "post.published", "post.failed", "post.partial", "post.cancelled", "post.recycled", "account.connected", "account.disconnected", "message.received", "comment.received"] isActive: true responses: '200': @@ -10349,7 +10518,7 @@ paths: type: array items: type: string - enum: [post.scheduled, post.published, post.failed, post.partial, post.recycled, account.connected, account.disconnected, message.received, comment.received] + enum: [post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled, account.connected, account.disconnected, message.received, comment.received] description: Events to subscribe to isActive: type: boolean @@ -10491,7 +10660,7 @@ paths: description: Filter by event type schema: type: string - enum: [post.scheduled, post.published, post.failed, post.partial, post.recycled, account.connected, account.disconnected, message.received, comment.received, webhook.test] + enum: [post.scheduled, post.published, post.failed, post.partial, post.cancelled, post.recycled, account.connected, account.disconnected, message.received, comment.received, webhook.test] - name: webhookId in: query description: Filter by webhook ID @@ -10830,6 +10999,8 @@ paths: description: | Fetch conversations (DMs) from all connected messaging accounts in a single API call. Supports filtering by profile and platform. Results are aggregated and deduplicated. Supported platforms: Facebook, Instagram, Twitter/X, Bluesky, Reddit, Telegram. + + **Twitter/X limitation:** X has replaced traditional DMs with encrypted "X Chat" for many accounts. Messages sent or received through encrypted X Chat are not accessible via X's API (the `/2/dm_events` endpoint only returns legacy unencrypted DMs). This means some Twitter/X conversations may show only outgoing messages or appear empty. This is an X platform limitation that affects all third-party applications. See [X's docs on encrypted messaging](https://help.x.com/en/using-x/about-chat) for more details. tags: [Messages] security: [{ bearerAuth: [] }] parameters: @@ -11074,7 +11245,10 @@ paths: get: operationId: getInboxConversationMessages summary: List messages - description: Fetch messages for a specific conversation. Requires accountId query parameter. + description: | + Fetch messages for a specific conversation. Requires accountId query parameter. + + **Twitter/X limitation:** X's encrypted "X Chat" messages are not accessible via the API. Conversations where the other participant uses encrypted X Chat may only show your outgoing messages. See the [list conversations endpoint](#/Messages/listInboxConversations) for more details. tags: [Messages] security: [{ bearerAuth: [] }] parameters: @@ -11363,6 +11537,230 @@ paths: '401': { $ref: '#/components/responses/Unauthorized' } '403': description: Inbox addon required + delete: + operationId: deleteInboxMessage + summary: Delete message + description: | + Delete a message from a conversation. Platform support varies: + - **Telegram**: Full delete (bot's own messages anytime, others if admin) + - **X/Twitter**: Full delete (own DM events only) + - **Bluesky**: Delete for self only (recipient still sees it) + - **Reddit**: Delete from sender's view only + - **Facebook, Instagram, WhatsApp**: Not supported (returns 400) + tags: [Messages] + security: [{ bearerAuth: [] }] + parameters: + - name: conversationId + in: path + required: true + schema: { type: string } + description: The conversation ID + - name: messageId + in: path + required: true + schema: { type: string } + description: The platform message ID to delete + - name: accountId + in: query + required: true + schema: { type: string } + description: Social account ID + responses: + '200': + description: Message deleted + content: + application/json: + schema: + type: object + properties: + success: { type: boolean } + '400': + description: Platform does not support deletion or invalid request + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Inbox addon required + '404': + description: Account or conversation not found + + /v1/inbox/conversations/{conversationId}/typing: + post: + operationId: sendTypingIndicator + summary: Send typing indicator + description: | + Show a typing indicator in a conversation. Platform support: + - **Facebook Messenger**: Shows "Page is typing..." for 20 seconds + - **Telegram**: Shows "Bot is typing..." for 5 seconds + - **All others**: Returns 200 but no-op (platform doesn't support it) + + Typing indicators are best-effort. The endpoint always returns 200 even if the platform call fails. + tags: [Messages] + security: [{ bearerAuth: [] }] + parameters: + - name: conversationId + in: path + required: true + schema: { type: string } + description: The conversation ID + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [accountId] + properties: + accountId: { type: string, description: Social account ID } + responses: + '200': + description: Typing indicator sent (or no-op on unsupported platforms) + content: + application/json: + schema: + type: object + properties: + success: { type: boolean } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Inbox addon required + '404': + description: Account or conversation not found + + /v1/inbox/conversations/{conversationId}/messages/{messageId}/reactions: + post: + operationId: addMessageReaction + summary: Add reaction + description: | + Add an emoji reaction to a message. Platform support: + - **Telegram**: Supports a subset of Unicode emoji reactions + - **WhatsApp**: Supports any standard emoji (one reaction per message per sender) + - **All others**: Returns 400 (not supported) + tags: [Messages] + security: [{ bearerAuth: [] }] + parameters: + - name: conversationId + in: path + required: true + schema: { type: string } + description: The conversation ID + - name: messageId + in: path + required: true + schema: { type: string } + description: The platform message ID to react to + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [accountId, emoji] + properties: + accountId: { type: string, description: Social account ID } + emoji: { type: string, description: 'Emoji character (e.g. "👍", "❤️")', example: '👍' } + responses: + '200': + description: Reaction added + content: + application/json: + schema: + type: object + properties: + success: { type: boolean } + '400': + description: Platform does not support reactions or invalid request + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Inbox addon required + '404': + description: Account or conversation not found + delete: + operationId: removeMessageReaction + summary: Remove reaction + description: | + Remove a reaction from a message. Platform support: + - **Telegram**: Send empty reaction array to clear + - **WhatsApp**: Send empty emoji to remove + - **All others**: Returns 400 (not supported) + tags: [Messages] + security: [{ bearerAuth: [] }] + parameters: + - name: conversationId + in: path + required: true + schema: { type: string } + description: The conversation ID + - name: messageId + in: path + required: true + schema: { type: string } + description: The platform message ID + - name: accountId + in: query + required: true + schema: { type: string } + description: Social account ID + responses: + '200': + description: Reaction removed + content: + application/json: + schema: + type: object + properties: + success: { type: boolean } + '400': + description: Platform does not support reactions or invalid request + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Inbox addon required + '404': + description: Account or conversation not found + + /v1/media/upload-direct: + post: + operationId: uploadMediaDirect + summary: Upload media file + description: | + Upload a media file using API key authentication and get back a publicly accessible URL. + The URL can be used as `attachmentUrl` when sending inbox messages. + + Files are stored in temporary storage and auto-delete after 7 days. + Maximum file size is 25MB. + + Unlike `/v1/media/upload` (which uses upload tokens for end-user flows), + this endpoint uses standard Bearer token authentication for programmatic use. + tags: [Messages] + security: [{ bearerAuth: [] }] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [file] + properties: + file: + type: string + format: binary + description: The file to upload (max 25MB) + contentType: + type: string + description: 'Override MIME type (e.g. "image/jpeg"). Auto-detected from file if not provided.' + responses: + '200': + description: File uploaded successfully + content: + application/json: + schema: + type: object + properties: + url: { type: string, description: Publicly accessible URL for the uploaded file } + filename: { type: string, description: Generated unique filename } + contentType: { type: string, description: MIME type of the file } + size: { type: integer, description: File size in bytes } + '400': + description: No file provided or file too large + '401': { $ref: '#/components/responses/Unauthorized' } /v1/accounts/{accountId}/messenger-menu: get: @@ -13462,8 +13860,9 @@ paths: components: type: array description: "Template components (header, body, footer, buttons). Required for custom templates, omit when using library_template_name." + minItems: 1 items: - type: object + $ref: '#/components/schemas/WhatsAppTemplateComponent' library_template_name: type: string description: | @@ -13486,7 +13885,7 @@ paths: properties: type: type: string - enum: [QUICK_REPLY, URL, PHONE_NUMBER] + enum: [quick_reply, url, phone_number] url: type: object properties: @@ -13502,8 +13901,20 @@ paths: category: "UTILITY" language: "en_US" components: + - type: "header" + format: "image" + example: + header_handle: ["https://example.com/header.jpg"] - type: "body" text: "Your order {{1}} has been confirmed. Expected delivery: {{2}}" + example: + body_text: [["ORD-12345", "March 31"]] + - type: "footer" + text: "Thank you for your purchase" + - type: "buttons" + buttons: + - type: "quick_reply" + text: "Track Order" library: summary: Library template (pre-approved, no review) value: @@ -13513,7 +13924,7 @@ paths: language: "en_US" library_template_name: "appointment_reminder" library_template_button_inputs: - - type: "URL" + - type: "url" url: base_url: "https://myapp.com/appointments/{{1}}" responses: @@ -13615,13 +14026,20 @@ paths: components: type: array description: Updated template components + minItems: 1 items: - type: object + $ref: '#/components/schemas/WhatsAppTemplateComponent' example: accountId: "507f1f77bcf86cd799439011" components: - type: "body" text: "Updated: Your order {{1}} is confirmed. Delivery by {{2}}" + example: + body_text: [["ORD-12345", "April 1"]] + - type: "buttons" + buttons: + - type: "quick_reply" + text: "Track Order" responses: '200': description: Template updated successfully @@ -16448,3 +16866,677 @@ paths: '401': { $ref: '#/components/responses/Unauthorized' } '404': { $ref: '#/components/responses/NotFound' } + # ── Ads ────────────────────────────────────────────────────────────────── + + /v1/ads: + get: + operationId: listAds + tags: [Ads] + summary: List ads + description: Returns a paginated list of ads with cached metrics. Use `source=all` to include externally-synced ads from platform ad managers. + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/PageParam' + - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 500, default: 50 } } + - { name: source, in: query, schema: { type: string, enum: [zernio, all], default: zernio }, description: "zernio = Zernio-created only, all = include external ads" } + - { name: status, in: query, schema: { type: string, enum: [active, paused, pending_review, rejected, completed, cancelled, error] } } + - { name: platform, in: query, schema: { type: string, enum: [facebook, instagram, tiktok, linkedin, pinterest, google, twitter] } } + - { name: accountId, in: query, schema: { type: string }, description: Social account ID } + - { name: profileId, in: query, schema: { type: string }, description: Profile ID } + - { name: campaignId, in: query, schema: { type: string }, description: Platform campaign ID (filter ads within a campaign) } + responses: + '200': + description: Paginated ads + content: + application/json: + schema: + type: object + properties: + ads: + type: array + items: { $ref: '#/components/schemas/Ad' } + pagination: { $ref: '#/components/schemas/Pagination' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + + /v1/ads/campaigns: + get: + operationId: listAdCampaigns + tags: [Ad Campaigns] + summary: List campaigns with aggregate metrics + description: | + Returns campaigns as virtual aggregations over ad documents grouped by platform campaign ID. + Metrics (spend, impressions, clicks, etc.) are summed across all ads in each campaign. + Campaign status is derived from child ad statuses (active > pending_review > paused > error > completed > cancelled > rejected). + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/PageParam' + - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 } } + - { name: source, in: query, schema: { type: string, enum: [zernio, all], default: zernio } } + - { name: platform, in: query, schema: { type: string, enum: [facebook, instagram, tiktok, linkedin, pinterest, google, twitter] } } + - { name: status, in: query, schema: { type: string, enum: [active, paused, pending_review, rejected, completed, cancelled, error] }, description: Filter by derived campaign status (post-aggregation) } + - { name: adAccountId, in: query, schema: { type: string }, description: Platform ad account ID (e.g. act_123 for Meta) } + - { name: accountId, in: query, schema: { type: string }, description: Social account ID } + - { name: profileId, in: query, schema: { type: string }, description: Profile ID } + responses: + '200': + description: Paginated campaigns + content: + application/json: + schema: + type: object + properties: + campaigns: + type: array + items: { $ref: '#/components/schemas/AdCampaign' } + pagination: { $ref: '#/components/schemas/Pagination' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + + /v1/ads/campaigns/{campaignId}/status: + put: + operationId: updateAdCampaignStatus + tags: [Ad Campaigns] + summary: Pause or resume a campaign + description: | + Updates the status of all ads in a campaign. Makes one platform API call (not per-ad) since status cascades through the campaign hierarchy. + Ads in terminal statuses (rejected, completed, cancelled) are automatically skipped. + security: + - bearerAuth: [] + parameters: + - { name: campaignId, in: path, required: true, schema: { type: string }, description: Platform campaign ID } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [status, platform] + properties: + status: { type: string, enum: [active, paused] } + platform: { type: string, enum: [facebook, instagram, tiktok, linkedin, pinterest, google, twitter] } + responses: + '200': + description: Campaign status updated + content: + application/json: + schema: + type: object + properties: + updated: { type: integer, description: Number of ads updated } + skipped: { type: integer, description: Number of ads skipped } + skippedReasons: { type: array, items: { type: string } } + message: { type: string, description: Human-readable summary (present when no ads were actionable) } + '400': + description: Invalid input or campaign spans multiple social accounts + '401': { $ref: '#/components/responses/Unauthorized' } + '404': + description: No ads found for this campaign + + /v1/ads/tree: + get: + operationId: getAdTree + tags: [Ad Campaigns] + summary: Get nested campaign/ad-set/ad tree + description: | + Returns a nested Campaign > Ad Set > Ad hierarchy with rolled-up metrics at each level. + Uses a two-stage aggregation: ads are grouped into ad sets, then ad sets into campaigns. + Pagination is at the campaign level. Ads without a campaign or ad set ID are grouped into + synthetic "Ungrouped" buckets. + security: + - bearerAuth: [] + parameters: + - $ref: '#/components/parameters/PageParam' + - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 20 }, description: Campaigns per page } + - { name: source, in: query, schema: { type: string, enum: [zernio, all], default: zernio } } + - { name: platform, in: query, schema: { type: string, enum: [facebook, instagram, tiktok, linkedin, pinterest, google, twitter] } } + - { name: status, in: query, schema: { type: string, enum: [active, paused, pending_review, rejected, completed, cancelled, error] }, description: Filter by derived campaign status (post-aggregation) } + - { name: adAccountId, in: query, schema: { type: string }, description: Platform ad account ID } + - { name: accountId, in: query, schema: { type: string }, description: Social account ID } + - { name: profileId, in: query, schema: { type: string }, description: Profile ID } + responses: + '200': + description: Nested campaign tree with pagination + content: + application/json: + schema: + type: object + properties: + campaigns: + type: array + items: { $ref: '#/components/schemas/AdTreeCampaign' } + pagination: { $ref: '#/components/schemas/Pagination' } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + + /v1/ads/{adId}: + get: + operationId: getAd + tags: [Ads] + summary: Get ad details + security: + - bearerAuth: [] + parameters: + - { name: adId, in: path, required: true, schema: { type: string } } + responses: + '200': + description: Ad details + content: + application/json: + schema: + type: object + properties: + ad: { $ref: '#/components/schemas/Ad' } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': { $ref: '#/components/responses/NotFound' } + put: + operationId: updateAd + tags: [Ads] + summary: Update ad (pause/resume, budget, targeting, name) + description: Update one or more fields on an ad. Status changes and budget updates are propagated to the platform. Targeting updates are Meta-only. + security: + - bearerAuth: [] + parameters: + - { name: adId, in: path, required: true, schema: { type: string } } + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + status: { type: string, enum: [active, paused] } + budget: + type: object + properties: + amount: { type: number, description: "Minimum varies by platform: TikTok=$20, Pinterest=$5, others=$1" } + type: { type: string, enum: [daily, lifetime] } + targeting: + type: object + description: Meta-only. Targeting updates for other platforms are not supported after creation. + properties: + ageMin: { type: integer, minimum: 13, maximum: 65 } + ageMax: { type: integer, minimum: 13, maximum: 65 } + countries: { type: array, items: { type: string } } + interests: { type: array, items: { type: string } } + name: { type: string } + responses: + '200': + description: Ad updated + content: + application/json: + schema: + type: object + properties: + ad: { $ref: '#/components/schemas/Ad' } + message: { type: string } + '400': + description: Invalid status transition or budget below minimum + '401': { $ref: '#/components/responses/Unauthorized' } + '404': { $ref: '#/components/responses/NotFound' } + delete: + operationId: deleteAd + tags: [Ads] + summary: Cancel an ad + description: Cancels the ad on the platform and marks it as cancelled in the database. The ad is preserved for history. + security: + - bearerAuth: [] + parameters: + - { name: adId, in: path, required: true, schema: { type: string } } + responses: + '200': + description: Ad cancelled + content: + application/json: + schema: + type: object + properties: + message: { type: string } + '401': { $ref: '#/components/responses/Unauthorized' } + '404': { $ref: '#/components/responses/NotFound' } + + /v1/ads/{adId}/analytics: + get: + operationId: getAdAnalytics + tags: [Ads] + summary: Get ad analytics with daily breakdown + description: | + Returns real-time analytics from the platform API (not cached). Includes summary metrics, + daily breakdown, and optional demographic breakdowns (Meta and TikTok only). + security: + - bearerAuth: [] + parameters: + - { name: adId, in: path, required: true, schema: { type: string } } + - name: breakdowns + in: query + schema: { type: string } + description: "Comma-separated breakdown dimensions. Meta: age, gender, country, publisher_platform, device_platform, region. TikTok: gender, age, country_code, platform, ac, language." + responses: + '200': + description: Ad analytics + content: + application/json: + schema: + type: object + properties: + ad: + type: object + properties: + id: { type: string } + name: { type: string } + platform: { type: string } + status: { type: string } + analytics: + type: object + properties: + summary: { $ref: '#/components/schemas/AdMetrics' } + daily: + type: array + items: + allOf: + - { $ref: '#/components/schemas/AdMetrics' } + - type: object + properties: + date: { type: string, format: date } + breakdowns: + type: object + additionalProperties: + type: array + items: { type: object } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + '404': { $ref: '#/components/responses/NotFound' } + + /v1/ads/accounts: + get: + operationId: listAdAccounts + tags: [Ads] + summary: List ad accounts for a social account + description: Returns the platform ad accounts available for the given social account (e.g. Meta ad accounts, TikTok advertiser IDs, Google Ads customer IDs). + security: + - bearerAuth: [] + parameters: + - { name: accountId, in: query, required: true, schema: { type: string }, description: Social account ID } + responses: + '200': + description: Ad accounts + content: + application/json: + schema: + type: object + properties: + accounts: + type: array + items: + type: object + properties: + id: { type: string, description: "Platform ad account ID (e.g. act_123)" } + name: { type: string } + currency: { type: string } + status: { type: string } + '401': { $ref: '#/components/responses/Unauthorized' } + '422': + description: Platform ads connection required (TikTok Ads, X Ads) or Instagram missing linked Facebook account + + /v1/ads/boost: + post: + operationId: boostPost + tags: [Ads] + summary: Boost an existing post as a paid ad + description: Creates a paid ad campaign from an existing published post. Creates the full platform campaign hierarchy (campaign, ad set, ad). + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [accountId, adAccountId, name, goal, budget] + properties: + postId: { type: string, description: Zernio post ID (provide this or platformPostId) } + platformPostId: { type: string, description: Platform post ID (alternative to postId) } + accountId: { type: string, description: Social account ID } + adAccountId: { type: string, description: Platform ad account ID } + name: { type: string, maxLength: 255 } + goal: { type: string, enum: [engagement, traffic, awareness, video_views] } + budget: + type: object + required: [amount, type] + properties: + amount: { type: number, description: "Minimum varies: TikTok=$20, Pinterest=$5, others=$1" } + type: { type: string, enum: [daily, lifetime] } + currency: { type: string, example: USD } + schedule: + type: object + properties: + startDate: { type: string, format: date-time } + endDate: { type: string, format: date-time, description: Required for lifetime budgets } + targeting: + type: object + properties: + ageMin: { type: integer, minimum: 13, maximum: 65 } + ageMax: { type: integer, minimum: 13, maximum: 65 } + countries: { type: array, items: { type: string } } + interests: { type: array, items: { type: string } } + bidAmount: { type: number, description: "Max bid cap (Meta only)" } + tracking: + type: object + description: "Meta only. Tracking specs (pixel, URL tags)." + properties: + pixelId: { type: string } + urlTags: { type: string } + specialAdCategories: + type: array + description: "Meta only. Required for housing, employment, credit, or political ads." + items: { type: string, enum: [HOUSING, EMPLOYMENT, CREDIT, ISSUES_ELECTIONS_POLITICS] } + responses: + '201': + description: Ad created + content: + application/json: + schema: + type: object + properties: + ad: { $ref: '#/components/schemas/Ad' } + message: { type: string } + '400': + description: Missing required fields or invalid values + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + '422': + description: Platform ads connection required (TikTok Ads, X Ads) or missing linked account + + /v1/ads/create: + post: + operationId: createStandaloneAd + tags: [Ads] + summary: Create a standalone ad with custom creative + description: Creates a paid ad with custom creative (headline, body, image/video, link). Creates the full platform campaign hierarchy. + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [accountId, adAccountId, name, goal, budgetAmount, budgetType, body] + properties: + accountId: { type: string } + adAccountId: { type: string } + name: { type: string, maxLength: 255 } + goal: { type: string, enum: [engagement, traffic, awareness, video_views] } + budgetAmount: { type: number } + budgetType: { type: string, enum: [daily, lifetime] } + currency: { type: string } + headline: { type: string, description: "Required for most platforms. Max: Meta=255, Google=30, Pinterest=100" } + longHeadline: { type: string, maxLength: 90, description: "Google Display only" } + body: { type: string, description: "Max: Google=90, Pinterest=500" } + callToAction: { type: string, enum: [LEARN_MORE, SHOP_NOW, SIGN_UP, BOOK_TRAVEL, CONTACT_US, DOWNLOAD, GET_OFFER, GET_QUOTE, SUBSCRIBE, WATCH_MORE], description: Meta only } + linkUrl: { type: string, format: uri } + imageUrl: { type: string, format: uri, description: "Image URL (or video URL for TikTok). Not required for Google Search campaigns." } + businessName: { type: string, maxLength: 25, description: "Google Display only" } + boardId: { type: string, description: "Pinterest only. Board ID (auto-creates if not provided)." } + countries: { type: array, items: { type: string } } + ageMin: { type: integer, minimum: 13, maximum: 65 } + ageMax: { type: integer, minimum: 13, maximum: 65 } + interests: { type: array, items: { type: string } } + endDate: { type: string, format: date-time, description: Required for lifetime budgets } + audienceId: { type: string, description: Custom audience ID for targeting } + campaignType: { type: string, enum: [display, search], default: display, description: Google only } + keywords: { type: array, items: { type: string }, description: Google Search only } + additionalHeadlines: { type: array, items: { type: string }, description: "Google Search RSA only. Extra headlines." } + additionalDescriptions: { type: array, items: { type: string }, description: "Google Search RSA only. Extra descriptions." } + responses: + '201': + description: Ad created + content: + application/json: + schema: + type: object + properties: + ad: { $ref: '#/components/schemas/Ad' } + message: { type: string } + '400': + description: Missing required fields or invalid values + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + '422': + description: Platform ads connection required (TikTok Ads, X Ads) or missing linked account + + /v1/ads/sync: + post: + operationId: syncExternalAds + tags: [Ads] + summary: Sync external ads from platform ad managers + description: Discovers and imports ads created outside Zernio (e.g. in Meta Ads Manager, Google Ads). Upserts new ads and updates metrics/status for existing ones. Also runs automatically every 30 minutes. + security: + - bearerAuth: [] + responses: + '200': + description: Sync completed + content: + application/json: + schema: + type: object + properties: + success: { type: boolean } + synced: { type: integer, description: New ads imported } + skipped: { type: integer, description: Already-known ads (skipped import, metrics still refreshed) } + errors: { type: integer, description: Failed ad imports } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + + /v1/ads/interests: + get: + operationId: searchAdInterests + tags: [Ads] + summary: Search targeting interests + description: Search for interest-based targeting options available on the platform. + security: + - bearerAuth: [] + parameters: + - { name: q, in: query, required: true, schema: { type: string }, description: Search query } + - { name: accountId, in: query, required: true, schema: { type: string }, description: Social account ID } + responses: + '200': + description: Matching interests + content: + application/json: + schema: + type: object + properties: + interests: + type: array + items: + type: object + properties: + id: { type: string } + name: { type: string } + category: { type: string } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + + /v1/ads/audiences: + get: + operationId: listAdAudiences + tags: [Ad Audiences] + summary: List custom audiences + description: Returns custom audiences for the given ad account. Supports Meta, Google, TikTok, and Pinterest. + security: + - bearerAuth: [] + parameters: + - { name: accountId, in: query, required: true, schema: { type: string }, description: Social account ID } + - { name: adAccountId, in: query, required: true, schema: { type: string }, description: Platform ad account ID } + - { name: platform, in: query, schema: { type: string, enum: [facebook, instagram, googleads, tiktok, pinterest] } } + responses: + '200': + description: Audiences + content: + application/json: + schema: + type: object + properties: + audiences: + type: array + items: + type: object + properties: + id: { type: string, nullable: true } + platformAudienceId: { type: string } + name: { type: string } + description: { type: string } + type: { type: string, enum: [customer_list, website, lookalike] } + platform: { type: string } + size: { type: integer } + status: { type: string } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + post: + operationId: createAdAudience + tags: [Ad Audiences] + summary: Create a custom audience (Meta only) + description: Create a customer list, website retargeting, or lookalike audience on Meta (Facebook/Instagram). + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [accountId, adAccountId, name, type] + properties: + accountId: { type: string } + adAccountId: { type: string, description: "Must start with act_" } + name: { type: string, maxLength: 255 } + description: { type: string } + type: { type: string, enum: [customer_list, website, lookalike] } + pixelId: { type: string, description: Required for website audiences } + retentionDays: { type: integer, minimum: 1, maximum: 180, description: Required for website audiences } + sourceAudienceId: { type: string, description: Required for lookalike audiences } + country: { type: string, description: "2-letter code, required for lookalike audiences" } + ratio: { type: number, minimum: 0.01, maximum: 0.20, description: Required for lookalike audiences } + rule: { type: object, description: "Pixel event rule for website audiences (optional)" } + customerFileSource: { type: string, description: "Data source declaration for GDPR compliance (customer_list only)" } + responses: + '201': + description: Audience created + content: + application/json: + schema: + type: object + properties: + audience: { type: object } + message: { type: string } + '400': + description: Missing required fields + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + + /v1/ads/audiences/{audienceId}: + get: + operationId: getAdAudience + tags: [Ad Audiences] + summary: Get audience details + description: Returns the local audience record and fresh data from Meta (if available). + security: + - bearerAuth: [] + parameters: + - { name: audienceId, in: path, required: true, schema: { type: string } } + responses: + '200': + description: Audience details + content: + application/json: + schema: + type: object + properties: + audience: { type: object } + metaData: { type: object, nullable: true, description: Fresh data from Meta API } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + '404': { $ref: '#/components/responses/NotFound' } + delete: + operationId: deleteAdAudience + tags: [Ad Audiences] + summary: Delete a custom audience + description: Deletes the audience from both Meta and the local database. + security: + - bearerAuth: [] + parameters: + - { name: audienceId, in: path, required: true, schema: { type: string } } + responses: + '200': + description: Audience deleted + content: + application/json: + schema: + type: object + properties: + message: { type: string } + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + '404': { $ref: '#/components/responses/NotFound' } + + /v1/ads/audiences/{audienceId}/users: + post: + operationId: addUsersToAdAudience + tags: [Ad Audiences] + summary: Add users to a customer list audience + description: Upload user data (emails and/or phone numbers) to a customer_list audience. Data is SHA256-hashed server-side before sending to Meta. Max 10,000 users per request. + security: + - bearerAuth: [] + parameters: + - { name: audienceId, in: path, required: true, schema: { type: string } } + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [users] + properties: + users: + type: array + maxItems: 10000 + items: + type: object + properties: + email: { type: string, format: email } + phone: { type: string } + description: Each user must have at least email or phone + responses: + '200': + description: Users added + content: + application/json: + schema: + type: object + properties: + message: { type: string } + numReceived: { type: integer } + numInvalid: { type: integer } + '400': + description: Invalid input (empty users array, missing email/phone) + '401': { $ref: '#/components/responses/Unauthorized' } + '403': + description: Ads add-on required + '404': { $ref: '#/components/responses/NotFound' } + '422': + description: Audience is not a customer_list type or has no platform ID yet diff --git a/pyproject.toml b/pyproject.toml index af891d8..790d607 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "zernio-sdk" -version = "1.3.27" +version = "1.3.36" description = "The official Python library for the Zernio API" readme = "README.md" requires-python = ">=3.10" diff --git a/scripts/generate_mcp_tools.py b/scripts/generate_mcp_tools.py index 9cc82bb..abfb297 100644 --- a/scripts/generate_mcp_tools.py +++ b/scripts/generate_mcp_tools.py @@ -46,6 +46,7 @@ "GMB Attributes": "accounts", "GMB Place Actions": "accounts", "LinkedIn Mentions": "accounts", + "Validate": "validate", } # Operations to SKIP (not useful for MCP) diff --git a/scripts/generate_resources.py b/scripts/generate_resources.py index e093a2a..f5c141d 100644 --- a/scripts/generate_resources.py +++ b/scripts/generate_resources.py @@ -47,6 +47,7 @@ "GMB Attributes": "accounts", # Group under accounts "GMB Place Actions": "accounts", # Group under accounts "LinkedIn Mentions": "accounts", # Group under accounts + "Validate": "validate", } # Resource descriptions for docstrings @@ -67,6 +68,7 @@ "connect": "OAuth connection flows", "reddit": "Reddit search and feed", "invites": "Team invitations", + "validate": "Content and subreddit validation tools", } @@ -414,28 +416,43 @@ def generate_resource_class( return "\n".join(lines) + "\n" -def generate_init_file(resources: list[str]) -> str: - """Generate __init__.py for resources.""" +def generate_init_file(generated_resources: list[str], manual_resources: list[str]) -> str: + """Generate resources/__init__.py, preferring manual implementations over auto-generated. + + For resources that have a manual .py file in resources/ (e.g. posts.py, accounts.py), + import from the manual file. For all others, import from _generated/. + """ lines = [ '"""', - "Auto-generated resource exports.", + "API resources (auto-updated by generate_resources.py).", "", - "DO NOT EDIT THIS FILE MANUALLY.", - "Run `python scripts/generate_resources.py` to regenerate.", + "Manual resources with Pydantic validation live in this directory.", + "Auto-generated resources live in _generated/.", + "This file is regenerated automatically; do not edit by hand.", '"""', "", - "from __future__ import annotations", - "", ] - # Import each resource - for resource in sorted(resources): + all_resources = sorted(set(generated_resources) | set(manual_resources)) + + # Build import lines sorted by full module path to match ruff isort. + # ruff sorts all relative imports as one block by module path: + # ._generated.x comes before .x (since '_' < 'a' in ASCII). + import_lines: list[tuple[str, str]] = [] + for resource in all_resources: class_name = "".join(word.title() for word in resource.split("_")) + "Resource" - lines.append(f"from ._generated.{resource} import {class_name}") + if resource in manual_resources: + import_lines.append((f".{resource}", f"from .{resource} import {class_name}")) + else: + import_lines.append((f"._generated.{resource}", f"from ._generated.{resource} import {class_name}")) + + import_lines.sort(key=lambda x: x[0]) + for _, line in import_lines: + lines.append(line) lines.append("") lines.append("__all__ = [") - for resource in sorted(resources): + for resource in all_resources: class_name = "".join(word.title() for word in resource.split("_")) + "Resource" lines.append(f' "{class_name}",') lines.append("]") @@ -443,14 +460,50 @@ def generate_init_file(resources: list[str]) -> str: return "\n".join(lines) + "\n" -def generate_client_resources(resources: list[str]) -> str: - """Generate the resource initialization code for the client.""" - lines = [] - for resource in sorted(resources): +def update_client_file(client_path: Path, all_resources: list[str]) -> None: + """Auto-update late_client.py imports and resource registration. + + Rewrites the import block from ..resources and the resource init lines + between marker comments, preserving the rest of the file. + """ + content = client_path.read_text() + + # Build new import block, sorted by class name to match ruff isort + class_names = sorted( + "".join(word.title() for word in r.split("_")) + "Resource" + for r in all_resources + ) + + import_block = "from ..resources import (\n" + for cn in class_names: + import_block += f" {cn},\n" + import_block += ")" + + # Replace the existing import block: from ..resources import (...) + content = re.sub( + r"from \.\.resources import \(\n(?:.*?\n)*?\)", + import_block, + content, + ) + + # Build new resource registration block + reg_lines = [] + for resource in sorted(all_resources): class_name = "".join(word.title() for word in resource.split("_")) + "Resource" - attr_name = resource - lines.append(f" self.{attr_name} = {class_name}(self)") - return "\n".join(lines) + reg_lines.append(f" self.{resource} = {class_name}(self)") + reg_block = "\n".join(reg_lines) + + # Replace everything between the markers + content = re.sub( + r"( +# --- auto-registered resources \(do not edit\) ---\n)" + r".*?" + r"( +# --- end auto-registered resources ---)", + r"\1" + reg_block + r"\n\2", + content, + flags=re.DOTALL, + ) + + client_path.write_text(content) def main() -> int: @@ -495,11 +548,21 @@ def main() -> int: "params": extract_parameters(operation), }) - # Create output directory - output_dir = project_root / "src" / "late" / "resources" / "_generated" + # Paths + resources_dir = project_root / "src" / "late" / "resources" + output_dir = resources_dir / "_generated" output_dir.mkdir(parents=True, exist_ok=True) + client_path = project_root / "src" / "late" / "client" / "late_client.py" + + # Detect manual resource files (hand-written implementations that override + # auto-generated ones, e.g. posts.py, accounts.py) + manual_resources = [ + p.stem + for p in resources_dir.glob("*.py") + if p.stem not in ("__init__", "base") + ] - # Generate each resource file + # Generate each auto-generated resource file generated_resources = [] for resource_name, operations in resources.items(): code = generate_resource_class(resource_name, operations) @@ -508,7 +571,7 @@ def main() -> int: generated_resources.append(resource_name) print(f"Generated {output_file.name} with {len(operations)} methods") - # Generate __init__.py for _generated + # Generate _generated/__init__.py init_content = '"""Auto-generated resources."""\n\nfrom __future__ import annotations\n\n' for resource in sorted(generated_resources): class_name = "".join(word.title() for word in resource.split("_")) + "Resource" @@ -518,15 +581,19 @@ def main() -> int: class_name = "".join(word.title() for word in resource.split("_")) + "Resource" init_content += f' "{class_name}",\n' init_content += "]\n" - (output_dir / "__init__.py").write_text(init_content) - print(f"\nGenerated {len(generated_resources)} resource classes") - print(f"Output: {output_dir}") + # Auto-update resources/__init__.py (prefers manual over auto-generated) + parent_init = generate_init_file(generated_resources, manual_resources) + (resources_dir / "__init__.py").write_text(parent_init) + print(f"\nUpdated resources/__init__.py ({len(set(generated_resources) | set(manual_resources))} resources)") + + # Auto-update late_client.py imports and resource registration + all_resources = sorted(set(generated_resources) | set(manual_resources)) + update_client_file(client_path, all_resources) + print(f"Updated late_client.py ({len(all_resources)} resources)") - # Print resource initialization code for the client - print("\n# Add to Late client __init__:") - print(generate_client_resources(generated_resources)) + print(f"\nGenerated {len(generated_resources)} resource classes in {output_dir}") return 0 diff --git a/src/late/__init__.py b/src/late/__init__.py index cb6f1a9..8769c1a 100644 --- a/src/late/__init__.py +++ b/src/late/__init__.py @@ -42,7 +42,7 @@ Visibility, ) -__version__ = "1.3.27" +__version__ = "1.3.36" __all__ = [ # Client diff --git a/src/late/client/late_client.py b/src/late/client/late_client.py index bd2a892..98c0419 100644 --- a/src/late/client/late_client.py +++ b/src/late/client/late_client.py @@ -10,12 +10,19 @@ from ..resources import ( AccountGroupsResource, + AccountSettingsResource, AccountsResource, + AdAudiencesResource, + AdCampaignsResource, + AdsResource, AnalyticsResource, ApiKeysResource, + BroadcastsResource, + CommentAutomationsResource, CommentsResource, ConnectResource, - InboxResource, + ContactsResource, + CustomFieldsResource, InvitesResource, LogsResource, MediaResource, @@ -25,10 +32,15 @@ QueueResource, RedditResource, ReviewsResource, + SequencesResource, ToolsResource, + TwitterEngagementResource, UsageResource, UsersResource, + ValidateResource, WebhooksResource, + WhatsappPhoneNumbersResource, + WhatsappResource, ) from .base import BaseClient @@ -100,31 +112,40 @@ def __init__( resolved_key, base_url=base_url, timeout=timeout, max_retries=max_retries ) - # Core resources (manual with Pydantic validation) - self.posts = PostsResource(self) - self.profiles = ProfilesResource(self) + # --- auto-registered resources (do not edit) --- + self.account_groups = AccountGroupsResource(self) + self.account_settings = AccountSettingsResource(self) self.accounts = AccountsResource(self) - self.users = UsersResource(self) - self.media = MediaResource(self) + self.ad_audiences = AdAudiencesResource(self) + self.ad_campaigns = AdCampaignsResource(self) + self.ads = AdsResource(self) self.analytics = AnalyticsResource(self) - self.tools = ToolsResource(self) - self.queue = QueueResource(self) - - # Inbox resources (comments, DMs, reviews) - self.inbox = InboxResource(self) + self.api_keys = ApiKeysResource(self) + self.broadcasts = BroadcastsResource(self) + self.comment_automations = CommentAutomationsResource(self) self.comments = CommentsResource(self) - self.messages = MessagesResource(self) - self.reviews = ReviewsResource(self) - - # Additional resources (auto-generated) - self.webhooks = WebhooksResource(self) - self.logs = LogsResource(self) self.connect = ConnectResource(self) + self.contacts = ContactsResource(self) + self.custom_fields = CustomFieldsResource(self) self.invites = InvitesResource(self) - self.api_keys = ApiKeysResource(self) - self.account_groups = AccountGroupsResource(self) - self.usage = UsageResource(self) + self.logs = LogsResource(self) + self.media = MediaResource(self) + self.messages = MessagesResource(self) + self.posts = PostsResource(self) + self.profiles = ProfilesResource(self) + self.queue = QueueResource(self) self.reddit = RedditResource(self) + self.reviews = ReviewsResource(self) + self.sequences = SequencesResource(self) + self.tools = ToolsResource(self) + self.twitter_engagement = TwitterEngagementResource(self) + self.usage = UsageResource(self) + self.users = UsersResource(self) + self.validate = ValidateResource(self) + self.webhooks = WebhooksResource(self) + self.whatsapp = WhatsappResource(self) + self.whatsapp_phone_numbers = WhatsappPhoneNumbersResource(self) + # --- end auto-registered resources --- async def __aenter__(self) -> Zernio: """Async context manager entry.""" diff --git a/src/late/mcp/generated_tools.py b/src/late/mcp/generated_tools.py index 7d3dffc..165ef2a 100644 --- a/src/late/mcp/generated_tools.py +++ b/src/late/mcp/generated_tools.py @@ -13,35 +13,37 @@ def _format_response(response: Any) -> str: """Format SDK response for MCP output.""" if response is None: return "Success" - if hasattr(response, '__dict__'): + if hasattr(response, "__dict__"): # Handle response objects - if hasattr(response, 'posts') and response.posts: + if hasattr(response, "posts") and response.posts: posts = response.posts lines = [f"Found {len(posts)} post(s):"] for p in posts[:10]: - content = str(getattr(p, 'content', ''))[:50] - status = getattr(p, 'status', 'unknown') + content = str(getattr(p, "content", ""))[:50] + status = getattr(p, "status", "unknown") lines.append(f"- [{status}] {content}...") return "\n".join(lines) - if hasattr(response, 'accounts') and response.accounts: + if hasattr(response, "accounts") and response.accounts: accs = response.accounts lines = [f"Found {len(accs)} account(s):"] for a in accs[:10]: - platform = getattr(a, 'platform', '?') - username = getattr(a, 'username', None) or getattr(a, 'displayName', '?') + platform = getattr(a, "platform", "?") + username = getattr(a, "username", None) or getattr( + a, "displayName", "?" + ) lines.append(f"- {platform}: {username}") return "\n".join(lines) - if hasattr(response, 'profiles') and response.profiles: + if hasattr(response, "profiles") and response.profiles: profiles = response.profiles lines = [f"Found {len(profiles)} profile(s):"] for p in profiles[:10]: - name = getattr(p, 'name', 'Unnamed') + name = getattr(p, "name", "Unnamed") lines.append(f"- {name}") return "\n".join(lines) - if hasattr(response, 'post') and response.post: + if hasattr(response, "post") and response.post: p = response.post return f"Post ID: {getattr(p, 'field_id', 'N/A')}\nStatus: {getattr(p, 'status', 'N/A')}" - if hasattr(response, 'profile') and response.profile: + if hasattr(response, "profile") and response.profile: p = response.profile return f"Profile: {getattr(p, 'name', 'N/A')} (ID: {getattr(p, 'field_id', 'N/A')})" return str(response) @@ -52,7 +54,6 @@ def register_generated_tools(mcp, _get_client): # ACCOUNT_GROUPS - @mcp.tool() def account_groups_list_account_groups() -> str: """List groups""" @@ -61,8 +62,7 @@ def account_groups_list_account_groups() -> str: response = client.account_groups.list_account_groups() return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def account_groups_create_account_group(name: str, account_ids: str) -> str: @@ -73,14 +73,17 @@ def account_groups_create_account_group(name: str, account_ids: str) -> str: account_ids: (required)""" client = _get_client() try: - response = client.account_groups.create_account_group(name=name, account_ids=account_ids) + response = client.account_groups.create_account_group( + name=name, account_ids=account_ids + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def account_groups_update_account_group(group_id: str, name: str = "", account_ids: str = "") -> str: + def account_groups_update_account_group( + group_id: str, name: str = "", account_ids: str = "" + ) -> str: """Update group Args: @@ -89,11 +92,12 @@ def account_groups_update_account_group(group_id: str, name: str = "", account_i account_ids""" client = _get_client() try: - response = client.account_groups.update_account_group(group_id=group_id, name=name, account_ids=account_ids) + response = client.account_groups.update_account_group( + group_id=group_id, name=name, account_ids=account_ids + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def account_groups_delete_account_group(group_id: str) -> str: @@ -106,11 +110,10 @@ def account_groups_delete_account_group(group_id: str) -> str: response = client.account_groups.delete_account_group(group_id=group_id) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # ACCOUNT_SETTINGS - @mcp.tool() def account_settings_get_messenger_menu(account_id: str) -> str: """Get FB persistent menu @@ -122,11 +125,12 @@ def account_settings_get_messenger_menu(account_id: str) -> str: response = client.account_settings.get_messenger_menu(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def account_settings_set_messenger_menu(account_id: str, persistent_menu: str) -> str: + def account_settings_set_messenger_menu( + account_id: str, persistent_menu: str + ) -> str: """Set FB persistent menu Args: @@ -134,11 +138,12 @@ def account_settings_set_messenger_menu(account_id: str, persistent_menu: str) - persistent_menu: Persistent menu configuration array (Meta format) (required)""" client = _get_client() try: - response = client.account_settings.set_messenger_menu(account_id=account_id, persistent_menu=persistent_menu) + response = client.account_settings.set_messenger_menu( + account_id=account_id, persistent_menu=persistent_menu + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def account_settings_delete_messenger_menu(account_id: str) -> str: @@ -148,11 +153,12 @@ def account_settings_delete_messenger_menu(account_id: str) -> str: account_id: (required)""" client = _get_client() try: - response = client.account_settings.delete_messenger_menu(account_id=account_id) + response = client.account_settings.delete_messenger_menu( + account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def account_settings_get_instagram_ice_breakers(account_id: str) -> str: @@ -162,14 +168,17 @@ def account_settings_get_instagram_ice_breakers(account_id: str) -> str: account_id: (required)""" client = _get_client() try: - response = client.account_settings.get_instagram_ice_breakers(account_id=account_id) + response = client.account_settings.get_instagram_ice_breakers( + account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def account_settings_set_instagram_ice_breakers(account_id: str, ice_breakers: str) -> str: + def account_settings_set_instagram_ice_breakers( + account_id: str, ice_breakers: str + ) -> str: """Set IG ice breakers Args: @@ -177,11 +186,12 @@ def account_settings_set_instagram_ice_breakers(account_id: str, ice_breakers: s ice_breakers: (required)""" client = _get_client() try: - response = client.account_settings.set_instagram_ice_breakers(account_id=account_id, ice_breakers=ice_breakers) + response = client.account_settings.set_instagram_ice_breakers( + account_id=account_id, ice_breakers=ice_breakers + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def account_settings_delete_instagram_ice_breakers(account_id: str) -> str: @@ -191,11 +201,12 @@ def account_settings_delete_instagram_ice_breakers(account_id: str) -> str: account_id: (required)""" client = _get_client() try: - response = client.account_settings.delete_instagram_ice_breakers(account_id=account_id) + response = client.account_settings.delete_instagram_ice_breakers( + account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def account_settings_get_telegram_commands(account_id: str) -> str: @@ -205,11 +216,12 @@ def account_settings_get_telegram_commands(account_id: str) -> str: account_id: (required)""" client = _get_client() try: - response = client.account_settings.get_telegram_commands(account_id=account_id) + response = client.account_settings.get_telegram_commands( + account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def account_settings_set_telegram_commands(account_id: str, commands: str) -> str: @@ -220,11 +232,12 @@ def account_settings_set_telegram_commands(account_id: str, commands: str) -> st commands: (required)""" client = _get_client() try: - response = client.account_settings.set_telegram_commands(account_id=account_id, commands=commands) + response = client.account_settings.set_telegram_commands( + account_id=account_id, commands=commands + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def account_settings_delete_telegram_commands(account_id: str) -> str: @@ -234,32 +247,52 @@ def account_settings_delete_telegram_commands(account_id: str) -> str: account_id: (required)""" client = _get_client() try: - response = client.account_settings.delete_telegram_commands(account_id=account_id) + response = client.account_settings.delete_telegram_commands( + account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # ACCOUNTS - @mcp.tool() - def accounts_list_accounts(profile_id: str = "", platform: str = "", include_over_limit: bool = False) -> str: + def accounts_list_accounts( + profile_id: str = "", + platform: str = "", + include_over_limit: bool = False, + page: int = 0, + limit: int = 0, + ) -> str: """List accounts Args: profile_id: Filter accounts by profile ID platform: Filter accounts by platform (e.g. "instagram", "twitter"). - include_over_limit: When true, includes accounts from over-limit profiles.""" + include_over_limit: When true, includes accounts from over-limit profiles. + page: Page number (1-based). When provided with limit, enables server-side pagination. Omit for all accounts. + limit: Page size. Required alongside page for pagination.""" client = _get_client() try: - response = client.accounts.list_accounts(profile_id=profile_id, platform=platform, include_over_limit=include_over_limit) + response = client.accounts.list_accounts( + profile_id=profile_id, + platform=platform, + include_over_limit=include_over_limit, + page=page, + limit=limit, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_get_follower_stats(account_ids: str = "", profile_id: str = "", from_date: str = "", to_date: str = "", granularity: str = "daily") -> str: + def accounts_get_follower_stats( + account_ids: str = "", + profile_id: str = "", + from_date: str = "", + to_date: str = "", + granularity: str = "daily", + ) -> str: """Get follower stats Args: @@ -270,14 +303,21 @@ def accounts_get_follower_stats(account_ids: str = "", profile_id: str = "", fro granularity: Data aggregation level""" client = _get_client() try: - response = client.accounts.get_follower_stats(account_ids=account_ids, profile_id=profile_id, from_date=from_date, to_date=to_date, granularity=granularity) + response = client.accounts.get_follower_stats( + account_ids=account_ids, + profile_id=profile_id, + from_date=from_date, + to_date=to_date, + granularity=granularity, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_update_account(account_id: str, username: str = "", display_name: str = "") -> str: + def accounts_update_account( + account_id: str, username: str = "", display_name: str = "" + ) -> str: """Update account Args: @@ -286,11 +326,12 @@ def accounts_update_account(account_id: str, username: str = "", display_name: s display_name""" client = _get_client() try: - response = client.accounts.update_account(account_id=account_id, username=username, display_name=display_name) + response = client.accounts.update_account( + account_id=account_id, username=username, display_name=display_name + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def accounts_delete_account(account_id: str) -> str: @@ -303,11 +344,12 @@ def accounts_delete_account(account_id: str) -> str: response = client.accounts.delete_account(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_get_all_accounts_health(profile_id: str = "", platform: str = "", status: str = "") -> str: + def accounts_get_all_accounts_health( + profile_id: str = "", platform: str = "", status: str = "" + ) -> str: """Check accounts health Args: @@ -316,11 +358,12 @@ def accounts_get_all_accounts_health(profile_id: str = "", platform: str = "", s status: Filter by health status""" client = _get_client() try: - response = client.accounts.get_all_accounts_health(profile_id=profile_id, platform=platform, status=status) + response = client.accounts.get_all_accounts_health( + profile_id=profile_id, platform=platform, status=status + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def accounts_get_account_health(account_id: str) -> str: @@ -333,11 +376,12 @@ def accounts_get_account_health(account_id: str) -> str: response = client.accounts.get_account_health(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_get_tik_tok_creator_info(account_id: str, media_type: str = "video") -> str: + def accounts_get_tik_tok_creator_info( + account_id: str, media_type: str = "video" + ) -> str: """Get TikTok creator info Args: @@ -345,14 +389,20 @@ def accounts_get_tik_tok_creator_info(account_id: str, media_type: str = "video" media_type: The media type to get creator info for (affects available interaction settings)""" client = _get_client() try: - response = client.accounts.get_tik_tok_creator_info(account_id=account_id, media_type=media_type) + response = client.accounts.get_tik_tok_creator_info( + account_id=account_id, media_type=media_type + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_get_google_business_reviews(account_id: str, location_id: str = "", page_size: int = 50, page_token: str = "") -> str: + def accounts_get_google_business_reviews( + account_id: str, + location_id: str = "", + page_size: int = 50, + page_token: str = "", + ) -> str: """Get reviews Args: @@ -362,14 +412,20 @@ def accounts_get_google_business_reviews(account_id: str, location_id: str = "", page_token: Pagination token from previous response""" client = _get_client() try: - response = client.accounts.get_google_business_reviews(account_id=account_id, location_id=location_id, page_size=page_size, page_token=page_token) + response = client.accounts.get_google_business_reviews( + account_id=account_id, + location_id=location_id, + page_size=page_size, + page_token=page_token, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_get_google_business_food_menus(account_id: str, location_id: str = "") -> str: + def accounts_get_google_business_food_menus( + account_id: str, location_id: str = "" + ) -> str: """Get food menus Args: @@ -377,14 +433,17 @@ def accounts_get_google_business_food_menus(account_id: str, location_id: str = location_id: Override which location to query. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.""" client = _get_client() try: - response = client.accounts.get_google_business_food_menus(account_id=account_id, location_id=location_id) + response = client.accounts.get_google_business_food_menus( + account_id=account_id, location_id=location_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_update_google_business_food_menus(account_id: str, menus: str, location_id: str = "", update_mask: str = "") -> str: + def accounts_update_google_business_food_menus( + account_id: str, menus: str, location_id: str = "", update_mask: str = "" + ) -> str: """Update food menus Args: @@ -394,14 +453,20 @@ def accounts_update_google_business_food_menus(account_id: str, menus: str, loca update_mask: Field mask for partial updates (e.g. "menus")""" client = _get_client() try: - response = client.accounts.update_google_business_food_menus(account_id=account_id, location_id=location_id, menus=menus, update_mask=update_mask) + response = client.accounts.update_google_business_food_menus( + account_id=account_id, + location_id=location_id, + menus=menus, + update_mask=update_mask, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_get_google_business_location_details(account_id: str, location_id: str = "", read_mask: str = "") -> str: + def accounts_get_google_business_location_details( + account_id: str, location_id: str = "", read_mask: str = "" + ) -> str: """Get location details Args: @@ -410,14 +475,26 @@ def accounts_get_google_business_location_details(account_id: str, location_id: read_mask: Comma-separated fields to return. Available: name, title, phoneNumbers, categories, storefrontAddress, websiteUri, regularHours, specialHours, serviceArea, serviceItems, profile, openInfo, metadata, moreHours.""" client = _get_client() try: - response = client.accounts.get_google_business_location_details(account_id=account_id, location_id=location_id, read_mask=read_mask) + response = client.accounts.get_google_business_location_details( + account_id=account_id, location_id=location_id, read_mask=read_mask + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_update_google_business_location_details(account_id: str, update_mask: str, location_id: str = "", regular_hours: str = "", special_hours: str = "", profile: str = "", website_uri: str = "", phone_numbers: str = "", categories: str = "", service_items: str = "") -> str: + def accounts_update_google_business_location_details( + account_id: str, + update_mask: str, + location_id: str = "", + regular_hours: str = "", + special_hours: str = "", + profile: str = "", + website_uri: str = "", + phone_numbers: str = "", + categories: str = "", + service_items: str = "", + ) -> str: """Update location details Args: @@ -433,14 +510,29 @@ def accounts_update_google_business_location_details(account_id: str, update_mas service_items: Services offered by the business. Use updateMask='serviceItems' to update.""" client = _get_client() try: - response = client.accounts.update_google_business_location_details(account_id=account_id, location_id=location_id, update_mask=update_mask, regular_hours=regular_hours, special_hours=special_hours, profile=profile, website_uri=website_uri, phone_numbers=phone_numbers, categories=categories, service_items=service_items) + response = client.accounts.update_google_business_location_details( + account_id=account_id, + location_id=location_id, + update_mask=update_mask, + regular_hours=regular_hours, + special_hours=special_hours, + profile=profile, + website_uri=website_uri, + phone_numbers=phone_numbers, + categories=categories, + service_items=service_items, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_list_google_business_media(account_id: str, location_id: str = "", page_size: int = 100, page_token: str = "") -> str: + def accounts_list_google_business_media( + account_id: str, + location_id: str = "", + page_size: int = 100, + page_token: str = "", + ) -> str: """List media Args: @@ -450,14 +542,25 @@ def accounts_list_google_business_media(account_id: str, location_id: str = "", page_token: Pagination token from previous response""" client = _get_client() try: - response = client.accounts.list_google_business_media(account_id=account_id, location_id=location_id, page_size=page_size, page_token=page_token) + response = client.accounts.list_google_business_media( + account_id=account_id, + location_id=location_id, + page_size=page_size, + page_token=page_token, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_create_google_business_media(account_id: str, source_url: str, location_id: str = "", media_format: str = "PHOTO", description: str = "", category: str = "") -> str: + def accounts_create_google_business_media( + account_id: str, + source_url: str, + location_id: str = "", + media_format: str = "PHOTO", + description: str = "", + category: str = "", + ) -> str: """Upload photo Args: @@ -469,14 +572,22 @@ def accounts_create_google_business_media(account_id: str, source_url: str, loca category: Where the photo appears on the listing""" client = _get_client() try: - response = client.accounts.create_google_business_media(account_id=account_id, location_id=location_id, source_url=source_url, media_format=media_format, description=description, category=category) + response = client.accounts.create_google_business_media( + account_id=account_id, + location_id=location_id, + source_url=source_url, + media_format=media_format, + description=description, + category=category, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_delete_google_business_media(account_id: str, media_id: str, location_id: str = "") -> str: + def accounts_delete_google_business_media( + account_id: str, media_id: str, location_id: str = "" + ) -> str: """Delete photo Args: @@ -485,14 +596,17 @@ def accounts_delete_google_business_media(account_id: str, media_id: str, locati media_id: The media item ID to delete (required)""" client = _get_client() try: - response = client.accounts.delete_google_business_media(account_id=account_id, location_id=location_id, media_id=media_id) + response = client.accounts.delete_google_business_media( + account_id=account_id, location_id=location_id, media_id=media_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_get_google_business_attributes(account_id: str, location_id: str = "") -> str: + def accounts_get_google_business_attributes( + account_id: str, location_id: str = "" + ) -> str: """Get attributes Args: @@ -500,14 +614,17 @@ def accounts_get_google_business_attributes(account_id: str, location_id: str = location_id: Override which location to query. If omitted, uses the account's selected location. Use GET /gmb-locations to list valid IDs.""" client = _get_client() try: - response = client.accounts.get_google_business_attributes(account_id=account_id, location_id=location_id) + response = client.accounts.get_google_business_attributes( + account_id=account_id, location_id=location_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_update_google_business_attributes(account_id: str, attributes: str, attribute_mask: str, location_id: str = "") -> str: + def accounts_update_google_business_attributes( + account_id: str, attributes: str, attribute_mask: str, location_id: str = "" + ) -> str: """Update attributes Args: @@ -517,14 +634,23 @@ def accounts_update_google_business_attributes(account_id: str, attributes: str, attribute_mask: Comma-separated attribute names to update (e.g. 'has_delivery,has_takeout') (required)""" client = _get_client() try: - response = client.accounts.update_google_business_attributes(account_id=account_id, location_id=location_id, attributes=attributes, attribute_mask=attribute_mask) + response = client.accounts.update_google_business_attributes( + account_id=account_id, + location_id=location_id, + attributes=attributes, + attribute_mask=attribute_mask, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_list_google_business_place_actions(account_id: str, location_id: str = "", page_size: int = 100, page_token: str = "") -> str: + def accounts_list_google_business_place_actions( + account_id: str, + location_id: str = "", + page_size: int = 100, + page_token: str = "", + ) -> str: """List action links Args: @@ -534,14 +660,20 @@ def accounts_list_google_business_place_actions(account_id: str, location_id: st page_token""" client = _get_client() try: - response = client.accounts.list_google_business_place_actions(account_id=account_id, location_id=location_id, page_size=page_size, page_token=page_token) + response = client.accounts.list_google_business_place_actions( + account_id=account_id, + location_id=location_id, + page_size=page_size, + page_token=page_token, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_create_google_business_place_action(account_id: str, uri: str, place_action_type: str, location_id: str = "") -> str: + def accounts_create_google_business_place_action( + account_id: str, uri: str, place_action_type: str, location_id: str = "" + ) -> str: """Create action link Args: @@ -551,14 +683,20 @@ def accounts_create_google_business_place_action(account_id: str, uri: str, plac place_action_type: Type of action (required)""" client = _get_client() try: - response = client.accounts.create_google_business_place_action(account_id=account_id, location_id=location_id, uri=uri, place_action_type=place_action_type) + response = client.accounts.create_google_business_place_action( + account_id=account_id, + location_id=location_id, + uri=uri, + place_action_type=place_action_type, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_delete_google_business_place_action(account_id: str, name: str, location_id: str = "") -> str: + def accounts_delete_google_business_place_action( + account_id: str, name: str, location_id: str = "" + ) -> str: """Delete action link Args: @@ -567,14 +705,17 @@ def accounts_delete_google_business_place_action(account_id: str, name: str, loc name: The resource name of the place action link (e.g. locations/123/placeActionLinks/456) (required)""" client = _get_client() try: - response = client.accounts.delete_google_business_place_action(account_id=account_id, location_id=location_id, name=name) + response = client.accounts.delete_google_business_place_action( + account_id=account_id, location_id=location_id, name=name + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def accounts_get_linked_in_mentions(account_id: str, url: str, display_name: str = "") -> str: + def accounts_get_linked_in_mentions( + account_id: str, url: str, display_name: str = "" + ) -> str: """Resolve LinkedIn mention Args: @@ -583,16 +724,527 @@ def accounts_get_linked_in_mentions(account_id: str, url: str, display_name: str display_name: Exact display name as shown on LinkedIn. Required for person mentions to be clickable. Optional for org mentions.""" client = _get_client() try: - response = client.accounts.get_linked_in_mentions(account_id=account_id, url=url, display_name=display_name) + response = client.accounts.get_linked_in_mentions( + account_id=account_id, url=url, display_name=display_name + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" - # ANALYTICS + # AD_AUDIENCES + + @mcp.tool() + def ad_audiences_list_ad_audiences( + account_id: str, ad_account_id: str, platform: str = "" + ) -> str: + """List custom audiences + + Args: + account_id: Social account ID (required) + ad_account_id: Platform ad account ID (required) + platform""" + client = _get_client() + try: + response = client.ad_audiences.list_ad_audiences( + account_id=account_id, ad_account_id=ad_account_id, platform=platform + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ad_audiences_create_ad_audience( + account_id: str, + ad_account_id: str, + name: str, + type: str, + description: str = "", + pixel_id: str = "", + retention_days: int = 0, + source_audience_id: str = "", + country: str = "", + ratio: float = 0.0, + rule: str = "", + customer_file_source: str = "", + ) -> str: + """Create a custom audience (Meta only) + + Args: + account_id: (required) + ad_account_id: Must start with act_ (required) + name: (required) + description + type: (required) + pixel_id: Required for website audiences + retention_days: Required for website audiences + source_audience_id: Required for lookalike audiences + country: 2-letter code, required for lookalike audiences + ratio: Required for lookalike audiences + rule: Pixel event rule for website audiences (optional) + customer_file_source: Data source declaration for GDPR compliance (customer_list only)""" + client = _get_client() + try: + response = client.ad_audiences.create_ad_audience( + account_id=account_id, + ad_account_id=ad_account_id, + name=name, + description=description, + type=type, + pixel_id=pixel_id, + retention_days=retention_days, + source_audience_id=source_audience_id, + country=country, + ratio=ratio, + rule=rule, + customer_file_source=customer_file_source, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ad_audiences_get_ad_audience(audience_id: str) -> str: + """Get audience details + + Args: + audience_id: (required)""" + client = _get_client() + try: + response = client.ad_audiences.get_ad_audience(audience_id=audience_id) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ad_audiences_delete_ad_audience(audience_id: str) -> str: + """Delete a custom audience + + Args: + audience_id: (required)""" + client = _get_client() + try: + response = client.ad_audiences.delete_ad_audience(audience_id=audience_id) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ad_audiences_add_users_to_ad_audience(audience_id: str, users: str) -> str: + """Add users to a customer list audience + + Args: + audience_id: (required) + users: (required)""" + client = _get_client() + try: + response = client.ad_audiences.add_users_to_ad_audience( + audience_id=audience_id, users=users + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + # AD_CAMPAIGNS + + @mcp.tool() + def ad_campaigns_list_ad_campaigns( + page: int = 1, + limit: int = 20, + source: str = "zernio", + platform: str = "", + status: str = "", + ad_account_id: str = "", + account_id: str = "", + profile_id: str = "", + ) -> str: + """List campaigns with aggregate metrics + + Args: + page: Page number + limit + source + platform + status: Filter by derived campaign status (post-aggregation) + ad_account_id: Platform ad account ID (e.g. act_123 for Meta) + account_id: Social account ID + profile_id: Profile ID""" + client = _get_client() + try: + response = client.ad_campaigns.list_ad_campaigns( + page=page, + limit=limit, + source=source, + platform=platform, + status=status, + ad_account_id=ad_account_id, + account_id=account_id, + profile_id=profile_id, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ad_campaigns_update_ad_campaign_status( + campaign_id: str, status: str, platform: str + ) -> str: + """Pause or resume a campaign + + Args: + campaign_id: Platform campaign ID (required) + status: (required) + platform: (required)""" + client = _get_client() + try: + response = client.ad_campaigns.update_ad_campaign_status( + campaign_id=campaign_id, status=status, platform=platform + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ad_campaigns_get_ad_tree( + page: int = 1, + limit: int = 20, + source: str = "zernio", + platform: str = "", + status: str = "", + ad_account_id: str = "", + account_id: str = "", + profile_id: str = "", + ) -> str: + """Get nested campaign/ad-set/ad tree + + Args: + page: Page number + limit: Campaigns per page + source + platform + status: Filter by derived campaign status (post-aggregation) + ad_account_id: Platform ad account ID + account_id: Social account ID + profile_id: Profile ID""" + client = _get_client() + try: + response = client.ad_campaigns.get_ad_tree( + page=page, + limit=limit, + source=source, + platform=platform, + status=status, + ad_account_id=ad_account_id, + account_id=account_id, + profile_id=profile_id, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + # ADS + + @mcp.tool() + def ads_list_ads( + page: int = 1, + limit: int = 50, + source: str = "zernio", + status: str = "", + platform: str = "", + account_id: str = "", + profile_id: str = "", + campaign_id: str = "", + ) -> str: + """List ads + + Args: + page: Page number + limit + source: zernio = Zernio-created only, all = include external ads + status + platform + account_id: Social account ID + profile_id: Profile ID + campaign_id: Platform campaign ID (filter ads within a campaign)""" + client = _get_client() + try: + response = client.ads.list_ads( + page=page, + limit=limit, + source=source, + status=status, + platform=platform, + account_id=account_id, + profile_id=profile_id, + campaign_id=campaign_id, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ads_get_ad(ad_id: str) -> str: + """Get ad details + + Args: + ad_id: (required)""" + client = _get_client() + try: + response = client.ads.get_ad(ad_id=ad_id) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ads_update_ad( + ad_id: str, + status: str = "", + budget: str = "", + targeting: str = "", + name: str = "", + ) -> str: + """Update ad (pause/resume, budget, targeting, name) + + Args: + ad_id: (required) + status + budget + targeting: Meta-only. Targeting updates for other platforms are not supported after creation. + name""" + client = _get_client() + try: + response = client.ads.update_ad( + ad_id=ad_id, + status=status, + budget=budget, + targeting=targeting, + name=name, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ads_delete_ad(ad_id: str) -> str: + """Cancel an ad + + Args: + ad_id: (required)""" + client = _get_client() + try: + response = client.ads.delete_ad(ad_id=ad_id) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + @mcp.tool() + def ads_get_ad_analytics(ad_id: str, breakdowns: str = "") -> str: + """Get ad analytics with daily breakdown + + Args: + ad_id: (required) + breakdowns: Comma-separated breakdown dimensions. Meta: age, gender, country, publisher_platform, device_platform, region. TikTok: gender, age, country_code, platform, ac, language.""" + client = _get_client() + try: + response = client.ads.get_ad_analytics(ad_id=ad_id, breakdowns=breakdowns) + return _format_response(response) + except Exception as e: + return f"Error: {e}" @mcp.tool() - def analytics_get_analytics(post_id: str = "", platform: str = "", profile_id: str = "", account_id: str = "", source: str = "all", from_date: str = "", to_date: str = "", limit: int = 50, page: int = 1, sort_by: str = "date", order: str = "desc") -> str: + def ads_list_ad_accounts(account_id: str) -> str: + """List ad accounts for a social account + + Args: + account_id: Social account ID (required)""" + client = _get_client() + try: + response = client.ads.list_ad_accounts(account_id=account_id) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ads_boost_post( + account_id: str, + ad_account_id: str, + name: str, + goal: str, + budget: str, + post_id: str = "", + platform_post_id: str = "", + currency: str = "", + schedule: str = "", + targeting: str = "", + bid_amount: float = 0.0, + tracking: str = "", + special_ad_categories: str = "", + ) -> str: + """Boost an existing post as a paid ad + + Args: + post_id: Zernio post ID (provide this or platformPostId) + platform_post_id: Platform post ID (alternative to postId) + account_id: Social account ID (required) + ad_account_id: Platform ad account ID (required) + name: (required) + goal: (required) + budget: (required) + currency + schedule + targeting + bid_amount: Max bid cap (Meta only) + tracking: Meta only. Tracking specs (pixel, URL tags). + special_ad_categories: Meta only. Required for housing, employment, credit, or political ads.""" + client = _get_client() + try: + response = client.ads.boost_post( + post_id=post_id, + platform_post_id=platform_post_id, + account_id=account_id, + ad_account_id=ad_account_id, + name=name, + goal=goal, + budget=budget, + currency=currency, + schedule=schedule, + targeting=targeting, + bid_amount=bid_amount, + tracking=tracking, + special_ad_categories=special_ad_categories, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ads_create_standalone_ad( + account_id: str, + ad_account_id: str, + name: str, + goal: str, + budget_amount: float, + budget_type: str, + body: str, + currency: str = "", + headline: str = "", + long_headline: str = "", + call_to_action: str = "", + link_url: str = "", + image_url: str = "", + business_name: str = "", + board_id: str = "", + countries: str = "", + age_min: int = 0, + age_max: int = 0, + interests: str = "", + end_date: str = "", + audience_id: str = "", + campaign_type: str = "display", + keywords: str = "", + additional_headlines: str = "", + additional_descriptions: str = "", + ) -> str: + """Create a standalone ad with custom creative + + Args: + account_id: (required) + ad_account_id: (required) + name: (required) + goal: (required) + budget_amount: (required) + budget_type: (required) + currency + headline: Required for most platforms. Max: Meta=255, Google=30, Pinterest=100 + long_headline: Google Display only + body: Max: Google=90, Pinterest=500 (required) + call_to_action: Meta only + link_url + image_url: Image URL (or video URL for TikTok). Not required for Google Search campaigns. + business_name: Google Display only + board_id: Pinterest only. Board ID (auto-creates if not provided). + countries + age_min + age_max + interests + end_date: Required for lifetime budgets + audience_id: Custom audience ID for targeting + campaign_type: Google only + keywords: Google Search only + additional_headlines: Google Search RSA only. Extra headlines. + additional_descriptions: Google Search RSA only. Extra descriptions.""" + client = _get_client() + try: + response = client.ads.create_standalone_ad( + account_id=account_id, + ad_account_id=ad_account_id, + name=name, + goal=goal, + budget_amount=budget_amount, + budget_type=budget_type, + currency=currency, + headline=headline, + long_headline=long_headline, + body=body, + call_to_action=call_to_action, + link_url=link_url, + image_url=image_url, + business_name=business_name, + board_id=board_id, + countries=countries, + age_min=age_min, + age_max=age_max, + interests=interests, + end_date=end_date, + audience_id=audience_id, + campaign_type=campaign_type, + keywords=keywords, + additional_headlines=additional_headlines, + additional_descriptions=additional_descriptions, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ads_sync_external_ads() -> str: + """Sync external ads from platform ad managers""" + client = _get_client() + try: + response = client.ads.sync_external_ads() + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def ads_search_ad_interests(q: str, account_id: str) -> str: + """Search targeting interests + + Args: + q: Search query (required) + account_id: Social account ID (required)""" + client = _get_client() + try: + response = client.ads.search_ad_interests(q=q, account_id=account_id) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + # ANALYTICS + + @mcp.tool() + def analytics_get_analytics( + post_id: str = "", + platform: str = "", + profile_id: str = "", + account_id: str = "", + source: str = "all", + from_date: str = "", + to_date: str = "", + limit: int = 50, + page: int = 1, + sort_by: str = "date", + order: str = "desc", + ) -> str: """Get post analytics Args: @@ -609,14 +1261,27 @@ def analytics_get_analytics(post_id: str = "", platform: str = "", profile_id: s order: Sort order""" client = _get_client() try: - response = client.analytics.get_analytics(post_id=post_id, platform=platform, profile_id=profile_id, account_id=account_id, source=source, from_date=from_date, to_date=to_date, limit=limit, page=page, sort_by=sort_by, order=order) + response = client.analytics.get_analytics( + post_id=post_id, + platform=platform, + profile_id=profile_id, + account_id=account_id, + source=source, + from_date=from_date, + to_date=to_date, + limit=limit, + page=page, + sort_by=sort_by, + order=order, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def analytics_get_you_tube_daily_views(video_id: str, account_id: str, start_date: str = "", end_date: str = "") -> str: + def analytics_get_you_tube_daily_views( + video_id: str, account_id: str, start_date: str = "", end_date: str = "" + ) -> str: """Get YouTube daily views Args: @@ -626,56 +1291,89 @@ def analytics_get_you_tube_daily_views(video_id: str, account_id: str, start_dat end_date: End date (YYYY-MM-DD). Defaults to 3 days ago (YouTube data latency).""" client = _get_client() try: - response = client.analytics.get_you_tube_daily_views(video_id=video_id, account_id=account_id, start_date=start_date, end_date=end_date) + response = client.analytics.get_you_tube_daily_views( + video_id=video_id, + account_id=account_id, + start_date=start_date, + end_date=end_date, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def analytics_get_instagram_account_insights(account_id: str, metrics: str = "", since: str = "", until: str = "", metric_type: str = "total_value", breakdown: str = "") -> str: + def analytics_get_instagram_account_insights( + account_id: str, + metrics: str = "", + since: str = "", + until: str = "", + metric_type: str = "total_value", + breakdown: str = "", + ) -> str: """Get Instagram account-level insights - Args: - account_id: The Zernio SocialAccount ID for the Instagram account (required) - metrics: Comma-separated list of metrics. Defaults to "reach,views,accounts_engaged,total_interactions". - Valid metrics: reach, views, accounts_engaged, total_interactions, comments, likes, saves, shares, - replies, reposts, follows_and_unfollows, profile_links_taps. - Note: only "reach" supports metricType=time_series. All other metrics are total_value only. - since: Start date (YYYY-MM-DD). Defaults to 30 days ago. - until: End date (YYYY-MM-DD). Defaults to today. - metric_type: "total_value" (default) returns aggregated totals and supports breakdowns. - "time_series" returns daily values but only works with the "reach" metric. - breakdown: Breakdown dimension (only valid with metricType=total_value). - Valid values depend on the metric: media_product_type, follow_type, follower_type, contact_button_type.""" - client = _get_client() - try: - response = client.analytics.get_instagram_account_insights(account_id=account_id, metrics=metrics, since=since, until=until, metric_type=metric_type, breakdown=breakdown) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def analytics_get_instagram_demographics(account_id: str, metric: str = "follower_demographics", breakdown: str = "", timeframe: str = "this_month") -> str: + Args: + account_id: The Zernio SocialAccount ID for the Instagram account (required) + metrics: Comma-separated list of metrics. Defaults to "reach,views,accounts_engaged,total_interactions". + Valid metrics: reach, views, accounts_engaged, total_interactions, comments, likes, saves, shares, + replies, reposts, follows_and_unfollows, profile_links_taps. + Note: only "reach" supports metricType=time_series. All other metrics are total_value only. + since: Start date (YYYY-MM-DD). Defaults to 30 days ago. + until: End date (YYYY-MM-DD). Defaults to today. + metric_type: "total_value" (default) returns aggregated totals and supports breakdowns. + "time_series" returns daily values but only works with the "reach" metric. + breakdown: Breakdown dimension (only valid with metricType=total_value). + Valid values depend on the metric: media_product_type, follow_type, follower_type, contact_button_type.""" + client = _get_client() + try: + response = client.analytics.get_instagram_account_insights( + account_id=account_id, + metrics=metrics, + since=since, + until=until, + metric_type=metric_type, + breakdown=breakdown, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def analytics_get_instagram_demographics( + account_id: str, + metric: str = "follower_demographics", + breakdown: str = "", + timeframe: str = "this_month", + ) -> str: """Get Instagram audience demographics - Args: - account_id: The Zernio SocialAccount ID for the Instagram account (required) - metric: "follower_demographics" for follower audience data, or "engaged_audience_demographics" for engaged viewers. - breakdown: Comma-separated list of demographic dimensions: age, city, country, gender. - Defaults to all four if omitted. - timeframe: Time period for demographic data. Defaults to "this_month".""" + Args: + account_id: The Zernio SocialAccount ID for the Instagram account (required) + metric: "follower_demographics" for follower audience data, or "engaged_audience_demographics" for engaged viewers. + breakdown: Comma-separated list of demographic dimensions: age, city, country, gender. + Defaults to all four if omitted. + timeframe: Time period for demographic data. Defaults to "this_month".""" client = _get_client() try: - response = client.analytics.get_instagram_demographics(account_id=account_id, metric=metric, breakdown=breakdown, timeframe=timeframe) + response = client.analytics.get_instagram_demographics( + account_id=account_id, + metric=metric, + breakdown=breakdown, + timeframe=timeframe, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def analytics_get_daily_metrics(platform: str = "", profile_id: str = "", account_id: str = "", from_date: str = "", to_date: str = "", source: str = "all") -> str: + def analytics_get_daily_metrics( + platform: str = "", + profile_id: str = "", + account_id: str = "", + from_date: str = "", + to_date: str = "", + source: str = "all", + ) -> str: """Get daily aggregated metrics Args: @@ -687,14 +1385,22 @@ def analytics_get_daily_metrics(platform: str = "", profile_id: str = "", accoun source: Filter by post origin. "late" for posts published via Zernio, "external" for posts imported from platforms.""" client = _get_client() try: - response = client.analytics.get_daily_metrics(platform=platform, profile_id=profile_id, account_id=account_id, from_date=from_date, to_date=to_date, source=source) + response = client.analytics.get_daily_metrics( + platform=platform, + profile_id=profile_id, + account_id=account_id, + from_date=from_date, + to_date=to_date, + source=source, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def analytics_get_best_time_to_post(platform: str = "", profile_id: str = "", source: str = "all") -> str: + def analytics_get_best_time_to_post( + platform: str = "", profile_id: str = "", source: str = "all" + ) -> str: """Get best times to post Args: @@ -703,14 +1409,17 @@ def analytics_get_best_time_to_post(platform: str = "", profile_id: str = "", so source: Filter by post origin. "late" for posts published via Zernio, "external" for posts imported from platforms.""" client = _get_client() try: - response = client.analytics.get_best_time_to_post(platform=platform, profile_id=profile_id, source=source) + response = client.analytics.get_best_time_to_post( + platform=platform, profile_id=profile_id, source=source + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def analytics_get_content_decay(platform: str = "", profile_id: str = "", source: str = "all") -> str: + def analytics_get_content_decay( + platform: str = "", profile_id: str = "", source: str = "all" + ) -> str: """Get content performance decay Args: @@ -719,14 +1428,17 @@ def analytics_get_content_decay(platform: str = "", profile_id: str = "", source source: Filter by post origin. "late" for posts published via Zernio, "external" for posts imported from platforms.""" client = _get_client() try: - response = client.analytics.get_content_decay(platform=platform, profile_id=profile_id, source=source) + response = client.analytics.get_content_decay( + platform=platform, profile_id=profile_id, source=source + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def analytics_get_posting_frequency(platform: str = "", profile_id: str = "", source: str = "all") -> str: + def analytics_get_posting_frequency( + platform: str = "", profile_id: str = "", source: str = "all" + ) -> str: """Get posting frequency vs engagement Args: @@ -735,31 +1447,41 @@ def analytics_get_posting_frequency(platform: str = "", profile_id: str = "", so source: Filter by post origin. "late" for posts published via Zernio, "external" for posts imported from platforms.""" client = _get_client() try: - response = client.analytics.get_posting_frequency(platform=platform, profile_id=profile_id, source=source) + response = client.analytics.get_posting_frequency( + platform=platform, profile_id=profile_id, source=source + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def analytics_get_post_timeline(post_id: str, from_date: str = "", to_date: str = "") -> str: + def analytics_get_post_timeline( + post_id: str, from_date: str = "", to_date: str = "" + ) -> str: """Get post analytics timeline - Args: - post_id: The post to fetch timeline for. Accepts an ExternalPost ID, a platformPostId, or a Zernio Post ID. - (required) - from_date: Start of date range (ISO 8601). Defaults to 90 days ago. - to_date: End of date range (ISO 8601). Defaults to now.""" + Args: + post_id: The post to fetch timeline for. Accepts an ExternalPost ID, a platformPostId, or a Zernio Post ID. + (required) + from_date: Start of date range (ISO 8601). Defaults to 90 days ago. + to_date: End of date range (ISO 8601). Defaults to now.""" client = _get_client() try: - response = client.analytics.get_post_timeline(post_id=post_id, from_date=from_date, to_date=to_date) + response = client.analytics.get_post_timeline( + post_id=post_id, from_date=from_date, to_date=to_date + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def analytics_get_linked_in_aggregate_analytics(account_id: str, aggregation: str = "TOTAL", start_date: str = "", end_date: str = "", metrics: str = "") -> str: + def analytics_get_linked_in_aggregate_analytics( + account_id: str, + aggregation: str = "TOTAL", + start_date: str = "", + end_date: str = "", + metrics: str = "", + ) -> str: """Get LinkedIn aggregate stats Args: @@ -770,11 +1492,16 @@ def analytics_get_linked_in_aggregate_analytics(account_id: str, aggregation: st metrics: Comma-separated metrics: IMPRESSION, MEMBERS_REACHED, REACTION, COMMENT, RESHARE. Omit for all.""" client = _get_client() try: - response = client.analytics.get_linked_in_aggregate_analytics(account_id=account_id, aggregation=aggregation, start_date=start_date, end_date=end_date, metrics=metrics) + response = client.analytics.get_linked_in_aggregate_analytics( + account_id=account_id, + aggregation=aggregation, + start_date=start_date, + end_date=end_date, + metrics=metrics, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def analytics_get_linked_in_post_analytics(account_id: str, urn: str) -> str: @@ -785,14 +1512,17 @@ def analytics_get_linked_in_post_analytics(account_id: str, urn: str) -> str: urn: The LinkedIn post URN (required)""" client = _get_client() try: - response = client.analytics.get_linked_in_post_analytics(account_id=account_id, urn=urn) + response = client.analytics.get_linked_in_post_analytics( + account_id=account_id, urn=urn + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def analytics_get_linked_in_post_reactions(account_id: str, urn: str, limit: int = 25, cursor: str = "") -> str: + def analytics_get_linked_in_post_reactions( + account_id: str, urn: str, limit: int = 25, cursor: str = "" + ) -> str: """Get LinkedIn post reactions Args: @@ -802,14 +1532,15 @@ def analytics_get_linked_in_post_reactions(account_id: str, urn: str, limit: int cursor: Offset-based pagination start index""" client = _get_client() try: - response = client.analytics.get_linked_in_post_reactions(account_id=account_id, urn=urn, limit=limit, cursor=cursor) + response = client.analytics.get_linked_in_post_reactions( + account_id=account_id, urn=urn, limit=limit, cursor=cursor + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # API_KEYS - @mcp.tool() def api_keys_list_api_keys() -> str: """List keys""" @@ -818,11 +1549,16 @@ def api_keys_list_api_keys() -> str: response = client.api_keys.list_api_keys() return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def api_keys_create_api_key(name: str, expires_in: int = 0, scope: str = "full", profile_ids: str = "", permission: str = "read-write") -> str: + def api_keys_create_api_key( + name: str, + expires_in: int = 0, + scope: str = "full", + profile_ids: str = "", + permission: str = "read-write", + ) -> str: """Create key Args: @@ -833,11 +1569,16 @@ def api_keys_create_api_key(name: str, expires_in: int = 0, scope: str = "full", permission: 'read-write' allows all operations (default), 'read' restricts to GET requests only""" client = _get_client() try: - response = client.api_keys.create_api_key(name=name, expires_in=expires_in, scope=scope, profile_ids=profile_ids, permission=permission) + response = client.api_keys.create_api_key( + name=name, + expires_in=expires_in, + scope=scope, + profile_ids=profile_ids, + permission=permission, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def api_keys_delete_api_key(key_id: str) -> str: @@ -850,13 +1591,18 @@ def api_keys_delete_api_key(key_id: str) -> str: response = client.api_keys.delete_api_key(key_id=key_id) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # BROADCASTS - @mcp.tool() - def broadcasts_list_broadcasts(profile_id: str = "", status: str = "", platform: str = "", limit: int = 50, skip: int = 0) -> str: + def broadcasts_list_broadcasts( + profile_id: str = "", + status: str = "", + platform: str = "", + limit: int = 50, + skip: int = 0, + ) -> str: """List broadcasts Args: @@ -867,14 +1613,28 @@ def broadcasts_list_broadcasts(profile_id: str = "", status: str = "", platform: skip""" client = _get_client() try: - response = client.broadcasts.list_broadcasts(profile_id=profile_id, status=status, platform=platform, limit=limit, skip=skip) + response = client.broadcasts.list_broadcasts( + profile_id=profile_id, + status=status, + platform=platform, + limit=limit, + skip=skip, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def broadcasts_create_broadcast(profile_id: str, account_id: str, platform: str, name: str, description: str = "", message: str = "", template: str = "", segment_filters: str = "") -> str: + def broadcasts_create_broadcast( + profile_id: str, + account_id: str, + platform: str, + name: str, + description: str = "", + message: str = "", + template: str = "", + segment_filters: str = "", + ) -> str: """Create a broadcast draft Args: @@ -888,11 +1648,19 @@ def broadcasts_create_broadcast(profile_id: str, account_id: str, platform: str, segment_filters""" client = _get_client() try: - response = client.broadcasts.create_broadcast(profile_id=profile_id, account_id=account_id, platform=platform, name=name, description=description, message=message, template=template, segment_filters=segment_filters) + response = client.broadcasts.create_broadcast( + profile_id=profile_id, + account_id=account_id, + platform=platform, + name=name, + description=description, + message=message, + template=template, + segment_filters=segment_filters, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def broadcasts_get_broadcast(broadcast_id: str) -> str: @@ -905,8 +1673,7 @@ def broadcasts_get_broadcast(broadcast_id: str) -> str: response = client.broadcasts.get_broadcast(broadcast_id=broadcast_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def broadcasts_update_broadcast(broadcast_id: str) -> str: @@ -919,8 +1686,7 @@ def broadcasts_update_broadcast(broadcast_id: str) -> str: response = client.broadcasts.update_broadcast(broadcast_id=broadcast_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def broadcasts_delete_broadcast(broadcast_id: str) -> str: @@ -933,8 +1699,7 @@ def broadcasts_delete_broadcast(broadcast_id: str) -> str: response = client.broadcasts.delete_broadcast(broadcast_id=broadcast_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def broadcasts_send_broadcast(broadcast_id: str) -> str: @@ -947,8 +1712,7 @@ def broadcasts_send_broadcast(broadcast_id: str) -> str: response = client.broadcasts.send_broadcast(broadcast_id=broadcast_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def broadcasts_schedule_broadcast(broadcast_id: str, scheduled_at: str) -> str: @@ -959,11 +1723,12 @@ def broadcasts_schedule_broadcast(broadcast_id: str, scheduled_at: str) -> str: scheduled_at: (required)""" client = _get_client() try: - response = client.broadcasts.schedule_broadcast(broadcast_id=broadcast_id, scheduled_at=scheduled_at) + response = client.broadcasts.schedule_broadcast( + broadcast_id=broadcast_id, scheduled_at=scheduled_at + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def broadcasts_cancel_broadcast(broadcast_id: str) -> str: @@ -976,11 +1741,12 @@ def broadcasts_cancel_broadcast(broadcast_id: str) -> str: response = client.broadcasts.cancel_broadcast(broadcast_id=broadcast_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def broadcasts_list_broadcast_recipients(broadcast_id: str, status: str = "", limit: int = 50, skip: int = 0) -> str: + def broadcasts_list_broadcast_recipients( + broadcast_id: str, status: str = "", limit: int = 50, skip: int = 0 + ) -> str: """List broadcast recipients Args: @@ -990,14 +1756,20 @@ def broadcasts_list_broadcast_recipients(broadcast_id: str, status: str = "", li skip""" client = _get_client() try: - response = client.broadcasts.list_broadcast_recipients(broadcast_id=broadcast_id, status=status, limit=limit, skip=skip) + response = client.broadcasts.list_broadcast_recipients( + broadcast_id=broadcast_id, status=status, limit=limit, skip=skip + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def broadcasts_add_broadcast_recipients(broadcast_id: str, contact_ids: str = "", phones: str = "", use_segment: bool = False) -> str: + def broadcasts_add_broadcast_recipients( + broadcast_id: str, + contact_ids: str = "", + phones: str = "", + use_segment: bool = False, + ) -> str: """Add recipients to a broadcast Args: @@ -1007,14 +1779,18 @@ def broadcasts_add_broadcast_recipients(broadcast_id: str, contact_ids: str = "" use_segment: Auto-populate from broadcast segment filters""" client = _get_client() try: - response = client.broadcasts.add_broadcast_recipients(broadcast_id=broadcast_id, contact_ids=contact_ids, phones=phones, use_segment=use_segment) + response = client.broadcasts.add_broadcast_recipients( + broadcast_id=broadcast_id, + contact_ids=contact_ids, + phones=phones, + use_segment=use_segment, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # COMMENT_AUTOMATIONS - @mcp.tool() def comment_automations_list_comment_automations(profile_id: str = "") -> str: """List comment-to-DM automations @@ -1023,14 +1799,26 @@ def comment_automations_list_comment_automations(profile_id: str = "") -> str: profile_id: Filter by profile. Omit to list across all profiles""" client = _get_client() try: - response = client.comment_automations.list_comment_automations(profile_id=profile_id) + response = client.comment_automations.list_comment_automations( + profile_id=profile_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comment_automations_create_comment_automation(profile_id: str, account_id: str, platform_post_id: str, name: str, dm_message: str, post_id: str = "", post_title: str = "", keywords: str = "", match_mode: str = "contains", comment_reply: str = "") -> str: + def comment_automations_create_comment_automation( + profile_id: str, + account_id: str, + platform_post_id: str, + name: str, + dm_message: str, + post_id: str = "", + post_title: str = "", + keywords: str = "", + match_mode: str = "contains", + comment_reply: str = "", + ) -> str: """Create a comment-to-DM automation Args: @@ -1046,11 +1834,21 @@ def comment_automations_create_comment_automation(profile_id: str, account_id: s comment_reply: Optional public reply to the comment""" client = _get_client() try: - response = client.comment_automations.create_comment_automation(profile_id=profile_id, account_id=account_id, platform_post_id=platform_post_id, post_id=post_id, post_title=post_title, name=name, keywords=keywords, match_mode=match_mode, dm_message=dm_message, comment_reply=comment_reply) + response = client.comment_automations.create_comment_automation( + profile_id=profile_id, + account_id=account_id, + platform_post_id=platform_post_id, + post_id=post_id, + post_title=post_title, + name=name, + keywords=keywords, + match_mode=match_mode, + dm_message=dm_message, + comment_reply=comment_reply, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def comment_automations_get_comment_automation(automation_id: str) -> str: @@ -1060,14 +1858,23 @@ def comment_automations_get_comment_automation(automation_id: str) -> str: automation_id: (required)""" client = _get_client() try: - response = client.comment_automations.get_comment_automation(automation_id=automation_id) + response = client.comment_automations.get_comment_automation( + automation_id=automation_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comment_automations_update_comment_automation(automation_id: str, name: str = "", keywords: str = "", match_mode: str = "", dm_message: str = "", comment_reply: str = "", is_active: bool = False) -> str: + def comment_automations_update_comment_automation( + automation_id: str, + name: str = "", + keywords: str = "", + match_mode: str = "", + dm_message: str = "", + comment_reply: str = "", + is_active: bool = False, + ) -> str: """Update automation settings Args: @@ -1080,11 +1887,18 @@ def comment_automations_update_comment_automation(automation_id: str, name: str is_active""" client = _get_client() try: - response = client.comment_automations.update_comment_automation(automation_id=automation_id, name=name, keywords=keywords, match_mode=match_mode, dm_message=dm_message, comment_reply=comment_reply, is_active=is_active) + response = client.comment_automations.update_comment_automation( + automation_id=automation_id, + name=name, + keywords=keywords, + match_mode=match_mode, + dm_message=dm_message, + comment_reply=comment_reply, + is_active=is_active, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def comment_automations_delete_comment_automation(automation_id: str) -> str: @@ -1094,14 +1908,17 @@ def comment_automations_delete_comment_automation(automation_id: str) -> str: automation_id: (required)""" client = _get_client() try: - response = client.comment_automations.delete_comment_automation(automation_id=automation_id) + response = client.comment_automations.delete_comment_automation( + automation_id=automation_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comment_automations_list_comment_automation_logs(automation_id: str, status: str = "", limit: int = 50, skip: int = 0) -> str: + def comment_automations_list_comment_automation_logs( + automation_id: str, status: str = "", limit: int = 50, skip: int = 0 + ) -> str: """List trigger logs for an automation Args: @@ -1111,16 +1928,27 @@ def comment_automations_list_comment_automation_logs(automation_id: str, status: skip""" client = _get_client() try: - response = client.comment_automations.list_comment_automation_logs(automation_id=automation_id, status=status, limit=limit, skip=skip) + response = client.comment_automations.list_comment_automation_logs( + automation_id=automation_id, status=status, limit=limit, skip=skip + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # COMMENTS - @mcp.tool() - def comments_list_inbox_comments(profile_id: str = "", platform: str = "", min_comments: int = 0, since: str = "", sort_by: str = "date", sort_order: str = "desc", limit: int = 50, cursor: str = "", account_id: str = "") -> str: + def comments_list_inbox_comments( + profile_id: str = "", + platform: str = "", + min_comments: int = 0, + since: str = "", + sort_by: str = "date", + sort_order: str = "desc", + limit: int = 50, + cursor: str = "", + account_id: str = "", + ) -> str: """List commented posts Args: @@ -1135,14 +1963,30 @@ def comments_list_inbox_comments(profile_id: str = "", platform: str = "", min_c account_id: Filter by specific social account ID""" client = _get_client() try: - response = client.comments.list_inbox_comments(profile_id=profile_id, platform=platform, min_comments=min_comments, since=since, sort_by=sort_by, sort_order=sort_order, limit=limit, cursor=cursor, account_id=account_id) + response = client.comments.list_inbox_comments( + profile_id=profile_id, + platform=platform, + min_comments=min_comments, + since=since, + sort_by=sort_by, + sort_order=sort_order, + limit=limit, + cursor=cursor, + account_id=account_id, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comments_get_inbox_post_comments(post_id: str, account_id: str, subreddit: str = "", limit: int = 25, cursor: str = "", comment_id: str = "") -> str: + def comments_get_inbox_post_comments( + post_id: str, + account_id: str, + subreddit: str = "", + limit: int = 25, + cursor: str = "", + comment_id: str = "", + ) -> str: """Get post comments Args: @@ -1154,14 +1998,28 @@ def comments_get_inbox_post_comments(post_id: str, account_id: str, subreddit: s comment_id: (Reddit only) Get replies to a specific comment""" client = _get_client() try: - response = client.comments.get_inbox_post_comments(post_id=post_id, account_id=account_id, subreddit=subreddit, limit=limit, cursor=cursor, comment_id=comment_id) + response = client.comments.get_inbox_post_comments( + post_id=post_id, + account_id=account_id, + subreddit=subreddit, + limit=limit, + cursor=cursor, + comment_id=comment_id, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comments_reply_to_inbox_post(post_id: str, account_id: str, message: str, comment_id: str = "", parent_cid: str = "", root_uri: str = "", root_cid: str = "") -> str: + def comments_reply_to_inbox_post( + post_id: str, + account_id: str, + message: str, + comment_id: str = "", + parent_cid: str = "", + root_uri: str = "", + root_cid: str = "", + ) -> str: """Reply to comment Args: @@ -1174,14 +2032,23 @@ def comments_reply_to_inbox_post(post_id: str, account_id: str, message: str, co root_cid: (Bluesky only) Root post CID""" client = _get_client() try: - response = client.comments.reply_to_inbox_post(post_id=post_id, account_id=account_id, message=message, comment_id=comment_id, parent_cid=parent_cid, root_uri=root_uri, root_cid=root_cid) + response = client.comments.reply_to_inbox_post( + post_id=post_id, + account_id=account_id, + message=message, + comment_id=comment_id, + parent_cid=parent_cid, + root_uri=root_uri, + root_cid=root_cid, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comments_delete_inbox_comment(post_id: str, account_id: str, comment_id: str) -> str: + def comments_delete_inbox_comment( + post_id: str, account_id: str, comment_id: str + ) -> str: """Delete comment Args: @@ -1190,14 +2057,17 @@ def comments_delete_inbox_comment(post_id: str, account_id: str, comment_id: str comment_id: (required)""" client = _get_client() try: - response = client.comments.delete_inbox_comment(post_id=post_id, account_id=account_id, comment_id=comment_id) + response = client.comments.delete_inbox_comment( + post_id=post_id, account_id=account_id, comment_id=comment_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comments_hide_inbox_comment(post_id: str, comment_id: str, account_id: str) -> str: + def comments_hide_inbox_comment( + post_id: str, comment_id: str, account_id: str + ) -> str: """Hide comment Args: @@ -1206,14 +2076,17 @@ def comments_hide_inbox_comment(post_id: str, comment_id: str, account_id: str) account_id: The social account ID (required)""" client = _get_client() try: - response = client.comments.hide_inbox_comment(post_id=post_id, comment_id=comment_id, account_id=account_id) + response = client.comments.hide_inbox_comment( + post_id=post_id, comment_id=comment_id, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comments_unhide_inbox_comment(post_id: str, comment_id: str, account_id: str) -> str: + def comments_unhide_inbox_comment( + post_id: str, comment_id: str, account_id: str + ) -> str: """Unhide comment Args: @@ -1222,14 +2095,17 @@ def comments_unhide_inbox_comment(post_id: str, comment_id: str, account_id: str account_id: (required)""" client = _get_client() try: - response = client.comments.unhide_inbox_comment(post_id=post_id, comment_id=comment_id, account_id=account_id) + response = client.comments.unhide_inbox_comment( + post_id=post_id, comment_id=comment_id, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comments_like_inbox_comment(post_id: str, comment_id: str, account_id: str, cid: str = "") -> str: + def comments_like_inbox_comment( + post_id: str, comment_id: str, account_id: str, cid: str = "" + ) -> str: """Like comment Args: @@ -1239,14 +2115,17 @@ def comments_like_inbox_comment(post_id: str, comment_id: str, account_id: str, cid: (Bluesky only) Content identifier for the comment""" client = _get_client() try: - response = client.comments.like_inbox_comment(post_id=post_id, comment_id=comment_id, account_id=account_id, cid=cid) + response = client.comments.like_inbox_comment( + post_id=post_id, comment_id=comment_id, account_id=account_id, cid=cid + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comments_unlike_inbox_comment(post_id: str, comment_id: str, account_id: str, like_uri: str = "") -> str: + def comments_unlike_inbox_comment( + post_id: str, comment_id: str, account_id: str, like_uri: str = "" + ) -> str: """Unlike comment Args: @@ -1256,14 +2135,20 @@ def comments_unlike_inbox_comment(post_id: str, comment_id: str, account_id: str like_uri: (Bluesky only) The like URI returned when liking""" client = _get_client() try: - response = client.comments.unlike_inbox_comment(post_id=post_id, comment_id=comment_id, account_id=account_id, like_uri=like_uri) + response = client.comments.unlike_inbox_comment( + post_id=post_id, + comment_id=comment_id, + account_id=account_id, + like_uri=like_uri, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def comments_send_private_reply_to_comment(post_id: str, comment_id: str, account_id: str, message: str) -> str: + def comments_send_private_reply_to_comment( + post_id: str, comment_id: str, account_id: str, message: str + ) -> str: """Send private reply Args: @@ -1273,16 +2158,22 @@ def comments_send_private_reply_to_comment(post_id: str, comment_id: str, accoun message: The message text to send as a private DM (required)""" client = _get_client() try: - response = client.comments.send_private_reply_to_comment(post_id=post_id, comment_id=comment_id, account_id=account_id, message=message) + response = client.comments.send_private_reply_to_comment( + post_id=post_id, + comment_id=comment_id, + account_id=account_id, + message=message, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # CONNECT - @mcp.tool() - def connect_get_connect_url(platform: str, profile_id: str, redirect_url: str = "", headless: bool = False) -> str: + def connect_get_connect_url( + platform: str, profile_id: str, redirect_url: str = "", headless: bool = False + ) -> str: """Get OAuth connect URL Args: @@ -1292,14 +2183,20 @@ def connect_get_connect_url(platform: str, profile_id: str, redirect_url: str = headless: When true, the user is redirected to your redirect_url with raw OAuth data (code, state) instead of Zernio's default account selection UI. Use this to build a custom connect experience.""" client = _get_client() try: - response = client.connect.get_connect_url(platform=platform, profile_id=profile_id, redirect_url=redirect_url, headless=headless) + response = client.connect.get_connect_url( + platform=platform, + profile_id=profile_id, + redirect_url=redirect_url, + headless=headless, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_handle_o_auth_callback(platform: str, code: str, state: str, profile_id: str) -> str: + def connect_handle_o_auth_callback( + platform: str, code: str, state: str, profile_id: str + ) -> str: """Complete OAuth callback Args: @@ -1309,11 +2206,12 @@ def connect_handle_o_auth_callback(platform: str, code: str, state: str, profile profile_id: (required)""" client = _get_client() try: - response = client.connect.handle_o_auth_callback(platform=platform, code=code, state=state, profile_id=profile_id) + response = client.connect.handle_o_auth_callback( + platform=platform, code=code, state=state, profile_id=profile_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_list_facebook_pages(profile_id: str, temp_token: str) -> str: @@ -1324,14 +2222,21 @@ def connect_list_facebook_pages(profile_id: str, temp_token: str) -> str: temp_token: Temporary Facebook access token from the OAuth callback redirect (required)""" client = _get_client() try: - response = client.connect.list_facebook_pages(profile_id=profile_id, temp_token=temp_token) + response = client.connect.list_facebook_pages( + profile_id=profile_id, temp_token=temp_token + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_select_facebook_page(profile_id: str, page_id: str, temp_token: str, user_profile: str = "", redirect_url: str = "") -> str: + def connect_select_facebook_page( + profile_id: str, + page_id: str, + temp_token: str, + user_profile: str = "", + redirect_url: str = "", + ) -> str: """Select Facebook page Args: @@ -1342,11 +2247,16 @@ def connect_select_facebook_page(profile_id: str, page_id: str, temp_token: str, redirect_url: Optional custom redirect URL to return to after selection""" client = _get_client() try: - response = client.connect.select_facebook_page(profile_id=profile_id, page_id=page_id, temp_token=temp_token, user_profile=user_profile, redirect_url=redirect_url) + response = client.connect.select_facebook_page( + profile_id=profile_id, + page_id=page_id, + temp_token=temp_token, + user_profile=user_profile, + redirect_url=redirect_url, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_list_google_business_locations(profile_id: str, temp_token: str) -> str: @@ -1357,14 +2267,21 @@ def connect_list_google_business_locations(profile_id: str, temp_token: str) -> temp_token: Temporary Google access token from the OAuth callback redirect (required)""" client = _get_client() try: - response = client.connect.list_google_business_locations(profile_id=profile_id, temp_token=temp_token) + response = client.connect.list_google_business_locations( + profile_id=profile_id, temp_token=temp_token + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_select_google_business_location(profile_id: str, location_id: str, temp_token: str, user_profile: str = "", redirect_url: str = "") -> str: + def connect_select_google_business_location( + profile_id: str, + location_id: str, + temp_token: str, + user_profile: str = "", + redirect_url: str = "", + ) -> str: """Select GBP location Args: @@ -1375,11 +2292,16 @@ def connect_select_google_business_location(profile_id: str, location_id: str, t redirect_url: Optional custom redirect URL to return to after selection""" client = _get_client() try: - response = client.connect.select_google_business_location(profile_id=profile_id, location_id=location_id, temp_token=temp_token, user_profile=user_profile, redirect_url=redirect_url) + response = client.connect.select_google_business_location( + profile_id=profile_id, + location_id=location_id, + temp_token=temp_token, + user_profile=user_profile, + redirect_url=redirect_url, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_get_pending_o_auth_data(token: str) -> str: @@ -1392,8 +2314,7 @@ def connect_get_pending_o_auth_data(token: str) -> str: response = client.connect.get_pending_o_auth_data(token=token) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_list_linked_in_organizations(temp_token: str, org_ids: str) -> str: @@ -1404,14 +2325,22 @@ def connect_list_linked_in_organizations(temp_token: str, org_ids: str) -> str: org_ids: Comma-separated list of organization IDs to fetch details for (max 100) (required)""" client = _get_client() try: - response = client.connect.list_linked_in_organizations(temp_token=temp_token, org_ids=org_ids) + response = client.connect.list_linked_in_organizations( + temp_token=temp_token, org_ids=org_ids + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_select_linked_in_organization(profile_id: str, temp_token: str, user_profile: str, account_type: str, selected_organization: str = "", redirect_url: str = "") -> str: + def connect_select_linked_in_organization( + profile_id: str, + temp_token: str, + user_profile: str, + account_type: str, + selected_organization: str = "", + redirect_url: str = "", + ) -> str: """Select LinkedIn org Args: @@ -1423,14 +2352,22 @@ def connect_select_linked_in_organization(profile_id: str, temp_token: str, user redirect_url""" client = _get_client() try: - response = client.connect.select_linked_in_organization(profile_id=profile_id, temp_token=temp_token, user_profile=user_profile, account_type=account_type, selected_organization=selected_organization, redirect_url=redirect_url) + response = client.connect.select_linked_in_organization( + profile_id=profile_id, + temp_token=temp_token, + user_profile=user_profile, + account_type=account_type, + selected_organization=selected_organization, + redirect_url=redirect_url, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_list_pinterest_boards_for_selection(profile_id: str, temp_token: str) -> str: + def connect_list_pinterest_boards_for_selection( + profile_id: str, temp_token: str + ) -> str: """List Pinterest boards Args: @@ -1438,14 +2375,24 @@ def connect_list_pinterest_boards_for_selection(profile_id: str, temp_token: str temp_token: Temporary Pinterest access token from the OAuth callback redirect (required)""" client = _get_client() try: - response = client.connect.list_pinterest_boards_for_selection(profile_id=profile_id, temp_token=temp_token) + response = client.connect.list_pinterest_boards_for_selection( + profile_id=profile_id, temp_token=temp_token + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_select_pinterest_board(profile_id: str, board_id: str, temp_token: str, board_name: str = "", user_profile: str = "", refresh_token: str = "", expires_in: int = 0, redirect_url: str = "") -> str: + def connect_select_pinterest_board( + profile_id: str, + board_id: str, + temp_token: str, + board_name: str = "", + user_profile: str = "", + refresh_token: str = "", + expires_in: int = 0, + redirect_url: str = "", + ) -> str: """Select Pinterest board Args: @@ -1459,11 +2406,19 @@ def connect_select_pinterest_board(profile_id: str, board_id: str, temp_token: s redirect_url: Custom redirect URL after connection completes""" client = _get_client() try: - response = client.connect.select_pinterest_board(profile_id=profile_id, board_id=board_id, board_name=board_name, temp_token=temp_token, user_profile=user_profile, refresh_token=refresh_token, expires_in=expires_in, redirect_url=redirect_url) + response = client.connect.select_pinterest_board( + profile_id=profile_id, + board_id=board_id, + board_name=board_name, + temp_token=temp_token, + user_profile=user_profile, + refresh_token=refresh_token, + expires_in=expires_in, + redirect_url=redirect_url, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_list_snapchat_profiles(profile_id: str, temp_token: str) -> str: @@ -1474,14 +2429,23 @@ def connect_list_snapchat_profiles(profile_id: str, temp_token: str) -> str: temp_token: Temporary Snapchat access token from the OAuth callback redirect (required)""" client = _get_client() try: - response = client.connect.list_snapchat_profiles(profile_id=profile_id, temp_token=temp_token) + response = client.connect.list_snapchat_profiles( + profile_id=profile_id, temp_token=temp_token + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_select_snapchat_profile(profile_id: str, selected_public_profile: str, temp_token: str, user_profile: str, refresh_token: str = "", expires_in: int = 0, redirect_url: str = "") -> str: + def connect_select_snapchat_profile( + profile_id: str, + selected_public_profile: str, + temp_token: str, + user_profile: str, + refresh_token: str = "", + expires_in: int = 0, + redirect_url: str = "", + ) -> str: """Select Snapchat profile Args: @@ -1494,14 +2458,23 @@ def connect_select_snapchat_profile(profile_id: str, selected_public_profile: st redirect_url: Custom redirect URL after connection completes""" client = _get_client() try: - response = client.connect.select_snapchat_profile(profile_id=profile_id, selected_public_profile=selected_public_profile, temp_token=temp_token, user_profile=user_profile, refresh_token=refresh_token, expires_in=expires_in, redirect_url=redirect_url) + response = client.connect.select_snapchat_profile( + profile_id=profile_id, + selected_public_profile=selected_public_profile, + temp_token=temp_token, + user_profile=user_profile, + refresh_token=refresh_token, + expires_in=expires_in, + redirect_url=redirect_url, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_bluesky_credentials(identifier: str, app_password: str, state: str, redirect_uri: str = "") -> str: + def connect_bluesky_credentials( + identifier: str, app_password: str, state: str, redirect_uri: str = "" + ) -> str: """Connect Bluesky account Args: @@ -1511,14 +2484,20 @@ def connect_bluesky_credentials(identifier: str, app_password: str, state: str, redirect_uri: Optional URL to redirect to after successful connection""" client = _get_client() try: - response = client.connect.connect_bluesky_credentials(identifier=identifier, app_password=app_password, state=state, redirect_uri=redirect_uri) + response = client.connect.connect_bluesky_credentials( + identifier=identifier, + app_password=app_password, + state=state, + redirect_uri=redirect_uri, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_whats_app_credentials(profile_id: str, access_token: str, waba_id: str, phone_number_id: str) -> str: + def connect_whats_app_credentials( + profile_id: str, access_token: str, waba_id: str, phone_number_id: str + ) -> str: """Connect WhatsApp via credentials Args: @@ -1528,11 +2507,15 @@ def connect_whats_app_credentials(profile_id: str, access_token: str, waba_id: s phone_number_id: Phone Number ID from Meta WhatsApp Manager (required)""" client = _get_client() try: - response = client.connect.connect_whats_app_credentials(profile_id=profile_id, access_token=access_token, waba_id=waba_id, phone_number_id=phone_number_id) + response = client.connect.connect_whats_app_credentials( + profile_id=profile_id, + access_token=access_token, + waba_id=waba_id, + phone_number_id=phone_number_id, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_get_telegram_connect_status(profile_id: str) -> str: @@ -1545,8 +2528,7 @@ def connect_get_telegram_connect_status(profile_id: str) -> str: response = client.connect.get_telegram_connect_status(profile_id=profile_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_initiate_telegram_connect(chat_id: str, profile_id: str) -> str: @@ -1557,11 +2539,12 @@ def connect_initiate_telegram_connect(chat_id: str, profile_id: str) -> str: profile_id: The profile ID to connect the account to (required)""" client = _get_client() try: - response = client.connect.initiate_telegram_connect(chat_id=chat_id, profile_id=profile_id) + response = client.connect.initiate_telegram_connect( + chat_id=chat_id, profile_id=profile_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_complete_telegram_connect(code: str) -> str: @@ -1574,8 +2557,7 @@ def connect_complete_telegram_connect(code: str) -> str: response = client.connect.complete_telegram_connect(code=code) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_get_facebook_pages(account_id: str) -> str: @@ -1588,8 +2570,7 @@ def connect_get_facebook_pages(account_id: str) -> str: response = client.connect.get_facebook_pages(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_update_facebook_page(account_id: str, selected_page_id: str) -> str: @@ -1600,11 +2581,12 @@ def connect_update_facebook_page(account_id: str, selected_page_id: str) -> str: selected_page_id: (required)""" client = _get_client() try: - response = client.connect.update_facebook_page(account_id=account_id, selected_page_id=selected_page_id) + response = client.connect.update_facebook_page( + account_id=account_id, selected_page_id=selected_page_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_get_linked_in_organizations(account_id: str) -> str: @@ -1617,11 +2599,12 @@ def connect_get_linked_in_organizations(account_id: str) -> str: response = client.connect.get_linked_in_organizations(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_update_linked_in_organization(account_id: str, account_type: str, selected_organization: str = "") -> str: + def connect_update_linked_in_organization( + account_id: str, account_type: str, selected_organization: str = "" + ) -> str: """Switch LinkedIn account type Args: @@ -1630,11 +2613,14 @@ def connect_update_linked_in_organization(account_id: str, account_type: str, se selected_organization""" client = _get_client() try: - response = client.connect.update_linked_in_organization(account_id=account_id, account_type=account_type, selected_organization=selected_organization) + response = client.connect.update_linked_in_organization( + account_id=account_id, + account_type=account_type, + selected_organization=selected_organization, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_get_pinterest_boards(account_id: str) -> str: @@ -1647,11 +2633,12 @@ def connect_get_pinterest_boards(account_id: str) -> str: response = client.connect.get_pinterest_boards(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_update_pinterest_boards(account_id: str, default_board_id: str, default_board_name: str = "") -> str: + def connect_update_pinterest_boards( + account_id: str, default_board_id: str, default_board_name: str = "" + ) -> str: """Set default Pinterest board Args: @@ -1660,11 +2647,48 @@ def connect_update_pinterest_boards(account_id: str, default_board_id: str, defa default_board_name""" client = _get_client() try: - response = client.connect.update_pinterest_boards(account_id=account_id, default_board_id=default_board_id, default_board_name=default_board_name) + response = client.connect.update_pinterest_boards( + account_id=account_id, + default_board_id=default_board_id, + default_board_name=default_board_name, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" + @mcp.tool() + def connect_get_youtube_playlists(account_id: str) -> str: + """List YouTube playlists + + Args: + account_id: (required)""" + client = _get_client() + try: + response = client.connect.get_youtube_playlists(account_id=account_id) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def connect_update_youtube_default_playlist( + account_id: str, default_playlist_id: str, default_playlist_name: str = "" + ) -> str: + """Set default YouTube playlist + + Args: + account_id: (required) + default_playlist_id: (required) + default_playlist_name""" + client = _get_client() + try: + response = client.connect.update_youtube_default_playlist( + account_id=account_id, + default_playlist_id=default_playlist_id, + default_playlist_name=default_playlist_name, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" @mcp.tool() def connect_get_gmb_locations(account_id: str) -> str: @@ -1677,8 +2701,7 @@ def connect_get_gmb_locations(account_id: str) -> str: response = client.connect.get_gmb_locations(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_update_gmb_location(account_id: str, selected_location_id: str) -> str: @@ -1689,11 +2712,12 @@ def connect_update_gmb_location(account_id: str, selected_location_id: str) -> s selected_location_id: (required)""" client = _get_client() try: - response = client.connect.update_gmb_location(account_id=account_id, selected_location_id=selected_location_id) + response = client.connect.update_gmb_location( + account_id=account_id, selected_location_id=selected_location_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_get_reddit_subreddits(account_id: str) -> str: @@ -1706,11 +2730,12 @@ def connect_get_reddit_subreddits(account_id: str) -> str: response = client.connect.get_reddit_subreddits(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def connect_update_reddit_subreddits(account_id: str, default_subreddit: str) -> str: + def connect_update_reddit_subreddits( + account_id: str, default_subreddit: str + ) -> str: """Set default subreddit Args: @@ -1718,11 +2743,12 @@ def connect_update_reddit_subreddits(account_id: str, default_subreddit: str) -> default_subreddit: (required)""" client = _get_client() try: - response = client.connect.update_reddit_subreddits(account_id=account_id, default_subreddit=default_subreddit) + response = client.connect.update_reddit_subreddits( + account_id=account_id, default_subreddit=default_subreddit + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def connect_get_reddit_flairs(account_id: str, subreddit: str) -> str: @@ -1733,16 +2759,25 @@ def connect_get_reddit_flairs(account_id: str, subreddit: str) -> str: subreddit: Subreddit name (without "r/" prefix) to fetch flairs for (required)""" client = _get_client() try: - response = client.connect.get_reddit_flairs(account_id=account_id, subreddit=subreddit) + response = client.connect.get_reddit_flairs( + account_id=account_id, subreddit=subreddit + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # CONTACTS - @mcp.tool() - def contacts_list_contacts(profile_id: str = "", search: str = "", tag: str = "", platform: str = "", is_subscribed: str = "", limit: int = 50, skip: int = 0) -> str: + def contacts_list_contacts( + profile_id: str = "", + search: str = "", + tag: str = "", + platform: str = "", + is_subscribed: str = "", + limit: int = 50, + skip: int = 0, + ) -> str: """List contacts Args: @@ -1755,14 +2790,33 @@ def contacts_list_contacts(profile_id: str = "", search: str = "", tag: str = "" skip""" client = _get_client() try: - response = client.contacts.list_contacts(profile_id=profile_id, search=search, tag=tag, platform=platform, is_subscribed=is_subscribed, limit=limit, skip=skip) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def contacts_create_contact(profile_id: str, name: str, email: str = "", company: str = "", tags: str = "", is_subscribed: bool = True, notes: str = "", account_id: str = "", platform: str = "", platform_identifier: str = "", display_identifier: str = "") -> str: + response = client.contacts.list_contacts( + profile_id=profile_id, + search=search, + tag=tag, + platform=platform, + is_subscribed=is_subscribed, + limit=limit, + skip=skip, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def contacts_create_contact( + profile_id: str, + name: str, + email: str = "", + company: str = "", + tags: str = "", + is_subscribed: bool = True, + notes: str = "", + account_id: str = "", + platform: str = "", + platform_identifier: str = "", + display_identifier: str = "", + ) -> str: """Create a contact Args: @@ -1779,11 +2833,22 @@ def contacts_create_contact(profile_id: str, name: str, email: str = "", company display_identifier""" client = _get_client() try: - response = client.contacts.create_contact(profile_id=profile_id, name=name, email=email, company=company, tags=tags, is_subscribed=is_subscribed, notes=notes, account_id=account_id, platform=platform, platform_identifier=platform_identifier, display_identifier=display_identifier) + response = client.contacts.create_contact( + profile_id=profile_id, + name=name, + email=email, + company=company, + tags=tags, + is_subscribed=is_subscribed, + notes=notes, + account_id=account_id, + platform=platform, + platform_identifier=platform_identifier, + display_identifier=display_identifier, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def contacts_get_contact(contact_id: str) -> str: @@ -1796,11 +2861,20 @@ def contacts_get_contact(contact_id: str) -> str: response = client.contacts.get_contact(contact_id=contact_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def contacts_update_contact(contact_id: str, name: str = "", email: str = "", company: str = "", avatar_url: str = "", tags: str = "", is_subscribed: bool = False, is_blocked: bool = False, notes: str = "") -> str: + def contacts_update_contact( + contact_id: str, + name: str = "", + email: str = "", + company: str = "", + avatar_url: str = "", + tags: str = "", + is_subscribed: bool = False, + is_blocked: bool = False, + notes: str = "", + ) -> str: """Update a contact Args: @@ -1815,11 +2889,20 @@ def contacts_update_contact(contact_id: str, name: str = "", email: str = "", co notes""" client = _get_client() try: - response = client.contacts.update_contact(contact_id=contact_id, name=name, email=email, company=company, avatar_url=avatar_url, tags=tags, is_subscribed=is_subscribed, is_blocked=is_blocked, notes=notes) + response = client.contacts.update_contact( + contact_id=contact_id, + name=name, + email=email, + company=company, + avatar_url=avatar_url, + tags=tags, + is_subscribed=is_subscribed, + is_blocked=is_blocked, + notes=notes, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def contacts_delete_contact(contact_id: str) -> str: @@ -1832,8 +2915,7 @@ def contacts_delete_contact(contact_id: str) -> str: response = client.contacts.delete_contact(contact_id=contact_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def contacts_get_contact_channels(contact_id: str) -> str: @@ -1846,11 +2928,12 @@ def contacts_get_contact_channels(contact_id: str) -> str: response = client.contacts.get_contact_channels(contact_id=contact_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def contacts_bulk_create_contacts(profile_id: str, account_id: str, platform: str, contacts: str) -> str: + def contacts_bulk_create_contacts( + profile_id: str, account_id: str, platform: str, contacts: str + ) -> str: """Bulk create contacts Args: @@ -1860,16 +2943,22 @@ def contacts_bulk_create_contacts(profile_id: str, account_id: str, platform: st contacts: (required)""" client = _get_client() try: - response = client.contacts.bulk_create_contacts(profile_id=profile_id, account_id=account_id, platform=platform, contacts=contacts) + response = client.contacts.bulk_create_contacts( + profile_id=profile_id, + account_id=account_id, + platform=platform, + contacts=contacts, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # CUSTOM_FIELDS - @mcp.tool() - def custom_fields_set_contact_field_value(contact_id: str, slug: str, value: str) -> str: + def custom_fields_set_contact_field_value( + contact_id: str, slug: str, value: str + ) -> str: """Set a custom field value Args: @@ -1878,11 +2967,12 @@ def custom_fields_set_contact_field_value(contact_id: str, slug: str, value: str value: Field value (type depends on field definition) (required)""" client = _get_client() try: - response = client.custom_fields.set_contact_field_value(contact_id=contact_id, slug=slug, value=value) + response = client.custom_fields.set_contact_field_value( + contact_id=contact_id, slug=slug, value=value + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def custom_fields_clear_contact_field_value(contact_id: str, slug: str) -> str: @@ -1893,11 +2983,12 @@ def custom_fields_clear_contact_field_value(contact_id: str, slug: str) -> str: slug: (required)""" client = _get_client() try: - response = client.custom_fields.clear_contact_field_value(contact_id=contact_id, slug=slug) + response = client.custom_fields.clear_contact_field_value( + contact_id=contact_id, slug=slug + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def custom_fields_list_custom_fields(profile_id: str = "") -> str: @@ -1910,11 +3001,12 @@ def custom_fields_list_custom_fields(profile_id: str = "") -> str: response = client.custom_fields.list_custom_fields(profile_id=profile_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def custom_fields_create_custom_field(profile_id: str, name: str, type: str, slug: str = "", options: str = "") -> str: + def custom_fields_create_custom_field( + profile_id: str, name: str, type: str, slug: str = "", options: str = "" + ) -> str: """Create a custom field definition Args: @@ -1925,14 +3017,17 @@ def custom_fields_create_custom_field(profile_id: str, name: str, type: str, slu options: Required for select type""" client = _get_client() try: - response = client.custom_fields.create_custom_field(profile_id=profile_id, name=name, slug=slug, type=type, options=options) + response = client.custom_fields.create_custom_field( + profile_id=profile_id, name=name, slug=slug, type=type, options=options + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def custom_fields_update_custom_field(field_id: str, name: str = "", options: str = "") -> str: + def custom_fields_update_custom_field( + field_id: str, name: str = "", options: str = "" + ) -> str: """Update a custom field definition Args: @@ -1941,11 +3036,12 @@ def custom_fields_update_custom_field(field_id: str, name: str = "", options: st options""" client = _get_client() try: - response = client.custom_fields.update_custom_field(field_id=field_id, name=name, options=options) + response = client.custom_fields.update_custom_field( + field_id=field_id, name=name, options=options + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def custom_fields_delete_custom_field(field_id: str) -> str: @@ -1958,11 +3054,10 @@ def custom_fields_delete_custom_field(field_id: str) -> str: response = client.custom_fields.delete_custom_field(field_id=field_id) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # INVITES - @mcp.tool() def invites_create_invite_token(scope: str, profile_ids: str = "") -> str: """Create invite token @@ -1972,16 +3067,25 @@ def invites_create_invite_token(scope: str, profile_ids: str = "") -> str: profile_ids: Required if scope is 'profiles'. Array of profile IDs to grant access to.""" client = _get_client() try: - response = client.invites.create_invite_token(scope=scope, profile_ids=profile_ids) + response = client.invites.create_invite_token( + scope=scope, profile_ids=profile_ids + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # LOGS - @mcp.tool() - def logs_list_posts_logs(status: str = "", platform: str = "", action: str = "", days: int = 7, limit: int = 50, skip: int = 0, search: str = "") -> str: + def logs_list_posts_logs( + status: str = "", + platform: str = "", + action: str = "", + days: int = 7, + limit: int = 50, + skip: int = 0, + search: str = "", + ) -> str: """List publishing logs Args: @@ -1994,14 +3098,28 @@ def logs_list_posts_logs(status: str = "", platform: str = "", action: str = "", search: Search through log entries by text content.""" client = _get_client() try: - response = client.logs.list_posts_logs(status=status, platform=platform, action=action, days=days, limit=limit, skip=skip, search=search) + response = client.logs.list_posts_logs( + status=status, + platform=platform, + action=action, + days=days, + limit=limit, + skip=skip, + search=search, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def logs_list_connection_logs(platform: str = "", event_type: str = "", status: str = "", days: int = 7, limit: int = 50, skip: int = 0) -> str: + def logs_list_connection_logs( + platform: str = "", + event_type: str = "", + status: str = "", + days: int = 7, + limit: int = 50, + skip: int = 0, + ) -> str: """List connection logs Args: @@ -2013,11 +3131,17 @@ def logs_list_connection_logs(platform: str = "", event_type: str = "", status: skip: Number of logs to skip (for pagination)""" client = _get_client() try: - response = client.logs.list_connection_logs(platform=platform, event_type=event_type, status=status, days=days, limit=limit, skip=skip) + response = client.logs.list_connection_logs( + platform=platform, + event_type=event_type, + status=status, + days=days, + limit=limit, + skip=skip, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def logs_get_post_logs(post_id: str, limit: int = 50) -> str: @@ -2031,13 +3155,14 @@ def logs_get_post_logs(post_id: str, limit: int = 50) -> str: response = client.logs.get_post_logs(post_id=post_id, limit=limit) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # MEDIA - @mcp.tool() - def media_get_media_presigned_url(filename: str, content_type: str, size: int = 0) -> str: + def media_get_media_presigned_url( + filename: str, content_type: str, size: int = 0 + ) -> str: """Get presigned upload URL Args: @@ -2046,16 +3171,25 @@ def media_get_media_presigned_url(filename: str, content_type: str, size: int = size: Optional file size in bytes for pre-validation (max 5GB)""" client = _get_client() try: - response = client.media.get_media_presigned_url(filename=filename, content_type=content_type, size=size) + response = client.media.get_media_presigned_url( + filename=filename, content_type=content_type, size=size + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # MESSAGES - @mcp.tool() - def messages_list_inbox_conversations(profile_id: str = "", platform: str = "", status: str = "", sort_order: str = "desc", limit: int = 50, cursor: str = "", account_id: str = "") -> str: + def messages_list_inbox_conversations( + profile_id: str = "", + platform: str = "", + status: str = "", + sort_order: str = "desc", + limit: int = 50, + cursor: str = "", + account_id: str = "", + ) -> str: """List conversations Args: @@ -2068,11 +3202,18 @@ def messages_list_inbox_conversations(profile_id: str = "", platform: str = "", account_id: Filter by specific social account ID""" client = _get_client() try: - response = client.messages.list_inbox_conversations(profile_id=profile_id, platform=platform, status=status, sort_order=sort_order, limit=limit, cursor=cursor, account_id=account_id) + response = client.messages.list_inbox_conversations( + profile_id=profile_id, + platform=platform, + status=status, + sort_order=sort_order, + limit=limit, + cursor=cursor, + account_id=account_id, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def messages_get_inbox_conversation(conversation_id: str, account_id: str) -> str: @@ -2083,14 +3224,17 @@ def messages_get_inbox_conversation(conversation_id: str, account_id: str) -> st account_id: The social account ID (required)""" client = _get_client() try: - response = client.messages.get_inbox_conversation(conversation_id=conversation_id, account_id=account_id) + response = client.messages.get_inbox_conversation( + conversation_id=conversation_id, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def messages_update_inbox_conversation(conversation_id: str, account_id: str, status: str) -> str: + def messages_update_inbox_conversation( + conversation_id: str, account_id: str, status: str + ) -> str: """Update conversation status Args: @@ -2099,14 +3243,17 @@ def messages_update_inbox_conversation(conversation_id: str, account_id: str, st status: (required)""" client = _get_client() try: - response = client.messages.update_inbox_conversation(conversation_id=conversation_id, account_id=account_id, status=status) + response = client.messages.update_inbox_conversation( + conversation_id=conversation_id, account_id=account_id, status=status + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def messages_get_inbox_conversation_messages(conversation_id: str, account_id: str) -> str: + def messages_get_inbox_conversation_messages( + conversation_id: str, account_id: str + ) -> str: """List messages Args: @@ -2114,14 +3261,28 @@ def messages_get_inbox_conversation_messages(conversation_id: str, account_id: s account_id: Social account ID (required)""" client = _get_client() try: - response = client.messages.get_inbox_conversation_messages(conversation_id=conversation_id, account_id=account_id) + response = client.messages.get_inbox_conversation_messages( + conversation_id=conversation_id, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def messages_send_inbox_message(conversation_id: str, account_id: str, message: str = "", attachment_url: str = "", attachment_type: str = "", quick_replies: str = "", buttons: str = "", template: str = "", reply_markup: str = "", messaging_type: str = "", message_tag: str = "", reply_to: str = "") -> str: + def messages_send_inbox_message( + conversation_id: str, + account_id: str, + message: str = "", + attachment_url: str = "", + attachment_type: str = "", + quick_replies: str = "", + buttons: str = "", + template: str = "", + reply_markup: str = "", + messaging_type: str = "", + message_tag: str = "", + reply_to: str = "", + ) -> str: """Send message Args: @@ -2139,34 +3300,160 @@ def messages_send_inbox_message(conversation_id: str, account_id: str, message: reply_to: Platform message ID to reply to (Telegram only).""" client = _get_client() try: - response = client.messages.send_inbox_message(conversation_id=conversation_id, account_id=account_id, message=message, attachment_url=attachment_url, attachment_type=attachment_type, quick_replies=quick_replies, buttons=buttons, template=template, reply_markup=reply_markup, messaging_type=messaging_type, message_tag=message_tag, reply_to=reply_to) + response = client.messages.send_inbox_message( + conversation_id=conversation_id, + account_id=account_id, + message=message, + attachment_url=attachment_url, + attachment_type=attachment_type, + quick_replies=quick_replies, + buttons=buttons, + template=template, + reply_markup=reply_markup, + messaging_type=messaging_type, + message_tag=message_tag, + reply_to=reply_to, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def messages_edit_inbox_message( + conversation_id: str, + message_id: str, + account_id: str, + text: str = "", + reply_markup: str = "", + ) -> str: + """Edit message + + Args: + conversation_id: The conversation ID (required) + message_id: The Telegram message ID to edit (required) + account_id: Social account ID (required) + text: New message text + reply_markup: New inline keyboard markup""" + client = _get_client() + try: + response = client.messages.edit_inbox_message( + conversation_id=conversation_id, + message_id=message_id, + account_id=account_id, + text=text, + reply_markup=reply_markup, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" + + @mcp.tool() + def messages_delete_inbox_message( + conversation_id: str, message_id: str, account_id: str + ) -> str: + """Delete message + Args: + conversation_id: The conversation ID (required) + message_id: The platform message ID to delete (required) + account_id: Social account ID (required)""" + client = _get_client() + try: + response = client.messages.delete_inbox_message( + conversation_id=conversation_id, + message_id=message_id, + account_id=account_id, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" @mcp.tool() - def messages_edit_inbox_message(conversation_id: str, message_id: str, account_id: str, text: str = "", reply_markup: str = "") -> str: - """Edit message + def messages_send_typing_indicator(conversation_id: str, account_id: str) -> str: + """Send typing indicator Args: conversation_id: The conversation ID (required) - message_id: The Telegram message ID to edit (required) + account_id: Social account ID (required)""" + client = _get_client() + try: + response = client.messages.send_typing_indicator( + conversation_id=conversation_id, account_id=account_id + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def messages_add_message_reaction( + conversation_id: str, message_id: str, account_id: str, emoji: str + ) -> str: + """Add reaction + + Args: + conversation_id: The conversation ID (required) + message_id: The platform message ID to react to (required) account_id: Social account ID (required) - text: New message text - reply_markup: New inline keyboard markup""" + emoji: Emoji character (e.g. "👍", "❤️") (required)""" client = _get_client() try: - response = client.messages.edit_inbox_message(conversation_id=conversation_id, message_id=message_id, account_id=account_id, text=text, reply_markup=reply_markup) + response = client.messages.add_message_reaction( + conversation_id=conversation_id, + message_id=message_id, + account_id=account_id, + emoji=emoji, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" - # POSTS + @mcp.tool() + def messages_remove_message_reaction( + conversation_id: str, message_id: str, account_id: str + ) -> str: + """Remove reaction + + Args: + conversation_id: The conversation ID (required) + message_id: The platform message ID (required) + account_id: Social account ID (required)""" + client = _get_client() + try: + response = client.messages.remove_message_reaction( + conversation_id=conversation_id, + message_id=message_id, + account_id=account_id, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def messages_upload_media_direct() -> str: + """Upload media file""" + client = _get_client() + try: + response = client.messages.upload_media_direct() + return _format_response(response) + except Exception as e: + return f"Error: {e}" + # POSTS @mcp.tool() - def posts_list_posts(page: int = 1, limit: int = 10, status: str = "", platform: str = "", profile_id: str = "", created_by: str = "", date_from: str = "", date_to: str = "", include_hidden: bool = False, search: str = "", sort_by: str = "scheduled-desc") -> str: + def posts_list_posts( + page: int = 1, + limit: int = 10, + status: str = "", + platform: str = "", + profile_id: str = "", + created_by: str = "", + date_from: str = "", + date_to: str = "", + include_hidden: bool = False, + search: str = "", + sort_by: str = "scheduled-desc", + ) -> str: """List posts Args: @@ -2183,11 +3470,22 @@ def posts_list_posts(page: int = 1, limit: int = 10, status: str = "", platform: sort_by: Sort order for results.""" client = _get_client() try: - response = client.posts.list_posts(page=page, limit=limit, status=status, platform=platform, profile_id=profile_id, created_by=created_by, date_from=date_from, date_to=date_to, include_hidden=include_hidden, search=search, sort_by=sort_by) + response = client.posts.list_posts( + page=page, + limit=limit, + status=status, + platform=platform, + profile_id=profile_id, + created_by=created_by, + date_from=date_from, + date_to=date_to, + include_hidden=include_hidden, + search=search, + sort_by=sort_by, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def posts_get_post(post_id: str) -> str: @@ -2200,11 +3498,16 @@ def posts_get_post(post_id: str) -> str: response = client.posts.get_post(post_id=post_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def posts_update_post(post_id: str, content: str = "", scheduled_for: str = "", tiktok_settings: str = "", recycling: str = "") -> str: + def posts_update_post( + post_id: str, + content: str = "", + scheduled_for: str = "", + tiktok_settings: str = "", + recycling: str = "", + ) -> str: """Update post Args: @@ -2215,11 +3518,16 @@ def posts_update_post(post_id: str, content: str = "", scheduled_for: str = "", recycling""" client = _get_client() try: - response = client.posts.update_post(post_id=post_id, content=content, scheduled_for=scheduled_for, tiktok_settings=tiktok_settings, recycling=recycling) + response = client.posts.update_post( + post_id=post_id, + content=content, + scheduled_for=scheduled_for, + tiktok_settings=tiktok_settings, + recycling=recycling, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def posts_delete_post(post_id: str) -> str: @@ -2232,8 +3540,7 @@ def posts_delete_post(post_id: str) -> str: response = client.posts.delete_post(post_id=post_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def posts_bulk_upload_posts(dry_run: bool = False) -> str: @@ -2246,8 +3553,7 @@ def posts_bulk_upload_posts(dry_run: bool = False) -> str: response = client.posts.bulk_upload_posts(dry_run=dry_run) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def posts_unpublish_post(post_id: str, platform: str) -> str: @@ -2261,31 +3567,63 @@ def posts_unpublish_post(post_id: str, platform: str) -> str: response = client.posts.unpublish_post(post_id=post_id, platform=platform) return _format_response(response) except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def posts_update_post_metadata(post_id: str, platform: str, title: str = "", description: str = "", tags: str = "", category_id: str = "", privacy_status: str = "") -> str: + return f"Error: {e}" + + @mcp.tool() + def posts_update_post_metadata( + post_id: str, + platform: str, + video_id: str = "", + account_id: str = "", + title: str = "", + description: str = "", + tags: str = "", + category_id: str = "", + privacy_status: str = "", + thumbnail_url: str = "", + made_for_kids: bool = False, + contains_synthetic_media: bool = False, + playlist_id: str = "", + ) -> str: """Update post metadata Args: - post_id: (required) + post_id: Zernio post ID, or "_" when using direct video ID mode (required) platform: The platform to update metadata on (required) + video_id: YouTube video ID (required for direct mode, ignored for post-based mode) + account_id: Zernio social account ID (required for direct mode, ignored for post-based mode) title: New video title (max 100 characters for YouTube) description: New video description tags: Array of keyword tags (max 500 characters combined for YouTube) category_id: YouTube video category ID - privacy_status: Video privacy setting""" - client = _get_client() - try: - response = client.posts.update_post_metadata(post_id=post_id, platform=platform, title=title, description=description, tags=tags, category_id=category_id, privacy_status=privacy_status) - return _format_response(response) - except Exception as e: - return f'Error: {e}' + privacy_status: Video privacy setting + thumbnail_url: Public URL of a custom thumbnail image (JPEG, PNG, or GIF, max 2 MB, recommended 1280x720). Works on any video you own, including existing videos not published through Zernio. The channel must be verified (phone verification) to set custom thumbnails. + made_for_kids: COPPA compliance flag. Set true for child-directed content (restricts comments, notifications, ad targeting). + contains_synthetic_media: AI-generated content disclosure. Set true if the video contains synthetic content that could be mistaken for real. YouTube may add a label. + playlist_id: YouTube playlist ID to add the video to (e.g. 'PLxxxxxxxxxxxxx'). Use GET /v1/accounts/{id}/youtube-playlists to list available playlists. Only playlists owned by the channel are supported.""" + client = _get_client() + try: + response = client.posts.update_post_metadata( + post_id=post_id, + platform=platform, + video_id=video_id, + account_id=account_id, + title=title, + description=description, + tags=tags, + category_id=category_id, + privacy_status=privacy_status, + thumbnail_url=thumbnail_url, + made_for_kids=made_for_kids, + contains_synthetic_media=contains_synthetic_media, + playlist_id=playlist_id, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" # PROFILES - @mcp.tool() def profiles_list_profiles(include_over_limit: bool = False) -> str: """List profiles @@ -2294,14 +3632,17 @@ def profiles_list_profiles(include_over_limit: bool = False) -> str: include_over_limit: When true, includes over-limit profiles (marked with isOverLimit: true).""" client = _get_client() try: - response = client.profiles.list_profiles(include_over_limit=include_over_limit) + response = client.profiles.list_profiles( + include_over_limit=include_over_limit + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def profiles_create_profile(name: str, description: str = "", color: str = "") -> str: + def profiles_create_profile( + name: str, description: str = "", color: str = "" + ) -> str: """Create profile Args: @@ -2310,11 +3651,12 @@ def profiles_create_profile(name: str, description: str = "", color: str = "") - color""" client = _get_client() try: - response = client.profiles.create_profile(name=name, description=description, color=color) + response = client.profiles.create_profile( + name=name, description=description, color=color + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def profiles_get_profile(profile_id: str) -> str: @@ -2327,11 +3669,16 @@ def profiles_get_profile(profile_id: str) -> str: response = client.profiles.get_profile(profile_id=profile_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def profiles_update_profile(profile_id: str, name: str = "", description: str = "", color: str = "", is_default: bool = False) -> str: + def profiles_update_profile( + profile_id: str, + name: str = "", + description: str = "", + color: str = "", + is_default: bool = False, + ) -> str: """Update profile Args: @@ -2342,11 +3689,16 @@ def profiles_update_profile(profile_id: str, name: str = "", description: str = is_default""" client = _get_client() try: - response = client.profiles.update_profile(profile_id=profile_id, name=name, description=description, color=color, is_default=is_default) + response = client.profiles.update_profile( + profile_id=profile_id, + name=name, + description=description, + color=color, + is_default=is_default, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def profiles_delete_profile(profile_id: str) -> str: @@ -2359,13 +3711,14 @@ def profiles_delete_profile(profile_id: str) -> str: response = client.profiles.delete_profile(profile_id=profile_id) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # QUEUE - @mcp.tool() - def queue_list_queue_slots(profile_id: str, queue_id: str = "", all: str = "") -> str: + def queue_list_queue_slots( + profile_id: str, queue_id: str = "", all: str = "" + ) -> str: """List schedules Args: @@ -2374,14 +3727,17 @@ def queue_list_queue_slots(profile_id: str, queue_id: str = "", all: str = "") - all: Set to 'true' to list all queues for the profile""" client = _get_client() try: - response = client.queue.list_queue_slots(profile_id=profile_id, queue_id=queue_id, all=all) + response = client.queue.list_queue_slots( + profile_id=profile_id, queue_id=queue_id, all=all + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def queue_create_queue_slot(profile_id: str, name: str, timezone: str, slots: str, active: bool = True) -> str: + def queue_create_queue_slot( + profile_id: str, name: str, timezone: str, slots: str, active: bool = True + ) -> str: """Create schedule Args: @@ -2392,14 +3748,28 @@ def queue_create_queue_slot(profile_id: str, name: str, timezone: str, slots: st active""" client = _get_client() try: - response = client.queue.create_queue_slot(profile_id=profile_id, name=name, timezone=timezone, slots=slots, active=active) + response = client.queue.create_queue_slot( + profile_id=profile_id, + name=name, + timezone=timezone, + slots=slots, + active=active, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def queue_update_queue_slot(profile_id: str, timezone: str, slots: str, queue_id: str = "", name: str = "", active: bool = True, set_as_default: bool = False, reshuffle_existing: bool = False) -> str: + def queue_update_queue_slot( + profile_id: str, + timezone: str, + slots: str, + queue_id: str = "", + name: str = "", + active: bool = True, + set_as_default: bool = False, + reshuffle_existing: bool = False, + ) -> str: """Update schedule Args: @@ -2413,11 +3783,19 @@ def queue_update_queue_slot(profile_id: str, timezone: str, slots: str, queue_id reshuffle_existing: Whether to reschedule existing queued posts to match new slots""" client = _get_client() try: - response = client.queue.update_queue_slot(profile_id=profile_id, queue_id=queue_id, name=name, timezone=timezone, slots=slots, active=active, set_as_default=set_as_default, reshuffle_existing=reshuffle_existing) + response = client.queue.update_queue_slot( + profile_id=profile_id, + queue_id=queue_id, + name=name, + timezone=timezone, + slots=slots, + active=active, + set_as_default=set_as_default, + reshuffle_existing=reshuffle_existing, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def queue_delete_queue_slot(profile_id: str, queue_id: str) -> str: @@ -2428,14 +3806,17 @@ def queue_delete_queue_slot(profile_id: str, queue_id: str) -> str: queue_id: Queue ID to delete (required)""" client = _get_client() try: - response = client.queue.delete_queue_slot(profile_id=profile_id, queue_id=queue_id) + response = client.queue.delete_queue_slot( + profile_id=profile_id, queue_id=queue_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def queue_preview_queue(profile_id: str, queue_id: str = "", count: int = 20) -> str: + def queue_preview_queue( + profile_id: str, queue_id: str = "", count: int = 20 + ) -> str: """Preview upcoming slots Args: @@ -2444,11 +3825,12 @@ def queue_preview_queue(profile_id: str, queue_id: str = "", count: int = 20) -> count""" client = _get_client() try: - response = client.queue.preview_queue(profile_id=profile_id, queue_id=queue_id, count=count) + response = client.queue.preview_queue( + profile_id=profile_id, queue_id=queue_id, count=count + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def queue_get_next_queue_slot(profile_id: str, queue_id: str = "") -> str: @@ -2459,16 +3841,25 @@ def queue_get_next_queue_slot(profile_id: str, queue_id: str = "") -> str: queue_id: Specific queue ID (optional, defaults to profile's default queue)""" client = _get_client() try: - response = client.queue.get_next_queue_slot(profile_id=profile_id, queue_id=queue_id) + response = client.queue.get_next_queue_slot( + profile_id=profile_id, queue_id=queue_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # REDDIT - @mcp.tool() - def reddit_search_reddit(account_id: str, q: str, subreddit: str = "", restrict_sr: str = "", sort: str = "new", limit: int = 25, after: str = "") -> str: + def reddit_search_reddit( + account_id: str, + q: str, + subreddit: str = "", + restrict_sr: str = "", + sort: str = "new", + limit: int = 25, + after: str = "", + ) -> str: """Search posts Args: @@ -2481,14 +3872,28 @@ def reddit_search_reddit(account_id: str, q: str, subreddit: str = "", restrict_ after""" client = _get_client() try: - response = client.reddit.search_reddit(account_id=account_id, subreddit=subreddit, q=q, restrict_sr=restrict_sr, sort=sort, limit=limit, after=after) + response = client.reddit.search_reddit( + account_id=account_id, + subreddit=subreddit, + q=q, + restrict_sr=restrict_sr, + sort=sort, + limit=limit, + after=after, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def reddit_get_reddit_feed(account_id: str, subreddit: str = "", sort: str = "hot", limit: int = 25, after: str = "", t: str = "") -> str: + def reddit_get_reddit_feed( + account_id: str, + subreddit: str = "", + sort: str = "hot", + limit: int = 25, + after: str = "", + t: str = "", + ) -> str: """Get subreddit feed Args: @@ -2500,16 +3905,33 @@ def reddit_get_reddit_feed(account_id: str, subreddit: str = "", sort: str = "ho t""" client = _get_client() try: - response = client.reddit.get_reddit_feed(account_id=account_id, subreddit=subreddit, sort=sort, limit=limit, after=after, t=t) + response = client.reddit.get_reddit_feed( + account_id=account_id, + subreddit=subreddit, + sort=sort, + limit=limit, + after=after, + t=t, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # REVIEWS - @mcp.tool() - def reviews_list_inbox_reviews(profile_id: str = "", platform: str = "", min_rating: int = 0, max_rating: int = 0, has_reply: bool = False, sort_by: str = "date", sort_order: str = "desc", limit: int = 25, cursor: str = "", account_id: str = "") -> str: + def reviews_list_inbox_reviews( + profile_id: str = "", + platform: str = "", + min_rating: int = 0, + max_rating: int = 0, + has_reply: bool = False, + sort_by: str = "date", + sort_order: str = "desc", + limit: int = 25, + cursor: str = "", + account_id: str = "", + ) -> str: """List reviews Args: @@ -2525,14 +3947,26 @@ def reviews_list_inbox_reviews(profile_id: str = "", platform: str = "", min_rat account_id: Filter by specific social account ID""" client = _get_client() try: - response = client.reviews.list_inbox_reviews(profile_id=profile_id, platform=platform, min_rating=min_rating, max_rating=max_rating, has_reply=has_reply, sort_by=sort_by, sort_order=sort_order, limit=limit, cursor=cursor, account_id=account_id) + response = client.reviews.list_inbox_reviews( + profile_id=profile_id, + platform=platform, + min_rating=min_rating, + max_rating=max_rating, + has_reply=has_reply, + sort_by=sort_by, + sort_order=sort_order, + limit=limit, + cursor=cursor, + account_id=account_id, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def reviews_reply_to_inbox_review(review_id: str, account_id: str, message: str) -> str: + def reviews_reply_to_inbox_review( + review_id: str, account_id: str, message: str + ) -> str: """Reply to review Args: @@ -2541,11 +3975,12 @@ def reviews_reply_to_inbox_review(review_id: str, account_id: str, message: str) message: (required)""" client = _get_client() try: - response = client.reviews.reply_to_inbox_review(review_id=review_id, account_id=account_id, message=message) + response = client.reviews.reply_to_inbox_review( + review_id=review_id, account_id=account_id, message=message + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def reviews_delete_inbox_review_reply(review_id: str, account_id: str) -> str: @@ -2556,16 +3991,19 @@ def reviews_delete_inbox_review_reply(review_id: str, account_id: str) -> str: account_id: (required)""" client = _get_client() try: - response = client.reviews.delete_inbox_review_reply(review_id=review_id, account_id=account_id) + response = client.reviews.delete_inbox_review_reply( + review_id=review_id, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # SEQUENCES - @mcp.tool() - def sequences_list_sequences(profile_id: str = "", status: str = "", limit: int = 50, skip: int = 0) -> str: + def sequences_list_sequences( + profile_id: str = "", status: str = "", limit: int = 50, skip: int = 0 + ) -> str: """List sequences Args: @@ -2575,14 +4013,24 @@ def sequences_list_sequences(profile_id: str = "", status: str = "", limit: int skip""" client = _get_client() try: - response = client.sequences.list_sequences(profile_id=profile_id, status=status, limit=limit, skip=skip) + response = client.sequences.list_sequences( + profile_id=profile_id, status=status, limit=limit, skip=skip + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def sequences_create_sequence(profile_id: str, account_id: str, platform: str, name: str, description: str = "", steps: str = "", exit_on_reply: bool = True, exit_on_unsubscribe: bool = True) -> str: + def sequences_create_sequence( + profile_id: str, + account_id: str, + platform: str, + name: str, + description: str = "", + steps: str = "", + exit_on_reply: bool = True, + exit_on_unsubscribe: bool = True, + ) -> str: """Create a sequence Args: @@ -2596,11 +4044,19 @@ def sequences_create_sequence(profile_id: str, account_id: str, platform: str, n exit_on_unsubscribe""" client = _get_client() try: - response = client.sequences.create_sequence(profile_id=profile_id, account_id=account_id, platform=platform, name=name, description=description, steps=steps, exit_on_reply=exit_on_reply, exit_on_unsubscribe=exit_on_unsubscribe) + response = client.sequences.create_sequence( + profile_id=profile_id, + account_id=account_id, + platform=platform, + name=name, + description=description, + steps=steps, + exit_on_reply=exit_on_reply, + exit_on_unsubscribe=exit_on_unsubscribe, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def sequences_get_sequence(sequence_id: str) -> str: @@ -2613,8 +4069,7 @@ def sequences_get_sequence(sequence_id: str) -> str: response = client.sequences.get_sequence(sequence_id=sequence_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def sequences_update_sequence(sequence_id: str) -> str: @@ -2627,8 +4082,7 @@ def sequences_update_sequence(sequence_id: str) -> str: response = client.sequences.update_sequence(sequence_id=sequence_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def sequences_delete_sequence(sequence_id: str) -> str: @@ -2641,8 +4095,7 @@ def sequences_delete_sequence(sequence_id: str) -> str: response = client.sequences.delete_sequence(sequence_id=sequence_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def sequences_activate_sequence(sequence_id: str) -> str: @@ -2655,8 +4108,7 @@ def sequences_activate_sequence(sequence_id: str) -> str: response = client.sequences.activate_sequence(sequence_id=sequence_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def sequences_pause_sequence(sequence_id: str) -> str: @@ -2669,11 +4121,12 @@ def sequences_pause_sequence(sequence_id: str) -> str: response = client.sequences.pause_sequence(sequence_id=sequence_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def sequences_enroll_contacts(sequence_id: str, contact_ids: str, channel_ids: str = "") -> str: + def sequences_enroll_contacts( + sequence_id: str, contact_ids: str, channel_ids: str = "" + ) -> str: """Enroll contacts in a sequence Args: @@ -2682,11 +4135,14 @@ def sequences_enroll_contacts(sequence_id: str, contact_ids: str, channel_ids: s channel_ids: Optional. Auto-detected if not provided.""" client = _get_client() try: - response = client.sequences.enroll_contacts(sequence_id=sequence_id, contact_ids=contact_ids, channel_ids=channel_ids) + response = client.sequences.enroll_contacts( + sequence_id=sequence_id, + contact_ids=contact_ids, + channel_ids=channel_ids, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def sequences_unenroll_contact(sequence_id: str, contact_id: str) -> str: @@ -2697,14 +4153,17 @@ def sequences_unenroll_contact(sequence_id: str, contact_id: str) -> str: contact_id: (required)""" client = _get_client() try: - response = client.sequences.unenroll_contact(sequence_id=sequence_id, contact_id=contact_id) + response = client.sequences.unenroll_contact( + sequence_id=sequence_id, contact_id=contact_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def sequences_list_sequence_enrollments(sequence_id: str, status: str = "", limit: int = 50, skip: int = 0) -> str: + def sequences_list_sequence_enrollments( + sequence_id: str, status: str = "", limit: int = 50, skip: int = 0 + ) -> str: """List enrollments for a sequence Args: @@ -2714,151 +4173,15 @@ def sequences_list_sequence_enrollments(sequence_id: str, status: str = "", limi skip""" client = _get_client() try: - response = client.sequences.list_sequence_enrollments(sequence_id=sequence_id, status=status, limit=limit, skip=skip) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - # TOOLS - - - @mcp.tool() - def tools_download_you_tube_video(url: str, action: str = "download", format: str = "video", quality: str = "hd", format_id: str = "") -> str: - """Download YouTube video - - Args: - url: YouTube video URL or video ID (required) - action: Action to perform: 'download' returns download URL, 'formats' lists available formats - format: Desired format (when action=download) - quality: Desired quality (when action=download) - format_id: Specific format ID from formats list""" - client = _get_client() - try: - response = client.tools.download_you_tube_video(url=url, action=action, format=format, quality=quality, format_id=format_id) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def tools_get_you_tube_transcript(url: str, lang: str = "en") -> str: - """Get YouTube transcript - - Args: - url: YouTube video URL or video ID (required) - lang: Language code for transcript""" - client = _get_client() - try: - response = client.tools.get_you_tube_transcript(url=url, lang=lang) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def tools_download_instagram_media(url: str) -> str: - """Download Instagram media - - Args: - url: Instagram reel or post URL (required)""" - client = _get_client() - try: - response = client.tools.download_instagram_media(url=url) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def tools_check_instagram_hashtags(hashtags: str) -> str: - """Check IG hashtag bans - - Args: - hashtags: (required)""" - client = _get_client() - try: - response = client.tools.check_instagram_hashtags(hashtags=hashtags) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def tools_download_tik_tok_video(url: str, action: str = "download", format_id: str = "") -> str: - """Download TikTok video - - Args: - url: TikTok video URL or ID (required) - action: 'formats' to list available formats - format_id: Specific format ID (0 = no watermark, etc.)""" - client = _get_client() - try: - response = client.tools.download_tik_tok_video(url=url, action=action, format_id=format_id) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def tools_download_twitter_media(url: str, action: str = "download", format_id: str = "") -> str: - """Download Twitter/X media - - Args: - url: Twitter/X post URL (required) - action - format_id""" - client = _get_client() - try: - response = client.tools.download_twitter_media(url=url, action=action, format_id=format_id) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def tools_download_facebook_video(url: str) -> str: - """Download Facebook video - - Args: - url: Facebook video or reel URL (required)""" - client = _get_client() - try: - response = client.tools.download_facebook_video(url=url) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def tools_download_linked_in_video(url: str) -> str: - """Download LinkedIn video - - Args: - url: LinkedIn post URL (required)""" - client = _get_client() - try: - response = client.tools.download_linked_in_video(url=url) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def tools_download_bluesky_media(url: str) -> str: - """Download Bluesky media - - Args: - url: Bluesky post URL (required)""" - client = _get_client() - try: - response = client.tools.download_bluesky_media(url=url) + response = client.sequences.list_sequence_enrollments( + sequence_id=sequence_id, status=status, limit=limit, skip=skip + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # TWITTER_ENGAGEMENT - @mcp.tool() def twitter_engagement_retweet_post(account_id: str, tweet_id: str) -> str: """Retweet a post @@ -2868,11 +4191,12 @@ def twitter_engagement_retweet_post(account_id: str, tweet_id: str) -> str: tweet_id: The ID of the tweet to retweet (required)""" client = _get_client() try: - response = client.twitter_engagement.retweet_post(account_id=account_id, tweet_id=tweet_id) + response = client.twitter_engagement.retweet_post( + account_id=account_id, tweet_id=tweet_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def twitter_engagement_undo_retweet(account_id: str, tweet_id: str) -> str: @@ -2883,11 +4207,12 @@ def twitter_engagement_undo_retweet(account_id: str, tweet_id: str) -> str: tweet_id: The ID of the original tweet to un-retweet (required)""" client = _get_client() try: - response = client.twitter_engagement.undo_retweet(account_id=account_id, tweet_id=tweet_id) + response = client.twitter_engagement.undo_retweet( + account_id=account_id, tweet_id=tweet_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def twitter_engagement_bookmark_post(account_id: str, tweet_id: str) -> str: @@ -2898,11 +4223,12 @@ def twitter_engagement_bookmark_post(account_id: str, tweet_id: str) -> str: tweet_id: The ID of the tweet to bookmark (required)""" client = _get_client() try: - response = client.twitter_engagement.bookmark_post(account_id=account_id, tweet_id=tweet_id) + response = client.twitter_engagement.bookmark_post( + account_id=account_id, tweet_id=tweet_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def twitter_engagement_remove_bookmark(account_id: str, tweet_id: str) -> str: @@ -2913,11 +4239,12 @@ def twitter_engagement_remove_bookmark(account_id: str, tweet_id: str) -> str: tweet_id: The ID of the tweet to unbookmark (required)""" client = _get_client() try: - response = client.twitter_engagement.remove_bookmark(account_id=account_id, tweet_id=tweet_id) + response = client.twitter_engagement.remove_bookmark( + account_id=account_id, tweet_id=tweet_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def twitter_engagement_follow_user(account_id: str, target_user_id: str) -> str: @@ -2928,11 +4255,12 @@ def twitter_engagement_follow_user(account_id: str, target_user_id: str) -> str: target_user_id: The Twitter ID of the user to follow (required)""" client = _get_client() try: - response = client.twitter_engagement.follow_user(account_id=account_id, target_user_id=target_user_id) + response = client.twitter_engagement.follow_user( + account_id=account_id, target_user_id=target_user_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def twitter_engagement_unfollow_user(account_id: str, target_user_id: str) -> str: @@ -2943,14 +4271,15 @@ def twitter_engagement_unfollow_user(account_id: str, target_user_id: str) -> st target_user_id: The Twitter ID of the user to unfollow (required)""" client = _get_client() try: - response = client.twitter_engagement.unfollow_user(account_id=account_id, target_user_id=target_user_id) + response = client.twitter_engagement.unfollow_user( + account_id=account_id, target_user_id=target_user_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # USAGE - @mcp.tool() def usage_get_usage_stats() -> str: """Get plan and usage stats""" @@ -2959,11 +4288,10 @@ def usage_get_usage_stats() -> str: response = client.usage.get_usage_stats() return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # USERS - @mcp.tool() def users_list_users() -> str: """List users""" @@ -2972,8 +4300,7 @@ def users_list_users() -> str: response = client.users.list_users() return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def users_get_user(user_id: str) -> str: @@ -2986,11 +4313,10 @@ def users_get_user(user_id: str) -> str: response = client.users.get_user(user_id=user_id) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # VALIDATE - @mcp.tool() def validate_post_length(text: str) -> str: """Validate post character count @@ -3002,8 +4328,7 @@ def validate_post_length(text: str) -> str: response = client.validate.validate_post_length(text=text) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def validate_post(platforms: str, content: str = "", media_items: str = "") -> str: @@ -3015,11 +4340,12 @@ def validate_post(platforms: str, content: str = "", media_items: str = "") -> s media_items: Root media items shared across platforms""" client = _get_client() try: - response = client.validate.validate_post(content=content, platforms=platforms, media_items=media_items) + response = client.validate.validate_post( + content=content, platforms=platforms, media_items=media_items + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def validate_media(url: str) -> str: @@ -3032,8 +4358,7 @@ def validate_media(url: str) -> str: response = client.validate.validate_media(url=url) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def validate_subreddit(name: str) -> str: @@ -3046,11 +4371,10 @@ def validate_subreddit(name: str) -> str: response = client.validate.validate_subreddit(name=name) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # WEBHOOKS - @mcp.tool() def webhooks_get_webhook_settings() -> str: """List webhooks""" @@ -3059,11 +4383,17 @@ def webhooks_get_webhook_settings() -> str: response = client.webhooks.get_webhook_settings() return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def webhooks_create_webhook_settings(name: str = "", url: str = "", secret: str = "", events: str = "", is_active: bool = False, custom_headers: str = "") -> str: + def webhooks_create_webhook_settings( + name: str = "", + url: str = "", + secret: str = "", + events: str = "", + is_active: bool = False, + custom_headers: str = "", + ) -> str: """Create webhook Args: @@ -3075,14 +4405,28 @@ def webhooks_create_webhook_settings(name: str = "", url: str = "", secret: str custom_headers: Custom headers to include in webhook requests""" client = _get_client() try: - response = client.webhooks.create_webhook_settings(name=name, url=url, secret=secret, events=events, is_active=is_active, custom_headers=custom_headers) + response = client.webhooks.create_webhook_settings( + name=name, + url=url, + secret=secret, + events=events, + is_active=is_active, + custom_headers=custom_headers, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def webhooks_update_webhook_settings(id: str, name: str = "", url: str = "", secret: str = "", events: str = "", is_active: bool = False, custom_headers: str = "") -> str: + def webhooks_update_webhook_settings( + id: str, + name: str = "", + url: str = "", + secret: str = "", + events: str = "", + is_active: bool = False, + custom_headers: str = "", + ) -> str: """Update webhook Args: @@ -3095,11 +4439,18 @@ def webhooks_update_webhook_settings(id: str, name: str = "", url: str = "", sec custom_headers: Custom headers to include in webhook requests""" client = _get_client() try: - response = client.webhooks.update_webhook_settings(id=id, name=name, url=url, secret=secret, events=events, is_active=is_active, custom_headers=custom_headers) + response = client.webhooks.update_webhook_settings( + id=id, + name=name, + url=url, + secret=secret, + events=events, + is_active=is_active, + custom_headers=custom_headers, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def webhooks_delete_webhook_settings(id: str) -> str: @@ -3112,8 +4463,7 @@ def webhooks_delete_webhook_settings(id: str) -> str: response = client.webhooks.delete_webhook_settings(id=id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def webhooks_test_webhook(webhook_id: str) -> str: @@ -3126,11 +4476,12 @@ def webhooks_test_webhook(webhook_id: str) -> str: response = client.webhooks.test_webhook(webhook_id=webhook_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def webhooks_get_webhook_logs(limit: int = 50, status: str = "", event: str = "", webhook_id: str = "") -> str: + def webhooks_get_webhook_logs( + limit: int = 50, status: str = "", event: str = "", webhook_id: str = "" + ) -> str: """Get delivery logs Args: @@ -3140,16 +4491,19 @@ def webhooks_get_webhook_logs(limit: int = 50, status: str = "", event: str = "" webhook_id: Filter by webhook ID""" client = _get_client() try: - response = client.webhooks.get_webhook_logs(limit=limit, status=status, event=event, webhook_id=webhook_id) + response = client.webhooks.get_webhook_logs( + limit=limit, status=status, event=event, webhook_id=webhook_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # WHATSAPP - @mcp.tool() - def whatsapp_send_whats_app_bulk(account_id: str, recipients: str, template: str) -> str: + def whatsapp_send_whats_app_bulk( + account_id: str, recipients: str, template: str + ) -> str: """Bulk send template messages Args: @@ -3158,14 +4512,23 @@ def whatsapp_send_whats_app_bulk(account_id: str, recipients: str, template: str template: (required)""" client = _get_client() try: - response = client.whatsapp.send_whats_app_bulk(account_id=account_id, recipients=recipients, template=template) + response = client.whatsapp.send_whats_app_bulk( + account_id=account_id, recipients=recipients, template=template + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_get_whats_app_contacts(account_id: str, search: str = "", tag: str = "", group: str = "", opted_in: str = "", limit: int = 50, skip: int = 0) -> str: + def whatsapp_get_whats_app_contacts( + account_id: str, + search: str = "", + tag: str = "", + group: str = "", + opted_in: str = "", + limit: int = 50, + skip: int = 0, + ) -> str: """List contacts Args: @@ -3178,14 +4541,32 @@ def whatsapp_get_whats_app_contacts(account_id: str, search: str = "", tag: str skip: Offset for pagination""" client = _get_client() try: - response = client.whatsapp.get_whats_app_contacts(account_id=account_id, search=search, tag=tag, group=group, opted_in=opted_in, limit=limit, skip=skip) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - - - @mcp.tool() - def whatsapp_create_whats_app_contact(account_id: str, phone: str, name: str, email: str = "", company: str = "", tags: str = "", groups: str = "", is_opted_in: bool = True, custom_fields: str = "", notes: str = "") -> str: + response = client.whatsapp.get_whats_app_contacts( + account_id=account_id, + search=search, + tag=tag, + group=group, + opted_in=opted_in, + limit=limit, + skip=skip, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" + + @mcp.tool() + def whatsapp_create_whats_app_contact( + account_id: str, + phone: str, + name: str, + email: str = "", + company: str = "", + tags: str = "", + groups: str = "", + is_opted_in: bool = True, + custom_fields: str = "", + notes: str = "", + ) -> str: """Create contact Args: @@ -3201,11 +4582,21 @@ def whatsapp_create_whats_app_contact(account_id: str, phone: str, name: str, em notes: Notes about the contact""" client = _get_client() try: - response = client.whatsapp.create_whats_app_contact(account_id=account_id, phone=phone, name=name, email=email, company=company, tags=tags, groups=groups, is_opted_in=is_opted_in, custom_fields=custom_fields, notes=notes) + response = client.whatsapp.create_whats_app_contact( + account_id=account_id, + phone=phone, + name=name, + email=email, + company=company, + tags=tags, + groups=groups, + is_opted_in=is_opted_in, + custom_fields=custom_fields, + notes=notes, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_get_whats_app_contact(contact_id: str) -> str: @@ -3218,11 +4609,21 @@ def whatsapp_get_whats_app_contact(contact_id: str) -> str: response = client.whatsapp.get_whats_app_contact(contact_id=contact_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_update_whats_app_contact(contact_id: str, name: str = "", email: str = "", company: str = "", tags: str = "", groups: str = "", is_opted_in: bool = False, is_blocked: bool = False, custom_fields: str = "", notes: str = "") -> str: + def whatsapp_update_whats_app_contact( + contact_id: str, + name: str = "", + email: str = "", + company: str = "", + tags: str = "", + groups: str = "", + is_opted_in: bool = False, + is_blocked: bool = False, + custom_fields: str = "", + notes: str = "", + ) -> str: """Update contact Args: @@ -3238,11 +4639,21 @@ def whatsapp_update_whats_app_contact(contact_id: str, name: str = "", email: st notes: Notes about the contact""" client = _get_client() try: - response = client.whatsapp.update_whats_app_contact(contact_id=contact_id, name=name, email=email, company=company, tags=tags, groups=groups, is_opted_in=is_opted_in, is_blocked=is_blocked, custom_fields=custom_fields, notes=notes) + response = client.whatsapp.update_whats_app_contact( + contact_id=contact_id, + name=name, + email=email, + company=company, + tags=tags, + groups=groups, + is_opted_in=is_opted_in, + is_blocked=is_blocked, + custom_fields=custom_fields, + notes=notes, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_delete_whats_app_contact(contact_id: str) -> str: @@ -3255,11 +4666,16 @@ def whatsapp_delete_whats_app_contact(contact_id: str) -> str: response = client.whatsapp.delete_whats_app_contact(contact_id=contact_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_import_whats_app_contacts(account_id: str, contacts: str, default_tags: str = "", default_groups: str = "", skip_duplicates: bool = True) -> str: + def whatsapp_import_whats_app_contacts( + account_id: str, + contacts: str, + default_tags: str = "", + default_groups: str = "", + skip_duplicates: bool = True, + ) -> str: """Bulk import contacts Args: @@ -3270,14 +4686,21 @@ def whatsapp_import_whats_app_contacts(account_id: str, contacts: str, default_t skip_duplicates: Skip contacts with existing phone numbers""" client = _get_client() try: - response = client.whatsapp.import_whats_app_contacts(account_id=account_id, contacts=contacts, default_tags=default_tags, default_groups=default_groups, skip_duplicates=skip_duplicates) + response = client.whatsapp.import_whats_app_contacts( + account_id=account_id, + contacts=contacts, + default_tags=default_tags, + default_groups=default_groups, + skip_duplicates=skip_duplicates, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_bulk_update_whats_app_contacts(action: str, contact_ids: str, tags: str = "", groups: str = "") -> str: + def whatsapp_bulk_update_whats_app_contacts( + action: str, contact_ids: str, tags: str = "", groups: str = "" + ) -> str: """Bulk update contacts Args: @@ -3287,11 +4710,12 @@ def whatsapp_bulk_update_whats_app_contacts(action: str, contact_ids: str, tags: groups: Groups to add or remove (required for addGroups/removeGroups)""" client = _get_client() try: - response = client.whatsapp.bulk_update_whats_app_contacts(action=action, contact_ids=contact_ids, tags=tags, groups=groups) + response = client.whatsapp.bulk_update_whats_app_contacts( + action=action, contact_ids=contact_ids, tags=tags, groups=groups + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_bulk_delete_whats_app_contacts(contact_ids: str) -> str: @@ -3301,11 +4725,12 @@ def whatsapp_bulk_delete_whats_app_contacts(contact_ids: str) -> str: contact_ids: Contact IDs to delete (max 500) (required)""" client = _get_client() try: - response = client.whatsapp.bulk_delete_whats_app_contacts(contact_ids=contact_ids) + response = client.whatsapp.bulk_delete_whats_app_contacts( + contact_ids=contact_ids + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_get_whats_app_groups(account_id: str) -> str: @@ -3318,11 +4743,12 @@ def whatsapp_get_whats_app_groups(account_id: str) -> str: response = client.whatsapp.get_whats_app_groups(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_rename_whats_app_group(account_id: str, old_name: str, new_name: str) -> str: + def whatsapp_rename_whats_app_group( + account_id: str, old_name: str, new_name: str + ) -> str: """Rename group Args: @@ -3331,11 +4757,12 @@ def whatsapp_rename_whats_app_group(account_id: str, old_name: str, new_name: st new_name: New group name (required)""" client = _get_client() try: - response = client.whatsapp.rename_whats_app_group(account_id=account_id, old_name=old_name, new_name=new_name) + response = client.whatsapp.rename_whats_app_group( + account_id=account_id, old_name=old_name, new_name=new_name + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_delete_whats_app_group(account_id: str, group_name: str) -> str: @@ -3346,11 +4773,12 @@ def whatsapp_delete_whats_app_group(account_id: str, group_name: str) -> str: group_name: Group name to delete (required)""" client = _get_client() try: - response = client.whatsapp.delete_whats_app_group(account_id=account_id, group_name=group_name) + response = client.whatsapp.delete_whats_app_group( + account_id=account_id, group_name=group_name + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_get_whats_app_templates(account_id: str) -> str: @@ -3363,34 +4791,50 @@ def whatsapp_get_whats_app_templates(account_id: str) -> str: response = client.whatsapp.get_whats_app_templates(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_create_whats_app_template(account_id: str, name: str, category: str, language: str, components: str = "", library_template_name: str = "", library_template_body_inputs: str = "", library_template_button_inputs: str = "") -> str: + def whatsapp_create_whats_app_template( + account_id: str, + name: str, + category: str, + language: str, + components: str = "", + library_template_name: str = "", + library_template_body_inputs: str = "", + library_template_button_inputs: str = "", + ) -> str: """Create template - Args: - account_id: WhatsApp social account ID (required) - name: Template name (lowercase, letters/numbers/underscores, must start with a letter) (required) - category: Template category (required) - language: Template language code (e.g., en_US) (required) - components: Template components (header, body, footer, buttons). Required for custom templates, omit when using library_template_name. - library_template_name: Name of a pre-built template from Meta's template library (e.g., "appointment_reminder", - "auto_pay_reminder_1", "address_update"). When provided, the template is pre-approved - by Meta with no review wait. Omit `components` when using this field. - library_template_body_inputs: Optional body customizations for library templates. Available options depend on the - template (e.g., add_contact_number, add_learn_more_link, add_security_recommendation, - add_track_package_link, code_expiration_minutes). - library_template_button_inputs: Optional button customizations for library templates. Each item specifies button type - and configuration (e.g., URL, phone number, quick reply).""" - client = _get_client() - try: - response = client.whatsapp.create_whats_app_template(account_id=account_id, name=name, category=category, language=language, components=components, library_template_name=library_template_name, library_template_body_inputs=library_template_body_inputs, library_template_button_inputs=library_template_button_inputs) - return _format_response(response) - except Exception as e: - return f'Error: {e}' - + Args: + account_id: WhatsApp social account ID (required) + name: Template name (lowercase, letters/numbers/underscores, must start with a letter) (required) + category: Template category (required) + language: Template language code (e.g., en_US) (required) + components: Template components (header, body, footer, buttons). Required for custom templates, omit when using library_template_name. + library_template_name: Name of a pre-built template from Meta's template library (e.g., "appointment_reminder", + "auto_pay_reminder_1", "address_update"). When provided, the template is pre-approved + by Meta with no review wait. Omit `components` when using this field. + library_template_body_inputs: Optional body customizations for library templates. Available options depend on the + template (e.g., add_contact_number, add_learn_more_link, add_security_recommendation, + add_track_package_link, code_expiration_minutes). + library_template_button_inputs: Optional button customizations for library templates. Each item specifies button type + and configuration (e.g., URL, phone number, quick reply).""" + client = _get_client() + try: + response = client.whatsapp.create_whats_app_template( + account_id=account_id, + name=name, + category=category, + language=language, + components=components, + library_template_name=library_template_name, + library_template_body_inputs=library_template_body_inputs, + library_template_button_inputs=library_template_button_inputs, + ) + return _format_response(response) + except Exception as e: + return f"Error: {e}" @mcp.tool() def whatsapp_get_whats_app_template(template_name: str, account_id: str) -> str: @@ -3401,14 +4845,17 @@ def whatsapp_get_whats_app_template(template_name: str, account_id: str) -> str: account_id: WhatsApp social account ID (required)""" client = _get_client() try: - response = client.whatsapp.get_whats_app_template(template_name=template_name, account_id=account_id) + response = client.whatsapp.get_whats_app_template( + template_name=template_name, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_update_whats_app_template(template_name: str, account_id: str, components: str) -> str: + def whatsapp_update_whats_app_template( + template_name: str, account_id: str, components: str + ) -> str: """Update template Args: @@ -3417,11 +4864,14 @@ def whatsapp_update_whats_app_template(template_name: str, account_id: str, comp components: Updated template components (required)""" client = _get_client() try: - response = client.whatsapp.update_whats_app_template(template_name=template_name, account_id=account_id, components=components) + response = client.whatsapp.update_whats_app_template( + template_name=template_name, + account_id=account_id, + components=components, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_delete_whats_app_template(template_name: str, account_id: str) -> str: @@ -3432,14 +4882,17 @@ def whatsapp_delete_whats_app_template(template_name: str, account_id: str) -> s account_id: WhatsApp social account ID (required)""" client = _get_client() try: - response = client.whatsapp.delete_whats_app_template(template_name=template_name, account_id=account_id) + response = client.whatsapp.delete_whats_app_template( + template_name=template_name, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_get_whats_app_broadcasts(account_id: str, status: str = "", limit: int = 50, skip: int = 0) -> str: + def whatsapp_get_whats_app_broadcasts( + account_id: str, status: str = "", limit: int = 50, skip: int = 0 + ) -> str: """List broadcasts Args: @@ -3449,14 +4902,21 @@ def whatsapp_get_whats_app_broadcasts(account_id: str, status: str = "", limit: skip: Offset for pagination""" client = _get_client() try: - response = client.whatsapp.get_whats_app_broadcasts(account_id=account_id, status=status, limit=limit, skip=skip) + response = client.whatsapp.get_whats_app_broadcasts( + account_id=account_id, status=status, limit=limit, skip=skip + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_create_whats_app_broadcast(account_id: str, name: str, template: str, description: str = "", recipients: str = "") -> str: + def whatsapp_create_whats_app_broadcast( + account_id: str, + name: str, + template: str, + description: str = "", + recipients: str = "", + ) -> str: """Create broadcast Args: @@ -3467,11 +4927,16 @@ def whatsapp_create_whats_app_broadcast(account_id: str, name: str, template: st recipients: Initial recipients (optional)""" client = _get_client() try: - response = client.whatsapp.create_whats_app_broadcast(account_id=account_id, name=name, description=description, template=template, recipients=recipients) + response = client.whatsapp.create_whats_app_broadcast( + account_id=account_id, + name=name, + description=description, + template=template, + recipients=recipients, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_get_whats_app_broadcast(broadcast_id: str) -> str: @@ -3481,11 +4946,12 @@ def whatsapp_get_whats_app_broadcast(broadcast_id: str) -> str: broadcast_id: Broadcast ID (required)""" client = _get_client() try: - response = client.whatsapp.get_whats_app_broadcast(broadcast_id=broadcast_id) + response = client.whatsapp.get_whats_app_broadcast( + broadcast_id=broadcast_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_delete_whats_app_broadcast(broadcast_id: str) -> str: @@ -3495,11 +4961,12 @@ def whatsapp_delete_whats_app_broadcast(broadcast_id: str) -> str: broadcast_id: Broadcast ID (required)""" client = _get_client() try: - response = client.whatsapp.delete_whats_app_broadcast(broadcast_id=broadcast_id) + response = client.whatsapp.delete_whats_app_broadcast( + broadcast_id=broadcast_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_send_whats_app_broadcast(broadcast_id: str) -> str: @@ -3509,14 +4976,17 @@ def whatsapp_send_whats_app_broadcast(broadcast_id: str) -> str: broadcast_id: Broadcast ID (required)""" client = _get_client() try: - response = client.whatsapp.send_whats_app_broadcast(broadcast_id=broadcast_id) + response = client.whatsapp.send_whats_app_broadcast( + broadcast_id=broadcast_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_schedule_whats_app_broadcast(broadcast_id: str, scheduled_at: str) -> str: + def whatsapp_schedule_whats_app_broadcast( + broadcast_id: str, scheduled_at: str + ) -> str: """Schedule broadcast Args: @@ -3524,11 +4994,12 @@ def whatsapp_schedule_whats_app_broadcast(broadcast_id: str, scheduled_at: str) scheduled_at: ISO 8601 date-time for sending (must be in the future, max 30 days) (required)""" client = _get_client() try: - response = client.whatsapp.schedule_whats_app_broadcast(broadcast_id=broadcast_id, scheduled_at=scheduled_at) + response = client.whatsapp.schedule_whats_app_broadcast( + broadcast_id=broadcast_id, scheduled_at=scheduled_at + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_cancel_whats_app_broadcast_schedule(broadcast_id: str) -> str: @@ -3538,14 +5009,17 @@ def whatsapp_cancel_whats_app_broadcast_schedule(broadcast_id: str) -> str: broadcast_id: Broadcast ID (required)""" client = _get_client() try: - response = client.whatsapp.cancel_whats_app_broadcast_schedule(broadcast_id=broadcast_id) + response = client.whatsapp.cancel_whats_app_broadcast_schedule( + broadcast_id=broadcast_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_get_whats_app_broadcast_recipients(broadcast_id: str, status: str = "", limit: int = 100, skip: int = 0) -> str: + def whatsapp_get_whats_app_broadcast_recipients( + broadcast_id: str, status: str = "", limit: int = 100, skip: int = 0 + ) -> str: """List recipients Args: @@ -3555,14 +5029,17 @@ def whatsapp_get_whats_app_broadcast_recipients(broadcast_id: str, status: str = skip: Offset for pagination""" client = _get_client() try: - response = client.whatsapp.get_whats_app_broadcast_recipients(broadcast_id=broadcast_id, status=status, limit=limit, skip=skip) + response = client.whatsapp.get_whats_app_broadcast_recipients( + broadcast_id=broadcast_id, status=status, limit=limit, skip=skip + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_add_whats_app_broadcast_recipients(broadcast_id: str, recipients: str) -> str: + def whatsapp_add_whats_app_broadcast_recipients( + broadcast_id: str, recipients: str + ) -> str: """Add recipients Args: @@ -3570,14 +5047,17 @@ def whatsapp_add_whats_app_broadcast_recipients(broadcast_id: str, recipients: s recipients: Recipients to add (max 1000) (required)""" client = _get_client() try: - response = client.whatsapp.add_whats_app_broadcast_recipients(broadcast_id=broadcast_id, recipients=recipients) + response = client.whatsapp.add_whats_app_broadcast_recipients( + broadcast_id=broadcast_id, recipients=recipients + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_remove_whats_app_broadcast_recipients(broadcast_id: str, phones: str) -> str: + def whatsapp_remove_whats_app_broadcast_recipients( + broadcast_id: str, phones: str + ) -> str: """Remove recipients Args: @@ -3585,11 +5065,12 @@ def whatsapp_remove_whats_app_broadcast_recipients(broadcast_id: str, phones: st phones: Phone numbers to remove (required)""" client = _get_client() try: - response = client.whatsapp.remove_whats_app_broadcast_recipients(broadcast_id=broadcast_id, phones=phones) + response = client.whatsapp.remove_whats_app_broadcast_recipients( + broadcast_id=broadcast_id, phones=phones + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_get_whats_app_business_profile(account_id: str) -> str: @@ -3599,14 +5080,24 @@ def whatsapp_get_whats_app_business_profile(account_id: str) -> str: account_id: WhatsApp social account ID (required)""" client = _get_client() try: - response = client.whatsapp.get_whats_app_business_profile(account_id=account_id) + response = client.whatsapp.get_whats_app_business_profile( + account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_update_whats_app_business_profile(account_id: str, about: str = "", address: str = "", description: str = "", email: str = "", websites: str = "", vertical: str = "", profile_picture_handle: str = "") -> str: + def whatsapp_update_whats_app_business_profile( + account_id: str, + about: str = "", + address: str = "", + description: str = "", + email: str = "", + websites: str = "", + vertical: str = "", + profile_picture_handle: str = "", + ) -> str: """Update business profile Args: @@ -3620,11 +5111,19 @@ def whatsapp_update_whats_app_business_profile(account_id: str, about: str = "", profile_picture_handle: Handle from resumable upload for profile picture""" client = _get_client() try: - response = client.whatsapp.update_whats_app_business_profile(account_id=account_id, about=about, address=address, description=description, email=email, websites=websites, vertical=vertical, profile_picture_handle=profile_picture_handle) + response = client.whatsapp.update_whats_app_business_profile( + account_id=account_id, + about=about, + address=address, + description=description, + email=email, + websites=websites, + vertical=vertical, + profile_picture_handle=profile_picture_handle, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_upload_whats_app_profile_photo() -> str: @@ -3634,8 +5133,7 @@ def whatsapp_upload_whats_app_profile_photo() -> str: response = client.whatsapp.upload_whats_app_profile_photo() return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_get_whats_app_display_name(account_id: str) -> str: @@ -3648,11 +5146,12 @@ def whatsapp_get_whats_app_display_name(account_id: str) -> str: response = client.whatsapp.get_whats_app_display_name(account_id=account_id) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_update_whats_app_display_name(account_id: str, display_name: str) -> str: + def whatsapp_update_whats_app_display_name( + account_id: str, display_name: str + ) -> str: """Request display name change Args: @@ -3660,14 +5159,17 @@ def whatsapp_update_whats_app_display_name(account_id: str, display_name: str) - display_name: New display name (must follow WhatsApp naming guidelines) (required)""" client = _get_client() try: - response = client.whatsapp.update_whats_app_display_name(account_id=account_id, display_name=display_name) + response = client.whatsapp.update_whats_app_display_name( + account_id=account_id, display_name=display_name + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_list_whats_app_group_chats(account_id: str, limit: int = 25, after: str = "") -> str: + def whatsapp_list_whats_app_group_chats( + account_id: str, limit: int = 25, after: str = "" + ) -> str: """List active groups Args: @@ -3676,14 +5178,20 @@ def whatsapp_list_whats_app_group_chats(account_id: str, limit: int = 25, after: after: Pagination cursor""" client = _get_client() try: - response = client.whatsapp.list_whats_app_group_chats(account_id=account_id, limit=limit, after=after) + response = client.whatsapp.list_whats_app_group_chats( + account_id=account_id, limit=limit, after=after + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_create_whats_app_group_chat(account_id: str, subject: str, description: str = "", join_approval_mode: str = "") -> str: + def whatsapp_create_whats_app_group_chat( + account_id: str, + subject: str, + description: str = "", + join_approval_mode: str = "", + ) -> str: """Create group Args: @@ -3693,11 +5201,15 @@ def whatsapp_create_whats_app_group_chat(account_id: str, subject: str, descript join_approval_mode: Whether users need approval to join via invite link""" client = _get_client() try: - response = client.whatsapp.create_whats_app_group_chat(account_id=account_id, subject=subject, description=description, join_approval_mode=join_approval_mode) + response = client.whatsapp.create_whats_app_group_chat( + account_id=account_id, + subject=subject, + description=description, + join_approval_mode=join_approval_mode, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_get_whats_app_group_chat(group_id: str, account_id: str) -> str: @@ -3708,14 +5220,21 @@ def whatsapp_get_whats_app_group_chat(group_id: str, account_id: str) -> str: account_id: WhatsApp social account ID (required)""" client = _get_client() try: - response = client.whatsapp.get_whats_app_group_chat(group_id=group_id, account_id=account_id) + response = client.whatsapp.get_whats_app_group_chat( + group_id=group_id, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_update_whats_app_group_chat(group_id: str, account_id: str, subject: str = "", description: str = "", join_approval_mode: str = "") -> str: + def whatsapp_update_whats_app_group_chat( + group_id: str, + account_id: str, + subject: str = "", + description: str = "", + join_approval_mode: str = "", + ) -> str: """Update group settings Args: @@ -3726,11 +5245,16 @@ def whatsapp_update_whats_app_group_chat(group_id: str, account_id: str, subject join_approval_mode""" client = _get_client() try: - response = client.whatsapp.update_whats_app_group_chat(group_id=group_id, account_id=account_id, subject=subject, description=description, join_approval_mode=join_approval_mode) + response = client.whatsapp.update_whats_app_group_chat( + group_id=group_id, + account_id=account_id, + subject=subject, + description=description, + join_approval_mode=join_approval_mode, + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_delete_whats_app_group_chat(group_id: str, account_id: str) -> str: @@ -3741,14 +5265,17 @@ def whatsapp_delete_whats_app_group_chat(group_id: str, account_id: str) -> str: account_id: WhatsApp social account ID (required)""" client = _get_client() try: - response = client.whatsapp.delete_whats_app_group_chat(group_id=group_id, account_id=account_id) + response = client.whatsapp.delete_whats_app_group_chat( + group_id=group_id, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_add_whats_app_group_participants(group_id: str, account_id: str, phone_numbers: str) -> str: + def whatsapp_add_whats_app_group_participants( + group_id: str, account_id: str, phone_numbers: str + ) -> str: """Add participants Args: @@ -3757,14 +5284,17 @@ def whatsapp_add_whats_app_group_participants(group_id: str, account_id: str, ph phone_numbers: Phone numbers in E.164 format (max 8) (required)""" client = _get_client() try: - response = client.whatsapp.add_whats_app_group_participants(group_id=group_id, account_id=account_id, phone_numbers=phone_numbers) + response = client.whatsapp.add_whats_app_group_participants( + group_id=group_id, account_id=account_id, phone_numbers=phone_numbers + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_remove_whats_app_group_participants(group_id: str, account_id: str, phone_numbers: str) -> str: + def whatsapp_remove_whats_app_group_participants( + group_id: str, account_id: str, phone_numbers: str + ) -> str: """Remove participants Args: @@ -3773,14 +5303,17 @@ def whatsapp_remove_whats_app_group_participants(group_id: str, account_id: str, phone_numbers: Phone numbers to remove (required)""" client = _get_client() try: - response = client.whatsapp.remove_whats_app_group_participants(group_id=group_id, account_id=account_id, phone_numbers=phone_numbers) + response = client.whatsapp.remove_whats_app_group_participants( + group_id=group_id, account_id=account_id, phone_numbers=phone_numbers + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_create_whats_app_group_invite_link(group_id: str, account_id: str) -> str: + def whatsapp_create_whats_app_group_invite_link( + group_id: str, account_id: str + ) -> str: """Create invite link Args: @@ -3788,14 +5321,17 @@ def whatsapp_create_whats_app_group_invite_link(group_id: str, account_id: str) account_id: WhatsApp social account ID (required)""" client = _get_client() try: - response = client.whatsapp.create_whats_app_group_invite_link(group_id=group_id, account_id=account_id) + response = client.whatsapp.create_whats_app_group_invite_link( + group_id=group_id, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_list_whats_app_group_join_requests(group_id: str, account_id: str) -> str: + def whatsapp_list_whats_app_group_join_requests( + group_id: str, account_id: str + ) -> str: """List join requests Args: @@ -3803,14 +5339,17 @@ def whatsapp_list_whats_app_group_join_requests(group_id: str, account_id: str) account_id: WhatsApp social account ID (required)""" client = _get_client() try: - response = client.whatsapp.list_whats_app_group_join_requests(group_id=group_id, account_id=account_id) + response = client.whatsapp.list_whats_app_group_join_requests( + group_id=group_id, account_id=account_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_approve_whats_app_group_join_requests(group_id: str, account_id: str, phone_numbers: str) -> str: + def whatsapp_approve_whats_app_group_join_requests( + group_id: str, account_id: str, phone_numbers: str + ) -> str: """Approve join requests Args: @@ -3819,14 +5358,17 @@ def whatsapp_approve_whats_app_group_join_requests(group_id: str, account_id: st phone_numbers: Phone numbers to approve (required)""" client = _get_client() try: - response = client.whatsapp.approve_whats_app_group_join_requests(group_id=group_id, account_id=account_id, phone_numbers=phone_numbers) + response = client.whatsapp.approve_whats_app_group_join_requests( + group_id=group_id, account_id=account_id, phone_numbers=phone_numbers + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_reject_whats_app_group_join_requests(group_id: str, account_id: str, phone_numbers: str) -> str: + def whatsapp_reject_whats_app_group_join_requests( + group_id: str, account_id: str, phone_numbers: str + ) -> str: """Reject join requests Args: @@ -3835,16 +5377,19 @@ def whatsapp_reject_whats_app_group_join_requests(group_id: str, account_id: str phone_numbers: Phone numbers to reject (required)""" client = _get_client() try: - response = client.whatsapp.reject_whats_app_group_join_requests(group_id=group_id, account_id=account_id, phone_numbers=phone_numbers) + response = client.whatsapp.reject_whats_app_group_join_requests( + group_id=group_id, account_id=account_id, phone_numbers=phone_numbers + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" # WHATSAPP_PHONE_NUMBERS - @mcp.tool() - def whatsapp_phone_numbers_get_whats_app_phone_numbers(status: str = "", profile_id: str = "") -> str: + def whatsapp_phone_numbers_get_whats_app_phone_numbers( + status: str = "", profile_id: str = "" + ) -> str: """List phone numbers Args: @@ -3852,11 +5397,12 @@ def whatsapp_phone_numbers_get_whats_app_phone_numbers(status: str = "", profile profile_id: Filter by profile""" client = _get_client() try: - response = client.whatsapp_phone_numbers.get_whats_app_phone_numbers(status=status, profile_id=profile_id) + response = client.whatsapp_phone_numbers.get_whats_app_phone_numbers( + status=status, profile_id=profile_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_phone_numbers_purchase_whats_app_phone_number(profile_id: str) -> str: @@ -3866,11 +5412,12 @@ def whatsapp_phone_numbers_purchase_whats_app_phone_number(profile_id: str) -> s profile_id: Profile to associate the number with (required)""" client = _get_client() try: - response = client.whatsapp_phone_numbers.purchase_whats_app_phone_number(profile_id=profile_id) + response = client.whatsapp_phone_numbers.purchase_whats_app_phone_number( + profile_id=profile_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() def whatsapp_phone_numbers_get_whats_app_phone_number(phone_number_id: str) -> str: @@ -3880,21 +5427,26 @@ def whatsapp_phone_numbers_get_whats_app_phone_number(phone_number_id: str) -> s phone_number_id: Phone number record ID (required)""" client = _get_client() try: - response = client.whatsapp_phone_numbers.get_whats_app_phone_number(phone_number_id=phone_number_id) + response = client.whatsapp_phone_numbers.get_whats_app_phone_number( + phone_number_id=phone_number_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' - + return f"Error: {e}" @mcp.tool() - def whatsapp_phone_numbers_release_whats_app_phone_number(phone_number_id: str) -> str: + def whatsapp_phone_numbers_release_whats_app_phone_number( + phone_number_id: str, + ) -> str: """Release phone number Args: phone_number_id: Phone number record ID (required)""" client = _get_client() try: - response = client.whatsapp_phone_numbers.release_whats_app_phone_number(phone_number_id=phone_number_id) + response = client.whatsapp_phone_numbers.release_whats_app_phone_number( + phone_number_id=phone_number_id + ) return _format_response(response) except Exception as e: - return f'Error: {e}' + return f"Error: {e}" diff --git a/src/late/models/__init__.py b/src/late/models/__init__.py index 56bc4de..70f9266 100644 --- a/src/late/models/__init__.py +++ b/src/late/models/__init__.py @@ -11,41 +11,29 @@ # Import specific commonly used models for convenience from ._generated.models import ( AccountGetResponse, - # Accounts responses AccountsListResponse, AccountWithFollowerStats, - CaptionResponse, - DownloadFormat, - # Tools responses - DownloadResponse, ErrorResponse, FacebookPlatformData, FollowerStatsResponse, - HashtagCheckResponse, - HashtagInfo, InstagramPlatformData, LinkedInPlatformData, MediaItem, - # Media responses MediaUploadResponse, - # Base responses Pagination, PinterestPlatformData, PlatformTarget, - # Core models Post, PostCreateResponse, PostDeleteResponse, PostGetResponse, PostRetryResponse, - # Posts responses PostsListResponse, PostUpdateResponse, Profile, ProfileCreateResponse, ProfileDeleteResponse, ProfileGetResponse, - # Profiles responses ProfilesListResponse, ProfileUpdateResponse, QueueDeleteResponse, @@ -53,22 +41,16 @@ QueuePreviewResponse, QueueSchedule, QueueSlot, - # Queue responses QueueSlotsResponse, QueueUpdateResponse, SocialAccount, - # Enums Status, - # Platform-specific TikTokPlatformData, - TranscriptResponse, - TranscriptSegment, TwitterPlatformData, Type, UploadedFile, UploadTokenResponse, UploadTokenStatusResponse, - # Users responses User, UserGetResponse, UsersListResponse, @@ -76,13 +58,19 @@ YouTubePlatformData, ) -# SDK-specific models (not from OpenAPI) -from .responses import ( +# SDK-specific models (not from OpenAPI) — intentionally override generated stubs +from .responses import ( # type: ignore[assignment] + CaptionResponse, + DownloadFormat, + DownloadResponse, + HashtagCheckResponse, + HashtagInfo, MediaLargeUploadResponse, + TranscriptResponse, + TranscriptSegment, ) __all__ = [ - # Core models "Post", "MediaItem", "PlatformTarget", @@ -90,11 +78,9 @@ "SocialAccount", "QueueSlot", "QueueSchedule", - # Enums "Status", "Type", "Visibility", - # Platform-specific "TikTokPlatformData", "TwitterPlatformData", "InstagramPlatformData", @@ -102,40 +88,33 @@ "LinkedInPlatformData", "YouTubePlatformData", "PinterestPlatformData", - # Base responses "Pagination", "ErrorResponse", - # Posts responses "PostsListResponse", "PostGetResponse", "PostCreateResponse", "PostUpdateResponse", "PostDeleteResponse", "PostRetryResponse", - # Profiles responses "ProfilesListResponse", "ProfileGetResponse", "ProfileCreateResponse", "ProfileUpdateResponse", "ProfileDeleteResponse", - # Accounts responses "AccountsListResponse", "AccountGetResponse", "FollowerStatsResponse", "AccountWithFollowerStats", - # Media responses "MediaUploadResponse", "MediaLargeUploadResponse", "UploadedFile", "UploadTokenResponse", "UploadTokenStatusResponse", - # Queue responses "QueueSlotsResponse", "QueueUpdateResponse", "QueueDeleteResponse", "QueuePreviewResponse", "QueueNextSlotResponse", - # Tools responses "DownloadResponse", "DownloadFormat", "TranscriptResponse", @@ -143,7 +122,6 @@ "HashtagCheckResponse", "HashtagInfo", "CaptionResponse", - # Users responses "User", "UsersListResponse", "UserGetResponse", diff --git a/src/late/models/_generated/models.py b/src/late/models/_generated/models.py index f9a864e..35e2b5f 100644 --- a/src/late/models/_generated/models.py +++ b/src/late/models/_generated/models.py @@ -1,12 +1,12 @@ # generated by datamodel-codegen: # filename: openapi.yaml -# timestamp: 2026-03-26T15:28:29+00:00 +# timestamp: 2026-04-02T14:37:24+00:00 from __future__ import annotations from datetime import date as date_aliased from enum import Enum -from typing import Annotated, Any, Dict, List +from typing import Annotated, Any, Dict, List, Literal from pydantic import AnyUrl, AwareDatetime, BaseModel, Field, RootModel @@ -16,6 +16,138 @@ class ErrorResponse(BaseModel): details: Dict[str, Any] | None = None +class Type(Enum): + QUICK_REPLY = "quick_reply" + URL = "url" + PHONE_NUMBER = "phone_number" + OTP = "otp" + FLOW = "flow" + MPM = "mpm" + CATALOG = "catalog" + + +class OtpType(Enum): + """ + Required when type is otp + """ + + COPY_CODE = "copy_code" + ONE_TAP = "one_tap" + ZERO_TAP = "zero_tap" + + +class WhatsAppTemplateButton(BaseModel): + type: Type + text: str + url: AnyUrl | None = None + """ + Required when type is URL + """ + example: List[str] | None = None + """ + Example values for URL suffix variables + """ + phone_number: str | None = None + """ + Required when type is phone_number + """ + otp_type: OtpType | None = None + """ + Required when type is otp + """ + autofill_text: str | None = None + package_name: str | None = None + signature_hash: str | None = None + flow_id: str | None = None + flow_name: str | None = None + flow_json: str | None = None + flow_action: str | None = None + navigate_screen: str | None = None + + +class Type1(Enum): + HEADER = "header" + + +class Format(Enum): + TEXT = "text" + IMAGE = "image" + VIDEO = "video" + GIF = "gif" + DOCUMENT = "document" + LOCATION = "location" + + +class Example(BaseModel): + header_text: List[str] | None = None + """ + Sample values for header text variables + """ + header_handle: List[AnyUrl] | None = None + """ + When the header format is a media type (image, video, gif, document), provide a public URL here. Zernio will download and upload it to WhatsApp on your behalf, replacing it with the internal file handle before creating the template. + """ + + +class WhatsAppHeaderComponent(BaseModel): + type: Literal["header"] + format: Format + text: str | None = None + """ + Header text (may include {{1}} variable). Used when format is TEXT. + """ + example: Example | None = None + + +class Type2(Enum): + BODY = "body" + + +class Example1(BaseModel): + body_text: List[List[str]] | None = None + """ + Sample values for body variables (array of arrays) + """ + + +class WhatsAppBodyComponent(BaseModel): + type: Literal["body"] + text: str + """ + Body text with optional {{n}} variables + """ + add_security_recommendation: bool | None = None + """ + Add security recommendation text (authentication templates only) + """ + example: Example1 | None = None + + +class Type3(Enum): + FOOTER = "footer" + + +class WhatsAppFooterComponent(BaseModel): + type: Literal["footer"] + text: str | None = None + """ + Static footer text + """ + code_expiration_minutes: Annotated[int | None, Field(ge=1)] = None + """ + OTP code expiry in minutes (authentication templates only) + """ + + +class Type4(Enum): + BUTTONS = "buttons" + + +class WhatsAppButtonsComponent(BaseModel): + type: Literal["buttons"] + buttons: Annotated[List[WhatsAppTemplateButton], Field(min_length=1)] + + class FoodMenuLabel(BaseModel): displayName: str """ @@ -289,6 +421,7 @@ class Event(Enum): POST_PUBLISHED = "post.published" POST_FAILED = "post.failed" POST_PARTIAL = "post.partial" + POST_CANCELLED = "post.cancelled" POST_RECYCLED = "post.recycled" ACCOUNT_CONNECTED = "account.connected" ACCOUNT_DISCONNECTED = "account.disconnected" @@ -344,6 +477,7 @@ class Event1(Enum): POST_PUBLISHED = "post.published" POST_FAILED = "post.failed" POST_PARTIAL = "post.partial" + POST_CANCELLED = "post.cancelled" POST_RECYCLED = "post.recycled" ACCOUNT_CONNECTED = "account.connected" ACCOUNT_DISCONNECTED = "account.disconnected" @@ -406,23 +540,25 @@ class Event2(Enum): POST_PUBLISHED = "post.published" POST_FAILED = "post.failed" POST_PARTIAL = "post.partial" + POST_CANCELLED = "post.cancelled" POST_RECYCLED = "post.recycled" class Platform(BaseModel): - platform: str | None = None - status: str | None = None + platform: str + status: str + platformPostId: str | None = None publishedUrl: str | None = None error: str | None = None class Post(BaseModel): - id: str | None = None - content: str | None = None - status: str | None = None - scheduledFor: AwareDatetime | None = None + id: str + content: str + status: str + scheduledFor: AwareDatetime publishedAt: AwareDatetime | None = None - platforms: List[Platform] | None = None + platforms: List[Platform] class WebhookPayloadPost(BaseModel): @@ -430,9 +566,13 @@ class WebhookPayloadPost(BaseModel): Webhook payload for post events """ - event: Event2 | None = None - post: Post | None = None - timestamp: AwareDatetime | None = None + id: str + """ + Stable webhook event ID + """ + event: Event2 + post: Post + timestamp: AwareDatetime class Event3(Enum): @@ -440,16 +580,16 @@ class Event3(Enum): class Account(BaseModel): - accountId: str | None = None + accountId: str """ The account's unique identifier (same as used in /v1/accounts/{accountId}) """ - profileId: str | None = None + profileId: str """ The profile's unique identifier this account belongs to """ - platform: str | None = None - username: str | None = None + platform: str + username: str displayName: str | None = None @@ -458,9 +598,13 @@ class WebhookPayloadAccountConnected(BaseModel): Webhook payload for account connected events """ - event: Event3 | None = None - account: Account | None = None - timestamp: AwareDatetime | None = None + id: str + """ + Stable webhook event ID + """ + event: Event3 + account: Account + timestamp: AwareDatetime class Event4(Enum): @@ -477,22 +621,22 @@ class DisconnectionType(Enum): class Account1(BaseModel): - accountId: str | None = None + accountId: str """ The account's unique identifier (same as used in /v1/accounts/{accountId}) """ - profileId: str | None = None + profileId: str """ The profile's unique identifier this account belongs to """ - platform: str | None = None - username: str | None = None + platform: str + username: str displayName: str | None = None - disconnectionType: DisconnectionType | None = None + disconnectionType: DisconnectionType """ Whether the disconnection was intentional (user action) or unintentional (token expired/revoked) """ - reason: str | None = None + reason: str """ Human-readable reason for the disconnection """ @@ -503,9 +647,13 @@ class WebhookPayloadAccountDisconnected(BaseModel): Webhook payload for account disconnected events """ - event: Event4 | None = None - account: Account1 | None = None - timestamp: AwareDatetime | None = None + id: str + """ + Stable webhook event ID + """ + event: Event4 + account: Account1 + timestamp: AwareDatetime class Event5(Enum): @@ -523,7 +671,7 @@ class Platform1(Enum): class Author(BaseModel): - id: str | None = None + id: str """ Author's platform ID """ @@ -533,53 +681,53 @@ class Author(BaseModel): class Comment(BaseModel): - id: str | None = None + id: str """ Platform comment ID """ - postId: str | None = None + postId: str """ Internal post ID """ - platformPostId: str | None = None + platformPostId: str """ Platform's post ID """ - platform: Platform1 | None = None - text: str | None = None + platform: Platform1 + text: str """ Comment text content """ - author: Author | None = None - createdAt: AwareDatetime | None = None - isReply: bool | None = None + author: Author + createdAt: AwareDatetime + isReply: bool """ Whether this is a reply to another comment """ - parentCommentId: str | None = None + parentCommentId: Annotated[str | None, Field(...)] = None """ Parent comment ID if this is a reply """ class Post1(BaseModel): - id: str | None = None + id: str """ Internal post ID """ - platformPostId: str | None = None + platformPostId: str """ Platform's post ID """ class Account2(BaseModel): - id: str | None = None + id: str """ Social account ID """ - platform: str | None = None - username: str | None = None + platform: str + username: str class WebhookPayloadComment(BaseModel): @@ -587,11 +735,15 @@ class WebhookPayloadComment(BaseModel): Webhook payload for comment received events (Instagram, Facebook, Twitter/X, YouTube, LinkedIn, Bluesky, Reddit) """ - event: Event5 | None = None - comment: Comment | None = None - post: Post1 | None = None - account: Account2 | None = None - timestamp: AwareDatetime | None = None + id: str + """ + Stable webhook event ID + """ + event: Event5 + comment: Comment + post: Post1 + account: Account2 + timestamp: AwareDatetime class Event6(Enum): @@ -602,20 +754,20 @@ class Platform2(Enum): INSTAGRAM = "instagram" FACEBOOK = "facebook" TELEGRAM = "telegram" - BLUESKY = "bluesky" - REDDIT = "reddit" + WHATSAPP = "whatsapp" class Direction(Enum): INCOMING = "incoming" + OUTGOING = "outgoing" class Attachment(BaseModel): - type: str | None = None + type: str """ Attachment type (image, video, file, sticker, audio) """ - url: str | None = None + url: str """ Attachment URL (may expire for Meta platforms) """ @@ -649,7 +801,7 @@ class InstagramProfile(BaseModel): class Sender(BaseModel): - id: str | None = None + id: str name: str | None = None username: str | None = None picture: str | None = None @@ -660,39 +812,28 @@ class Sender(BaseModel): class Message(BaseModel): - id: str | None = None + id: str """ Internal message ID """ - conversationId: str | None = None + conversationId: str """ Internal conversation ID """ - platform: Platform2 | None = None - platformMessageId: str | None = None + platform: Platform2 + platformMessageId: str """ Platform's message ID """ - direction: Direction | None = None - text: str | None = None + direction: Direction + text: Annotated[str | None, Field(...)] = None """ Message text content """ - attachments: List[Attachment] | None = None - sender: Sender | None = None - sentAt: AwareDatetime | None = None - isRead: bool | None = None - - -class ParticipantVerifiedType(Enum): - """ - X/Twitter verified badge type. Only present for Twitter/X conversations. - """ - - BLUE = "blue" - GOVERNMENT = "government" - BUSINESS = "business" - NONE = "none" + attachments: List[Attachment] + sender: Sender + sentAt: AwareDatetime + isRead: bool class Status1(Enum): @@ -701,26 +842,22 @@ class Status1(Enum): class Conversation(BaseModel): - id: str | None = None - platformConversationId: str | None = None + id: str + platformConversationId: str participantId: str | None = None participantName: str | None = None participantUsername: str | None = None participantPicture: str | None = None - participantVerifiedType: ParticipantVerifiedType | None = None - """ - X/Twitter verified badge type. Only present for Twitter/X conversations. - """ - status: Status1 | None = None + status: Status1 class Account3(BaseModel): - id: str | None = None + id: str """ Social account ID """ - platform: str | None = None - username: str | None = None + platform: str + username: str displayName: str | None = None @@ -749,18 +886,43 @@ class Metadata(BaseModel): class WebhookPayloadMessage(BaseModel): """ - Webhook payload for message received events (DMs from Instagram, Facebook, Telegram, Bluesky, Reddit) + Webhook payload for message received events """ - event: Event6 | None = None - message: Message | None = None - conversation: Conversation | None = None - account: Account3 | None = None + id: str + """ + Stable webhook event ID + """ + event: Event6 + message: Message + conversation: Conversation + account: Account3 metadata: Metadata | None = None """ Interactive message metadata (present when message is a quick reply tap, postback button tap, or inline keyboard callback) """ - timestamp: AwareDatetime | None = None + timestamp: AwareDatetime + + +class Event7(Enum): + WEBHOOK_TEST = "webhook.test" + + +class WebhookPayloadTest(BaseModel): + """ + Webhook payload for test deliveries + """ + + id: str + """ + Stable webhook event ID + """ + event: Event7 + message: str + """ + Human-readable test message + """ + timestamp: AwareDatetime class PostId(BaseModel): @@ -1011,7 +1173,7 @@ class ConnectionLog(BaseModel): createdAt: AwareDatetime | None = None -class Type(Enum): +class Type5(Enum): IMAGE = "image" VIDEO = "video" GIF = "gif" @@ -1023,7 +1185,7 @@ class MediaItem(BaseModel): Media referenced in posts. URLs must be publicly reachable over HTTPS. Use POST /v1/media/presign for uploads up to 5GB. Zernio auto-compresses images and videos that exceed platform limits (videos over 200 MB may not be compressed). """ - type: Type | None = None + type: Type5 | None = None url: AnyUrl | None = None title: str | None = None """ @@ -1044,7 +1206,7 @@ class MediaItem(BaseModel): """ instagramThumbnail: AnyUrl | None = None """ - Optional custom cover image URL for Instagram Reels + Custom cover image URL for Instagram Reels. Can also be set via platformSpecificData.instagramThumbnail or platformSpecificData.reelCover. Resolution order: this field > platformSpecificData.instagramThumbnail > platformSpecificData.reelCover > platformSpecificData.thumbnailUrl (legacy). """ tiktokProcessed: bool | None = None """ @@ -1382,7 +1544,15 @@ class InstagramPlatformData(BaseModel): """ thumbOffset: Annotated[int | None, Field(examples=[5000], ge=0)] = None """ - Millisecond offset from video start for the Reel thumbnail. Ignored if a custom thumbnail URL is provided. Defaults to 0. + Millisecond offset from video start for the Reel cover frame. Ignored when instagramThumbnail or reelCover is provided. Defaults to 0. + """ + instagramThumbnail: AnyUrl | None = None + """ + Custom cover image URL for Instagram Reels (JPG or PNG, publicly accessible). Overrides thumbOffset when provided. Also accepted as reelCover (alias). + """ + reelCover: AnyUrl | None = None + """ + Alias for instagramThumbnail. If both are provided, instagramThumbnail takes priority. """ @@ -1471,9 +1641,13 @@ class YouTubePlatformData(BaseModel): """ YouTube video category ID. Defaults to 22 (People & Blogs). Common: 1 (Film), 2 (Autos), 10 (Music), 15 (Pets), 17 (Sports), 20 (Gaming), 23 (Comedy), 24 (Entertainment), 25 (News), 26 (Howto), 27 (Education), 28 (Science & Tech). """ + playlistId: str | None = None + """ + Optional YouTube playlist ID to add the video to after upload (e.g. 'PLxxxxxxxxxxxxx'). Use GET /v1/accounts/{id}/youtube-playlists to list available playlists. Works for both immediate and scheduled uploads. Quota cost: 50 YouTube API units per call. + """ -class Type1(Enum): +class Type6(Enum): """ Button action type: LEARN_MORE, BOOK, ORDER, SHOP, SIGN_UP, CALL """ @@ -1491,7 +1665,7 @@ class CallToAction(BaseModel): Optional call-to-action button displayed on the post """ - type: Type1 + type: Type6 """ Button action type: LEARN_MORE, BOOK, ORDER, SHOP, SIGN_UP, CALL """ @@ -1792,6 +1966,18 @@ class SocialAccount(BaseModel): """ Last time follower count was updated (only included if user has analytics add-on) """ + metadata: Dict[str, Any] | None = None + """ + Platform-specific metadata. Fields vary by platform. For WhatsApp accounts, includes: + - `qualityRating`: Phone number quality rating from Meta (`GREEN`, `YELLOW`, `RED`, or `UNKNOWN`) + - `nameStatus`: Display name review status (`APPROVED`, `PENDING_REVIEW`, `DECLINED`, or `NONE`). Messages cannot be sent until the display name is approved by Meta. + - `messagingLimitTier`: Maximum unique business-initiated conversations per 24h rolling window (`TIER_250`, `TIER_1K`, `TIER_10K`, `TIER_100K`, or `TIER_UNLIMITED`). Scales automatically as quality rating improves. + - `verifiedName`: Meta-verified business display name + - `displayPhoneNumber`: Formatted phone number (e.g., "+1 555-123-4567") + - `wabaId`: WhatsApp Business Account ID + - `phoneNumberId`: Meta phone number ID + + """ class AccountStats(BaseModel): @@ -2061,13 +2247,13 @@ class MediaType1(Enum): TEXT = "text" -class Type2(Enum): +class Type7(Enum): IMAGE = "image" VIDEO = "video" class MediaItem1(BaseModel): - type: Type2 | None = None + type: Type7 | None = None url: AnyUrl | None = None """ Direct URL to the media @@ -2122,7 +2308,7 @@ class MediaType2(Enum): class MediaItem2(BaseModel): - type: Type2 | None = None + type: Type7 | None = None url: AnyUrl | None = None """ Direct URL to the media @@ -2336,14 +2522,14 @@ class FollowerStatsResponse(BaseModel): aggregation: Aggregation2 | None = None -class Type4(Enum): +class Type9(Enum): IMAGE = "image" VIDEO = "video" DOCUMENT = "document" class UploadedFile(BaseModel): - type: Type4 | None = None + type: Type9 | None = None url: AnyUrl | None = None filename: str | None = None size: int | None = None @@ -2406,69 +2592,268 @@ class QueueNextSlotResponse(BaseModel): timezone: str | None = None -class DownloadFormat(BaseModel): - formatId: str | None = None - ext: str | None = None - resolution: str | None = None - filesize: int | None = None - quality: str | None = None +class User(BaseModel): + field_id: Annotated[str | None, Field(alias="_id")] = None + email: str | None = None + name: str | None = None + role: str | None = None + createdAt: AwareDatetime | None = None -class DownloadResponse(BaseModel): - url: AnyUrl | None = None - title: str | None = None - thumbnail: AnyUrl | None = None - duration: int | None = None - formats: List[DownloadFormat] | None = None +class UsersListResponse(BaseModel): + users: List[User] | None = None -class TranscriptSegment(BaseModel): - text: str | None = None - start: float | None = None - duration: float | None = None +class UserGetResponse(BaseModel): + user: User | None = None -class TranscriptResponse(BaseModel): - transcript: str | None = None - segments: List[TranscriptSegment] | None = None - language: str | None = None +class AdMetrics(BaseModel): + spend: float | None = None + impressions: int | None = None + reach: int | None = None + clicks: int | None = None + ctr: float | None = None + """ + Click-through rate (%) + """ + cpc: float | None = None + """ + Cost per click + """ + cpm: float | None = None + """ + Cost per 1000 impressions + """ + engagement: int | None = None + lastSyncedAt: AwareDatetime | None = None + """ + Present on individual ads only, not on campaign aggregations + """ + + +class Platform5(Enum): + FACEBOOK = "facebook" + INSTAGRAM = "instagram" + TIKTOK = "tiktok" + LINKEDIN = "linkedin" + PINTEREST = "pinterest" + GOOGLE = "google" + TWITTER = "twitter" class Status8(Enum): - SAFE = "safe" - BANNED = "banned" - RESTRICTED = "restricted" - UNKNOWN = "unknown" + ACTIVE = "active" + PAUSED = "paused" + PENDING_REVIEW = "pending_review" + REJECTED = "rejected" + COMPLETED = "completed" + CANCELLED = "cancelled" + ERROR = "error" -class HashtagInfo(BaseModel): - hashtag: str | None = None - status: Status8 | None = None - postCount: int | None = None +class AdType(Enum): + BOOST = "boost" + STANDALONE = "standalone" + +class Goal(Enum): + ENGAGEMENT = "engagement" + TRAFFIC = "traffic" + AWARENESS = "awareness" + VIDEO_VIEWS = "video_views" -class HashtagCheckResponse(BaseModel): - hashtags: List[HashtagInfo] | None = None +class Type10(Enum): + DAILY = "daily" + LIFETIME = "lifetime" -class CaptionResponse(BaseModel): - caption: str | None = None +class Budget(BaseModel): + amount: float | None = None + type: Type10 | None = None -class User(BaseModel): + +class Creative(BaseModel): + """ + Platform-specific creative data. Fields vary by platform. + """ + + thumbnailUrl: str | None = None + """ + Primary thumbnail/image URL + """ + imageUrl: str | None = None + """ + Alternative image URL + """ + mediaUrls: List[str] | None = None + """ + All media URLs for this ad (carousel images, multiple assets). Populated for Meta (carousel child_attachments), Google Ads (responsive display marketing_images), and LinkedIn (multi-image posts). + """ + body: str | None = None + """ + Ad copy/text + """ + googleHeadline: str | None = None + """ + Google Ads headline + """ + googleDescription: str | None = None + """ + Google Ads description + """ + linkUrl: str | None = None + """ + Destination URL + """ + pinterestImageUrl: str | None = None + pinterestTitle: str | None = None + pinterestDescription: str | None = None + + +class Schedule(BaseModel): + startDate: AwareDatetime | None = None + endDate: AwareDatetime | None = None + + +class Ad(BaseModel): field_id: Annotated[str | None, Field(alias="_id")] = None - email: str | None = None name: str | None = None - role: str | None = None + platform: Platform5 | None = None + status: Status8 | None = None + adType: AdType | None = None + goal: Goal | None = None + isExternal: bool | None = None + """ + True for ads synced from platform ad managers + """ + budget: Budget | None = None + metrics: AdMetrics | None = None + platformAdId: str | None = None + platformAdAccountId: str | None = None + platformCampaignId: str | None = None + platformAdSetId: str | None = None + campaignName: str | None = None + adSetName: str | None = None + creative: Creative | None = None + """ + Platform-specific creative data. Fields vary by platform. + """ + targeting: Dict[str, Any] | None = None + schedule: Schedule | None = None + rejectionReason: str | None = None createdAt: AwareDatetime | None = None + updatedAt: AwareDatetime | None = None -class UsersListResponse(BaseModel): - users: List[User] | None = None +class Status9(Enum): + """ + Derived from child ad statuses + """ + ACTIVE = "active" + PAUSED = "paused" + PENDING_REVIEW = "pending_review" + REJECTED = "rejected" + COMPLETED = "completed" + CANCELLED = "cancelled" + ERROR = "error" -class UserGetResponse(BaseModel): - user: User | None = None + +class Budget1(BaseModel): + amount: float | None = None + type: Type10 | None = None + + +class AdTreeAdSet(BaseModel): + """ + Ad set (or ad group/line item depending on platform) with rolled-up metrics and child ads + """ + + platformAdSetId: str | None = None + adSetName: str | None = None + status: Status9 | None = None + """ + Derived from child ad statuses + """ + adCount: int | None = None + budget: Budget1 | None = None + metrics: AdMetrics | None = None + ads: List[Ad] | None = None + """ + Individual ads within this ad set (capped at 100). Returns a subset of Ad fields from the aggregation (core fields like _id, name, platform, status, budget, metrics, creative, goal are included; targeting and schedule may be absent). + """ + + +class Budget2(BaseModel): + amount: float | None = None + type: Type10 | None = None + + +class AdTreeCampaign(BaseModel): + """ + Campaign with nested ad sets and rolled-up metrics + """ + + platformCampaignId: str | None = None + platform: Platform5 | None = None + campaignName: str | None = None + status: Status9 | None = None + """ + Derived from child ad statuses + """ + adCount: int | None = None + """ + Total ads across all ad sets + """ + adSetCount: int | None = None + budget: Budget2 | None = None + metrics: AdMetrics | None = None + platformAdAccountId: str | None = None + accountId: str | None = None + profileId: str | None = None + adSets: List[AdTreeAdSet] | None = None + + +class Budget3(BaseModel): + amount: float | None = None + type: Type10 | None = None + + +class AdCampaign(BaseModel): + platformCampaignId: str | None = None + platform: Platform5 | None = None + campaignName: str | None = None + status: Status9 | None = None + """ + Derived from child ad statuses + """ + adCount: int | None = None + budget: Budget3 | None = None + metrics: AdMetrics | None = None + platformAdAccountId: str | None = None + accountId: str | None = None + profileId: str | None = None + earliestAd: AwareDatetime | None = None + latestAd: AwareDatetime | None = None + + +class WhatsAppTemplateComponent( + RootModel[ + WhatsAppHeaderComponent + | WhatsAppBodyComponent + | WhatsAppFooterComponent + | WhatsAppButtonsComponent + ] +): + root: Annotated[ + WhatsAppHeaderComponent + | WhatsAppBodyComponent + | WhatsAppFooterComponent + | WhatsAppButtonsComponent, + Field(discriminator="type"), + ] class PlatformTarget(BaseModel): diff --git a/src/late/models/responses.py b/src/late/models/responses.py index f1ef4e2..ba36442 100644 --- a/src/late/models/responses.py +++ b/src/late/models/responses.py @@ -7,7 +7,63 @@ from __future__ import annotations -from pydantic import BaseModel +from enum import Enum + +from pydantic import AnyUrl, BaseModel + +# --------------------------------------------------------------------------- +# Tools endpoint models (endpoints removed from OpenAPI spec but still +# functional for existing customers) +# --------------------------------------------------------------------------- + + +class DownloadFormat(BaseModel): + formatId: str | None = None + ext: str | None = None + resolution: str | None = None + filesize: int | None = None + quality: str | None = None + + +class DownloadResponse(BaseModel): + url: AnyUrl | None = None + title: str | None = None + thumbnail: AnyUrl | None = None + duration: int | None = None + formats: list[DownloadFormat] | None = None + + +class TranscriptSegment(BaseModel): + text: str | None = None + start: float | None = None + duration: float | None = None + + +class TranscriptResponse(BaseModel): + transcript: str | None = None + segments: list[TranscriptSegment] | None = None + language: str | None = None + + +class HashtagStatus(str, Enum): + SAFE = "safe" + BANNED = "banned" + RESTRICTED = "restricted" + UNKNOWN = "unknown" + + +class HashtagInfo(BaseModel): + hashtag: str | None = None + status: HashtagStatus | None = None + postCount: int | None = None + + +class HashtagCheckResponse(BaseModel): + hashtags: list[HashtagInfo] | None = None + + +class CaptionResponse(BaseModel): + caption: str | None = None class MediaLargeUploadResponse(BaseModel): diff --git a/src/late/resources/__init__.py b/src/late/resources/__init__.py index 4d1c92f..148dab3 100644 --- a/src/late/resources/__init__.py +++ b/src/late/resources/__init__.py @@ -1,21 +1,35 @@ -"""Late API resources. +""" +API resources (auto-updated by generate_resources.py). -Manual resources with Pydantic validation are used for the main resources. -Auto-generated resources are used for additional endpoints. +Manual resources with Pydantic validation live in this directory. +Auto-generated resources live in _generated/. +This file is regenerated automatically; do not edit by hand. """ from ._generated.account_groups import AccountGroupsResource +from ._generated.account_settings import AccountSettingsResource +from ._generated.ad_audiences import AdAudiencesResource +from ._generated.ad_campaigns import AdCampaignsResource +from ._generated.ads import AdsResource from ._generated.api_keys import ApiKeysResource +from ._generated.broadcasts import BroadcastsResource +from ._generated.comment_automations import CommentAutomationsResource from ._generated.comments import CommentsResource from ._generated.connect import ConnectResource -from ._generated.inbox import InboxResource +from ._generated.contacts import ContactsResource +from ._generated.custom_fields import CustomFieldsResource from ._generated.invites import InvitesResource from ._generated.logs import LogsResource from ._generated.messages import MessagesResource from ._generated.reddit import RedditResource from ._generated.reviews import ReviewsResource +from ._generated.sequences import SequencesResource +from ._generated.twitter_engagement import TwitterEngagementResource from ._generated.usage import UsageResource +from ._generated.validate import ValidateResource from ._generated.webhooks import WebhooksResource +from ._generated.whatsapp import WhatsappResource +from ._generated.whatsapp_phone_numbers import WhatsappPhoneNumbersResource from .accounts import AccountsResource from .analytics import AnalyticsResource from .media import MediaResource @@ -27,12 +41,19 @@ __all__ = [ "AccountGroupsResource", + "AccountSettingsResource", "AccountsResource", + "AdAudiencesResource", + "AdCampaignsResource", + "AdsResource", "AnalyticsResource", "ApiKeysResource", + "BroadcastsResource", + "CommentAutomationsResource", "CommentsResource", "ConnectResource", - "InboxResource", + "ContactsResource", + "CustomFieldsResource", "InvitesResource", "LogsResource", "MediaResource", @@ -42,8 +63,13 @@ "QueueResource", "RedditResource", "ReviewsResource", + "SequencesResource", "ToolsResource", + "TwitterEngagementResource", "UsageResource", "UsersResource", + "ValidateResource", "WebhooksResource", + "WhatsappResource", + "WhatsappPhoneNumbersResource", ] diff --git a/src/late/resources/_generated/__init__.py b/src/late/resources/_generated/__init__.py index 8b93c80..e5f7c09 100644 --- a/src/late/resources/_generated/__init__.py +++ b/src/late/resources/_generated/__init__.py @@ -5,6 +5,9 @@ from .account_groups import AccountGroupsResource from .account_settings import AccountSettingsResource from .accounts import AccountsResource +from .ad_audiences import AdAudiencesResource +from .ad_campaigns import AdCampaignsResource +from .ads import AdsResource from .analytics import AnalyticsResource from .api_keys import ApiKeysResource from .broadcasts import BroadcastsResource @@ -23,7 +26,6 @@ from .reddit import RedditResource from .reviews import ReviewsResource from .sequences import SequencesResource -from .tools import ToolsResource from .twitter_engagement import TwitterEngagementResource from .usage import UsageResource from .users import UsersResource @@ -36,6 +38,9 @@ "AccountGroupsResource", "AccountSettingsResource", "AccountsResource", + "AdAudiencesResource", + "AdCampaignsResource", + "AdsResource", "AnalyticsResource", "ApiKeysResource", "BroadcastsResource", @@ -54,7 +59,6 @@ "RedditResource", "ReviewsResource", "SequencesResource", - "ToolsResource", "TwitterEngagementResource", "UsageResource", "UsersResource", diff --git a/src/late/resources/_generated/account_groups.py b/src/late/resources/_generated/account_groups.py index 6af52f6..fbaa202 100644 --- a/src/late/resources/_generated/account_groups.py +++ b/src/late/resources/_generated/account_groups.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -60,13 +56,7 @@ def create_account_group(self, name: str, account_ids: list[str]) -> dict[str, A ) return self._client._post("/v1/account-groups", data=payload) - def update_account_group( - self, - group_id: str, - *, - name: str | None = None, - account_ids: list[str] | None = None, - ) -> dict[str, Any]: + def update_account_group(self, group_id: str, *, name: str | None = None, account_ids: list[str] | None = None) -> dict[str, Any]: """Update group""" payload = self._build_payload( name=name, @@ -82,9 +72,7 @@ async def alist_account_groups(self) -> dict[str, Any]: """List groups (async)""" return await self._client._aget("/v1/account-groups") - async def acreate_account_group( - self, name: str, account_ids: list[str] - ) -> dict[str, Any]: + async def acreate_account_group(self, name: str, account_ids: list[str]) -> dict[str, Any]: """Create group (async)""" payload = self._build_payload( name=name, @@ -92,13 +80,7 @@ async def acreate_account_group( ) return await self._client._apost("/v1/account-groups", data=payload) - async def aupdate_account_group( - self, - group_id: str, - *, - name: str | None = None, - account_ids: list[str] | None = None, - ) -> dict[str, Any]: + async def aupdate_account_group(self, group_id: str, *, name: str | None = None, account_ids: list[str] | None = None) -> dict[str, Any]: """Update group (async)""" payload = self._build_payload( name=name, diff --git a/src/late/resources/_generated/account_settings.py b/src/late/resources/_generated/account_settings.py index 7db320a..304aecb 100644 --- a/src/late/resources/_generated/account_settings.py +++ b/src/late/resources/_generated/account_settings.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -52,16 +48,12 @@ def get_messenger_menu(self, account_id: str) -> dict[str, Any]: """Get FB persistent menu""" return self._client._get(f"/v1/accounts/{account_id}/messenger-menu") - def set_messenger_menu( - self, account_id: str, persistent_menu: list[dict[str, Any]] - ) -> dict[str, Any]: + def set_messenger_menu(self, account_id: str, persistent_menu: list[dict[str, Any]]) -> dict[str, Any]: """Set FB persistent menu""" payload = self._build_payload( persistent_menu=persistent_menu, ) - return self._client._put( - f"/v1/accounts/{account_id}/messenger-menu", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/messenger-menu", data=payload) def delete_messenger_menu(self, account_id: str) -> dict[str, Any]: """Delete FB persistent menu""" @@ -71,16 +63,12 @@ def get_instagram_ice_breakers(self, account_id: str) -> dict[str, Any]: """Get IG ice breakers""" return self._client._get(f"/v1/accounts/{account_id}/instagram-ice-breakers") - def set_instagram_ice_breakers( - self, account_id: str, ice_breakers: list[dict[str, Any]] - ) -> dict[str, Any]: + def set_instagram_ice_breakers(self, account_id: str, ice_breakers: list[dict[str, Any]]) -> dict[str, Any]: """Set IG ice breakers""" payload = self._build_payload( ice_breakers=ice_breakers, ) - return self._client._put( - f"/v1/accounts/{account_id}/instagram-ice-breakers", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/instagram-ice-breakers", data=payload) def delete_instagram_ice_breakers(self, account_id: str) -> dict[str, Any]: """Delete IG ice breakers""" @@ -90,16 +78,12 @@ def get_telegram_commands(self, account_id: str) -> dict[str, Any]: """Get TG bot commands""" return self._client._get(f"/v1/accounts/{account_id}/telegram-commands") - def set_telegram_commands( - self, account_id: str, commands: list[dict[str, Any]] - ) -> dict[str, Any]: + def set_telegram_commands(self, account_id: str, commands: list[dict[str, Any]]) -> dict[str, Any]: """Set TG bot commands""" payload = self._build_payload( commands=commands, ) - return self._client._put( - f"/v1/accounts/{account_id}/telegram-commands", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/telegram-commands", data=payload) def delete_telegram_commands(self, account_id: str) -> dict[str, Any]: """Delete TG bot commands""" @@ -109,16 +93,12 @@ async def aget_messenger_menu(self, account_id: str) -> dict[str, Any]: """Get FB persistent menu (async)""" return await self._client._aget(f"/v1/accounts/{account_id}/messenger-menu") - async def aset_messenger_menu( - self, account_id: str, persistent_menu: list[dict[str, Any]] - ) -> dict[str, Any]: + async def aset_messenger_menu(self, account_id: str, persistent_menu: list[dict[str, Any]]) -> dict[str, Any]: """Set FB persistent menu (async)""" payload = self._build_payload( persistent_menu=persistent_menu, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/messenger-menu", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/messenger-menu", data=payload) async def adelete_messenger_menu(self, account_id: str) -> dict[str, Any]: """Delete FB persistent menu (async)""" @@ -126,44 +106,30 @@ async def adelete_messenger_menu(self, account_id: str) -> dict[str, Any]: async def aget_instagram_ice_breakers(self, account_id: str) -> dict[str, Any]: """Get IG ice breakers (async)""" - return await self._client._aget( - f"/v1/accounts/{account_id}/instagram-ice-breakers" - ) + return await self._client._aget(f"/v1/accounts/{account_id}/instagram-ice-breakers") - async def aset_instagram_ice_breakers( - self, account_id: str, ice_breakers: list[dict[str, Any]] - ) -> dict[str, Any]: + async def aset_instagram_ice_breakers(self, account_id: str, ice_breakers: list[dict[str, Any]]) -> dict[str, Any]: """Set IG ice breakers (async)""" payload = self._build_payload( ice_breakers=ice_breakers, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/instagram-ice-breakers", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/instagram-ice-breakers", data=payload) async def adelete_instagram_ice_breakers(self, account_id: str) -> dict[str, Any]: """Delete IG ice breakers (async)""" - return await self._client._adelete( - f"/v1/accounts/{account_id}/instagram-ice-breakers" - ) + return await self._client._adelete(f"/v1/accounts/{account_id}/instagram-ice-breakers") async def aget_telegram_commands(self, account_id: str) -> dict[str, Any]: """Get TG bot commands (async)""" return await self._client._aget(f"/v1/accounts/{account_id}/telegram-commands") - async def aset_telegram_commands( - self, account_id: str, commands: list[dict[str, Any]] - ) -> dict[str, Any]: + async def aset_telegram_commands(self, account_id: str, commands: list[dict[str, Any]]) -> dict[str, Any]: """Set TG bot commands (async)""" payload = self._build_payload( commands=commands, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/telegram-commands", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/telegram-commands", data=payload) async def adelete_telegram_commands(self, account_id: str) -> dict[str, Any]: """Delete TG bot commands (async)""" - return await self._client._adelete( - f"/v1/accounts/{account_id}/telegram-commands" - ) + return await self._client._adelete(f"/v1/accounts/{account_id}/telegram-commands") diff --git a/src/late/resources/_generated/accounts.py b/src/late/resources/_generated/accounts.py index 44d7272..dc57422 100644 --- a/src/late/resources/_generated/accounts.py +++ b/src/late/resources/_generated/accounts.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,30 +44,18 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_accounts( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - include_over_limit: bool | None = False, - ) -> dict[str, Any]: + def list_accounts(self, *, profile_id: str | None = None, platform: str | None = None, include_over_limit: bool | None = False, page: int | None = None, limit: int | None = None) -> dict[str, Any]: """List accounts""" params = self._build_params( profile_id=profile_id, platform=platform, include_over_limit=include_over_limit, + page=page, + limit=limit, ) return self._client._get("/v1/accounts", params=params) - def get_follower_stats( - self, - *, - account_ids: str | None = None, - profile_id: str | None = None, - from_date: str | None = None, - to_date: str | None = None, - granularity: str | None = "daily", - ) -> dict[str, Any]: + def get_follower_stats(self, *, account_ids: str | None = None, profile_id: str | None = None, from_date: str | None = None, to_date: str | None = None, granularity: str | None = "daily") -> dict[str, Any]: """Get follower stats""" params = self._build_params( account_ids=account_ids, @@ -82,13 +66,7 @@ def get_follower_stats( ) return self._client._get("/v1/accounts/follower-stats", params=params) - def update_account( - self, - account_id: str, - *, - username: str | None = None, - display_name: str | None = None, - ) -> dict[str, Any]: + def update_account(self, account_id: str, *, username: str | None = None, display_name: str | None = None) -> dict[str, Any]: """Update account""" payload = self._build_payload( username=username, @@ -100,13 +78,7 @@ def delete_account(self, account_id: str) -> dict[str, Any]: """Disconnect account""" return self._client._delete(f"/v1/accounts/{account_id}") - def get_all_accounts_health( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - status: str | None = None, - ) -> dict[str, Any]: + def get_all_accounts_health(self, *, profile_id: str | None = None, platform: str | None = None, status: str | None = None) -> dict[str, Any]: """Check accounts health""" params = self._build_params( profile_id=profile_id, @@ -119,93 +91,46 @@ def get_account_health(self, account_id: str) -> dict[str, Any]: """Check account health""" return self._client._get(f"/v1/accounts/{account_id}/health") - def get_tik_tok_creator_info( - self, account_id: str, *, media_type: str | None = "video" - ) -> dict[str, Any]: + def get_tik_tok_creator_info(self, account_id: str, *, media_type: str | None = "video") -> dict[str, Any]: """Get TikTok creator info""" params = self._build_params( media_type=media_type, ) - return self._client._get( - f"/v1/accounts/{account_id}/tiktok/creator-info", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/tiktok/creator-info", params=params) - def get_google_business_reviews( - self, - account_id: str, - *, - location_id: str | None = None, - page_size: int | None = 50, - page_token: str | None = None, - ) -> dict[str, Any]: + def get_google_business_reviews(self, account_id: str, *, location_id: str | None = None, page_size: int | None = 50, page_token: str | None = None) -> dict[str, Any]: """Get reviews""" params = self._build_params( location_id=location_id, page_size=page_size, page_token=page_token, ) - return self._client._get( - f"/v1/accounts/{account_id}/gmb-reviews", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/gmb-reviews", params=params) - def get_google_business_food_menus( - self, account_id: str, *, location_id: str | None = None - ) -> dict[str, Any]: + def get_google_business_food_menus(self, account_id: str, *, location_id: str | None = None) -> dict[str, Any]: """Get food menus""" params = self._build_params( location_id=location_id, ) - return self._client._get( - f"/v1/accounts/{account_id}/gmb-food-menus", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/gmb-food-menus", params=params) - def update_google_business_food_menus( - self, - account_id: str, - menus: list[Any], - *, - location_id: str | None = None, - update_mask: str | None = None, - ) -> dict[str, Any]: + def update_google_business_food_menus(self, account_id: str, menus: list[Any], *, location_id: str | None = None, update_mask: str | None = None) -> dict[str, Any]: """Update food menus""" payload = self._build_payload( menus=menus, update_mask=update_mask, ) - return self._client._put( - f"/v1/accounts/{account_id}/gmb-food-menus", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/gmb-food-menus", data=payload) - def get_google_business_location_details( - self, - account_id: str, - *, - location_id: str | None = None, - read_mask: str | None = None, - ) -> dict[str, Any]: + def get_google_business_location_details(self, account_id: str, *, location_id: str | None = None, read_mask: str | None = None) -> dict[str, Any]: """Get location details""" params = self._build_params( location_id=location_id, read_mask=read_mask, ) - return self._client._get( - f"/v1/accounts/{account_id}/gmb-location-details", params=params - ) - - def update_google_business_location_details( - self, - account_id: str, - update_mask: str, - *, - location_id: str | None = None, - regular_hours: dict[str, Any] | None = None, - special_hours: dict[str, Any] | None = None, - profile: dict[str, Any] | None = None, - website_uri: str | None = None, - phone_numbers: dict[str, Any] | None = None, - categories: dict[str, Any] | None = None, - service_items: list[dict[str, Any]] | None = None, - ) -> dict[str, Any]: + return self._client._get(f"/v1/accounts/{account_id}/gmb-location-details", params=params) + + def update_google_business_location_details(self, account_id: str, update_mask: str, *, location_id: str | None = None, regular_hours: dict[str, Any] | None = None, special_hours: dict[str, Any] | None = None, profile: dict[str, Any] | None = None, website_uri: str | None = None, phone_numbers: dict[str, Any] | None = None, categories: dict[str, Any] | None = None, service_items: list[dict[str, Any]] | None = None) -> dict[str, Any]: """Update location details""" payload = self._build_payload( update_mask=update_mask, @@ -217,18 +142,9 @@ def update_google_business_location_details( categories=categories, service_items=service_items, ) - return self._client._put( - f"/v1/accounts/{account_id}/gmb-location-details", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/gmb-location-details", data=payload) - def list_google_business_media( - self, - account_id: str, - *, - location_id: str | None = None, - page_size: int | None = 100, - page_token: str | None = None, - ) -> dict[str, Any]: + def list_google_business_media(self, account_id: str, *, location_id: str | None = None, page_size: int | None = 100, page_token: str | None = None) -> dict[str, Any]: """List media""" params = self._build_params( location_id=location_id, @@ -237,16 +153,7 @@ def list_google_business_media( ) return self._client._get(f"/v1/accounts/{account_id}/gmb-media", params=params) - def create_google_business_media( - self, - account_id: str, - source_url: str, - *, - location_id: str | None = None, - media_format: str | None = "PHOTO", - description: str | None = None, - category: str | None = None, - ) -> dict[str, Any]: + def create_google_business_media(self, account_id: str, source_url: str, *, location_id: str | None = None, media_format: str | None = "PHOTO", description: str | None = None, category: str | None = None) -> dict[str, Any]: """Upload photo""" payload = self._build_payload( source_url=source_url, @@ -256,129 +163,74 @@ def create_google_business_media( ) return self._client._post(f"/v1/accounts/{account_id}/gmb-media", data=payload) - def delete_google_business_media( - self, account_id: str, media_id: str, *, location_id: str | None = None - ) -> dict[str, Any]: + def delete_google_business_media(self, account_id: str, media_id: str, *, location_id: str | None = None) -> dict[str, Any]: """Delete photo""" params = self._build_params( location_id=location_id, media_id=media_id, ) - return self._client._delete( - f"/v1/accounts/{account_id}/gmb-media", params=params - ) + return self._client._delete(f"/v1/accounts/{account_id}/gmb-media", params=params) - def get_google_business_attributes( - self, account_id: str, *, location_id: str | None = None - ) -> dict[str, Any]: + def get_google_business_attributes(self, account_id: str, *, location_id: str | None = None) -> dict[str, Any]: """Get attributes""" params = self._build_params( location_id=location_id, ) - return self._client._get( - f"/v1/accounts/{account_id}/gmb-attributes", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/gmb-attributes", params=params) - def update_google_business_attributes( - self, - account_id: str, - attributes: list[dict[str, Any]], - attribute_mask: str, - *, - location_id: str | None = None, - ) -> dict[str, Any]: + def update_google_business_attributes(self, account_id: str, attributes: list[dict[str, Any]], attribute_mask: str, *, location_id: str | None = None) -> dict[str, Any]: """Update attributes""" payload = self._build_payload( attributes=attributes, attribute_mask=attribute_mask, ) - return self._client._put( - f"/v1/accounts/{account_id}/gmb-attributes", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/gmb-attributes", data=payload) - def list_google_business_place_actions( - self, - account_id: str, - *, - location_id: str | None = None, - page_size: int | None = 100, - page_token: str | None = None, - ) -> dict[str, Any]: + def list_google_business_place_actions(self, account_id: str, *, location_id: str | None = None, page_size: int | None = 100, page_token: str | None = None) -> dict[str, Any]: """List action links""" params = self._build_params( location_id=location_id, page_size=page_size, page_token=page_token, ) - return self._client._get( - f"/v1/accounts/{account_id}/gmb-place-actions", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/gmb-place-actions", params=params) - def create_google_business_place_action( - self, - account_id: str, - uri: str, - place_action_type: str, - *, - location_id: str | None = None, - ) -> dict[str, Any]: + def create_google_business_place_action(self, account_id: str, uri: str, place_action_type: str, *, location_id: str | None = None) -> dict[str, Any]: """Create action link""" payload = self._build_payload( uri=uri, place_action_type=place_action_type, ) - return self._client._post( - f"/v1/accounts/{account_id}/gmb-place-actions", data=payload - ) + return self._client._post(f"/v1/accounts/{account_id}/gmb-place-actions", data=payload) - def delete_google_business_place_action( - self, account_id: str, name: str, *, location_id: str | None = None - ) -> dict[str, Any]: + def delete_google_business_place_action(self, account_id: str, name: str, *, location_id: str | None = None) -> dict[str, Any]: """Delete action link""" params = self._build_params( location_id=location_id, name=name, ) - return self._client._delete( - f"/v1/accounts/{account_id}/gmb-place-actions", params=params - ) + return self._client._delete(f"/v1/accounts/{account_id}/gmb-place-actions", params=params) - def get_linked_in_mentions( - self, account_id: str, url: str, *, display_name: str | None = None - ) -> dict[str, Any]: + def get_linked_in_mentions(self, account_id: str, url: str, *, display_name: str | None = None) -> dict[str, Any]: """Resolve LinkedIn mention""" params = self._build_params( url=url, display_name=display_name, ) - return self._client._get( - f"/v1/accounts/{account_id}/linkedin-mentions", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/linkedin-mentions", params=params) - async def alist_accounts( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - include_over_limit: bool | None = False, - ) -> dict[str, Any]: + async def alist_accounts(self, *, profile_id: str | None = None, platform: str | None = None, include_over_limit: bool | None = False, page: int | None = None, limit: int | None = None) -> dict[str, Any]: """List accounts (async)""" params = self._build_params( profile_id=profile_id, platform=platform, include_over_limit=include_over_limit, + page=page, + limit=limit, ) return await self._client._aget("/v1/accounts", params=params) - async def aget_follower_stats( - self, - *, - account_ids: str | None = None, - profile_id: str | None = None, - from_date: str | None = None, - to_date: str | None = None, - granularity: str | None = "daily", - ) -> dict[str, Any]: + async def aget_follower_stats(self, *, account_ids: str | None = None, profile_id: str | None = None, from_date: str | None = None, to_date: str | None = None, granularity: str | None = "daily") -> dict[str, Any]: """Get follower stats (async)""" params = self._build_params( account_ids=account_ids, @@ -389,13 +241,7 @@ async def aget_follower_stats( ) return await self._client._aget("/v1/accounts/follower-stats", params=params) - async def aupdate_account( - self, - account_id: str, - *, - username: str | None = None, - display_name: str | None = None, - ) -> dict[str, Any]: + async def aupdate_account(self, account_id: str, *, username: str | None = None, display_name: str | None = None) -> dict[str, Any]: """Update account (async)""" payload = self._build_payload( username=username, @@ -407,13 +253,7 @@ async def adelete_account(self, account_id: str) -> dict[str, Any]: """Disconnect account (async)""" return await self._client._adelete(f"/v1/accounts/{account_id}") - async def aget_all_accounts_health( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - status: str | None = None, - ) -> dict[str, Any]: + async def aget_all_accounts_health(self, *, profile_id: str | None = None, platform: str | None = None, status: str | None = None) -> dict[str, Any]: """Check accounts health (async)""" params = self._build_params( profile_id=profile_id, @@ -426,93 +266,46 @@ async def aget_account_health(self, account_id: str) -> dict[str, Any]: """Check account health (async)""" return await self._client._aget(f"/v1/accounts/{account_id}/health") - async def aget_tik_tok_creator_info( - self, account_id: str, *, media_type: str | None = "video" - ) -> dict[str, Any]: + async def aget_tik_tok_creator_info(self, account_id: str, *, media_type: str | None = "video") -> dict[str, Any]: """Get TikTok creator info (async)""" params = self._build_params( media_type=media_type, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/tiktok/creator-info", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/tiktok/creator-info", params=params) - async def aget_google_business_reviews( - self, - account_id: str, - *, - location_id: str | None = None, - page_size: int | None = 50, - page_token: str | None = None, - ) -> dict[str, Any]: + async def aget_google_business_reviews(self, account_id: str, *, location_id: str | None = None, page_size: int | None = 50, page_token: str | None = None) -> dict[str, Any]: """Get reviews (async)""" params = self._build_params( location_id=location_id, page_size=page_size, page_token=page_token, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/gmb-reviews", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/gmb-reviews", params=params) - async def aget_google_business_food_menus( - self, account_id: str, *, location_id: str | None = None - ) -> dict[str, Any]: + async def aget_google_business_food_menus(self, account_id: str, *, location_id: str | None = None) -> dict[str, Any]: """Get food menus (async)""" params = self._build_params( location_id=location_id, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/gmb-food-menus", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/gmb-food-menus", params=params) - async def aupdate_google_business_food_menus( - self, - account_id: str, - menus: list[Any], - *, - location_id: str | None = None, - update_mask: str | None = None, - ) -> dict[str, Any]: + async def aupdate_google_business_food_menus(self, account_id: str, menus: list[Any], *, location_id: str | None = None, update_mask: str | None = None) -> dict[str, Any]: """Update food menus (async)""" payload = self._build_payload( menus=menus, update_mask=update_mask, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/gmb-food-menus", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/gmb-food-menus", data=payload) - async def aget_google_business_location_details( - self, - account_id: str, - *, - location_id: str | None = None, - read_mask: str | None = None, - ) -> dict[str, Any]: + async def aget_google_business_location_details(self, account_id: str, *, location_id: str | None = None, read_mask: str | None = None) -> dict[str, Any]: """Get location details (async)""" params = self._build_params( location_id=location_id, read_mask=read_mask, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/gmb-location-details", params=params - ) - - async def aupdate_google_business_location_details( - self, - account_id: str, - update_mask: str, - *, - location_id: str | None = None, - regular_hours: dict[str, Any] | None = None, - special_hours: dict[str, Any] | None = None, - profile: dict[str, Any] | None = None, - website_uri: str | None = None, - phone_numbers: dict[str, Any] | None = None, - categories: dict[str, Any] | None = None, - service_items: list[dict[str, Any]] | None = None, - ) -> dict[str, Any]: + return await self._client._aget(f"/v1/accounts/{account_id}/gmb-location-details", params=params) + + async def aupdate_google_business_location_details(self, account_id: str, update_mask: str, *, location_id: str | None = None, regular_hours: dict[str, Any] | None = None, special_hours: dict[str, Any] | None = None, profile: dict[str, Any] | None = None, website_uri: str | None = None, phone_numbers: dict[str, Any] | None = None, categories: dict[str, Any] | None = None, service_items: list[dict[str, Any]] | None = None) -> dict[str, Any]: """Update location details (async)""" payload = self._build_payload( update_mask=update_mask, @@ -524,38 +317,18 @@ async def aupdate_google_business_location_details( categories=categories, service_items=service_items, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/gmb-location-details", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/gmb-location-details", data=payload) - async def alist_google_business_media( - self, - account_id: str, - *, - location_id: str | None = None, - page_size: int | None = 100, - page_token: str | None = None, - ) -> dict[str, Any]: + async def alist_google_business_media(self, account_id: str, *, location_id: str | None = None, page_size: int | None = 100, page_token: str | None = None) -> dict[str, Any]: """List media (async)""" params = self._build_params( location_id=location_id, page_size=page_size, page_token=page_token, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/gmb-media", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/gmb-media", params=params) - async def acreate_google_business_media( - self, - account_id: str, - source_url: str, - *, - location_id: str | None = None, - media_format: str | None = "PHOTO", - description: str | None = None, - category: str | None = None, - ) -> dict[str, Any]: + async def acreate_google_business_media(self, account_id: str, source_url: str, *, location_id: str | None = None, media_format: str | None = "PHOTO", description: str | None = None, category: str | None = None) -> dict[str, Any]: """Upload photo (async)""" payload = self._build_payload( source_url=source_url, @@ -563,105 +336,60 @@ async def acreate_google_business_media( description=description, category=category, ) - return await self._client._apost( - f"/v1/accounts/{account_id}/gmb-media", data=payload - ) + return await self._client._apost(f"/v1/accounts/{account_id}/gmb-media", data=payload) - async def adelete_google_business_media( - self, account_id: str, media_id: str, *, location_id: str | None = None - ) -> dict[str, Any]: + async def adelete_google_business_media(self, account_id: str, media_id: str, *, location_id: str | None = None) -> dict[str, Any]: """Delete photo (async)""" params = self._build_params( location_id=location_id, media_id=media_id, ) - return await self._client._adelete( - f"/v1/accounts/{account_id}/gmb-media", params=params - ) + return await self._client._adelete(f"/v1/accounts/{account_id}/gmb-media", params=params) - async def aget_google_business_attributes( - self, account_id: str, *, location_id: str | None = None - ) -> dict[str, Any]: + async def aget_google_business_attributes(self, account_id: str, *, location_id: str | None = None) -> dict[str, Any]: """Get attributes (async)""" params = self._build_params( location_id=location_id, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/gmb-attributes", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/gmb-attributes", params=params) - async def aupdate_google_business_attributes( - self, - account_id: str, - attributes: list[dict[str, Any]], - attribute_mask: str, - *, - location_id: str | None = None, - ) -> dict[str, Any]: + async def aupdate_google_business_attributes(self, account_id: str, attributes: list[dict[str, Any]], attribute_mask: str, *, location_id: str | None = None) -> dict[str, Any]: """Update attributes (async)""" payload = self._build_payload( attributes=attributes, attribute_mask=attribute_mask, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/gmb-attributes", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/gmb-attributes", data=payload) - async def alist_google_business_place_actions( - self, - account_id: str, - *, - location_id: str | None = None, - page_size: int | None = 100, - page_token: str | None = None, - ) -> dict[str, Any]: + async def alist_google_business_place_actions(self, account_id: str, *, location_id: str | None = None, page_size: int | None = 100, page_token: str | None = None) -> dict[str, Any]: """List action links (async)""" params = self._build_params( location_id=location_id, page_size=page_size, page_token=page_token, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/gmb-place-actions", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/gmb-place-actions", params=params) - async def acreate_google_business_place_action( - self, - account_id: str, - uri: str, - place_action_type: str, - *, - location_id: str | None = None, - ) -> dict[str, Any]: + async def acreate_google_business_place_action(self, account_id: str, uri: str, place_action_type: str, *, location_id: str | None = None) -> dict[str, Any]: """Create action link (async)""" payload = self._build_payload( uri=uri, place_action_type=place_action_type, ) - return await self._client._apost( - f"/v1/accounts/{account_id}/gmb-place-actions", data=payload - ) + return await self._client._apost(f"/v1/accounts/{account_id}/gmb-place-actions", data=payload) - async def adelete_google_business_place_action( - self, account_id: str, name: str, *, location_id: str | None = None - ) -> dict[str, Any]: + async def adelete_google_business_place_action(self, account_id: str, name: str, *, location_id: str | None = None) -> dict[str, Any]: """Delete action link (async)""" params = self._build_params( location_id=location_id, name=name, ) - return await self._client._adelete( - f"/v1/accounts/{account_id}/gmb-place-actions", params=params - ) + return await self._client._adelete(f"/v1/accounts/{account_id}/gmb-place-actions", params=params) - async def aget_linked_in_mentions( - self, account_id: str, url: str, *, display_name: str | None = None - ) -> dict[str, Any]: + async def aget_linked_in_mentions(self, account_id: str, url: str, *, display_name: str | None = None) -> dict[str, Any]: """Resolve LinkedIn mention (async)""" params = self._build_params( url=url, display_name=display_name, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/linkedin-mentions", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/linkedin-mentions", params=params) diff --git a/src/late/resources/_generated/ad_audiences.py b/src/late/resources/_generated/ad_audiences.py new file mode 100644 index 0000000..f09229a --- /dev/null +++ b/src/late/resources/_generated/ad_audiences.py @@ -0,0 +1,129 @@ +""" +Auto-generated ad_audiences resource. + +DO NOT EDIT THIS FILE MANUALLY. +Run `python scripts/generate_resources.py` to regenerate. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from ..client.base import BaseClient + + +class AdAudiencesResource: + """ + ad_audiences operations. + """ + + def __init__(self, client: BaseClient) -> None: + self._client = client + + def _build_params(self, **kwargs: Any) -> dict[str, Any]: + """Build query parameters, filtering None values.""" + def to_camel(s: str) -> str: + parts = s.split("_") + return parts[0] + "".join(p.title() for p in parts[1:]) + return {to_camel(k): v for k, v in kwargs.items() if v is not None} + + def _build_payload(self, **kwargs: Any) -> dict[str, Any]: + """Build request payload, filtering None values.""" + from datetime import datetime + def to_camel(s: str) -> str: + parts = s.split("_") + return parts[0] + "".join(p.title() for p in parts[1:]) + result: dict[str, Any] = {} + for k, v in kwargs.items(): + if v is None: + continue + if isinstance(v, datetime): + result[to_camel(k)] = v.isoformat() + else: + result[to_camel(k)] = v + return result + + def list_ad_audiences(self, account_id: str, ad_account_id: str, *, platform: str | None = None) -> dict[str, Any]: + """List custom audiences""" + params = self._build_params( + account_id=account_id, + ad_account_id=ad_account_id, + platform=platform, + ) + return self._client._get("/v1/ads/audiences", params=params) + + def create_ad_audience(self, account_id: str, ad_account_id: str, name: str, type: str, *, description: str | None = None, pixel_id: str | None = None, retention_days: int | None = None, source_audience_id: str | None = None, country: str | None = None, ratio: float | None = None, rule: dict[str, Any] | None = None, customer_file_source: str | None = None) -> dict[str, Any]: + """Create a custom audience (Meta only)""" + payload = self._build_payload( + account_id=account_id, + ad_account_id=ad_account_id, + name=name, + description=description, + type=type, + pixel_id=pixel_id, + retention_days=retention_days, + source_audience_id=source_audience_id, + country=country, + ratio=ratio, + rule=rule, + customer_file_source=customer_file_source, + ) + return self._client._post("/v1/ads/audiences", data=payload) + + def get_ad_audience(self, audience_id: str) -> dict[str, Any]: + """Get audience details""" + return self._client._get(f"/v1/ads/audiences/{audience_id}") + + def delete_ad_audience(self, audience_id: str) -> dict[str, Any]: + """Delete a custom audience""" + return self._client._delete(f"/v1/ads/audiences/{audience_id}") + + def add_users_to_ad_audience(self, audience_id: str, users: list[dict[str, Any]]) -> dict[str, Any]: + """Add users to a customer list audience""" + payload = self._build_payload( + users=users, + ) + return self._client._post(f"/v1/ads/audiences/{audience_id}/users", data=payload) + + async def alist_ad_audiences(self, account_id: str, ad_account_id: str, *, platform: str | None = None) -> dict[str, Any]: + """List custom audiences (async)""" + params = self._build_params( + account_id=account_id, + ad_account_id=ad_account_id, + platform=platform, + ) + return await self._client._aget("/v1/ads/audiences", params=params) + + async def acreate_ad_audience(self, account_id: str, ad_account_id: str, name: str, type: str, *, description: str | None = None, pixel_id: str | None = None, retention_days: int | None = None, source_audience_id: str | None = None, country: str | None = None, ratio: float | None = None, rule: dict[str, Any] | None = None, customer_file_source: str | None = None) -> dict[str, Any]: + """Create a custom audience (Meta only) (async)""" + payload = self._build_payload( + account_id=account_id, + ad_account_id=ad_account_id, + name=name, + description=description, + type=type, + pixel_id=pixel_id, + retention_days=retention_days, + source_audience_id=source_audience_id, + country=country, + ratio=ratio, + rule=rule, + customer_file_source=customer_file_source, + ) + return await self._client._apost("/v1/ads/audiences", data=payload) + + async def aget_ad_audience(self, audience_id: str) -> dict[str, Any]: + """Get audience details (async)""" + return await self._client._aget(f"/v1/ads/audiences/{audience_id}") + + async def adelete_ad_audience(self, audience_id: str) -> dict[str, Any]: + """Delete a custom audience (async)""" + return await self._client._adelete(f"/v1/ads/audiences/{audience_id}") + + async def aadd_users_to_ad_audience(self, audience_id: str, users: list[dict[str, Any]]) -> dict[str, Any]: + """Add users to a customer list audience (async)""" + payload = self._build_payload( + users=users, + ) + return await self._client._apost(f"/v1/ads/audiences/{audience_id}/users", data=payload) diff --git a/src/late/resources/_generated/ad_campaigns.py b/src/late/resources/_generated/ad_campaigns.py new file mode 100644 index 0000000..faabe1b --- /dev/null +++ b/src/late/resources/_generated/ad_campaigns.py @@ -0,0 +1,117 @@ +""" +Auto-generated ad_campaigns resource. + +DO NOT EDIT THIS FILE MANUALLY. +Run `python scripts/generate_resources.py` to regenerate. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from ..client.base import BaseClient + + +class AdCampaignsResource: + """ + ad_campaigns operations. + """ + + def __init__(self, client: BaseClient) -> None: + self._client = client + + def _build_params(self, **kwargs: Any) -> dict[str, Any]: + """Build query parameters, filtering None values.""" + def to_camel(s: str) -> str: + parts = s.split("_") + return parts[0] + "".join(p.title() for p in parts[1:]) + return {to_camel(k): v for k, v in kwargs.items() if v is not None} + + def _build_payload(self, **kwargs: Any) -> dict[str, Any]: + """Build request payload, filtering None values.""" + from datetime import datetime + def to_camel(s: str) -> str: + parts = s.split("_") + return parts[0] + "".join(p.title() for p in parts[1:]) + result: dict[str, Any] = {} + for k, v in kwargs.items(): + if v is None: + continue + if isinstance(v, datetime): + result[to_camel(k)] = v.isoformat() + else: + result[to_camel(k)] = v + return result + + def list_ad_campaigns(self, *, page: int | None = 1, limit: int | None = 20, source: str | None = "zernio", platform: str | None = None, status: str | None = None, ad_account_id: str | None = None, account_id: str | None = None, profile_id: str | None = None) -> dict[str, Any]: + """List campaigns with aggregate metrics""" + params = self._build_params( + page=page, + limit=limit, + source=source, + platform=platform, + status=status, + ad_account_id=ad_account_id, + account_id=account_id, + profile_id=profile_id, + ) + return self._client._get("/v1/ads/campaigns", params=params) + + def update_ad_campaign_status(self, campaign_id: str, status: str, platform: str) -> dict[str, Any]: + """Pause or resume a campaign""" + payload = self._build_payload( + status=status, + platform=platform, + ) + return self._client._put(f"/v1/ads/campaigns/{campaign_id}/status", data=payload) + + def get_ad_tree(self, *, page: int | None = 1, limit: int | None = 20, source: str | None = "zernio", platform: str | None = None, status: str | None = None, ad_account_id: str | None = None, account_id: str | None = None, profile_id: str | None = None) -> dict[str, Any]: + """Get nested campaign/ad-set/ad tree""" + params = self._build_params( + page=page, + limit=limit, + source=source, + platform=platform, + status=status, + ad_account_id=ad_account_id, + account_id=account_id, + profile_id=profile_id, + ) + return self._client._get("/v1/ads/tree", params=params) + + async def alist_ad_campaigns(self, *, page: int | None = 1, limit: int | None = 20, source: str | None = "zernio", platform: str | None = None, status: str | None = None, ad_account_id: str | None = None, account_id: str | None = None, profile_id: str | None = None) -> dict[str, Any]: + """List campaigns with aggregate metrics (async)""" + params = self._build_params( + page=page, + limit=limit, + source=source, + platform=platform, + status=status, + ad_account_id=ad_account_id, + account_id=account_id, + profile_id=profile_id, + ) + return await self._client._aget("/v1/ads/campaigns", params=params) + + async def aupdate_ad_campaign_status(self, campaign_id: str, status: str, platform: str) -> dict[str, Any]: + """Pause or resume a campaign (async)""" + payload = self._build_payload( + status=status, + platform=platform, + ) + return await self._client._aput(f"/v1/ads/campaigns/{campaign_id}/status", data=payload) + + async def aget_ad_tree(self, *, page: int | None = 1, limit: int | None = 20, source: str | None = "zernio", platform: str | None = None, status: str | None = None, ad_account_id: str | None = None, account_id: str | None = None, profile_id: str | None = None) -> dict[str, Any]: + """Get nested campaign/ad-set/ad tree (async)""" + params = self._build_params( + page=page, + limit=limit, + source=source, + platform=platform, + status=status, + ad_account_id=ad_account_id, + account_id=account_id, + profile_id=profile_id, + ) + return await self._client._aget("/v1/ads/tree", params=params) diff --git a/src/late/resources/_generated/ads.py b/src/late/resources/_generated/ads.py new file mode 100644 index 0000000..168df54 --- /dev/null +++ b/src/late/resources/_generated/ads.py @@ -0,0 +1,263 @@ +""" +Auto-generated ads resource. + +DO NOT EDIT THIS FILE MANUALLY. +Run `python scripts/generate_resources.py` to regenerate. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from datetime import datetime + + from ..client.base import BaseClient + + +class AdsResource: + """ + ads operations. + """ + + def __init__(self, client: BaseClient) -> None: + self._client = client + + def _build_params(self, **kwargs: Any) -> dict[str, Any]: + """Build query parameters, filtering None values.""" + def to_camel(s: str) -> str: + parts = s.split("_") + return parts[0] + "".join(p.title() for p in parts[1:]) + return {to_camel(k): v for k, v in kwargs.items() if v is not None} + + def _build_payload(self, **kwargs: Any) -> dict[str, Any]: + """Build request payload, filtering None values.""" + from datetime import datetime + def to_camel(s: str) -> str: + parts = s.split("_") + return parts[0] + "".join(p.title() for p in parts[1:]) + result: dict[str, Any] = {} + for k, v in kwargs.items(): + if v is None: + continue + if isinstance(v, datetime): + result[to_camel(k)] = v.isoformat() + else: + result[to_camel(k)] = v + return result + + def list_ads(self, *, page: int | None = 1, limit: int | None = 50, source: str | None = "zernio", status: str | None = None, platform: str | None = None, account_id: str | None = None, profile_id: str | None = None, campaign_id: str | None = None) -> dict[str, Any]: + """List ads""" + params = self._build_params( + page=page, + limit=limit, + source=source, + status=status, + platform=platform, + account_id=account_id, + profile_id=profile_id, + campaign_id=campaign_id, + ) + return self._client._get("/v1/ads", params=params) + + def get_ad(self, ad_id: str) -> dict[str, Any]: + """Get ad details""" + return self._client._get(f"/v1/ads/{ad_id}") + + def update_ad(self, ad_id: str, *, status: str | None = None, budget: dict[str, Any] | None = None, targeting: dict[str, Any] | None = None, name: str | None = None) -> dict[str, Any]: + """Update ad (pause/resume, budget, targeting, name)""" + payload = self._build_payload( + status=status, + budget=budget, + targeting=targeting, + name=name, + ) + return self._client._put(f"/v1/ads/{ad_id}", data=payload) + + def delete_ad(self, ad_id: str) -> dict[str, Any]: + """Cancel an ad""" + return self._client._delete(f"/v1/ads/{ad_id}") + + def get_ad_analytics(self, ad_id: str, *, breakdowns: str | None = None) -> dict[str, Any]: + """Get ad analytics with daily breakdown""" + params = self._build_params( + breakdowns=breakdowns, + ) + return self._client._get(f"/v1/ads/{ad_id}/analytics", params=params) + + def list_ad_accounts(self, account_id: str) -> dict[str, Any]: + """List ad accounts for a social account""" + params = self._build_params( + account_id=account_id, + ) + return self._client._get("/v1/ads/accounts", params=params) + + def boost_post(self, account_id: str, ad_account_id: str, name: str, goal: str, budget: dict[str, Any], *, post_id: str | None = None, platform_post_id: str | None = None, currency: str | None = None, schedule: dict[str, Any] | None = None, targeting: dict[str, Any] | None = None, bid_amount: float | None = None, tracking: dict[str, Any] | None = None, special_ad_categories: list[str] | None = None) -> dict[str, Any]: + """Boost an existing post as a paid ad""" + payload = self._build_payload( + post_id=post_id, + platform_post_id=platform_post_id, + account_id=account_id, + ad_account_id=ad_account_id, + name=name, + goal=goal, + budget=budget, + currency=currency, + schedule=schedule, + targeting=targeting, + bid_amount=bid_amount, + tracking=tracking, + special_ad_categories=special_ad_categories, + ) + return self._client._post("/v1/ads/boost", data=payload) + + def create_standalone_ad(self, account_id: str, ad_account_id: str, name: str, goal: str, budget_amount: float, budget_type: str, body: str, *, currency: str | None = None, headline: str | None = None, long_headline: str | None = None, call_to_action: str | None = None, link_url: str | None = None, image_url: str | None = None, business_name: str | None = None, board_id: str | None = None, countries: list[str] | None = None, age_min: int | None = None, age_max: int | None = None, interests: list[str] | None = None, end_date: datetime | str | None = None, audience_id: str | None = None, campaign_type: str | None = "display", keywords: list[str] | None = None, additional_headlines: list[str] | None = None, additional_descriptions: list[str] | None = None) -> dict[str, Any]: + """Create a standalone ad with custom creative""" + payload = self._build_payload( + account_id=account_id, + ad_account_id=ad_account_id, + name=name, + goal=goal, + budget_amount=budget_amount, + budget_type=budget_type, + currency=currency, + headline=headline, + long_headline=long_headline, + body=body, + call_to_action=call_to_action, + link_url=link_url, + image_url=image_url, + business_name=business_name, + board_id=board_id, + countries=countries, + age_min=age_min, + age_max=age_max, + interests=interests, + end_date=end_date, + audience_id=audience_id, + campaign_type=campaign_type, + keywords=keywords, + additional_headlines=additional_headlines, + additional_descriptions=additional_descriptions, + ) + return self._client._post("/v1/ads/create", data=payload) + + def sync_external_ads(self) -> dict[str, Any]: + """Sync external ads from platform ad managers""" + return self._client._post("/v1/ads/sync") + + def search_ad_interests(self, q: str, account_id: str) -> dict[str, Any]: + """Search targeting interests""" + params = self._build_params( + q=q, + account_id=account_id, + ) + return self._client._get("/v1/ads/interests", params=params) + + async def alist_ads(self, *, page: int | None = 1, limit: int | None = 50, source: str | None = "zernio", status: str | None = None, platform: str | None = None, account_id: str | None = None, profile_id: str | None = None, campaign_id: str | None = None) -> dict[str, Any]: + """List ads (async)""" + params = self._build_params( + page=page, + limit=limit, + source=source, + status=status, + platform=platform, + account_id=account_id, + profile_id=profile_id, + campaign_id=campaign_id, + ) + return await self._client._aget("/v1/ads", params=params) + + async def aget_ad(self, ad_id: str) -> dict[str, Any]: + """Get ad details (async)""" + return await self._client._aget(f"/v1/ads/{ad_id}") + + async def aupdate_ad(self, ad_id: str, *, status: str | None = None, budget: dict[str, Any] | None = None, targeting: dict[str, Any] | None = None, name: str | None = None) -> dict[str, Any]: + """Update ad (pause/resume, budget, targeting, name) (async)""" + payload = self._build_payload( + status=status, + budget=budget, + targeting=targeting, + name=name, + ) + return await self._client._aput(f"/v1/ads/{ad_id}", data=payload) + + async def adelete_ad(self, ad_id: str) -> dict[str, Any]: + """Cancel an ad (async)""" + return await self._client._adelete(f"/v1/ads/{ad_id}") + + async def aget_ad_analytics(self, ad_id: str, *, breakdowns: str | None = None) -> dict[str, Any]: + """Get ad analytics with daily breakdown (async)""" + params = self._build_params( + breakdowns=breakdowns, + ) + return await self._client._aget(f"/v1/ads/{ad_id}/analytics", params=params) + + async def alist_ad_accounts(self, account_id: str) -> dict[str, Any]: + """List ad accounts for a social account (async)""" + params = self._build_params( + account_id=account_id, + ) + return await self._client._aget("/v1/ads/accounts", params=params) + + async def aboost_post(self, account_id: str, ad_account_id: str, name: str, goal: str, budget: dict[str, Any], *, post_id: str | None = None, platform_post_id: str | None = None, currency: str | None = None, schedule: dict[str, Any] | None = None, targeting: dict[str, Any] | None = None, bid_amount: float | None = None, tracking: dict[str, Any] | None = None, special_ad_categories: list[str] | None = None) -> dict[str, Any]: + """Boost an existing post as a paid ad (async)""" + payload = self._build_payload( + post_id=post_id, + platform_post_id=platform_post_id, + account_id=account_id, + ad_account_id=ad_account_id, + name=name, + goal=goal, + budget=budget, + currency=currency, + schedule=schedule, + targeting=targeting, + bid_amount=bid_amount, + tracking=tracking, + special_ad_categories=special_ad_categories, + ) + return await self._client._apost("/v1/ads/boost", data=payload) + + async def acreate_standalone_ad(self, account_id: str, ad_account_id: str, name: str, goal: str, budget_amount: float, budget_type: str, body: str, *, currency: str | None = None, headline: str | None = None, long_headline: str | None = None, call_to_action: str | None = None, link_url: str | None = None, image_url: str | None = None, business_name: str | None = None, board_id: str | None = None, countries: list[str] | None = None, age_min: int | None = None, age_max: int | None = None, interests: list[str] | None = None, end_date: datetime | str | None = None, audience_id: str | None = None, campaign_type: str | None = "display", keywords: list[str] | None = None, additional_headlines: list[str] | None = None, additional_descriptions: list[str] | None = None) -> dict[str, Any]: + """Create a standalone ad with custom creative (async)""" + payload = self._build_payload( + account_id=account_id, + ad_account_id=ad_account_id, + name=name, + goal=goal, + budget_amount=budget_amount, + budget_type=budget_type, + currency=currency, + headline=headline, + long_headline=long_headline, + body=body, + call_to_action=call_to_action, + link_url=link_url, + image_url=image_url, + business_name=business_name, + board_id=board_id, + countries=countries, + age_min=age_min, + age_max=age_max, + interests=interests, + end_date=end_date, + audience_id=audience_id, + campaign_type=campaign_type, + keywords=keywords, + additional_headlines=additional_headlines, + additional_descriptions=additional_descriptions, + ) + return await self._client._apost("/v1/ads/create", data=payload) + + async def async_external_ads(self) -> dict[str, Any]: + """Sync external ads from platform ad managers (async)""" + return await self._client._apost("/v1/ads/sync") + + async def asearch_ad_interests(self, q: str, account_id: str) -> dict[str, Any]: + """Search targeting interests (async)""" + params = self._build_params( + q=q, + account_id=account_id, + ) + return await self._client._aget("/v1/ads/interests", params=params) diff --git a/src/late/resources/_generated/analytics.py b/src/late/resources/_generated/analytics.py index 02f2010..600f0cd 100644 --- a/src/late/resources/_generated/analytics.py +++ b/src/late/resources/_generated/analytics.py @@ -25,21 +25,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -50,21 +46,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def get_analytics( - self, - *, - post_id: str | None = None, - platform: str | None = None, - profile_id: str | None = None, - account_id: str | None = None, - source: str | None = "all", - from_date: str | None = None, - to_date: str | None = None, - limit: int | None = 50, - page: int | None = 1, - sort_by: str | None = "date", - order: str | None = "desc", - ) -> dict[str, Any]: + def get_analytics(self, *, post_id: str | None = None, platform: str | None = None, profile_id: str | None = None, account_id: str | None = None, source: str | None = "all", from_date: str | None = None, to_date: str | None = None, limit: int | None = 50, page: int | None = 1, sort_by: str | None = "date", order: str | None = "desc") -> dict[str, Any]: """Get post analytics""" params = self._build_params( post_id=post_id, @@ -81,14 +63,7 @@ def get_analytics( ) return self._client._get("/v1/analytics", params=params) - def get_you_tube_daily_views( - self, - video_id: str, - account_id: str, - *, - start_date: str | None = None, - end_date: str | None = None, - ) -> dict[str, Any]: + def get_you_tube_daily_views(self, video_id: str, account_id: str, *, start_date: str | None = None, end_date: str | None = None) -> dict[str, Any]: """Get YouTube daily views""" params = self._build_params( video_id=video_id, @@ -98,16 +73,7 @@ def get_you_tube_daily_views( ) return self._client._get("/v1/analytics/youtube/daily-views", params=params) - def get_instagram_account_insights( - self, - account_id: str, - *, - metrics: str | None = None, - since: str | None = None, - until: str | None = None, - metric_type: str | None = "total_value", - breakdown: str | None = None, - ) -> dict[str, Any]: + def get_instagram_account_insights(self, account_id: str, *, metrics: str | None = None, since: str | None = None, until: str | None = None, metric_type: str | None = "total_value", breakdown: str | None = None) -> dict[str, Any]: """Get Instagram account-level insights""" params = self._build_params( account_id=account_id, @@ -117,18 +83,9 @@ def get_instagram_account_insights( metric_type=metric_type, breakdown=breakdown, ) - return self._client._get( - "/v1/analytics/instagram/account-insights", params=params - ) + return self._client._get("/v1/analytics/instagram/account-insights", params=params) - def get_instagram_demographics( - self, - account_id: str, - *, - metric: str | None = "follower_demographics", - breakdown: str | None = None, - timeframe: str | None = "this_month", - ) -> dict[str, Any]: + def get_instagram_demographics(self, account_id: str, *, metric: str | None = "follower_demographics", breakdown: str | None = None, timeframe: str | None = "this_month") -> dict[str, Any]: """Get Instagram audience demographics""" params = self._build_params( account_id=account_id, @@ -138,16 +95,7 @@ def get_instagram_demographics( ) return self._client._get("/v1/analytics/instagram/demographics", params=params) - def get_daily_metrics( - self, - *, - platform: str | None = None, - profile_id: str | None = None, - account_id: str | None = None, - from_date: datetime | str | None = None, - to_date: datetime | str | None = None, - source: str | None = "all", - ) -> dict[str, Any]: + def get_daily_metrics(self, *, platform: str | None = None, profile_id: str | None = None, account_id: str | None = None, from_date: datetime | str | None = None, to_date: datetime | str | None = None, source: str | None = "all") -> dict[str, Any]: """Get daily aggregated metrics""" params = self._build_params( platform=platform, @@ -159,13 +107,7 @@ def get_daily_metrics( ) return self._client._get("/v1/analytics/daily-metrics", params=params) - def get_best_time_to_post( - self, - *, - platform: str | None = None, - profile_id: str | None = None, - source: str | None = "all", - ) -> dict[str, Any]: + def get_best_time_to_post(self, *, platform: str | None = None, profile_id: str | None = None, source: str | None = "all") -> dict[str, Any]: """Get best times to post""" params = self._build_params( platform=platform, @@ -174,13 +116,7 @@ def get_best_time_to_post( ) return self._client._get("/v1/analytics/best-time", params=params) - def get_content_decay( - self, - *, - platform: str | None = None, - profile_id: str | None = None, - source: str | None = "all", - ) -> dict[str, Any]: + def get_content_decay(self, *, platform: str | None = None, profile_id: str | None = None, source: str | None = "all") -> dict[str, Any]: """Get content performance decay""" params = self._build_params( platform=platform, @@ -189,13 +125,7 @@ def get_content_decay( ) return self._client._get("/v1/analytics/content-decay", params=params) - def get_posting_frequency( - self, - *, - platform: str | None = None, - profile_id: str | None = None, - source: str | None = "all", - ) -> dict[str, Any]: + def get_posting_frequency(self, *, platform: str | None = None, profile_id: str | None = None, source: str | None = "all") -> dict[str, Any]: """Get posting frequency vs engagement""" params = self._build_params( platform=platform, @@ -204,13 +134,7 @@ def get_posting_frequency( ) return self._client._get("/v1/analytics/posting-frequency", params=params) - def get_post_timeline( - self, - post_id: str, - *, - from_date: datetime | str | None = None, - to_date: datetime | str | None = None, - ) -> dict[str, Any]: + def get_post_timeline(self, post_id: str, *, from_date: datetime | str | None = None, to_date: datetime | str | None = None) -> dict[str, Any]: """Get post analytics timeline""" params = self._build_params( post_id=post_id, @@ -219,15 +143,7 @@ def get_post_timeline( ) return self._client._get("/v1/analytics/post-timeline", params=params) - def get_linked_in_aggregate_analytics( - self, - account_id: str, - *, - aggregation: str | None = "TOTAL", - start_date: str | None = None, - end_date: str | None = None, - metrics: str | None = None, - ) -> dict[str, Any]: + def get_linked_in_aggregate_analytics(self, account_id: str, *, aggregation: str | None = "TOTAL", start_date: str | None = None, end_date: str | None = None, metrics: str | None = None) -> dict[str, Any]: """Get LinkedIn aggregate stats""" params = self._build_params( aggregation=aggregation, @@ -235,52 +151,25 @@ def get_linked_in_aggregate_analytics( end_date=end_date, metrics=metrics, ) - return self._client._get( - f"/v1/accounts/{account_id}/linkedin-aggregate-analytics", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/linkedin-aggregate-analytics", params=params) def get_linked_in_post_analytics(self, account_id: str, urn: str) -> dict[str, Any]: """Get LinkedIn post stats""" params = self._build_params( urn=urn, ) - return self._client._get( - f"/v1/accounts/{account_id}/linkedin-post-analytics", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/linkedin-post-analytics", params=params) - def get_linked_in_post_reactions( - self, - account_id: str, - urn: str, - *, - limit: int | None = 25, - cursor: str | None = None, - ) -> dict[str, Any]: + def get_linked_in_post_reactions(self, account_id: str, urn: str, *, limit: int | None = 25, cursor: str | None = None) -> dict[str, Any]: """Get LinkedIn post reactions""" params = self._build_params( urn=urn, limit=limit, cursor=cursor, ) - return self._client._get( - f"/v1/accounts/{account_id}/linkedin-post-reactions", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/linkedin-post-reactions", params=params) - async def aget_analytics( - self, - *, - post_id: str | None = None, - platform: str | None = None, - profile_id: str | None = None, - account_id: str | None = None, - source: str | None = "all", - from_date: str | None = None, - to_date: str | None = None, - limit: int | None = 50, - page: int | None = 1, - sort_by: str | None = "date", - order: str | None = "desc", - ) -> dict[str, Any]: + async def aget_analytics(self, *, post_id: str | None = None, platform: str | None = None, profile_id: str | None = None, account_id: str | None = None, source: str | None = "all", from_date: str | None = None, to_date: str | None = None, limit: int | None = 50, page: int | None = 1, sort_by: str | None = "date", order: str | None = "desc") -> dict[str, Any]: """Get post analytics (async)""" params = self._build_params( post_id=post_id, @@ -297,14 +186,7 @@ async def aget_analytics( ) return await self._client._aget("/v1/analytics", params=params) - async def aget_you_tube_daily_views( - self, - video_id: str, - account_id: str, - *, - start_date: str | None = None, - end_date: str | None = None, - ) -> dict[str, Any]: + async def aget_you_tube_daily_views(self, video_id: str, account_id: str, *, start_date: str | None = None, end_date: str | None = None) -> dict[str, Any]: """Get YouTube daily views (async)""" params = self._build_params( video_id=video_id, @@ -312,20 +194,9 @@ async def aget_you_tube_daily_views( start_date=start_date, end_date=end_date, ) - return await self._client._aget( - "/v1/analytics/youtube/daily-views", params=params - ) + return await self._client._aget("/v1/analytics/youtube/daily-views", params=params) - async def aget_instagram_account_insights( - self, - account_id: str, - *, - metrics: str | None = None, - since: str | None = None, - until: str | None = None, - metric_type: str | None = "total_value", - breakdown: str | None = None, - ) -> dict[str, Any]: + async def aget_instagram_account_insights(self, account_id: str, *, metrics: str | None = None, since: str | None = None, until: str | None = None, metric_type: str | None = "total_value", breakdown: str | None = None) -> dict[str, Any]: """Get Instagram account-level insights (async)""" params = self._build_params( account_id=account_id, @@ -335,18 +206,9 @@ async def aget_instagram_account_insights( metric_type=metric_type, breakdown=breakdown, ) - return await self._client._aget( - "/v1/analytics/instagram/account-insights", params=params - ) + return await self._client._aget("/v1/analytics/instagram/account-insights", params=params) - async def aget_instagram_demographics( - self, - account_id: str, - *, - metric: str | None = "follower_demographics", - breakdown: str | None = None, - timeframe: str | None = "this_month", - ) -> dict[str, Any]: + async def aget_instagram_demographics(self, account_id: str, *, metric: str | None = "follower_demographics", breakdown: str | None = None, timeframe: str | None = "this_month") -> dict[str, Any]: """Get Instagram audience demographics (async)""" params = self._build_params( account_id=account_id, @@ -354,20 +216,9 @@ async def aget_instagram_demographics( breakdown=breakdown, timeframe=timeframe, ) - return await self._client._aget( - "/v1/analytics/instagram/demographics", params=params - ) + return await self._client._aget("/v1/analytics/instagram/demographics", params=params) - async def aget_daily_metrics( - self, - *, - platform: str | None = None, - profile_id: str | None = None, - account_id: str | None = None, - from_date: datetime | str | None = None, - to_date: datetime | str | None = None, - source: str | None = "all", - ) -> dict[str, Any]: + async def aget_daily_metrics(self, *, platform: str | None = None, profile_id: str | None = None, account_id: str | None = None, from_date: datetime | str | None = None, to_date: datetime | str | None = None, source: str | None = "all") -> dict[str, Any]: """Get daily aggregated metrics (async)""" params = self._build_params( platform=platform, @@ -379,13 +230,7 @@ async def aget_daily_metrics( ) return await self._client._aget("/v1/analytics/daily-metrics", params=params) - async def aget_best_time_to_post( - self, - *, - platform: str | None = None, - profile_id: str | None = None, - source: str | None = "all", - ) -> dict[str, Any]: + async def aget_best_time_to_post(self, *, platform: str | None = None, profile_id: str | None = None, source: str | None = "all") -> dict[str, Any]: """Get best times to post (async)""" params = self._build_params( platform=platform, @@ -394,13 +239,7 @@ async def aget_best_time_to_post( ) return await self._client._aget("/v1/analytics/best-time", params=params) - async def aget_content_decay( - self, - *, - platform: str | None = None, - profile_id: str | None = None, - source: str | None = "all", - ) -> dict[str, Any]: + async def aget_content_decay(self, *, platform: str | None = None, profile_id: str | None = None, source: str | None = "all") -> dict[str, Any]: """Get content performance decay (async)""" params = self._build_params( platform=platform, @@ -409,30 +248,16 @@ async def aget_content_decay( ) return await self._client._aget("/v1/analytics/content-decay", params=params) - async def aget_posting_frequency( - self, - *, - platform: str | None = None, - profile_id: str | None = None, - source: str | None = "all", - ) -> dict[str, Any]: + async def aget_posting_frequency(self, *, platform: str | None = None, profile_id: str | None = None, source: str | None = "all") -> dict[str, Any]: """Get posting frequency vs engagement (async)""" params = self._build_params( platform=platform, profile_id=profile_id, source=source, ) - return await self._client._aget( - "/v1/analytics/posting-frequency", params=params - ) + return await self._client._aget("/v1/analytics/posting-frequency", params=params) - async def aget_post_timeline( - self, - post_id: str, - *, - from_date: datetime | str | None = None, - to_date: datetime | str | None = None, - ) -> dict[str, Any]: + async def aget_post_timeline(self, post_id: str, *, from_date: datetime | str | None = None, to_date: datetime | str | None = None) -> dict[str, Any]: """Get post analytics timeline (async)""" params = self._build_params( post_id=post_id, @@ -441,15 +266,7 @@ async def aget_post_timeline( ) return await self._client._aget("/v1/analytics/post-timeline", params=params) - async def aget_linked_in_aggregate_analytics( - self, - account_id: str, - *, - aggregation: str | None = "TOTAL", - start_date: str | None = None, - end_date: str | None = None, - metrics: str | None = None, - ) -> dict[str, Any]: + async def aget_linked_in_aggregate_analytics(self, account_id: str, *, aggregation: str | None = "TOTAL", start_date: str | None = None, end_date: str | None = None, metrics: str | None = None) -> dict[str, Any]: """Get LinkedIn aggregate stats (async)""" params = self._build_params( aggregation=aggregation, @@ -457,35 +274,20 @@ async def aget_linked_in_aggregate_analytics( end_date=end_date, metrics=metrics, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/linkedin-aggregate-analytics", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/linkedin-aggregate-analytics", params=params) - async def aget_linked_in_post_analytics( - self, account_id: str, urn: str - ) -> dict[str, Any]: + async def aget_linked_in_post_analytics(self, account_id: str, urn: str) -> dict[str, Any]: """Get LinkedIn post stats (async)""" params = self._build_params( urn=urn, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/linkedin-post-analytics", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/linkedin-post-analytics", params=params) - async def aget_linked_in_post_reactions( - self, - account_id: str, - urn: str, - *, - limit: int | None = 25, - cursor: str | None = None, - ) -> dict[str, Any]: + async def aget_linked_in_post_reactions(self, account_id: str, urn: str, *, limit: int | None = 25, cursor: str | None = None) -> dict[str, Any]: """Get LinkedIn post reactions (async)""" params = self._build_params( urn=urn, limit=limit, cursor=cursor, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/linkedin-post-reactions", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/linkedin-post-reactions", params=params) diff --git a/src/late/resources/_generated/api_keys.py b/src/late/resources/_generated/api_keys.py index 74edfe8..d8b91f3 100644 --- a/src/late/resources/_generated/api_keys.py +++ b/src/late/resources/_generated/api_keys.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -52,15 +48,7 @@ def list_api_keys(self) -> dict[str, Any]: """List keys""" return self._client._get("/v1/api-keys") - def create_api_key( - self, - name: str, - *, - expires_in: int | None = None, - scope: str | None = "full", - profile_ids: list[str] | None = None, - permission: str | None = "read-write", - ) -> dict[str, Any]: + def create_api_key(self, name: str, *, expires_in: int | None = None, scope: str | None = "full", profile_ids: list[str] | None = None, permission: str | None = "read-write") -> dict[str, Any]: """Create key""" payload = self._build_payload( name=name, @@ -79,15 +67,7 @@ async def alist_api_keys(self) -> dict[str, Any]: """List keys (async)""" return await self._client._aget("/v1/api-keys") - async def acreate_api_key( - self, - name: str, - *, - expires_in: int | None = None, - scope: str | None = "full", - profile_ids: list[str] | None = None, - permission: str | None = "read-write", - ) -> dict[str, Any]: + async def acreate_api_key(self, name: str, *, expires_in: int | None = None, scope: str | None = "full", profile_ids: list[str] | None = None, permission: str | None = "read-write") -> dict[str, Any]: """Create key (async)""" payload = self._build_payload( name=name, diff --git a/src/late/resources/_generated/broadcasts.py b/src/late/resources/_generated/broadcasts.py index f0999bd..7783808 100644 --- a/src/late/resources/_generated/broadcasts.py +++ b/src/late/resources/_generated/broadcasts.py @@ -25,21 +25,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -50,15 +46,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_broadcasts( - self, - *, - profile_id: str | None = None, - status: str | None = None, - platform: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + def list_broadcasts(self, *, profile_id: str | None = None, status: str | None = None, platform: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List broadcasts""" params = self._build_params( profile_id=profile_id, @@ -69,18 +57,7 @@ def list_broadcasts( ) return self._client._get("/v1/broadcasts", params=params) - def create_broadcast( - self, - profile_id: str, - account_id: str, - platform: str, - name: str, - *, - description: str | None = None, - message: dict[str, Any] | None = None, - template: dict[str, Any] | None = None, - segment_filters: dict[str, Any] | None = None, - ) -> dict[str, Any]: + def create_broadcast(self, profile_id: str, account_id: str, platform: str, name: str, *, description: str | None = None, message: dict[str, Any] | None = None, template: dict[str, Any] | None = None, segment_filters: dict[str, Any] | None = None) -> dict[str, Any]: """Create a broadcast draft""" payload = self._build_payload( profile_id=profile_id, @@ -110,66 +87,36 @@ def send_broadcast(self, broadcast_id: str) -> dict[str, Any]: """Trigger immediate send""" return self._client._post(f"/v1/broadcasts/{broadcast_id}/send") - def schedule_broadcast( - self, broadcast_id: str, scheduled_at: datetime | str - ) -> dict[str, Any]: + def schedule_broadcast(self, broadcast_id: str, scheduled_at: datetime | str) -> dict[str, Any]: """Schedule broadcast for later""" payload = self._build_payload( scheduled_at=scheduled_at, ) - return self._client._post( - f"/v1/broadcasts/{broadcast_id}/schedule", data=payload - ) + return self._client._post(f"/v1/broadcasts/{broadcast_id}/schedule", data=payload) def cancel_broadcast(self, broadcast_id: str) -> dict[str, Any]: """Cancel a broadcast""" return self._client._post(f"/v1/broadcasts/{broadcast_id}/cancel") - def list_broadcast_recipients( - self, - broadcast_id: str, - *, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + def list_broadcast_recipients(self, broadcast_id: str, *, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List broadcast recipients""" params = self._build_params( status=status, limit=limit, skip=skip, ) - return self._client._get( - f"/v1/broadcasts/{broadcast_id}/recipients", params=params - ) + return self._client._get(f"/v1/broadcasts/{broadcast_id}/recipients", params=params) - def add_broadcast_recipients( - self, - broadcast_id: str, - *, - contact_ids: list[str] | None = None, - phones: list[str] | None = None, - use_segment: bool | None = None, - ) -> dict[str, Any]: + def add_broadcast_recipients(self, broadcast_id: str, *, contact_ids: list[str] | None = None, phones: list[str] | None = None, use_segment: bool | None = None) -> dict[str, Any]: """Add recipients to a broadcast""" payload = self._build_payload( contact_ids=contact_ids, phones=phones, use_segment=use_segment, ) - return self._client._post( - f"/v1/broadcasts/{broadcast_id}/recipients", data=payload - ) + return self._client._post(f"/v1/broadcasts/{broadcast_id}/recipients", data=payload) - async def alist_broadcasts( - self, - *, - profile_id: str | None = None, - status: str | None = None, - platform: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + async def alist_broadcasts(self, *, profile_id: str | None = None, status: str | None = None, platform: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List broadcasts (async)""" params = self._build_params( profile_id=profile_id, @@ -180,18 +127,7 @@ async def alist_broadcasts( ) return await self._client._aget("/v1/broadcasts", params=params) - async def acreate_broadcast( - self, - profile_id: str, - account_id: str, - platform: str, - name: str, - *, - description: str | None = None, - message: dict[str, Any] | None = None, - template: dict[str, Any] | None = None, - segment_filters: dict[str, Any] | None = None, - ) -> dict[str, Any]: + async def acreate_broadcast(self, profile_id: str, account_id: str, platform: str, name: str, *, description: str | None = None, message: dict[str, Any] | None = None, template: dict[str, Any] | None = None, segment_filters: dict[str, Any] | None = None) -> dict[str, Any]: """Create a broadcast draft (async)""" payload = self._build_payload( profile_id=profile_id, @@ -221,53 +157,31 @@ async def asend_broadcast(self, broadcast_id: str) -> dict[str, Any]: """Trigger immediate send (async)""" return await self._client._apost(f"/v1/broadcasts/{broadcast_id}/send") - async def aschedule_broadcast( - self, broadcast_id: str, scheduled_at: datetime | str - ) -> dict[str, Any]: + async def aschedule_broadcast(self, broadcast_id: str, scheduled_at: datetime | str) -> dict[str, Any]: """Schedule broadcast for later (async)""" payload = self._build_payload( scheduled_at=scheduled_at, ) - return await self._client._apost( - f"/v1/broadcasts/{broadcast_id}/schedule", data=payload - ) + return await self._client._apost(f"/v1/broadcasts/{broadcast_id}/schedule", data=payload) async def acancel_broadcast(self, broadcast_id: str) -> dict[str, Any]: """Cancel a broadcast (async)""" return await self._client._apost(f"/v1/broadcasts/{broadcast_id}/cancel") - async def alist_broadcast_recipients( - self, - broadcast_id: str, - *, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + async def alist_broadcast_recipients(self, broadcast_id: str, *, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List broadcast recipients (async)""" params = self._build_params( status=status, limit=limit, skip=skip, ) - return await self._client._aget( - f"/v1/broadcasts/{broadcast_id}/recipients", params=params - ) + return await self._client._aget(f"/v1/broadcasts/{broadcast_id}/recipients", params=params) - async def aadd_broadcast_recipients( - self, - broadcast_id: str, - *, - contact_ids: list[str] | None = None, - phones: list[str] | None = None, - use_segment: bool | None = None, - ) -> dict[str, Any]: + async def aadd_broadcast_recipients(self, broadcast_id: str, *, contact_ids: list[str] | None = None, phones: list[str] | None = None, use_segment: bool | None = None) -> dict[str, Any]: """Add recipients to a broadcast (async)""" payload = self._build_payload( contact_ids=contact_ids, phones=phones, use_segment=use_segment, ) - return await self._client._apost( - f"/v1/broadcasts/{broadcast_id}/recipients", data=payload - ) + return await self._client._apost(f"/v1/broadcasts/{broadcast_id}/recipients", data=payload) diff --git a/src/late/resources/_generated/comment_automations.py b/src/late/resources/_generated/comment_automations.py index 000e20a..49cc1f0 100644 --- a/src/late/resources/_generated/comment_automations.py +++ b/src/late/resources/_generated/comment_automations.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,29 +44,14 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_comment_automations( - self, *, profile_id: str | None = None - ) -> dict[str, Any]: + def list_comment_automations(self, *, profile_id: str | None = None) -> dict[str, Any]: """List comment-to-DM automations""" params = self._build_params( profile_id=profile_id, ) return self._client._get("/v1/comment-automations", params=params) - def create_comment_automation( - self, - profile_id: str, - account_id: str, - platform_post_id: str, - name: str, - dm_message: str, - *, - post_id: str | None = None, - post_title: str | None = None, - keywords: list[str] | None = None, - match_mode: str | None = "contains", - comment_reply: str | None = None, - ) -> dict[str, Any]: + def create_comment_automation(self, profile_id: str, account_id: str, platform_post_id: str, name: str, dm_message: str, *, post_id: str | None = None, post_title: str | None = None, keywords: list[str] | None = None, match_mode: str | None = "contains", comment_reply: str | None = None) -> dict[str, Any]: """Create a comment-to-DM automation""" payload = self._build_payload( profile_id=profile_id, @@ -90,17 +71,7 @@ def get_comment_automation(self, automation_id: str) -> dict[str, Any]: """Get automation details with recent logs""" return self._client._get(f"/v1/comment-automations/{automation_id}") - def update_comment_automation( - self, - automation_id: str, - *, - name: str | None = None, - keywords: list[str] | None = None, - match_mode: str | None = None, - dm_message: str | None = None, - comment_reply: str | None = None, - is_active: bool | None = None, - ) -> dict[str, Any]: + def update_comment_automation(self, automation_id: str, *, name: str | None = None, keywords: list[str] | None = None, match_mode: str | None = None, dm_message: str | None = None, comment_reply: str | None = None, is_active: bool | None = None) -> dict[str, Any]: """Update automation settings""" payload = self._build_payload( name=name, @@ -110,55 +81,29 @@ def update_comment_automation( comment_reply=comment_reply, is_active=is_active, ) - return self._client._patch( - f"/v1/comment-automations/{automation_id}", data=payload - ) + return self._client._patch(f"/v1/comment-automations/{automation_id}", data=payload) def delete_comment_automation(self, automation_id: str) -> dict[str, Any]: """Delete automation and all logs""" return self._client._delete(f"/v1/comment-automations/{automation_id}") - def list_comment_automation_logs( - self, - automation_id: str, - *, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + def list_comment_automation_logs(self, automation_id: str, *, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List trigger logs for an automation""" params = self._build_params( status=status, limit=limit, skip=skip, ) - return self._client._get( - f"/v1/comment-automations/{automation_id}/logs", params=params - ) + return self._client._get(f"/v1/comment-automations/{automation_id}/logs", params=params) - async def alist_comment_automations( - self, *, profile_id: str | None = None - ) -> dict[str, Any]: + async def alist_comment_automations(self, *, profile_id: str | None = None) -> dict[str, Any]: """List comment-to-DM automations (async)""" params = self._build_params( profile_id=profile_id, ) return await self._client._aget("/v1/comment-automations", params=params) - async def acreate_comment_automation( - self, - profile_id: str, - account_id: str, - platform_post_id: str, - name: str, - dm_message: str, - *, - post_id: str | None = None, - post_title: str | None = None, - keywords: list[str] | None = None, - match_mode: str | None = "contains", - comment_reply: str | None = None, - ) -> dict[str, Any]: + async def acreate_comment_automation(self, profile_id: str, account_id: str, platform_post_id: str, name: str, dm_message: str, *, post_id: str | None = None, post_title: str | None = None, keywords: list[str] | None = None, match_mode: str | None = "contains", comment_reply: str | None = None) -> dict[str, Any]: """Create a comment-to-DM automation (async)""" payload = self._build_payload( profile_id=profile_id, @@ -178,17 +123,7 @@ async def aget_comment_automation(self, automation_id: str) -> dict[str, Any]: """Get automation details with recent logs (async)""" return await self._client._aget(f"/v1/comment-automations/{automation_id}") - async def aupdate_comment_automation( - self, - automation_id: str, - *, - name: str | None = None, - keywords: list[str] | None = None, - match_mode: str | None = None, - dm_message: str | None = None, - comment_reply: str | None = None, - is_active: bool | None = None, - ) -> dict[str, Any]: + async def aupdate_comment_automation(self, automation_id: str, *, name: str | None = None, keywords: list[str] | None = None, match_mode: str | None = None, dm_message: str | None = None, comment_reply: str | None = None, is_active: bool | None = None) -> dict[str, Any]: """Update automation settings (async)""" payload = self._build_payload( name=name, @@ -198,28 +133,17 @@ async def aupdate_comment_automation( comment_reply=comment_reply, is_active=is_active, ) - return await self._client._apatch( - f"/v1/comment-automations/{automation_id}", data=payload - ) + return await self._client._apatch(f"/v1/comment-automations/{automation_id}", data=payload) async def adelete_comment_automation(self, automation_id: str) -> dict[str, Any]: """Delete automation and all logs (async)""" return await self._client._adelete(f"/v1/comment-automations/{automation_id}") - async def alist_comment_automation_logs( - self, - automation_id: str, - *, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + async def alist_comment_automation_logs(self, automation_id: str, *, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List trigger logs for an automation (async)""" params = self._build_params( status=status, limit=limit, skip=skip, ) - return await self._client._aget( - f"/v1/comment-automations/{automation_id}/logs", params=params - ) + return await self._client._aget(f"/v1/comment-automations/{automation_id}/logs", params=params) diff --git a/src/late/resources/_generated/comments.py b/src/late/resources/_generated/comments.py index 14a8cc3..b889e3d 100644 --- a/src/late/resources/_generated/comments.py +++ b/src/late/resources/_generated/comments.py @@ -25,21 +25,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -50,19 +46,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_inbox_comments( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - min_comments: int | None = None, - since: datetime | str | None = None, - sort_by: str | None = "date", - sort_order: str | None = "desc", - limit: int | None = 50, - cursor: str | None = None, - account_id: str | None = None, - ) -> dict[str, Any]: + def list_inbox_comments(self, *, profile_id: str | None = None, platform: str | None = None, min_comments: int | None = None, since: datetime | str | None = None, sort_by: str | None = "date", sort_order: str | None = "desc", limit: int | None = 50, cursor: str | None = None, account_id: str | None = None) -> dict[str, Any]: """List commented posts""" params = self._build_params( profile_id=profile_id, @@ -77,16 +61,7 @@ def list_inbox_comments( ) return self._client._get("/v1/inbox/comments", params=params) - def get_inbox_post_comments( - self, - post_id: str, - account_id: str, - *, - subreddit: str | None = None, - limit: int | None = 25, - cursor: str | None = None, - comment_id: str | None = None, - ) -> dict[str, Any]: + def get_inbox_post_comments(self, post_id: str, account_id: str, *, subreddit: str | None = None, limit: int | None = 25, cursor: str | None = None, comment_id: str | None = None) -> dict[str, Any]: """Get post comments""" params = self._build_params( account_id=account_id, @@ -97,17 +72,7 @@ def get_inbox_post_comments( ) return self._client._get(f"/v1/inbox/comments/{post_id}", params=params) - def reply_to_inbox_post( - self, - post_id: str, - account_id: str, - message: str, - *, - comment_id: str | None = None, - parent_cid: str | None = None, - root_uri: str | None = None, - root_cid: str | None = None, - ) -> dict[str, Any]: + def reply_to_inbox_post(self, post_id: str, account_id: str, message: str, *, comment_id: str | None = None, parent_cid: str | None = None, root_uri: str | None = None, root_cid: str | None = None) -> dict[str, Any]: """Reply to comment""" payload = self._build_payload( account_id=account_id, @@ -119,9 +84,7 @@ def reply_to_inbox_post( ) return self._client._post(f"/v1/inbox/comments/{post_id}", data=payload) - def delete_inbox_comment( - self, post_id: str, account_id: str, comment_id: str - ) -> dict[str, Any]: + def delete_inbox_comment(self, post_id: str, account_id: str, comment_id: str) -> dict[str, Any]: """Delete comment""" params = self._build_params( account_id=account_id, @@ -129,82 +92,45 @@ def delete_inbox_comment( ) return self._client._delete(f"/v1/inbox/comments/{post_id}", params=params) - def hide_inbox_comment( - self, post_id: str, comment_id: str, account_id: str - ) -> dict[str, Any]: + def hide_inbox_comment(self, post_id: str, comment_id: str, account_id: str) -> dict[str, Any]: """Hide comment""" payload = self._build_payload( account_id=account_id, ) - return self._client._post( - f"/v1/inbox/comments/{post_id}/{comment_id}/hide", data=payload - ) + return self._client._post(f"/v1/inbox/comments/{post_id}/{comment_id}/hide", data=payload) - def unhide_inbox_comment( - self, post_id: str, comment_id: str, account_id: str - ) -> dict[str, Any]: + def unhide_inbox_comment(self, post_id: str, comment_id: str, account_id: str) -> dict[str, Any]: """Unhide comment""" params = self._build_params( account_id=account_id, ) - return self._client._delete( - f"/v1/inbox/comments/{post_id}/{comment_id}/hide", params=params - ) + return self._client._delete(f"/v1/inbox/comments/{post_id}/{comment_id}/hide", params=params) - def like_inbox_comment( - self, post_id: str, comment_id: str, account_id: str, *, cid: str | None = None - ) -> dict[str, Any]: + def like_inbox_comment(self, post_id: str, comment_id: str, account_id: str, *, cid: str | None = None) -> dict[str, Any]: """Like comment""" payload = self._build_payload( account_id=account_id, cid=cid, ) - return self._client._post( - f"/v1/inbox/comments/{post_id}/{comment_id}/like", data=payload - ) + return self._client._post(f"/v1/inbox/comments/{post_id}/{comment_id}/like", data=payload) - def unlike_inbox_comment( - self, - post_id: str, - comment_id: str, - account_id: str, - *, - like_uri: str | None = None, - ) -> dict[str, Any]: + def unlike_inbox_comment(self, post_id: str, comment_id: str, account_id: str, *, like_uri: str | None = None) -> dict[str, Any]: """Unlike comment""" params = self._build_params( account_id=account_id, like_uri=like_uri, ) - return self._client._delete( - f"/v1/inbox/comments/{post_id}/{comment_id}/like", params=params - ) + return self._client._delete(f"/v1/inbox/comments/{post_id}/{comment_id}/like", params=params) - def send_private_reply_to_comment( - self, post_id: str, comment_id: str, account_id: str, message: str - ) -> dict[str, Any]: + def send_private_reply_to_comment(self, post_id: str, comment_id: str, account_id: str, message: str) -> dict[str, Any]: """Send private reply""" payload = self._build_payload( account_id=account_id, message=message, ) - return self._client._post( - f"/v1/inbox/comments/{post_id}/{comment_id}/private-reply", data=payload - ) + return self._client._post(f"/v1/inbox/comments/{post_id}/{comment_id}/private-reply", data=payload) - async def alist_inbox_comments( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - min_comments: int | None = None, - since: datetime | str | None = None, - sort_by: str | None = "date", - sort_order: str | None = "desc", - limit: int | None = 50, - cursor: str | None = None, - account_id: str | None = None, - ) -> dict[str, Any]: + async def alist_inbox_comments(self, *, profile_id: str | None = None, platform: str | None = None, min_comments: int | None = None, since: datetime | str | None = None, sort_by: str | None = "date", sort_order: str | None = "desc", limit: int | None = 50, cursor: str | None = None, account_id: str | None = None) -> dict[str, Any]: """List commented posts (async)""" params = self._build_params( profile_id=profile_id, @@ -219,16 +145,7 @@ async def alist_inbox_comments( ) return await self._client._aget("/v1/inbox/comments", params=params) - async def aget_inbox_post_comments( - self, - post_id: str, - account_id: str, - *, - subreddit: str | None = None, - limit: int | None = 25, - cursor: str | None = None, - comment_id: str | None = None, - ) -> dict[str, Any]: + async def aget_inbox_post_comments(self, post_id: str, account_id: str, *, subreddit: str | None = None, limit: int | None = 25, cursor: str | None = None, comment_id: str | None = None) -> dict[str, Any]: """Get post comments (async)""" params = self._build_params( account_id=account_id, @@ -239,17 +156,7 @@ async def aget_inbox_post_comments( ) return await self._client._aget(f"/v1/inbox/comments/{post_id}", params=params) - async def areply_to_inbox_post( - self, - post_id: str, - account_id: str, - message: str, - *, - comment_id: str | None = None, - parent_cid: str | None = None, - root_uri: str | None = None, - root_cid: str | None = None, - ) -> dict[str, Any]: + async def areply_to_inbox_post(self, post_id: str, account_id: str, message: str, *, comment_id: str | None = None, parent_cid: str | None = None, root_uri: str | None = None, root_cid: str | None = None) -> dict[str, Any]: """Reply to comment (async)""" payload = self._build_payload( account_id=account_id, @@ -261,77 +168,48 @@ async def areply_to_inbox_post( ) return await self._client._apost(f"/v1/inbox/comments/{post_id}", data=payload) - async def adelete_inbox_comment( - self, post_id: str, account_id: str, comment_id: str - ) -> dict[str, Any]: + async def adelete_inbox_comment(self, post_id: str, account_id: str, comment_id: str) -> dict[str, Any]: """Delete comment (async)""" params = self._build_params( account_id=account_id, comment_id=comment_id, ) - return await self._client._adelete( - f"/v1/inbox/comments/{post_id}", params=params - ) + return await self._client._adelete(f"/v1/inbox/comments/{post_id}", params=params) - async def ahide_inbox_comment( - self, post_id: str, comment_id: str, account_id: str - ) -> dict[str, Any]: + async def ahide_inbox_comment(self, post_id: str, comment_id: str, account_id: str) -> dict[str, Any]: """Hide comment (async)""" payload = self._build_payload( account_id=account_id, ) - return await self._client._apost( - f"/v1/inbox/comments/{post_id}/{comment_id}/hide", data=payload - ) + return await self._client._apost(f"/v1/inbox/comments/{post_id}/{comment_id}/hide", data=payload) - async def aunhide_inbox_comment( - self, post_id: str, comment_id: str, account_id: str - ) -> dict[str, Any]: + async def aunhide_inbox_comment(self, post_id: str, comment_id: str, account_id: str) -> dict[str, Any]: """Unhide comment (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._adelete( - f"/v1/inbox/comments/{post_id}/{comment_id}/hide", params=params - ) + return await self._client._adelete(f"/v1/inbox/comments/{post_id}/{comment_id}/hide", params=params) - async def alike_inbox_comment( - self, post_id: str, comment_id: str, account_id: str, *, cid: str | None = None - ) -> dict[str, Any]: + async def alike_inbox_comment(self, post_id: str, comment_id: str, account_id: str, *, cid: str | None = None) -> dict[str, Any]: """Like comment (async)""" payload = self._build_payload( account_id=account_id, cid=cid, ) - return await self._client._apost( - f"/v1/inbox/comments/{post_id}/{comment_id}/like", data=payload - ) + return await self._client._apost(f"/v1/inbox/comments/{post_id}/{comment_id}/like", data=payload) - async def aunlike_inbox_comment( - self, - post_id: str, - comment_id: str, - account_id: str, - *, - like_uri: str | None = None, - ) -> dict[str, Any]: + async def aunlike_inbox_comment(self, post_id: str, comment_id: str, account_id: str, *, like_uri: str | None = None) -> dict[str, Any]: """Unlike comment (async)""" params = self._build_params( account_id=account_id, like_uri=like_uri, ) - return await self._client._adelete( - f"/v1/inbox/comments/{post_id}/{comment_id}/like", params=params - ) + return await self._client._adelete(f"/v1/inbox/comments/{post_id}/{comment_id}/like", params=params) - async def asend_private_reply_to_comment( - self, post_id: str, comment_id: str, account_id: str, message: str - ) -> dict[str, Any]: + async def asend_private_reply_to_comment(self, post_id: str, comment_id: str, account_id: str, message: str) -> dict[str, Any]: """Send private reply (async)""" payload = self._build_payload( account_id=account_id, message=message, ) - return await self._client._apost( - f"/v1/inbox/comments/{post_id}/{comment_id}/private-reply", data=payload - ) + return await self._client._apost(f"/v1/inbox/comments/{post_id}/{comment_id}/private-reply", data=payload) diff --git a/src/late/resources/_generated/connect.py b/src/late/resources/_generated/connect.py index 1e8b8fc..5e0200e 100644 --- a/src/late/resources/_generated/connect.py +++ b/src/late/resources/_generated/connect.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,14 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def get_connect_url( - self, - platform: str, - profile_id: str, - *, - redirect_url: str | None = None, - headless: bool | None = False, - ) -> dict[str, Any]: + def get_connect_url(self, platform: str, profile_id: str, *, redirect_url: str | None = None, headless: bool | None = False) -> dict[str, Any]: """Get OAuth connect URL""" params = self._build_params( profile_id=profile_id, @@ -64,9 +53,7 @@ def get_connect_url( ) return self._client._get(f"/v1/connect/{platform}", params=params) - def handle_o_auth_callback( - self, platform: str, code: str, state: str, profile_id: str - ) -> dict[str, Any]: + def handle_o_auth_callback(self, platform: str, code: str, state: str, profile_id: str) -> dict[str, Any]: """Complete OAuth callback""" payload = self._build_payload( code=code, @@ -83,15 +70,7 @@ def list_facebook_pages(self, profile_id: str, temp_token: str) -> dict[str, Any ) return self._client._get("/v1/connect/facebook/select-page", params=params) - def select_facebook_page( - self, - profile_id: str, - page_id: str, - temp_token: str, - *, - user_profile: dict[str, Any] | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + def select_facebook_page(self, profile_id: str, page_id: str, temp_token: str, *, user_profile: dict[str, Any] | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select Facebook page""" payload = self._build_payload( profile_id=profile_id, @@ -102,9 +81,7 @@ def select_facebook_page( ) return self._client._post("/v1/connect/facebook/select-page", data=payload) - def list_google_business_locations( - self, profile_id: str, temp_token: str - ) -> dict[str, Any]: + def list_google_business_locations(self, profile_id: str, temp_token: str) -> dict[str, Any]: """List GBP locations""" params = self._build_params( profile_id=profile_id, @@ -112,15 +89,7 @@ def list_google_business_locations( ) return self._client._get("/v1/connect/googlebusiness/locations", params=params) - def select_google_business_location( - self, - profile_id: str, - location_id: str, - temp_token: str, - *, - user_profile: dict[str, Any] | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + def select_google_business_location(self, profile_id: str, location_id: str, temp_token: str, *, user_profile: dict[str, Any] | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select GBP location""" payload = self._build_payload( profile_id=profile_id, @@ -129,9 +98,7 @@ def select_google_business_location( user_profile=user_profile, redirect_url=redirect_url, ) - return self._client._post( - "/v1/connect/googlebusiness/select-location", data=payload - ) + return self._client._post("/v1/connect/googlebusiness/select-location", data=payload) def get_pending_o_auth_data(self, token: str) -> dict[str, Any]: """Get pending OAuth data""" @@ -140,9 +107,7 @@ def get_pending_o_auth_data(self, token: str) -> dict[str, Any]: ) return self._client._get("/v1/connect/pending-data", params=params) - def list_linked_in_organizations( - self, temp_token: str, org_ids: str - ) -> dict[str, Any]: + def list_linked_in_organizations(self, temp_token: str, org_ids: str) -> dict[str, Any]: """List LinkedIn orgs""" params = self._build_params( temp_token=temp_token, @@ -150,16 +115,7 @@ def list_linked_in_organizations( ) return self._client._get("/v1/connect/linkedin/organizations", params=params) - def select_linked_in_organization( - self, - profile_id: str, - temp_token: str, - user_profile: dict[str, Any], - account_type: str, - *, - selected_organization: dict[str, Any] | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + def select_linked_in_organization(self, profile_id: str, temp_token: str, user_profile: dict[str, Any], account_type: str, *, selected_organization: dict[str, Any] | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select LinkedIn org""" payload = self._build_payload( profile_id=profile_id, @@ -169,13 +125,9 @@ def select_linked_in_organization( selected_organization=selected_organization, redirect_url=redirect_url, ) - return self._client._post( - "/v1/connect/linkedin/select-organization", data=payload - ) + return self._client._post("/v1/connect/linkedin/select-organization", data=payload) - def list_pinterest_boards_for_selection( - self, x_connect_token: str, profile_id: str, temp_token: str - ) -> dict[str, Any]: + def list_pinterest_boards_for_selection(self, x_connect_token: str, profile_id: str, temp_token: str) -> dict[str, Any]: """List Pinterest boards""" params = self._build_params( profile_id=profile_id, @@ -183,18 +135,7 @@ def list_pinterest_boards_for_selection( ) return self._client._get("/v1/connect/pinterest/select-board", params=params) - def select_pinterest_board( - self, - profile_id: str, - board_id: str, - temp_token: str, - *, - board_name: str | None = None, - user_profile: dict[str, Any] | None = None, - refresh_token: str | None = None, - expires_in: int | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + def select_pinterest_board(self, profile_id: str, board_id: str, temp_token: str, *, board_name: str | None = None, user_profile: dict[str, Any] | None = None, refresh_token: str | None = None, expires_in: int | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select Pinterest board""" payload = self._build_payload( profile_id=profile_id, @@ -208,9 +149,7 @@ def select_pinterest_board( ) return self._client._post("/v1/connect/pinterest/select-board", data=payload) - def list_snapchat_profiles( - self, x_connect_token: str, profile_id: str, temp_token: str - ) -> dict[str, Any]: + def list_snapchat_profiles(self, x_connect_token: str, profile_id: str, temp_token: str) -> dict[str, Any]: """List Snapchat profiles""" params = self._build_params( profile_id=profile_id, @@ -218,18 +157,7 @@ def list_snapchat_profiles( ) return self._client._get("/v1/connect/snapchat/select-profile", params=params) - def select_snapchat_profile( - self, - profile_id: str, - selected_public_profile: dict[str, Any], - temp_token: str, - user_profile: dict[str, Any], - *, - x_connect_token: str | None = None, - refresh_token: str | None = None, - expires_in: int | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + def select_snapchat_profile(self, profile_id: str, selected_public_profile: dict[str, Any], temp_token: str, user_profile: dict[str, Any], *, x_connect_token: str | None = None, refresh_token: str | None = None, expires_in: int | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select Snapchat profile""" payload = self._build_payload( profile_id=profile_id, @@ -242,14 +170,7 @@ def select_snapchat_profile( ) return self._client._post("/v1/connect/snapchat/select-profile", data=payload) - def connect_bluesky_credentials( - self, - identifier: str, - app_password: str, - state: str, - *, - redirect_uri: str | None = None, - ) -> dict[str, Any]: + def connect_bluesky_credentials(self, identifier: str, app_password: str, state: str, *, redirect_uri: str | None = None) -> dict[str, Any]: """Connect Bluesky account""" payload = self._build_payload( identifier=identifier, @@ -259,9 +180,7 @@ def connect_bluesky_credentials( ) return self._client._post("/v1/connect/bluesky/credentials", data=payload) - def connect_whats_app_credentials( - self, profile_id: str, access_token: str, waba_id: str, phone_number_id: str - ) -> dict[str, Any]: + def connect_whats_app_credentials(self, profile_id: str, access_token: str, waba_id: str, phone_number_id: str) -> dict[str, Any]: """Connect WhatsApp via credentials""" payload = self._build_payload( profile_id=profile_id, @@ -278,9 +197,7 @@ def get_telegram_connect_status(self, profile_id: str) -> dict[str, Any]: ) return self._client._get("/v1/connect/telegram", params=params) - def initiate_telegram_connect( - self, chat_id: str, profile_id: str - ) -> dict[str, Any]: + def initiate_telegram_connect(self, chat_id: str, profile_id: str) -> dict[str, Any]: """Connect Telegram directly""" payload = self._build_payload( chat_id=chat_id, @@ -299,104 +216,79 @@ def get_facebook_pages(self, account_id: str) -> dict[str, Any]: """List Facebook pages""" return self._client._get(f"/v1/accounts/{account_id}/facebook-page") - def update_facebook_page( - self, account_id: str, selected_page_id: str - ) -> dict[str, Any]: + def update_facebook_page(self, account_id: str, selected_page_id: str) -> dict[str, Any]: """Update Facebook page""" payload = self._build_payload( selected_page_id=selected_page_id, ) - return self._client._put( - f"/v1/accounts/{account_id}/facebook-page", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/facebook-page", data=payload) def get_linked_in_organizations(self, account_id: str) -> dict[str, Any]: """List LinkedIn orgs""" return self._client._get(f"/v1/accounts/{account_id}/linkedin-organizations") - def update_linked_in_organization( - self, - account_id: str, - account_type: str, - *, - selected_organization: dict[str, Any] | None = None, - ) -> dict[str, Any]: + def update_linked_in_organization(self, account_id: str, account_type: str, *, selected_organization: dict[str, Any] | None = None) -> dict[str, Any]: """Switch LinkedIn account type""" payload = self._build_payload( account_type=account_type, selected_organization=selected_organization, ) - return self._client._put( - f"/v1/accounts/{account_id}/linkedin-organization", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/linkedin-organization", data=payload) def get_pinterest_boards(self, account_id: str) -> dict[str, Any]: """List Pinterest boards""" return self._client._get(f"/v1/accounts/{account_id}/pinterest-boards") - def update_pinterest_boards( - self, - account_id: str, - default_board_id: str, - *, - default_board_name: str | None = None, - ) -> dict[str, Any]: + def update_pinterest_boards(self, account_id: str, default_board_id: str, *, default_board_name: str | None = None) -> dict[str, Any]: """Set default Pinterest board""" payload = self._build_payload( default_board_id=default_board_id, default_board_name=default_board_name, ) - return self._client._put( - f"/v1/accounts/{account_id}/pinterest-boards", data=payload + return self._client._put(f"/v1/accounts/{account_id}/pinterest-boards", data=payload) + + def get_youtube_playlists(self, account_id: str) -> dict[str, Any]: + """List YouTube playlists""" + return self._client._get(f"/v1/accounts/{account_id}/youtube-playlists") + + def update_youtube_default_playlist(self, account_id: str, default_playlist_id: str, *, default_playlist_name: str | None = None) -> dict[str, Any]: + """Set default YouTube playlist""" + payload = self._build_payload( + default_playlist_id=default_playlist_id, + default_playlist_name=default_playlist_name, ) + return self._client._put(f"/v1/accounts/{account_id}/youtube-playlists", data=payload) def get_gmb_locations(self, account_id: str) -> dict[str, Any]: """List GBP locations""" return self._client._get(f"/v1/accounts/{account_id}/gmb-locations") - def update_gmb_location( - self, account_id: str, selected_location_id: str - ) -> dict[str, Any]: + def update_gmb_location(self, account_id: str, selected_location_id: str) -> dict[str, Any]: """Update GBP location""" payload = self._build_payload( selected_location_id=selected_location_id, ) - return self._client._put( - f"/v1/accounts/{account_id}/gmb-locations", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/gmb-locations", data=payload) def get_reddit_subreddits(self, account_id: str) -> dict[str, Any]: """List Reddit subreddits""" return self._client._get(f"/v1/accounts/{account_id}/reddit-subreddits") - def update_reddit_subreddits( - self, account_id: str, default_subreddit: str - ) -> dict[str, Any]: + def update_reddit_subreddits(self, account_id: str, default_subreddit: str) -> dict[str, Any]: """Set default subreddit""" payload = self._build_payload( default_subreddit=default_subreddit, ) - return self._client._put( - f"/v1/accounts/{account_id}/reddit-subreddits", data=payload - ) + return self._client._put(f"/v1/accounts/{account_id}/reddit-subreddits", data=payload) def get_reddit_flairs(self, account_id: str, subreddit: str) -> dict[str, Any]: """List subreddit flairs""" params = self._build_params( subreddit=subreddit, ) - return self._client._get( - f"/v1/accounts/{account_id}/reddit-flairs", params=params - ) + return self._client._get(f"/v1/accounts/{account_id}/reddit-flairs", params=params) - async def aget_connect_url( - self, - platform: str, - profile_id: str, - *, - redirect_url: str | None = None, - headless: bool | None = False, - ) -> dict[str, Any]: + async def aget_connect_url(self, platform: str, profile_id: str, *, redirect_url: str | None = None, headless: bool | None = False) -> dict[str, Any]: """Get OAuth connect URL (async)""" params = self._build_params( profile_id=profile_id, @@ -405,9 +297,7 @@ async def aget_connect_url( ) return await self._client._aget(f"/v1/connect/{platform}", params=params) - async def ahandle_o_auth_callback( - self, platform: str, code: str, state: str, profile_id: str - ) -> dict[str, Any]: + async def ahandle_o_auth_callback(self, platform: str, code: str, state: str, profile_id: str) -> dict[str, Any]: """Complete OAuth callback (async)""" payload = self._build_payload( code=code, @@ -416,27 +306,15 @@ async def ahandle_o_auth_callback( ) return await self._client._apost(f"/v1/connect/{platform}", data=payload) - async def alist_facebook_pages( - self, profile_id: str, temp_token: str - ) -> dict[str, Any]: + async def alist_facebook_pages(self, profile_id: str, temp_token: str) -> dict[str, Any]: """List Facebook pages (async)""" params = self._build_params( profile_id=profile_id, temp_token=temp_token, ) - return await self._client._aget( - "/v1/connect/facebook/select-page", params=params - ) + return await self._client._aget("/v1/connect/facebook/select-page", params=params) - async def aselect_facebook_page( - self, - profile_id: str, - page_id: str, - temp_token: str, - *, - user_profile: dict[str, Any] | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + async def aselect_facebook_page(self, profile_id: str, page_id: str, temp_token: str, *, user_profile: dict[str, Any] | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select Facebook page (async)""" payload = self._build_payload( profile_id=profile_id, @@ -445,31 +323,17 @@ async def aselect_facebook_page( user_profile=user_profile, redirect_url=redirect_url, ) - return await self._client._apost( - "/v1/connect/facebook/select-page", data=payload - ) + return await self._client._apost("/v1/connect/facebook/select-page", data=payload) - async def alist_google_business_locations( - self, profile_id: str, temp_token: str - ) -> dict[str, Any]: + async def alist_google_business_locations(self, profile_id: str, temp_token: str) -> dict[str, Any]: """List GBP locations (async)""" params = self._build_params( profile_id=profile_id, temp_token=temp_token, ) - return await self._client._aget( - "/v1/connect/googlebusiness/locations", params=params - ) + return await self._client._aget("/v1/connect/googlebusiness/locations", params=params) - async def aselect_google_business_location( - self, - profile_id: str, - location_id: str, - temp_token: str, - *, - user_profile: dict[str, Any] | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + async def aselect_google_business_location(self, profile_id: str, location_id: str, temp_token: str, *, user_profile: dict[str, Any] | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select GBP location (async)""" payload = self._build_payload( profile_id=profile_id, @@ -478,9 +342,7 @@ async def aselect_google_business_location( user_profile=user_profile, redirect_url=redirect_url, ) - return await self._client._apost( - "/v1/connect/googlebusiness/select-location", data=payload - ) + return await self._client._apost("/v1/connect/googlebusiness/select-location", data=payload) async def aget_pending_o_auth_data(self, token: str) -> dict[str, Any]: """Get pending OAuth data (async)""" @@ -489,28 +351,15 @@ async def aget_pending_o_auth_data(self, token: str) -> dict[str, Any]: ) return await self._client._aget("/v1/connect/pending-data", params=params) - async def alist_linked_in_organizations( - self, temp_token: str, org_ids: str - ) -> dict[str, Any]: + async def alist_linked_in_organizations(self, temp_token: str, org_ids: str) -> dict[str, Any]: """List LinkedIn orgs (async)""" params = self._build_params( temp_token=temp_token, org_ids=org_ids, ) - return await self._client._aget( - "/v1/connect/linkedin/organizations", params=params - ) + return await self._client._aget("/v1/connect/linkedin/organizations", params=params) - async def aselect_linked_in_organization( - self, - profile_id: str, - temp_token: str, - user_profile: dict[str, Any], - account_type: str, - *, - selected_organization: dict[str, Any] | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + async def aselect_linked_in_organization(self, profile_id: str, temp_token: str, user_profile: dict[str, Any], account_type: str, *, selected_organization: dict[str, Any] | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select LinkedIn org (async)""" payload = self._build_payload( profile_id=profile_id, @@ -520,34 +369,17 @@ async def aselect_linked_in_organization( selected_organization=selected_organization, redirect_url=redirect_url, ) - return await self._client._apost( - "/v1/connect/linkedin/select-organization", data=payload - ) + return await self._client._apost("/v1/connect/linkedin/select-organization", data=payload) - async def alist_pinterest_boards_for_selection( - self, x_connect_token: str, profile_id: str, temp_token: str - ) -> dict[str, Any]: + async def alist_pinterest_boards_for_selection(self, x_connect_token: str, profile_id: str, temp_token: str) -> dict[str, Any]: """List Pinterest boards (async)""" params = self._build_params( profile_id=profile_id, temp_token=temp_token, ) - return await self._client._aget( - "/v1/connect/pinterest/select-board", params=params - ) - - async def aselect_pinterest_board( - self, - profile_id: str, - board_id: str, - temp_token: str, - *, - board_name: str | None = None, - user_profile: dict[str, Any] | None = None, - refresh_token: str | None = None, - expires_in: int | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + return await self._client._aget("/v1/connect/pinterest/select-board", params=params) + + async def aselect_pinterest_board(self, profile_id: str, board_id: str, temp_token: str, *, board_name: str | None = None, user_profile: dict[str, Any] | None = None, refresh_token: str | None = None, expires_in: int | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select Pinterest board (async)""" payload = self._build_payload( profile_id=profile_id, @@ -559,34 +391,17 @@ async def aselect_pinterest_board( expires_in=expires_in, redirect_url=redirect_url, ) - return await self._client._apost( - "/v1/connect/pinterest/select-board", data=payload - ) + return await self._client._apost("/v1/connect/pinterest/select-board", data=payload) - async def alist_snapchat_profiles( - self, x_connect_token: str, profile_id: str, temp_token: str - ) -> dict[str, Any]: + async def alist_snapchat_profiles(self, x_connect_token: str, profile_id: str, temp_token: str) -> dict[str, Any]: """List Snapchat profiles (async)""" params = self._build_params( profile_id=profile_id, temp_token=temp_token, ) - return await self._client._aget( - "/v1/connect/snapchat/select-profile", params=params - ) - - async def aselect_snapchat_profile( - self, - profile_id: str, - selected_public_profile: dict[str, Any], - temp_token: str, - user_profile: dict[str, Any], - *, - x_connect_token: str | None = None, - refresh_token: str | None = None, - expires_in: int | None = None, - redirect_url: str | None = None, - ) -> dict[str, Any]: + return await self._client._aget("/v1/connect/snapchat/select-profile", params=params) + + async def aselect_snapchat_profile(self, profile_id: str, selected_public_profile: dict[str, Any], temp_token: str, user_profile: dict[str, Any], *, x_connect_token: str | None = None, refresh_token: str | None = None, expires_in: int | None = None, redirect_url: str | None = None) -> dict[str, Any]: """Select Snapchat profile (async)""" payload = self._build_payload( profile_id=profile_id, @@ -597,18 +412,9 @@ async def aselect_snapchat_profile( expires_in=expires_in, redirect_url=redirect_url, ) - return await self._client._apost( - "/v1/connect/snapchat/select-profile", data=payload - ) + return await self._client._apost("/v1/connect/snapchat/select-profile", data=payload) - async def aconnect_bluesky_credentials( - self, - identifier: str, - app_password: str, - state: str, - *, - redirect_uri: str | None = None, - ) -> dict[str, Any]: + async def aconnect_bluesky_credentials(self, identifier: str, app_password: str, state: str, *, redirect_uri: str | None = None) -> dict[str, Any]: """Connect Bluesky account (async)""" payload = self._build_payload( identifier=identifier, @@ -616,13 +422,9 @@ async def aconnect_bluesky_credentials( state=state, redirect_uri=redirect_uri, ) - return await self._client._apost( - "/v1/connect/bluesky/credentials", data=payload - ) + return await self._client._apost("/v1/connect/bluesky/credentials", data=payload) - async def aconnect_whats_app_credentials( - self, profile_id: str, access_token: str, waba_id: str, phone_number_id: str - ) -> dict[str, Any]: + async def aconnect_whats_app_credentials(self, profile_id: str, access_token: str, waba_id: str, phone_number_id: str) -> dict[str, Any]: """Connect WhatsApp via credentials (async)""" payload = self._build_payload( profile_id=profile_id, @@ -630,9 +432,7 @@ async def aconnect_whats_app_credentials( waba_id=waba_id, phone_number_id=phone_number_id, ) - return await self._client._apost( - "/v1/connect/whatsapp/credentials", data=payload - ) + return await self._client._apost("/v1/connect/whatsapp/credentials", data=payload) async def aget_telegram_connect_status(self, profile_id: str) -> dict[str, Any]: """Generate Telegram code (async)""" @@ -641,9 +441,7 @@ async def aget_telegram_connect_status(self, profile_id: str) -> dict[str, Any]: ) return await self._client._aget("/v1/connect/telegram", params=params) - async def ainitiate_telegram_connect( - self, chat_id: str, profile_id: str - ) -> dict[str, Any]: + async def ainitiate_telegram_connect(self, chat_id: str, profile_id: str) -> dict[str, Any]: """Connect Telegram directly (async)""" payload = self._build_payload( chat_id=chat_id, @@ -662,96 +460,74 @@ async def aget_facebook_pages(self, account_id: str) -> dict[str, Any]: """List Facebook pages (async)""" return await self._client._aget(f"/v1/accounts/{account_id}/facebook-page") - async def aupdate_facebook_page( - self, account_id: str, selected_page_id: str - ) -> dict[str, Any]: + async def aupdate_facebook_page(self, account_id: str, selected_page_id: str) -> dict[str, Any]: """Update Facebook page (async)""" payload = self._build_payload( selected_page_id=selected_page_id, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/facebook-page", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/facebook-page", data=payload) async def aget_linked_in_organizations(self, account_id: str) -> dict[str, Any]: """List LinkedIn orgs (async)""" - return await self._client._aget( - f"/v1/accounts/{account_id}/linkedin-organizations" - ) + return await self._client._aget(f"/v1/accounts/{account_id}/linkedin-organizations") - async def aupdate_linked_in_organization( - self, - account_id: str, - account_type: str, - *, - selected_organization: dict[str, Any] | None = None, - ) -> dict[str, Any]: + async def aupdate_linked_in_organization(self, account_id: str, account_type: str, *, selected_organization: dict[str, Any] | None = None) -> dict[str, Any]: """Switch LinkedIn account type (async)""" payload = self._build_payload( account_type=account_type, selected_organization=selected_organization, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/linkedin-organization", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/linkedin-organization", data=payload) async def aget_pinterest_boards(self, account_id: str) -> dict[str, Any]: """List Pinterest boards (async)""" return await self._client._aget(f"/v1/accounts/{account_id}/pinterest-boards") - async def aupdate_pinterest_boards( - self, - account_id: str, - default_board_id: str, - *, - default_board_name: str | None = None, - ) -> dict[str, Any]: + async def aupdate_pinterest_boards(self, account_id: str, default_board_id: str, *, default_board_name: str | None = None) -> dict[str, Any]: """Set default Pinterest board (async)""" payload = self._build_payload( default_board_id=default_board_id, default_board_name=default_board_name, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/pinterest-boards", data=payload + return await self._client._aput(f"/v1/accounts/{account_id}/pinterest-boards", data=payload) + + async def aget_youtube_playlists(self, account_id: str) -> dict[str, Any]: + """List YouTube playlists (async)""" + return await self._client._aget(f"/v1/accounts/{account_id}/youtube-playlists") + + async def aupdate_youtube_default_playlist(self, account_id: str, default_playlist_id: str, *, default_playlist_name: str | None = None) -> dict[str, Any]: + """Set default YouTube playlist (async)""" + payload = self._build_payload( + default_playlist_id=default_playlist_id, + default_playlist_name=default_playlist_name, ) + return await self._client._aput(f"/v1/accounts/{account_id}/youtube-playlists", data=payload) async def aget_gmb_locations(self, account_id: str) -> dict[str, Any]: """List GBP locations (async)""" return await self._client._aget(f"/v1/accounts/{account_id}/gmb-locations") - async def aupdate_gmb_location( - self, account_id: str, selected_location_id: str - ) -> dict[str, Any]: + async def aupdate_gmb_location(self, account_id: str, selected_location_id: str) -> dict[str, Any]: """Update GBP location (async)""" payload = self._build_payload( selected_location_id=selected_location_id, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/gmb-locations", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/gmb-locations", data=payload) async def aget_reddit_subreddits(self, account_id: str) -> dict[str, Any]: """List Reddit subreddits (async)""" return await self._client._aget(f"/v1/accounts/{account_id}/reddit-subreddits") - async def aupdate_reddit_subreddits( - self, account_id: str, default_subreddit: str - ) -> dict[str, Any]: + async def aupdate_reddit_subreddits(self, account_id: str, default_subreddit: str) -> dict[str, Any]: """Set default subreddit (async)""" payload = self._build_payload( default_subreddit=default_subreddit, ) - return await self._client._aput( - f"/v1/accounts/{account_id}/reddit-subreddits", data=payload - ) + return await self._client._aput(f"/v1/accounts/{account_id}/reddit-subreddits", data=payload) - async def aget_reddit_flairs( - self, account_id: str, subreddit: str - ) -> dict[str, Any]: + async def aget_reddit_flairs(self, account_id: str, subreddit: str) -> dict[str, Any]: """List subreddit flairs (async)""" params = self._build_params( subreddit=subreddit, ) - return await self._client._aget( - f"/v1/accounts/{account_id}/reddit-flairs", params=params - ) + return await self._client._aget(f"/v1/accounts/{account_id}/reddit-flairs", params=params) diff --git a/src/late/resources/_generated/contacts.py b/src/late/resources/_generated/contacts.py index 3c8b5b7..ad72c89 100644 --- a/src/late/resources/_generated/contacts.py +++ b/src/late/resources/_generated/contacts.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,17 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_contacts( - self, - *, - profile_id: str | None = None, - search: str | None = None, - tag: str | None = None, - platform: str | None = None, - is_subscribed: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + def list_contacts(self, *, profile_id: str | None = None, search: str | None = None, tag: str | None = None, platform: str | None = None, is_subscribed: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List contacts""" params = self._build_params( profile_id=profile_id, @@ -71,21 +57,7 @@ def list_contacts( ) return self._client._get("/v1/contacts", params=params) - def create_contact( - self, - profile_id: str, - name: str, - *, - email: str | None = None, - company: str | None = None, - tags: list[str] | None = None, - is_subscribed: bool | None = True, - notes: str | None = None, - account_id: str | None = None, - platform: str | None = None, - platform_identifier: str | None = None, - display_identifier: str | None = None, - ) -> dict[str, Any]: + def create_contact(self, profile_id: str, name: str, *, email: str | None = None, company: str | None = None, tags: list[str] | None = None, is_subscribed: bool | None = True, notes: str | None = None, account_id: str | None = None, platform: str | None = None, platform_identifier: str | None = None, display_identifier: str | None = None) -> dict[str, Any]: """Create a contact""" payload = self._build_payload( profile_id=profile_id, @@ -106,19 +78,7 @@ def get_contact(self, contact_id: str) -> dict[str, Any]: """Get contact with channels""" return self._client._get(f"/v1/contacts/{contact_id}") - def update_contact( - self, - contact_id: str, - *, - name: str | None = None, - email: str | None = None, - company: str | None = None, - avatar_url: str | None = None, - tags: list[str] | None = None, - is_subscribed: bool | None = None, - is_blocked: bool | None = None, - notes: str | None = None, - ) -> dict[str, Any]: + def update_contact(self, contact_id: str, *, name: str | None = None, email: str | None = None, company: str | None = None, avatar_url: str | None = None, tags: list[str] | None = None, is_subscribed: bool | None = None, is_blocked: bool | None = None, notes: str | None = None) -> dict[str, Any]: """Update a contact""" payload = self._build_payload( name=name, @@ -140,13 +100,7 @@ def get_contact_channels(self, contact_id: str) -> dict[str, Any]: """List channels for a contact""" return self._client._get(f"/v1/contacts/{contact_id}/channels") - def bulk_create_contacts( - self, - profile_id: str, - account_id: str, - platform: str, - contacts: list[dict[str, Any]], - ) -> dict[str, Any]: + def bulk_create_contacts(self, profile_id: str, account_id: str, platform: str, contacts: list[dict[str, Any]]) -> dict[str, Any]: """Bulk create contacts""" payload = self._build_payload( profile_id=profile_id, @@ -156,17 +110,7 @@ def bulk_create_contacts( ) return self._client._post("/v1/contacts/bulk", data=payload) - async def alist_contacts( - self, - *, - profile_id: str | None = None, - search: str | None = None, - tag: str | None = None, - platform: str | None = None, - is_subscribed: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + async def alist_contacts(self, *, profile_id: str | None = None, search: str | None = None, tag: str | None = None, platform: str | None = None, is_subscribed: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List contacts (async)""" params = self._build_params( profile_id=profile_id, @@ -179,21 +123,7 @@ async def alist_contacts( ) return await self._client._aget("/v1/contacts", params=params) - async def acreate_contact( - self, - profile_id: str, - name: str, - *, - email: str | None = None, - company: str | None = None, - tags: list[str] | None = None, - is_subscribed: bool | None = True, - notes: str | None = None, - account_id: str | None = None, - platform: str | None = None, - platform_identifier: str | None = None, - display_identifier: str | None = None, - ) -> dict[str, Any]: + async def acreate_contact(self, profile_id: str, name: str, *, email: str | None = None, company: str | None = None, tags: list[str] | None = None, is_subscribed: bool | None = True, notes: str | None = None, account_id: str | None = None, platform: str | None = None, platform_identifier: str | None = None, display_identifier: str | None = None) -> dict[str, Any]: """Create a contact (async)""" payload = self._build_payload( profile_id=profile_id, @@ -214,19 +144,7 @@ async def aget_contact(self, contact_id: str) -> dict[str, Any]: """Get contact with channels (async)""" return await self._client._aget(f"/v1/contacts/{contact_id}") - async def aupdate_contact( - self, - contact_id: str, - *, - name: str | None = None, - email: str | None = None, - company: str | None = None, - avatar_url: str | None = None, - tags: list[str] | None = None, - is_subscribed: bool | None = None, - is_blocked: bool | None = None, - notes: str | None = None, - ) -> dict[str, Any]: + async def aupdate_contact(self, contact_id: str, *, name: str | None = None, email: str | None = None, company: str | None = None, avatar_url: str | None = None, tags: list[str] | None = None, is_subscribed: bool | None = None, is_blocked: bool | None = None, notes: str | None = None) -> dict[str, Any]: """Update a contact (async)""" payload = self._build_payload( name=name, @@ -248,13 +166,7 @@ async def aget_contact_channels(self, contact_id: str) -> dict[str, Any]: """List channels for a contact (async)""" return await self._client._aget(f"/v1/contacts/{contact_id}/channels") - async def abulk_create_contacts( - self, - profile_id: str, - account_id: str, - platform: str, - contacts: list[dict[str, Any]], - ) -> dict[str, Any]: + async def abulk_create_contacts(self, profile_id: str, account_id: str, platform: str, contacts: list[dict[str, Any]]) -> dict[str, Any]: """Bulk create contacts (async)""" payload = self._build_payload( profile_id=profile_id, diff --git a/src/late/resources/_generated/custom_fields.py b/src/late/resources/_generated/custom_fields.py index 02d1678..81b80d2 100644 --- a/src/late/resources/_generated/custom_fields.py +++ b/src/late/resources/_generated/custom_fields.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,16 +44,12 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def set_contact_field_value( - self, contact_id: str, slug: str, value: Any - ) -> dict[str, Any]: + def set_contact_field_value(self, contact_id: str, slug: str, value: Any) -> dict[str, Any]: """Set a custom field value""" payload = self._build_payload( value=value, ) - return self._client._put( - f"/v1/contacts/{contact_id}/fields/{slug}", data=payload - ) + return self._client._put(f"/v1/contacts/{contact_id}/fields/{slug}", data=payload) def clear_contact_field_value(self, contact_id: str, slug: str) -> dict[str, Any]: """Clear a custom field value""" @@ -70,15 +62,7 @@ def list_custom_fields(self, *, profile_id: str | None = None) -> dict[str, Any] ) return self._client._get("/v1/custom-fields", params=params) - def create_custom_field( - self, - profile_id: str, - name: str, - type: str, - *, - slug: str | None = None, - options: list[str] | None = None, - ) -> dict[str, Any]: + def create_custom_field(self, profile_id: str, name: str, type: str, *, slug: str | None = None, options: list[str] | None = None) -> dict[str, Any]: """Create a custom field definition""" payload = self._build_payload( profile_id=profile_id, @@ -89,13 +73,7 @@ def create_custom_field( ) return self._client._post("/v1/custom-fields", data=payload) - def update_custom_field( - self, - field_id: str, - *, - name: str | None = None, - options: list[str] | None = None, - ) -> dict[str, Any]: + def update_custom_field(self, field_id: str, *, name: str | None = None, options: list[str] | None = None) -> dict[str, Any]: """Update a custom field definition""" payload = self._build_payload( name=name, @@ -107,41 +85,25 @@ def delete_custom_field(self, field_id: str) -> dict[str, Any]: """Delete a custom field definition""" return self._client._delete(f"/v1/custom-fields/{field_id}") - async def aset_contact_field_value( - self, contact_id: str, slug: str, value: Any - ) -> dict[str, Any]: + async def aset_contact_field_value(self, contact_id: str, slug: str, value: Any) -> dict[str, Any]: """Set a custom field value (async)""" payload = self._build_payload( value=value, ) - return await self._client._aput( - f"/v1/contacts/{contact_id}/fields/{slug}", data=payload - ) + return await self._client._aput(f"/v1/contacts/{contact_id}/fields/{slug}", data=payload) - async def aclear_contact_field_value( - self, contact_id: str, slug: str - ) -> dict[str, Any]: + async def aclear_contact_field_value(self, contact_id: str, slug: str) -> dict[str, Any]: """Clear a custom field value (async)""" return await self._client._adelete(f"/v1/contacts/{contact_id}/fields/{slug}") - async def alist_custom_fields( - self, *, profile_id: str | None = None - ) -> dict[str, Any]: + async def alist_custom_fields(self, *, profile_id: str | None = None) -> dict[str, Any]: """List custom field definitions (async)""" params = self._build_params( profile_id=profile_id, ) return await self._client._aget("/v1/custom-fields", params=params) - async def acreate_custom_field( - self, - profile_id: str, - name: str, - type: str, - *, - slug: str | None = None, - options: list[str] | None = None, - ) -> dict[str, Any]: + async def acreate_custom_field(self, profile_id: str, name: str, type: str, *, slug: str | None = None, options: list[str] | None = None) -> dict[str, Any]: """Create a custom field definition (async)""" payload = self._build_payload( profile_id=profile_id, @@ -152,13 +114,7 @@ async def acreate_custom_field( ) return await self._client._apost("/v1/custom-fields", data=payload) - async def aupdate_custom_field( - self, - field_id: str, - *, - name: str | None = None, - options: list[str] | None = None, - ) -> dict[str, Any]: + async def aupdate_custom_field(self, field_id: str, *, name: str | None = None, options: list[str] | None = None) -> dict[str, Any]: """Update a custom field definition (async)""" payload = self._build_payload( name=name, diff --git a/src/late/resources/_generated/invites.py b/src/late/resources/_generated/invites.py index d29eeb8..837b31a 100644 --- a/src/late/resources/_generated/invites.py +++ b/src/late/resources/_generated/invites.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,9 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def create_invite_token( - self, scope: str, *, profile_ids: list[str] | None = None - ) -> dict[str, Any]: + def create_invite_token(self, scope: str, *, profile_ids: list[str] | None = None) -> dict[str, Any]: """Create invite token""" payload = self._build_payload( scope=scope, @@ -58,9 +52,7 @@ def create_invite_token( ) return self._client._post("/v1/invite/tokens", data=payload) - async def acreate_invite_token( - self, scope: str, *, profile_ids: list[str] | None = None - ) -> dict[str, Any]: + async def acreate_invite_token(self, scope: str, *, profile_ids: list[str] | None = None) -> dict[str, Any]: """Create invite token (async)""" payload = self._build_payload( scope=scope, diff --git a/src/late/resources/_generated/logs.py b/src/late/resources/_generated/logs.py index a7577db..489a3f9 100644 --- a/src/late/resources/_generated/logs.py +++ b/src/late/resources/_generated/logs.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,17 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_posts_logs( - self, - *, - status: str | None = None, - platform: str | None = None, - action: str | None = None, - days: int | None = 7, - limit: int | None = 50, - skip: int | None = 0, - search: str | None = None, - ) -> dict[str, Any]: + def list_posts_logs(self, *, status: str | None = None, platform: str | None = None, action: str | None = None, days: int | None = 7, limit: int | None = 50, skip: int | None = 0, search: str | None = None) -> dict[str, Any]: """List publishing logs""" params = self._build_params( status=status, @@ -71,16 +57,7 @@ def list_posts_logs( ) return self._client._get("/v1/posts/logs", params=params) - def list_connection_logs( - self, - *, - platform: str | None = None, - event_type: str | None = None, - status: str | None = None, - days: int | None = 7, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + def list_connection_logs(self, *, platform: str | None = None, event_type: str | None = None, status: str | None = None, days: int | None = 7, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List connection logs""" params = self._build_params( platform=platform, @@ -99,17 +76,7 @@ def get_post_logs(self, post_id: str, *, limit: int | None = 50) -> dict[str, An ) return self._client._get(f"/v1/posts/{post_id}/logs", params=params) - async def alist_posts_logs( - self, - *, - status: str | None = None, - platform: str | None = None, - action: str | None = None, - days: int | None = 7, - limit: int | None = 50, - skip: int | None = 0, - search: str | None = None, - ) -> dict[str, Any]: + async def alist_posts_logs(self, *, status: str | None = None, platform: str | None = None, action: str | None = None, days: int | None = 7, limit: int | None = 50, skip: int | None = 0, search: str | None = None) -> dict[str, Any]: """List publishing logs (async)""" params = self._build_params( status=status, @@ -122,16 +89,7 @@ async def alist_posts_logs( ) return await self._client._aget("/v1/posts/logs", params=params) - async def alist_connection_logs( - self, - *, - platform: str | None = None, - event_type: str | None = None, - status: str | None = None, - days: int | None = 7, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + async def alist_connection_logs(self, *, platform: str | None = None, event_type: str | None = None, status: str | None = None, days: int | None = 7, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List connection logs (async)""" params = self._build_params( platform=platform, @@ -143,9 +101,7 @@ async def alist_connection_logs( ) return await self._client._aget("/v1/connections/logs", params=params) - async def aget_post_logs( - self, post_id: str, *, limit: int | None = 50 - ) -> dict[str, Any]: + async def aget_post_logs(self, post_id: str, *, limit: int | None = 50) -> dict[str, Any]: """Get post logs (async)""" params = self._build_params( limit=limit, diff --git a/src/late/resources/_generated/media.py b/src/late/resources/_generated/media.py index 6f99ecf..baeff0e 100644 --- a/src/late/resources/_generated/media.py +++ b/src/late/resources/_generated/media.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,9 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def get_media_presigned_url( - self, filename: str, content_type: str, *, size: int | None = None - ) -> dict[str, Any]: + def get_media_presigned_url(self, filename: str, content_type: str, *, size: int | None = None) -> dict[str, Any]: """Get presigned upload URL""" payload = self._build_payload( filename=filename, @@ -59,9 +53,7 @@ def get_media_presigned_url( ) return self._client._post("/v1/media/presign", data=payload) - async def aget_media_presigned_url( - self, filename: str, content_type: str, *, size: int | None = None - ) -> dict[str, Any]: + async def aget_media_presigned_url(self, filename: str, content_type: str, *, size: int | None = None) -> dict[str, Any]: """Get presigned upload URL (async)""" payload = self._build_payload( filename=filename, diff --git a/src/late/resources/_generated/messages.py b/src/late/resources/_generated/messages.py index 99f4cf2..5cc457d 100644 --- a/src/late/resources/_generated/messages.py +++ b/src/late/resources/_generated/messages.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,17 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_inbox_conversations( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - status: str | None = None, - sort_order: str | None = "desc", - limit: int | None = 50, - cursor: str | None = None, - account_id: str | None = None, - ) -> dict[str, Any]: + def list_inbox_conversations(self, *, profile_id: str | None = None, platform: str | None = None, status: str | None = None, sort_order: str | None = "desc", limit: int | None = 50, cursor: str | None = None, account_id: str | None = None) -> dict[str, Any]: """List conversations""" params = self._build_params( profile_id=profile_id, @@ -71,56 +57,29 @@ def list_inbox_conversations( ) return self._client._get("/v1/inbox/conversations", params=params) - def get_inbox_conversation( - self, conversation_id: str, account_id: str - ) -> dict[str, Any]: + def get_inbox_conversation(self, conversation_id: str, account_id: str) -> dict[str, Any]: """Get conversation""" params = self._build_params( account_id=account_id, ) - return self._client._get( - f"/v1/inbox/conversations/{conversation_id}", params=params - ) + return self._client._get(f"/v1/inbox/conversations/{conversation_id}", params=params) - def update_inbox_conversation( - self, conversation_id: str, account_id: str, status: str - ) -> dict[str, Any]: + def update_inbox_conversation(self, conversation_id: str, account_id: str, status: str) -> dict[str, Any]: """Update conversation status""" payload = self._build_payload( account_id=account_id, status=status, ) - return self._client._put( - f"/v1/inbox/conversations/{conversation_id}", data=payload - ) + return self._client._put(f"/v1/inbox/conversations/{conversation_id}", data=payload) - def get_inbox_conversation_messages( - self, conversation_id: str, account_id: str - ) -> dict[str, Any]: + def get_inbox_conversation_messages(self, conversation_id: str, account_id: str) -> dict[str, Any]: """List messages""" params = self._build_params( account_id=account_id, ) - return self._client._get( - f"/v1/inbox/conversations/{conversation_id}/messages", params=params - ) + return self._client._get(f"/v1/inbox/conversations/{conversation_id}/messages", params=params) - def send_inbox_message( - self, - conversation_id: str, - account_id: str, - *, - message: str | None = None, - attachment_url: str | None = None, - attachment_type: str | None = None, - quick_replies: list[dict[str, Any]] | None = None, - buttons: list[dict[str, Any]] | None = None, - template: dict[str, Any] | None = None, - reply_markup: dict[str, Any] | None = None, - messaging_type: str | None = None, - message_tag: str | None = None, - reply_to: str | None = None, - ) -> dict[str, Any]: + def send_inbox_message(self, conversation_id: str, account_id: str, *, message: str | None = None, attachment_url: str | None = None, attachment_type: str | None = None, quick_replies: list[dict[str, Any]] | None = None, buttons: list[dict[str, Any]] | None = None, template: dict[str, Any] | None = None, reply_markup: dict[str, Any] | None = None, messaging_type: str | None = None, message_tag: str | None = None, reply_to: str | None = None) -> dict[str, Any]: """Send message""" payload = self._build_payload( account_id=account_id, @@ -135,41 +94,51 @@ def send_inbox_message( message_tag=message_tag, reply_to=reply_to, ) - return self._client._post( - f"/v1/inbox/conversations/{conversation_id}/messages", data=payload - ) + return self._client._post(f"/v1/inbox/conversations/{conversation_id}/messages", data=payload) - def edit_inbox_message( - self, - conversation_id: str, - message_id: str, - account_id: str, - *, - text: str | None = None, - reply_markup: dict[str, Any] | None = None, - ) -> dict[str, Any]: + def edit_inbox_message(self, conversation_id: str, message_id: str, account_id: str, *, text: str | None = None, reply_markup: dict[str, Any] | None = None) -> dict[str, Any]: """Edit message""" payload = self._build_payload( account_id=account_id, text=text, reply_markup=reply_markup, ) - return self._client._patch( - f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}", - data=payload, + return self._client._patch(f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}", data=payload) + + def delete_inbox_message(self, conversation_id: str, message_id: str, account_id: str) -> dict[str, Any]: + """Delete message""" + params = self._build_params( + account_id=account_id, + ) + return self._client._delete(f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}", params=params) + + def send_typing_indicator(self, conversation_id: str, account_id: str) -> dict[str, Any]: + """Send typing indicator""" + payload = self._build_payload( + account_id=account_id, + ) + return self._client._post(f"/v1/inbox/conversations/{conversation_id}/typing", data=payload) + + def add_message_reaction(self, conversation_id: str, message_id: str, account_id: str, emoji: str) -> dict[str, Any]: + """Add reaction""" + payload = self._build_payload( + account_id=account_id, + emoji=emoji, + ) + return self._client._post(f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}/reactions", data=payload) + + def remove_message_reaction(self, conversation_id: str, message_id: str, account_id: str) -> dict[str, Any]: + """Remove reaction""" + params = self._build_params( + account_id=account_id, ) + return self._client._delete(f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}/reactions", params=params) + + def upload_media_direct(self) -> dict[str, Any]: + """Upload media file""" + return self._client._post("/v1/media/upload-direct") - async def alist_inbox_conversations( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - status: str | None = None, - sort_order: str | None = "desc", - limit: int | None = 50, - cursor: str | None = None, - account_id: str | None = None, - ) -> dict[str, Any]: + async def alist_inbox_conversations(self, *, profile_id: str | None = None, platform: str | None = None, status: str | None = None, sort_order: str | None = "desc", limit: int | None = 50, cursor: str | None = None, account_id: str | None = None) -> dict[str, Any]: """List conversations (async)""" params = self._build_params( profile_id=profile_id, @@ -182,56 +151,29 @@ async def alist_inbox_conversations( ) return await self._client._aget("/v1/inbox/conversations", params=params) - async def aget_inbox_conversation( - self, conversation_id: str, account_id: str - ) -> dict[str, Any]: + async def aget_inbox_conversation(self, conversation_id: str, account_id: str) -> dict[str, Any]: """Get conversation (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._aget( - f"/v1/inbox/conversations/{conversation_id}", params=params - ) + return await self._client._aget(f"/v1/inbox/conversations/{conversation_id}", params=params) - async def aupdate_inbox_conversation( - self, conversation_id: str, account_id: str, status: str - ) -> dict[str, Any]: + async def aupdate_inbox_conversation(self, conversation_id: str, account_id: str, status: str) -> dict[str, Any]: """Update conversation status (async)""" payload = self._build_payload( account_id=account_id, status=status, ) - return await self._client._aput( - f"/v1/inbox/conversations/{conversation_id}", data=payload - ) + return await self._client._aput(f"/v1/inbox/conversations/{conversation_id}", data=payload) - async def aget_inbox_conversation_messages( - self, conversation_id: str, account_id: str - ) -> dict[str, Any]: + async def aget_inbox_conversation_messages(self, conversation_id: str, account_id: str) -> dict[str, Any]: """List messages (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._aget( - f"/v1/inbox/conversations/{conversation_id}/messages", params=params - ) + return await self._client._aget(f"/v1/inbox/conversations/{conversation_id}/messages", params=params) - async def asend_inbox_message( - self, - conversation_id: str, - account_id: str, - *, - message: str | None = None, - attachment_url: str | None = None, - attachment_type: str | None = None, - quick_replies: list[dict[str, Any]] | None = None, - buttons: list[dict[str, Any]] | None = None, - template: dict[str, Any] | None = None, - reply_markup: dict[str, Any] | None = None, - messaging_type: str | None = None, - message_tag: str | None = None, - reply_to: str | None = None, - ) -> dict[str, Any]: + async def asend_inbox_message(self, conversation_id: str, account_id: str, *, message: str | None = None, attachment_url: str | None = None, attachment_type: str | None = None, quick_replies: list[dict[str, Any]] | None = None, buttons: list[dict[str, Any]] | None = None, template: dict[str, Any] | None = None, reply_markup: dict[str, Any] | None = None, messaging_type: str | None = None, message_tag: str | None = None, reply_to: str | None = None) -> dict[str, Any]: """Send message (async)""" payload = self._build_payload( account_id=account_id, @@ -246,26 +188,46 @@ async def asend_inbox_message( message_tag=message_tag, reply_to=reply_to, ) - return await self._client._apost( - f"/v1/inbox/conversations/{conversation_id}/messages", data=payload - ) + return await self._client._apost(f"/v1/inbox/conversations/{conversation_id}/messages", data=payload) - async def aedit_inbox_message( - self, - conversation_id: str, - message_id: str, - account_id: str, - *, - text: str | None = None, - reply_markup: dict[str, Any] | None = None, - ) -> dict[str, Any]: + async def aedit_inbox_message(self, conversation_id: str, message_id: str, account_id: str, *, text: str | None = None, reply_markup: dict[str, Any] | None = None) -> dict[str, Any]: """Edit message (async)""" payload = self._build_payload( account_id=account_id, text=text, reply_markup=reply_markup, ) - return await self._client._apatch( - f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}", - data=payload, + return await self._client._apatch(f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}", data=payload) + + async def adelete_inbox_message(self, conversation_id: str, message_id: str, account_id: str) -> dict[str, Any]: + """Delete message (async)""" + params = self._build_params( + account_id=account_id, + ) + return await self._client._adelete(f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}", params=params) + + async def asend_typing_indicator(self, conversation_id: str, account_id: str) -> dict[str, Any]: + """Send typing indicator (async)""" + payload = self._build_payload( + account_id=account_id, + ) + return await self._client._apost(f"/v1/inbox/conversations/{conversation_id}/typing", data=payload) + + async def aadd_message_reaction(self, conversation_id: str, message_id: str, account_id: str, emoji: str) -> dict[str, Any]: + """Add reaction (async)""" + payload = self._build_payload( + account_id=account_id, + emoji=emoji, ) + return await self._client._apost(f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}/reactions", data=payload) + + async def aremove_message_reaction(self, conversation_id: str, message_id: str, account_id: str) -> dict[str, Any]: + """Remove reaction (async)""" + params = self._build_params( + account_id=account_id, + ) + return await self._client._adelete(f"/v1/inbox/conversations/{conversation_id}/messages/{message_id}/reactions", params=params) + + async def aupload_media_direct(self) -> dict[str, Any]: + """Upload media file (async)""" + return await self._client._apost("/v1/media/upload-direct") diff --git a/src/late/resources/_generated/posts.py b/src/late/resources/_generated/posts.py index 5573952..62cb562 100644 --- a/src/late/resources/_generated/posts.py +++ b/src/late/resources/_generated/posts.py @@ -25,21 +25,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -50,21 +46,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_posts( - self, - *, - page: int | None = 1, - limit: int | None = 10, - status: str | None = None, - platform: str | None = None, - profile_id: str | None = None, - created_by: str | None = None, - date_from: str | None = None, - date_to: str | None = None, - include_hidden: bool | None = False, - search: str | None = None, - sort_by: str | None = "scheduled-desc", - ) -> dict[str, Any]: + def list_posts(self, *, page: int | None = 1, limit: int | None = 10, status: str | None = None, platform: str | None = None, profile_id: str | None = None, created_by: str | None = None, date_from: str | None = None, date_to: str | None = None, include_hidden: bool | None = False, search: str | None = None, sort_by: str | None = "scheduled-desc") -> dict[str, Any]: """List posts""" params = self._build_params( page=page, @@ -81,27 +63,7 @@ def list_posts( ) return self._client._get("/v1/posts", params=params) - def create_post( - self, - *, - title: str | None = None, - content: str | None = None, - media_items: list[dict[str, Any]] | None = None, - platforms: list[dict[str, Any]] | None = None, - scheduled_for: datetime | str | None = None, - publish_now: bool | None = False, - is_draft: bool | None = False, - timezone: str | None = "UTC", - tags: list[str] | None = None, - hashtags: list[str] | None = None, - mentions: list[str] | None = None, - crossposting_enabled: bool | None = True, - metadata: dict[str, Any] | None = None, - tiktok_settings: Any | None = None, - recycling: Any | None = None, - queued_from_profile: str | None = None, - queue_id: str | None = None, - ) -> dict[str, Any]: + def create_post(self, *, title: str | None = None, content: str | None = None, media_items: list[dict[str, Any]] | None = None, platforms: list[dict[str, Any]] | None = None, scheduled_for: datetime | str | None = None, publish_now: bool | None = False, is_draft: bool | None = False, timezone: str | None = "UTC", tags: list[str] | None = None, hashtags: list[str] | None = None, mentions: list[str] | None = None, crossposting_enabled: bool | None = True, metadata: dict[str, Any] | None = None, tiktok_settings: Any | None = None, recycling: Any | None = None, queued_from_profile: str | None = None, queue_id: str | None = None) -> dict[str, Any]: """Create post""" payload = self._build_payload( title=title, @@ -128,15 +90,7 @@ def get_post(self, post_id: str) -> dict[str, Any]: """Get post""" return self._client._get(f"/v1/posts/{post_id}") - def update_post( - self, - post_id: str, - *, - content: str | None = None, - scheduled_for: datetime | str | None = None, - tiktok_settings: Any | None = None, - recycling: Any | None = None, - ) -> dict[str, Any]: + def update_post(self, post_id: str, *, content: str | None = None, scheduled_for: datetime | str | None = None, tiktok_settings: Any | None = None, recycling: Any | None = None) -> dict[str, Any]: """Update post""" payload = self._build_payload( content=content, @@ -168,43 +122,25 @@ def unpublish_post(self, post_id: str, platform: str) -> dict[str, Any]: ) return self._client._post(f"/v1/posts/{post_id}/unpublish", data=payload) - def update_post_metadata( - self, - post_id: str, - platform: str, - *, - title: str | None = None, - description: str | None = None, - tags: list[str] | None = None, - category_id: str | None = None, - privacy_status: str | None = None, - ) -> dict[str, Any]: + def update_post_metadata(self, post_id: str, platform: str, *, video_id: str | None = None, account_id: str | None = None, title: str | None = None, description: str | None = None, tags: list[str] | None = None, category_id: str | None = None, privacy_status: str | None = None, thumbnail_url: str | None = None, made_for_kids: bool | None = None, contains_synthetic_media: bool | None = None, playlist_id: str | None = None) -> dict[str, Any]: """Update post metadata""" payload = self._build_payload( platform=platform, + video_id=video_id, + account_id=account_id, title=title, description=description, tags=tags, category_id=category_id, privacy_status=privacy_status, + thumbnail_url=thumbnail_url, + made_for_kids=made_for_kids, + contains_synthetic_media=contains_synthetic_media, + playlist_id=playlist_id, ) return self._client._post(f"/v1/posts/{post_id}/update-metadata", data=payload) - async def alist_posts( - self, - *, - page: int | None = 1, - limit: int | None = 10, - status: str | None = None, - platform: str | None = None, - profile_id: str | None = None, - created_by: str | None = None, - date_from: str | None = None, - date_to: str | None = None, - include_hidden: bool | None = False, - search: str | None = None, - sort_by: str | None = "scheduled-desc", - ) -> dict[str, Any]: + async def alist_posts(self, *, page: int | None = 1, limit: int | None = 10, status: str | None = None, platform: str | None = None, profile_id: str | None = None, created_by: str | None = None, date_from: str | None = None, date_to: str | None = None, include_hidden: bool | None = False, search: str | None = None, sort_by: str | None = "scheduled-desc") -> dict[str, Any]: """List posts (async)""" params = self._build_params( page=page, @@ -221,27 +157,7 @@ async def alist_posts( ) return await self._client._aget("/v1/posts", params=params) - async def acreate_post( - self, - *, - title: str | None = None, - content: str | None = None, - media_items: list[dict[str, Any]] | None = None, - platforms: list[dict[str, Any]] | None = None, - scheduled_for: datetime | str | None = None, - publish_now: bool | None = False, - is_draft: bool | None = False, - timezone: str | None = "UTC", - tags: list[str] | None = None, - hashtags: list[str] | None = None, - mentions: list[str] | None = None, - crossposting_enabled: bool | None = True, - metadata: dict[str, Any] | None = None, - tiktok_settings: Any | None = None, - recycling: Any | None = None, - queued_from_profile: str | None = None, - queue_id: str | None = None, - ) -> dict[str, Any]: + async def acreate_post(self, *, title: str | None = None, content: str | None = None, media_items: list[dict[str, Any]] | None = None, platforms: list[dict[str, Any]] | None = None, scheduled_for: datetime | str | None = None, publish_now: bool | None = False, is_draft: bool | None = False, timezone: str | None = "UTC", tags: list[str] | None = None, hashtags: list[str] | None = None, mentions: list[str] | None = None, crossposting_enabled: bool | None = True, metadata: dict[str, Any] | None = None, tiktok_settings: Any | None = None, recycling: Any | None = None, queued_from_profile: str | None = None, queue_id: str | None = None) -> dict[str, Any]: """Create post (async)""" payload = self._build_payload( title=title, @@ -268,15 +184,7 @@ async def aget_post(self, post_id: str) -> dict[str, Any]: """Get post (async)""" return await self._client._aget(f"/v1/posts/{post_id}") - async def aupdate_post( - self, - post_id: str, - *, - content: str | None = None, - scheduled_for: datetime | str | None = None, - tiktok_settings: Any | None = None, - recycling: Any | None = None, - ) -> dict[str, Any]: + async def aupdate_post(self, post_id: str, *, content: str | None = None, scheduled_for: datetime | str | None = None, tiktok_settings: Any | None = None, recycling: Any | None = None) -> dict[str, Any]: """Update post (async)""" payload = self._build_payload( content=content, @@ -290,9 +198,7 @@ async def adelete_post(self, post_id: str) -> dict[str, Any]: """Delete post (async)""" return await self._client._adelete(f"/v1/posts/{post_id}") - async def abulk_upload_posts( - self, *, dry_run: bool | None = False - ) -> dict[str, Any]: + async def abulk_upload_posts(self, *, dry_run: bool | None = False) -> dict[str, Any]: """Bulk upload from CSV (async)""" params = self._build_params( dry_run=dry_run, @@ -310,26 +216,20 @@ async def aunpublish_post(self, post_id: str, platform: str) -> dict[str, Any]: ) return await self._client._apost(f"/v1/posts/{post_id}/unpublish", data=payload) - async def aupdate_post_metadata( - self, - post_id: str, - platform: str, - *, - title: str | None = None, - description: str | None = None, - tags: list[str] | None = None, - category_id: str | None = None, - privacy_status: str | None = None, - ) -> dict[str, Any]: + async def aupdate_post_metadata(self, post_id: str, platform: str, *, video_id: str | None = None, account_id: str | None = None, title: str | None = None, description: str | None = None, tags: list[str] | None = None, category_id: str | None = None, privacy_status: str | None = None, thumbnail_url: str | None = None, made_for_kids: bool | None = None, contains_synthetic_media: bool | None = None, playlist_id: str | None = None) -> dict[str, Any]: """Update post metadata (async)""" payload = self._build_payload( platform=platform, + video_id=video_id, + account_id=account_id, title=title, description=description, tags=tags, category_id=category_id, privacy_status=privacy_status, + thumbnail_url=thumbnail_url, + made_for_kids=made_for_kids, + contains_synthetic_media=contains_synthetic_media, + playlist_id=playlist_id, ) - return await self._client._apost( - f"/v1/posts/{post_id}/update-metadata", data=payload - ) + return await self._client._apost(f"/v1/posts/{post_id}/update-metadata", data=payload) diff --git a/src/late/resources/_generated/profiles.py b/src/late/resources/_generated/profiles.py index 5a34709..e62c300 100644 --- a/src/late/resources/_generated/profiles.py +++ b/src/late/resources/_generated/profiles.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,18 +44,14 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_profiles( - self, *, include_over_limit: bool | None = False - ) -> dict[str, Any]: + def list_profiles(self, *, include_over_limit: bool | None = False) -> dict[str, Any]: """List profiles""" params = self._build_params( include_over_limit=include_over_limit, ) return self._client._get("/v1/profiles", params=params) - def create_profile( - self, name: str, *, description: str | None = None, color: str | None = None - ) -> dict[str, Any]: + def create_profile(self, name: str, *, description: str | None = None, color: str | None = None) -> dict[str, Any]: """Create profile""" payload = self._build_payload( name=name, @@ -72,15 +64,7 @@ def get_profile(self, profile_id: str) -> dict[str, Any]: """Get profile""" return self._client._get(f"/v1/profiles/{profile_id}") - def update_profile( - self, - profile_id: str, - *, - name: str | None = None, - description: str | None = None, - color: str | None = None, - is_default: bool | None = None, - ) -> dict[str, Any]: + def update_profile(self, profile_id: str, *, name: str | None = None, description: str | None = None, color: str | None = None, is_default: bool | None = None) -> dict[str, Any]: """Update profile""" payload = self._build_payload( name=name, @@ -94,18 +78,14 @@ def delete_profile(self, profile_id: str) -> dict[str, Any]: """Delete profile""" return self._client._delete(f"/v1/profiles/{profile_id}") - async def alist_profiles( - self, *, include_over_limit: bool | None = False - ) -> dict[str, Any]: + async def alist_profiles(self, *, include_over_limit: bool | None = False) -> dict[str, Any]: """List profiles (async)""" params = self._build_params( include_over_limit=include_over_limit, ) return await self._client._aget("/v1/profiles", params=params) - async def acreate_profile( - self, name: str, *, description: str | None = None, color: str | None = None - ) -> dict[str, Any]: + async def acreate_profile(self, name: str, *, description: str | None = None, color: str | None = None) -> dict[str, Any]: """Create profile (async)""" payload = self._build_payload( name=name, @@ -118,15 +98,7 @@ async def aget_profile(self, profile_id: str) -> dict[str, Any]: """Get profile (async)""" return await self._client._aget(f"/v1/profiles/{profile_id}") - async def aupdate_profile( - self, - profile_id: str, - *, - name: str | None = None, - description: str | None = None, - color: str | None = None, - is_default: bool | None = None, - ) -> dict[str, Any]: + async def aupdate_profile(self, profile_id: str, *, name: str | None = None, description: str | None = None, color: str | None = None, is_default: bool | None = None) -> dict[str, Any]: """Update profile (async)""" payload = self._build_payload( name=name, diff --git a/src/late/resources/_generated/queue.py b/src/late/resources/_generated/queue.py index 158cb82..e81f86c 100644 --- a/src/late/resources/_generated/queue.py +++ b/src/late/resources/_generated/queue.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,9 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_queue_slots( - self, profile_id: str, *, queue_id: str | None = None, all: str | None = None - ) -> dict[str, Any]: + def list_queue_slots(self, profile_id: str, *, queue_id: str | None = None, all: str | None = None) -> dict[str, Any]: """List schedules""" params = self._build_params( profile_id=profile_id, @@ -59,15 +53,7 @@ def list_queue_slots( ) return self._client._get("/v1/queue/slots", params=params) - def create_queue_slot( - self, - profile_id: str, - name: str, - timezone: str, - slots: list[Any], - *, - active: bool | None = True, - ) -> dict[str, Any]: + def create_queue_slot(self, profile_id: str, name: str, timezone: str, slots: list[Any], *, active: bool | None = True) -> dict[str, Any]: """Create schedule""" payload = self._build_payload( profile_id=profile_id, @@ -78,18 +64,7 @@ def create_queue_slot( ) return self._client._post("/v1/queue/slots", data=payload) - def update_queue_slot( - self, - profile_id: str, - timezone: str, - slots: list[Any], - *, - queue_id: str | None = None, - name: str | None = None, - active: bool | None = True, - set_as_default: bool | None = None, - reshuffle_existing: bool | None = False, - ) -> dict[str, Any]: + def update_queue_slot(self, profile_id: str, timezone: str, slots: list[Any], *, queue_id: str | None = None, name: str | None = None, active: bool | None = True, set_as_default: bool | None = None, reshuffle_existing: bool | None = False) -> dict[str, Any]: """Update schedule""" payload = self._build_payload( profile_id=profile_id, @@ -111,9 +86,7 @@ def delete_queue_slot(self, profile_id: str, queue_id: str) -> dict[str, Any]: ) return self._client._delete("/v1/queue/slots", params=params) - def preview_queue( - self, profile_id: str, *, queue_id: str | None = None, count: int | None = 20 - ) -> dict[str, Any]: + def preview_queue(self, profile_id: str, *, queue_id: str | None = None, count: int | None = 20) -> dict[str, Any]: """Preview upcoming slots""" params = self._build_params( profile_id=profile_id, @@ -122,9 +95,7 @@ def preview_queue( ) return self._client._get("/v1/queue/preview", params=params) - def get_next_queue_slot( - self, profile_id: str, *, queue_id: str | None = None - ) -> dict[str, Any]: + def get_next_queue_slot(self, profile_id: str, *, queue_id: str | None = None) -> dict[str, Any]: """Get next available slot""" params = self._build_params( profile_id=profile_id, @@ -132,9 +103,7 @@ def get_next_queue_slot( ) return self._client._get("/v1/queue/next-slot", params=params) - async def alist_queue_slots( - self, profile_id: str, *, queue_id: str | None = None, all: str | None = None - ) -> dict[str, Any]: + async def alist_queue_slots(self, profile_id: str, *, queue_id: str | None = None, all: str | None = None) -> dict[str, Any]: """List schedules (async)""" params = self._build_params( profile_id=profile_id, @@ -143,15 +112,7 @@ async def alist_queue_slots( ) return await self._client._aget("/v1/queue/slots", params=params) - async def acreate_queue_slot( - self, - profile_id: str, - name: str, - timezone: str, - slots: list[Any], - *, - active: bool | None = True, - ) -> dict[str, Any]: + async def acreate_queue_slot(self, profile_id: str, name: str, timezone: str, slots: list[Any], *, active: bool | None = True) -> dict[str, Any]: """Create schedule (async)""" payload = self._build_payload( profile_id=profile_id, @@ -162,18 +123,7 @@ async def acreate_queue_slot( ) return await self._client._apost("/v1/queue/slots", data=payload) - async def aupdate_queue_slot( - self, - profile_id: str, - timezone: str, - slots: list[Any], - *, - queue_id: str | None = None, - name: str | None = None, - active: bool | None = True, - set_as_default: bool | None = None, - reshuffle_existing: bool | None = False, - ) -> dict[str, Any]: + async def aupdate_queue_slot(self, profile_id: str, timezone: str, slots: list[Any], *, queue_id: str | None = None, name: str | None = None, active: bool | None = True, set_as_default: bool | None = None, reshuffle_existing: bool | None = False) -> dict[str, Any]: """Update schedule (async)""" payload = self._build_payload( profile_id=profile_id, @@ -187,9 +137,7 @@ async def aupdate_queue_slot( ) return await self._client._aput("/v1/queue/slots", data=payload) - async def adelete_queue_slot( - self, profile_id: str, queue_id: str - ) -> dict[str, Any]: + async def adelete_queue_slot(self, profile_id: str, queue_id: str) -> dict[str, Any]: """Delete schedule (async)""" params = self._build_params( profile_id=profile_id, @@ -197,9 +145,7 @@ async def adelete_queue_slot( ) return await self._client._adelete("/v1/queue/slots", params=params) - async def apreview_queue( - self, profile_id: str, *, queue_id: str | None = None, count: int | None = 20 - ) -> dict[str, Any]: + async def apreview_queue(self, profile_id: str, *, queue_id: str | None = None, count: int | None = 20) -> dict[str, Any]: """Preview upcoming slots (async)""" params = self._build_params( profile_id=profile_id, @@ -208,9 +154,7 @@ async def apreview_queue( ) return await self._client._aget("/v1/queue/preview", params=params) - async def aget_next_queue_slot( - self, profile_id: str, *, queue_id: str | None = None - ) -> dict[str, Any]: + async def aget_next_queue_slot(self, profile_id: str, *, queue_id: str | None = None) -> dict[str, Any]: """Get next available slot (async)""" params = self._build_params( profile_id=profile_id, diff --git a/src/late/resources/_generated/reddit.py b/src/late/resources/_generated/reddit.py index 318c48c..3f28826 100644 --- a/src/late/resources/_generated/reddit.py +++ b/src/late/resources/_generated/reddit.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,17 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def search_reddit( - self, - account_id: str, - q: str, - *, - subreddit: str | None = None, - restrict_sr: str | None = None, - sort: str | None = "new", - limit: int | None = 25, - after: str | None = None, - ) -> dict[str, Any]: + def search_reddit(self, account_id: str, q: str, *, subreddit: str | None = None, restrict_sr: str | None = None, sort: str | None = "new", limit: int | None = 25, after: str | None = None) -> dict[str, Any]: """Search posts""" params = self._build_params( account_id=account_id, @@ -71,16 +57,7 @@ def search_reddit( ) return self._client._get("/v1/reddit/search", params=params) - def get_reddit_feed( - self, - account_id: str, - *, - subreddit: str | None = None, - sort: str | None = "hot", - limit: int | None = 25, - after: str | None = None, - t: str | None = None, - ) -> dict[str, Any]: + def get_reddit_feed(self, account_id: str, *, subreddit: str | None = None, sort: str | None = "hot", limit: int | None = 25, after: str | None = None, t: str | None = None) -> dict[str, Any]: """Get subreddit feed""" params = self._build_params( account_id=account_id, @@ -92,17 +69,7 @@ def get_reddit_feed( ) return self._client._get("/v1/reddit/feed", params=params) - async def asearch_reddit( - self, - account_id: str, - q: str, - *, - subreddit: str | None = None, - restrict_sr: str | None = None, - sort: str | None = "new", - limit: int | None = 25, - after: str | None = None, - ) -> dict[str, Any]: + async def asearch_reddit(self, account_id: str, q: str, *, subreddit: str | None = None, restrict_sr: str | None = None, sort: str | None = "new", limit: int | None = 25, after: str | None = None) -> dict[str, Any]: """Search posts (async)""" params = self._build_params( account_id=account_id, @@ -115,16 +82,7 @@ async def asearch_reddit( ) return await self._client._aget("/v1/reddit/search", params=params) - async def aget_reddit_feed( - self, - account_id: str, - *, - subreddit: str | None = None, - sort: str | None = "hot", - limit: int | None = 25, - after: str | None = None, - t: str | None = None, - ) -> dict[str, Any]: + async def aget_reddit_feed(self, account_id: str, *, subreddit: str | None = None, sort: str | None = "hot", limit: int | None = 25, after: str | None = None, t: str | None = None) -> dict[str, Any]: """Get subreddit feed (async)""" params = self._build_params( account_id=account_id, diff --git a/src/late/resources/_generated/reviews.py b/src/late/resources/_generated/reviews.py index 8d37ea2..058484a 100644 --- a/src/late/resources/_generated/reviews.py +++ b/src/late/resources/_generated/reviews.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,20 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_inbox_reviews( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - min_rating: int | None = None, - max_rating: int | None = None, - has_reply: bool | None = None, - sort_by: str | None = "date", - sort_order: str | None = "desc", - limit: int | None = 25, - cursor: str | None = None, - account_id: str | None = None, - ) -> dict[str, Any]: + def list_inbox_reviews(self, *, profile_id: str | None = None, platform: str | None = None, min_rating: int | None = None, max_rating: int | None = None, has_reply: bool | None = None, sort_by: str | None = "date", sort_order: str | None = "desc", limit: int | None = 25, cursor: str | None = None, account_id: str | None = None) -> dict[str, Any]: """List reviews""" params = self._build_params( profile_id=profile_id, @@ -77,9 +60,7 @@ def list_inbox_reviews( ) return self._client._get("/v1/inbox/reviews", params=params) - def reply_to_inbox_review( - self, review_id: str, account_id: str, message: str - ) -> dict[str, Any]: + def reply_to_inbox_review(self, review_id: str, account_id: str, message: str) -> dict[str, Any]: """Reply to review""" payload = self._build_payload( account_id=account_id, @@ -87,26 +68,11 @@ def reply_to_inbox_review( ) return self._client._post(f"/v1/inbox/reviews/{review_id}/reply", data=payload) - def delete_inbox_review_reply( - self, review_id: str, account_id: str - ) -> dict[str, Any]: + def delete_inbox_review_reply(self, review_id: str, account_id: str) -> dict[str, Any]: """Delete review reply""" return self._client._delete(f"/v1/inbox/reviews/{review_id}/reply") - async def alist_inbox_reviews( - self, - *, - profile_id: str | None = None, - platform: str | None = None, - min_rating: int | None = None, - max_rating: int | None = None, - has_reply: bool | None = None, - sort_by: str | None = "date", - sort_order: str | None = "desc", - limit: int | None = 25, - cursor: str | None = None, - account_id: str | None = None, - ) -> dict[str, Any]: + async def alist_inbox_reviews(self, *, profile_id: str | None = None, platform: str | None = None, min_rating: int | None = None, max_rating: int | None = None, has_reply: bool | None = None, sort_by: str | None = "date", sort_order: str | None = "desc", limit: int | None = 25, cursor: str | None = None, account_id: str | None = None) -> dict[str, Any]: """List reviews (async)""" params = self._build_params( profile_id=profile_id, @@ -122,20 +88,14 @@ async def alist_inbox_reviews( ) return await self._client._aget("/v1/inbox/reviews", params=params) - async def areply_to_inbox_review( - self, review_id: str, account_id: str, message: str - ) -> dict[str, Any]: + async def areply_to_inbox_review(self, review_id: str, account_id: str, message: str) -> dict[str, Any]: """Reply to review (async)""" payload = self._build_payload( account_id=account_id, message=message, ) - return await self._client._apost( - f"/v1/inbox/reviews/{review_id}/reply", data=payload - ) + return await self._client._apost(f"/v1/inbox/reviews/{review_id}/reply", data=payload) - async def adelete_inbox_review_reply( - self, review_id: str, account_id: str - ) -> dict[str, Any]: + async def adelete_inbox_review_reply(self, review_id: str, account_id: str) -> dict[str, Any]: """Delete review reply (async)""" return await self._client._adelete(f"/v1/inbox/reviews/{review_id}/reply") diff --git a/src/late/resources/_generated/sequences.py b/src/late/resources/_generated/sequences.py index 5cf74a4..4e443c4 100644 --- a/src/late/resources/_generated/sequences.py +++ b/src/late/resources/_generated/sequences.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,14 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def list_sequences( - self, - *, - profile_id: str | None = None, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + def list_sequences(self, *, profile_id: str | None = None, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List sequences""" params = self._build_params( profile_id=profile_id, @@ -65,18 +54,7 @@ def list_sequences( ) return self._client._get("/v1/sequences", params=params) - def create_sequence( - self, - profile_id: str, - account_id: str, - platform: str, - name: str, - *, - description: str | None = None, - steps: list[dict[str, Any]] | None = None, - exit_on_reply: bool | None = True, - exit_on_unsubscribe: bool | None = True, - ) -> dict[str, Any]: + def create_sequence(self, profile_id: str, account_id: str, platform: str, name: str, *, description: str | None = None, steps: list[dict[str, Any]] | None = None, exit_on_reply: bool | None = True, exit_on_unsubscribe: bool | None = True) -> dict[str, Any]: """Create a sequence""" payload = self._build_payload( profile_id=profile_id, @@ -110,13 +88,7 @@ def pause_sequence(self, sequence_id: str) -> dict[str, Any]: """Pause a sequence""" return self._client._post(f"/v1/sequences/{sequence_id}/pause") - def enroll_contacts( - self, - sequence_id: str, - contact_ids: list[str], - *, - channel_ids: list[str] | None = None, - ) -> dict[str, Any]: + def enroll_contacts(self, sequence_id: str, contact_ids: list[str], *, channel_ids: list[str] | None = None) -> dict[str, Any]: """Enroll contacts in a sequence""" payload = self._build_payload( contact_ids=contact_ids, @@ -128,32 +100,16 @@ def unenroll_contact(self, sequence_id: str, contact_id: str) -> dict[str, Any]: """Unenroll a contact from a sequence""" return self._client._delete(f"/v1/sequences/{sequence_id}/enroll/{contact_id}") - def list_sequence_enrollments( - self, - sequence_id: str, - *, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + def list_sequence_enrollments(self, sequence_id: str, *, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List enrollments for a sequence""" params = self._build_params( status=status, limit=limit, skip=skip, ) - return self._client._get( - f"/v1/sequences/{sequence_id}/enrollments", params=params - ) + return self._client._get(f"/v1/sequences/{sequence_id}/enrollments", params=params) - async def alist_sequences( - self, - *, - profile_id: str | None = None, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + async def alist_sequences(self, *, profile_id: str | None = None, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List sequences (async)""" params = self._build_params( profile_id=profile_id, @@ -163,18 +119,7 @@ async def alist_sequences( ) return await self._client._aget("/v1/sequences", params=params) - async def acreate_sequence( - self, - profile_id: str, - account_id: str, - platform: str, - name: str, - *, - description: str | None = None, - steps: list[dict[str, Any]] | None = None, - exit_on_reply: bool | None = True, - exit_on_unsubscribe: bool | None = True, - ) -> dict[str, Any]: + async def acreate_sequence(self, profile_id: str, account_id: str, platform: str, name: str, *, description: str | None = None, steps: list[dict[str, Any]] | None = None, exit_on_reply: bool | None = True, exit_on_unsubscribe: bool | None = True) -> dict[str, Any]: """Create a sequence (async)""" payload = self._build_payload( profile_id=profile_id, @@ -208,44 +153,23 @@ async def apause_sequence(self, sequence_id: str) -> dict[str, Any]: """Pause a sequence (async)""" return await self._client._apost(f"/v1/sequences/{sequence_id}/pause") - async def aenroll_contacts( - self, - sequence_id: str, - contact_ids: list[str], - *, - channel_ids: list[str] | None = None, - ) -> dict[str, Any]: + async def aenroll_contacts(self, sequence_id: str, contact_ids: list[str], *, channel_ids: list[str] | None = None) -> dict[str, Any]: """Enroll contacts in a sequence (async)""" payload = self._build_payload( contact_ids=contact_ids, channel_ids=channel_ids, ) - return await self._client._apost( - f"/v1/sequences/{sequence_id}/enroll", data=payload - ) + return await self._client._apost(f"/v1/sequences/{sequence_id}/enroll", data=payload) - async def aunenroll_contact( - self, sequence_id: str, contact_id: str - ) -> dict[str, Any]: + async def aunenroll_contact(self, sequence_id: str, contact_id: str) -> dict[str, Any]: """Unenroll a contact from a sequence (async)""" - return await self._client._adelete( - f"/v1/sequences/{sequence_id}/enroll/{contact_id}" - ) + return await self._client._adelete(f"/v1/sequences/{sequence_id}/enroll/{contact_id}") - async def alist_sequence_enrollments( - self, - sequence_id: str, - *, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + async def alist_sequence_enrollments(self, sequence_id: str, *, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List enrollments for a sequence (async)""" params = self._build_params( status=status, limit=limit, skip=skip, ) - return await self._client._aget( - f"/v1/sequences/{sequence_id}/enrollments", params=params - ) + return await self._client._aget(f"/v1/sequences/{sequence_id}/enrollments", params=params) diff --git a/src/late/resources/_generated/twitter_engagement.py b/src/late/resources/_generated/twitter_engagement.py index d908215..08caf64 100644 --- a/src/late/resources/_generated/twitter_engagement.py +++ b/src/late/resources/_generated/twitter_engagement.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -128,9 +124,7 @@ async def aremove_bookmark(self, account_id: str, tweet_id: str) -> dict[str, An ) return await self._client._adelete("/v1/twitter/bookmark", params=params) - async def afollow_user( - self, account_id: str, target_user_id: str - ) -> dict[str, Any]: + async def afollow_user(self, account_id: str, target_user_id: str) -> dict[str, Any]: """Follow a user (async)""" payload = self._build_payload( account_id=account_id, @@ -138,9 +132,7 @@ async def afollow_user( ) return await self._client._apost("/v1/twitter/follow", data=payload) - async def aunfollow_user( - self, account_id: str, target_user_id: str - ) -> dict[str, Any]: + async def aunfollow_user(self, account_id: str, target_user_id: str) -> dict[str, Any]: """Unfollow a user (async)""" params = self._build_params( account_id=account_id, diff --git a/src/late/resources/_generated/usage.py b/src/late/resources/_generated/usage.py index d5aa576..469fb8c 100644 --- a/src/late/resources/_generated/usage.py +++ b/src/late/resources/_generated/usage.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: diff --git a/src/late/resources/_generated/users.py b/src/late/resources/_generated/users.py index 9c32545..139aa96 100644 --- a/src/late/resources/_generated/users.py +++ b/src/late/resources/_generated/users.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: diff --git a/src/late/resources/_generated/validate.py b/src/late/resources/_generated/validate.py index 988f4f4..741e6d2 100644 --- a/src/late/resources/_generated/validate.py +++ b/src/late/resources/_generated/validate.py @@ -15,7 +15,7 @@ class ValidateResource: """ - validate operations. + Content and subreddit validation tools. """ def __init__(self, client: BaseClient) -> None: @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -55,13 +51,7 @@ def validate_post_length(self, text: str) -> dict[str, Any]: ) return self._client._post("/v1/tools/validate/post-length", data=payload) - def validate_post( - self, - platforms: list[dict[str, Any]], - *, - content: str | None = None, - media_items: list[dict[str, Any]] | None = None, - ) -> dict[str, Any]: + def validate_post(self, platforms: list[dict[str, Any]], *, content: str | None = None, media_items: list[dict[str, Any]] | None = None) -> dict[str, Any]: """Validate post content""" payload = self._build_payload( content=content, @@ -91,13 +81,7 @@ async def avalidate_post_length(self, text: str) -> dict[str, Any]: ) return await self._client._apost("/v1/tools/validate/post-length", data=payload) - async def avalidate_post( - self, - platforms: list[dict[str, Any]], - *, - content: str | None = None, - media_items: list[dict[str, Any]] | None = None, - ) -> dict[str, Any]: + async def avalidate_post(self, platforms: list[dict[str, Any]], *, content: str | None = None, media_items: list[dict[str, Any]] | None = None) -> dict[str, Any]: """Validate post content (async)""" payload = self._build_payload( content=content, diff --git a/src/late/resources/_generated/webhooks.py b/src/late/resources/_generated/webhooks.py index 6994891..eb75d2f 100644 --- a/src/late/resources/_generated/webhooks.py +++ b/src/late/resources/_generated/webhooks.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -52,16 +48,7 @@ def get_webhook_settings(self) -> dict[str, Any]: """List webhooks""" return self._client._get("/v1/webhooks/settings") - def create_webhook_settings( - self, - *, - name: str | None = None, - url: str | None = None, - secret: str | None = None, - events: list[str] | None = None, - is_active: bool | None = None, - custom_headers: dict[str, Any] | None = None, - ) -> dict[str, Any]: + def create_webhook_settings(self, *, name: str | None = None, url: str | None = None, secret: str | None = None, events: list[str] | None = None, is_active: bool | None = None, custom_headers: dict[str, Any] | None = None) -> dict[str, Any]: """Create webhook""" payload = self._build_payload( name=name, @@ -73,17 +60,7 @@ def create_webhook_settings( ) return self._client._post("/v1/webhooks/settings", data=payload) - def update_webhook_settings( - self, - _id: str, - *, - name: str | None = None, - url: str | None = None, - secret: str | None = None, - events: list[str] | None = None, - is_active: bool | None = None, - custom_headers: dict[str, Any] | None = None, - ) -> dict[str, Any]: + def update_webhook_settings(self, _id: str, *, name: str | None = None, url: str | None = None, secret: str | None = None, events: list[str] | None = None, is_active: bool | None = None, custom_headers: dict[str, Any] | None = None) -> dict[str, Any]: """Update webhook""" payload = self._build_payload( _id=_id, @@ -110,14 +87,7 @@ def test_webhook(self, webhook_id: str) -> dict[str, Any]: ) return self._client._post("/v1/webhooks/test", data=payload) - def get_webhook_logs( - self, - *, - limit: int | None = 50, - status: str | None = None, - event: str | None = None, - webhook_id: str | None = None, - ) -> dict[str, Any]: + def get_webhook_logs(self, *, limit: int | None = 50, status: str | None = None, event: str | None = None, webhook_id: str | None = None) -> dict[str, Any]: """Get delivery logs""" params = self._build_params( limit=limit, @@ -131,16 +101,7 @@ async def aget_webhook_settings(self) -> dict[str, Any]: """List webhooks (async)""" return await self._client._aget("/v1/webhooks/settings") - async def acreate_webhook_settings( - self, - *, - name: str | None = None, - url: str | None = None, - secret: str | None = None, - events: list[str] | None = None, - is_active: bool | None = None, - custom_headers: dict[str, Any] | None = None, - ) -> dict[str, Any]: + async def acreate_webhook_settings(self, *, name: str | None = None, url: str | None = None, secret: str | None = None, events: list[str] | None = None, is_active: bool | None = None, custom_headers: dict[str, Any] | None = None) -> dict[str, Any]: """Create webhook (async)""" payload = self._build_payload( name=name, @@ -152,17 +113,7 @@ async def acreate_webhook_settings( ) return await self._client._apost("/v1/webhooks/settings", data=payload) - async def aupdate_webhook_settings( - self, - _id: str, - *, - name: str | None = None, - url: str | None = None, - secret: str | None = None, - events: list[str] | None = None, - is_active: bool | None = None, - custom_headers: dict[str, Any] | None = None, - ) -> dict[str, Any]: + async def aupdate_webhook_settings(self, _id: str, *, name: str | None = None, url: str | None = None, secret: str | None = None, events: list[str] | None = None, is_active: bool | None = None, custom_headers: dict[str, Any] | None = None) -> dict[str, Any]: """Update webhook (async)""" payload = self._build_payload( _id=_id, @@ -189,14 +140,7 @@ async def atest_webhook(self, webhook_id: str) -> dict[str, Any]: ) return await self._client._apost("/v1/webhooks/test", data=payload) - async def aget_webhook_logs( - self, - *, - limit: int | None = 50, - status: str | None = None, - event: str | None = None, - webhook_id: str | None = None, - ) -> dict[str, Any]: + async def aget_webhook_logs(self, *, limit: int | None = 50, status: str | None = None, event: str | None = None, webhook_id: str | None = None) -> dict[str, Any]: """Get delivery logs (async)""" params = self._build_params( limit=limit, diff --git a/src/late/resources/_generated/whatsapp.py b/src/late/resources/_generated/whatsapp.py index bcaf229..17405ee 100644 --- a/src/late/resources/_generated/whatsapp.py +++ b/src/late/resources/_generated/whatsapp.py @@ -25,21 +25,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -50,12 +46,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def send_whats_app_bulk( - self, - account_id: str, - recipients: list[dict[str, Any]], - template: dict[str, Any], - ) -> dict[str, Any]: + def send_whats_app_bulk(self, account_id: str, recipients: list[dict[str, Any]], template: dict[str, Any]) -> dict[str, Any]: """Bulk send template messages""" payload = self._build_payload( account_id=account_id, @@ -64,17 +55,7 @@ def send_whats_app_bulk( ) return self._client._post("/v1/whatsapp/bulk", data=payload) - def get_whats_app_contacts( - self, - account_id: str, - *, - search: str | None = None, - tag: str | None = None, - group: str | None = None, - opted_in: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + def get_whats_app_contacts(self, account_id: str, *, search: str | None = None, tag: str | None = None, group: str | None = None, opted_in: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List contacts""" params = self._build_params( account_id=account_id, @@ -87,20 +68,7 @@ def get_whats_app_contacts( ) return self._client._get("/v1/whatsapp/contacts", params=params) - def create_whats_app_contact( - self, - account_id: str, - phone: str, - name: str, - *, - email: str | None = None, - company: str | None = None, - tags: list[str] | None = None, - groups: list[str] | None = None, - is_opted_in: bool | None = True, - custom_fields: dict[str, Any] | None = None, - notes: str | None = None, - ) -> dict[str, Any]: + def create_whats_app_contact(self, account_id: str, phone: str, name: str, *, email: str | None = None, company: str | None = None, tags: list[str] | None = None, groups: list[str] | None = None, is_opted_in: bool | None = True, custom_fields: dict[str, Any] | None = None, notes: str | None = None) -> dict[str, Any]: """Create contact""" payload = self._build_payload( account_id=account_id, @@ -120,20 +88,7 @@ def get_whats_app_contact(self, contact_id: str) -> dict[str, Any]: """Get contact""" return self._client._get(f"/v1/whatsapp/contacts/{contact_id}") - def update_whats_app_contact( - self, - contact_id: str, - *, - name: str | None = None, - email: str | None = None, - company: str | None = None, - tags: list[str] | None = None, - groups: list[str] | None = None, - is_opted_in: bool | None = None, - is_blocked: bool | None = None, - custom_fields: dict[str, Any] | None = None, - notes: str | None = None, - ) -> dict[str, Any]: + def update_whats_app_contact(self, contact_id: str, *, name: str | None = None, email: str | None = None, company: str | None = None, tags: list[str] | None = None, groups: list[str] | None = None, is_opted_in: bool | None = None, is_blocked: bool | None = None, custom_fields: dict[str, Any] | None = None, notes: str | None = None) -> dict[str, Any]: """Update contact""" payload = self._build_payload( name=name, @@ -152,15 +107,7 @@ def delete_whats_app_contact(self, contact_id: str) -> dict[str, Any]: """Delete contact""" return self._client._delete(f"/v1/whatsapp/contacts/{contact_id}") - def import_whats_app_contacts( - self, - account_id: str, - contacts: list[dict[str, Any]], - *, - default_tags: list[str] | None = None, - default_groups: list[str] | None = None, - skip_duplicates: bool | None = True, - ) -> dict[str, Any]: + def import_whats_app_contacts(self, account_id: str, contacts: list[dict[str, Any]], *, default_tags: list[str] | None = None, default_groups: list[str] | None = None, skip_duplicates: bool | None = True) -> dict[str, Any]: """Bulk import contacts""" payload = self._build_payload( account_id=account_id, @@ -171,14 +118,7 @@ def import_whats_app_contacts( ) return self._client._post("/v1/whatsapp/contacts/import", data=payload) - def bulk_update_whats_app_contacts( - self, - action: str, - contact_ids: list[str], - *, - tags: list[str] | None = None, - groups: list[str] | None = None, - ) -> dict[str, Any]: + def bulk_update_whats_app_contacts(self, action: str, contact_ids: list[str], *, tags: list[str] | None = None, groups: list[str] | None = None) -> dict[str, Any]: """Bulk update contacts""" payload = self._build_payload( action=action, @@ -199,9 +139,7 @@ def get_whats_app_groups(self, account_id: str) -> dict[str, Any]: ) return self._client._get("/v1/whatsapp/groups", params=params) - def rename_whats_app_group( - self, account_id: str, old_name: str, new_name: str - ) -> dict[str, Any]: + def rename_whats_app_group(self, account_id: str, old_name: str, new_name: str) -> dict[str, Any]: """Rename group""" payload = self._build_payload( account_id=account_id, @@ -210,9 +148,7 @@ def rename_whats_app_group( ) return self._client._post("/v1/whatsapp/groups", data=payload) - def delete_whats_app_group( - self, account_id: str, group_name: str - ) -> dict[str, Any]: + def delete_whats_app_group(self, account_id: str, group_name: str) -> dict[str, Any]: """Delete group""" return self._client._delete("/v1/whatsapp/groups") @@ -223,18 +159,7 @@ def get_whats_app_templates(self, account_id: str) -> dict[str, Any]: ) return self._client._get("/v1/whatsapp/templates", params=params) - def create_whats_app_template( - self, - account_id: str, - name: str, - category: str, - language: str, - *, - components: list[dict[str, Any]] | None = None, - library_template_name: str | None = None, - library_template_body_inputs: dict[str, Any] | None = None, - library_template_button_inputs: list[dict[str, Any]] | None = None, - ) -> dict[str, Any]: + def create_whats_app_template(self, account_id: str, name: str, category: str, language: str, *, components: list[Any] | None = None, library_template_name: str | None = None, library_template_body_inputs: dict[str, Any] | None = None, library_template_button_inputs: list[dict[str, Any]] | None = None) -> dict[str, Any]: """Create template""" payload = self._build_payload( account_id=account_id, @@ -248,48 +173,29 @@ def create_whats_app_template( ) return self._client._post("/v1/whatsapp/templates", data=payload) - def get_whats_app_template( - self, template_name: str, account_id: str - ) -> dict[str, Any]: + def get_whats_app_template(self, template_name: str, account_id: str) -> dict[str, Any]: """Get template""" params = self._build_params( account_id=account_id, ) - return self._client._get( - f"/v1/whatsapp/templates/{template_name}", params=params - ) + return self._client._get(f"/v1/whatsapp/templates/{template_name}", params=params) - def update_whats_app_template( - self, template_name: str, account_id: str, components: list[dict[str, Any]] - ) -> dict[str, Any]: + def update_whats_app_template(self, template_name: str, account_id: str, components: list[Any]) -> dict[str, Any]: """Update template""" payload = self._build_payload( account_id=account_id, components=components, ) - return self._client._patch( - f"/v1/whatsapp/templates/{template_name}", data=payload - ) + return self._client._patch(f"/v1/whatsapp/templates/{template_name}", data=payload) - def delete_whats_app_template( - self, template_name: str, account_id: str - ) -> dict[str, Any]: + def delete_whats_app_template(self, template_name: str, account_id: str) -> dict[str, Any]: """Delete template""" params = self._build_params( account_id=account_id, ) - return self._client._delete( - f"/v1/whatsapp/templates/{template_name}", params=params - ) + return self._client._delete(f"/v1/whatsapp/templates/{template_name}", params=params) - def get_whats_app_broadcasts( - self, - account_id: str, - *, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + def get_whats_app_broadcasts(self, account_id: str, *, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List broadcasts""" params = self._build_params( account_id=account_id, @@ -299,15 +205,7 @@ def get_whats_app_broadcasts( ) return self._client._get("/v1/whatsapp/broadcasts", params=params) - def create_whats_app_broadcast( - self, - account_id: str, - name: str, - template: dict[str, Any], - *, - description: str | None = None, - recipients: list[dict[str, Any]] | None = None, - ) -> dict[str, Any]: + def create_whats_app_broadcast(self, account_id: str, name: str, template: dict[str, Any], *, description: str | None = None, recipients: list[dict[str, Any]] | None = None) -> dict[str, Any]: """Create broadcast""" payload = self._build_payload( account_id=account_id, @@ -330,57 +228,36 @@ def send_whats_app_broadcast(self, broadcast_id: str) -> dict[str, Any]: """Send broadcast""" return self._client._post(f"/v1/whatsapp/broadcasts/{broadcast_id}/send") - def schedule_whats_app_broadcast( - self, broadcast_id: str, scheduled_at: datetime | str - ) -> dict[str, Any]: + def schedule_whats_app_broadcast(self, broadcast_id: str, scheduled_at: datetime | str) -> dict[str, Any]: """Schedule broadcast""" payload = self._build_payload( scheduled_at=scheduled_at, ) - return self._client._post( - f"/v1/whatsapp/broadcasts/{broadcast_id}/schedule", data=payload - ) + return self._client._post(f"/v1/whatsapp/broadcasts/{broadcast_id}/schedule", data=payload) def cancel_whats_app_broadcast_schedule(self, broadcast_id: str) -> dict[str, Any]: """Cancel scheduled broadcast""" return self._client._delete(f"/v1/whatsapp/broadcasts/{broadcast_id}/schedule") - def get_whats_app_broadcast_recipients( - self, - broadcast_id: str, - *, - status: str | None = None, - limit: int | None = 100, - skip: int | None = 0, - ) -> dict[str, Any]: + def get_whats_app_broadcast_recipients(self, broadcast_id: str, *, status: str | None = None, limit: int | None = 100, skip: int | None = 0) -> dict[str, Any]: """List recipients""" params = self._build_params( status=status, limit=limit, skip=skip, ) - return self._client._get( - f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients", params=params - ) + return self._client._get(f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients", params=params) - def add_whats_app_broadcast_recipients( - self, broadcast_id: str, recipients: list[dict[str, Any]] - ) -> dict[str, Any]: + def add_whats_app_broadcast_recipients(self, broadcast_id: str, recipients: list[dict[str, Any]]) -> dict[str, Any]: """Add recipients""" payload = self._build_payload( recipients=recipients, ) - return self._client._patch( - f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients", data=payload - ) + return self._client._patch(f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients", data=payload) - def remove_whats_app_broadcast_recipients( - self, broadcast_id: str, phones: list[str] - ) -> dict[str, Any]: + def remove_whats_app_broadcast_recipients(self, broadcast_id: str, phones: list[str]) -> dict[str, Any]: """Remove recipients""" - return self._client._delete( - f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients" - ) + return self._client._delete(f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients") def get_whats_app_business_profile(self, account_id: str) -> dict[str, Any]: """Get business profile""" @@ -389,18 +266,7 @@ def get_whats_app_business_profile(self, account_id: str) -> dict[str, Any]: ) return self._client._get("/v1/whatsapp/business-profile", params=params) - def update_whats_app_business_profile( - self, - account_id: str, - *, - about: str | None = None, - address: str | None = None, - description: str | None = None, - email: str | None = None, - websites: list[str] | None = None, - vertical: str | None = None, - profile_picture_handle: str | None = None, - ) -> dict[str, Any]: + def update_whats_app_business_profile(self, account_id: str, *, about: str | None = None, address: str | None = None, description: str | None = None, email: str | None = None, websites: list[str] | None = None, vertical: str | None = None, profile_picture_handle: str | None = None) -> dict[str, Any]: """Update business profile""" payload = self._build_payload( account_id=account_id, @@ -423,25 +289,17 @@ def get_whats_app_display_name(self, account_id: str) -> dict[str, Any]: params = self._build_params( account_id=account_id, ) - return self._client._get( - "/v1/whatsapp/business-profile/display-name", params=params - ) + return self._client._get("/v1/whatsapp/business-profile/display-name", params=params) - def update_whats_app_display_name( - self, account_id: str, display_name: str - ) -> dict[str, Any]: + def update_whats_app_display_name(self, account_id: str, display_name: str) -> dict[str, Any]: """Request display name change""" payload = self._build_payload( account_id=account_id, display_name=display_name, ) - return self._client._post( - "/v1/whatsapp/business-profile/display-name", data=payload - ) + return self._client._post("/v1/whatsapp/business-profile/display-name", data=payload) - def list_whats_app_group_chats( - self, account_id: str, *, limit: int | None = 25, after: str | None = None - ) -> dict[str, Any]: + def list_whats_app_group_chats(self, account_id: str, *, limit: int | None = 25, after: str | None = None) -> dict[str, Any]: """List active groups""" params = self._build_params( account_id=account_id, @@ -450,14 +308,7 @@ def list_whats_app_group_chats( ) return self._client._get("/v1/whatsapp/wa-groups", params=params) - def create_whats_app_group_chat( - self, - account_id: str, - subject: str, - *, - description: str | None = None, - join_approval_mode: str | None = None, - ) -> dict[str, Any]: + def create_whats_app_group_chat(self, account_id: str, subject: str, *, description: str | None = None, join_approval_mode: str | None = None) -> dict[str, Any]: """Create group""" payload = self._build_payload( account_id=account_id, @@ -467,24 +318,14 @@ def create_whats_app_group_chat( ) return self._client._post("/v1/whatsapp/wa-groups", data=payload) - def get_whats_app_group_chat( - self, group_id: str, account_id: str - ) -> dict[str, Any]: + def get_whats_app_group_chat(self, group_id: str, account_id: str) -> dict[str, Any]: """Get group info""" params = self._build_params( account_id=account_id, ) return self._client._get(f"/v1/whatsapp/wa-groups/{group_id}", params=params) - def update_whats_app_group_chat( - self, - group_id: str, - account_id: str, - *, - subject: str | None = None, - description: str | None = None, - join_approval_mode: str | None = None, - ) -> dict[str, Any]: + def update_whats_app_group_chat(self, group_id: str, account_id: str, *, subject: str | None = None, description: str | None = None, join_approval_mode: str | None = None) -> dict[str, Any]: """Update group settings""" payload = self._build_payload( subject=subject, @@ -493,87 +334,56 @@ def update_whats_app_group_chat( ) return self._client._post(f"/v1/whatsapp/wa-groups/{group_id}", data=payload) - def delete_whats_app_group_chat( - self, group_id: str, account_id: str - ) -> dict[str, Any]: + def delete_whats_app_group_chat(self, group_id: str, account_id: str) -> dict[str, Any]: """Delete group""" params = self._build_params( account_id=account_id, ) return self._client._delete(f"/v1/whatsapp/wa-groups/{group_id}", params=params) - def add_whats_app_group_participants( - self, group_id: str, account_id: str, phone_numbers: list[str] - ) -> dict[str, Any]: + def add_whats_app_group_participants(self, group_id: str, account_id: str, phone_numbers: list[str]) -> dict[str, Any]: """Add participants""" payload = self._build_payload( phone_numbers=phone_numbers, ) - return self._client._post( - f"/v1/whatsapp/wa-groups/{group_id}/participants", data=payload - ) + return self._client._post(f"/v1/whatsapp/wa-groups/{group_id}/participants", data=payload) - def remove_whats_app_group_participants( - self, group_id: str, account_id: str, phone_numbers: list[str] - ) -> dict[str, Any]: + def remove_whats_app_group_participants(self, group_id: str, account_id: str, phone_numbers: list[str]) -> dict[str, Any]: """Remove participants""" params = self._build_params( account_id=account_id, ) - return self._client._delete( - f"/v1/whatsapp/wa-groups/{group_id}/participants", params=params - ) + return self._client._delete(f"/v1/whatsapp/wa-groups/{group_id}/participants", params=params) - def create_whats_app_group_invite_link( - self, group_id: str, account_id: str - ) -> dict[str, Any]: + def create_whats_app_group_invite_link(self, group_id: str, account_id: str) -> dict[str, Any]: """Create invite link""" params = self._build_params( account_id=account_id, ) - return self._client._post( - f"/v1/whatsapp/wa-groups/{group_id}/invite-link", params=params - ) + return self._client._post(f"/v1/whatsapp/wa-groups/{group_id}/invite-link", params=params) - def list_whats_app_group_join_requests( - self, group_id: str, account_id: str - ) -> dict[str, Any]: + def list_whats_app_group_join_requests(self, group_id: str, account_id: str) -> dict[str, Any]: """List join requests""" params = self._build_params( account_id=account_id, ) - return self._client._get( - f"/v1/whatsapp/wa-groups/{group_id}/join-requests", params=params - ) + return self._client._get(f"/v1/whatsapp/wa-groups/{group_id}/join-requests", params=params) - def approve_whats_app_group_join_requests( - self, group_id: str, account_id: str, phone_numbers: list[str] - ) -> dict[str, Any]: + def approve_whats_app_group_join_requests(self, group_id: str, account_id: str, phone_numbers: list[str]) -> dict[str, Any]: """Approve join requests""" payload = self._build_payload( phone_numbers=phone_numbers, ) - return self._client._post( - f"/v1/whatsapp/wa-groups/{group_id}/join-requests", data=payload - ) + return self._client._post(f"/v1/whatsapp/wa-groups/{group_id}/join-requests", data=payload) - def reject_whats_app_group_join_requests( - self, group_id: str, account_id: str, phone_numbers: list[str] - ) -> dict[str, Any]: + def reject_whats_app_group_join_requests(self, group_id: str, account_id: str, phone_numbers: list[str]) -> dict[str, Any]: """Reject join requests""" params = self._build_params( account_id=account_id, ) - return self._client._delete( - f"/v1/whatsapp/wa-groups/{group_id}/join-requests", params=params - ) + return self._client._delete(f"/v1/whatsapp/wa-groups/{group_id}/join-requests", params=params) - async def asend_whats_app_bulk( - self, - account_id: str, - recipients: list[dict[str, Any]], - template: dict[str, Any], - ) -> dict[str, Any]: + async def asend_whats_app_bulk(self, account_id: str, recipients: list[dict[str, Any]], template: dict[str, Any]) -> dict[str, Any]: """Bulk send template messages (async)""" payload = self._build_payload( account_id=account_id, @@ -582,17 +392,7 @@ async def asend_whats_app_bulk( ) return await self._client._apost("/v1/whatsapp/bulk", data=payload) - async def aget_whats_app_contacts( - self, - account_id: str, - *, - search: str | None = None, - tag: str | None = None, - group: str | None = None, - opted_in: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + async def aget_whats_app_contacts(self, account_id: str, *, search: str | None = None, tag: str | None = None, group: str | None = None, opted_in: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List contacts (async)""" params = self._build_params( account_id=account_id, @@ -605,20 +405,7 @@ async def aget_whats_app_contacts( ) return await self._client._aget("/v1/whatsapp/contacts", params=params) - async def acreate_whats_app_contact( - self, - account_id: str, - phone: str, - name: str, - *, - email: str | None = None, - company: str | None = None, - tags: list[str] | None = None, - groups: list[str] | None = None, - is_opted_in: bool | None = True, - custom_fields: dict[str, Any] | None = None, - notes: str | None = None, - ) -> dict[str, Any]: + async def acreate_whats_app_contact(self, account_id: str, phone: str, name: str, *, email: str | None = None, company: str | None = None, tags: list[str] | None = None, groups: list[str] | None = None, is_opted_in: bool | None = True, custom_fields: dict[str, Any] | None = None, notes: str | None = None) -> dict[str, Any]: """Create contact (async)""" payload = self._build_payload( account_id=account_id, @@ -638,20 +425,7 @@ async def aget_whats_app_contact(self, contact_id: str) -> dict[str, Any]: """Get contact (async)""" return await self._client._aget(f"/v1/whatsapp/contacts/{contact_id}") - async def aupdate_whats_app_contact( - self, - contact_id: str, - *, - name: str | None = None, - email: str | None = None, - company: str | None = None, - tags: list[str] | None = None, - groups: list[str] | None = None, - is_opted_in: bool | None = None, - is_blocked: bool | None = None, - custom_fields: dict[str, Any] | None = None, - notes: str | None = None, - ) -> dict[str, Any]: + async def aupdate_whats_app_contact(self, contact_id: str, *, name: str | None = None, email: str | None = None, company: str | None = None, tags: list[str] | None = None, groups: list[str] | None = None, is_opted_in: bool | None = None, is_blocked: bool | None = None, custom_fields: dict[str, Any] | None = None, notes: str | None = None) -> dict[str, Any]: """Update contact (async)""" payload = self._build_payload( name=name, @@ -664,23 +438,13 @@ async def aupdate_whats_app_contact( custom_fields=custom_fields, notes=notes, ) - return await self._client._aput( - f"/v1/whatsapp/contacts/{contact_id}", data=payload - ) + return await self._client._aput(f"/v1/whatsapp/contacts/{contact_id}", data=payload) async def adelete_whats_app_contact(self, contact_id: str) -> dict[str, Any]: """Delete contact (async)""" return await self._client._adelete(f"/v1/whatsapp/contacts/{contact_id}") - async def aimport_whats_app_contacts( - self, - account_id: str, - contacts: list[dict[str, Any]], - *, - default_tags: list[str] | None = None, - default_groups: list[str] | None = None, - skip_duplicates: bool | None = True, - ) -> dict[str, Any]: + async def aimport_whats_app_contacts(self, account_id: str, contacts: list[dict[str, Any]], *, default_tags: list[str] | None = None, default_groups: list[str] | None = None, skip_duplicates: bool | None = True) -> dict[str, Any]: """Bulk import contacts (async)""" payload = self._build_payload( account_id=account_id, @@ -691,14 +455,7 @@ async def aimport_whats_app_contacts( ) return await self._client._apost("/v1/whatsapp/contacts/import", data=payload) - async def abulk_update_whats_app_contacts( - self, - action: str, - contact_ids: list[str], - *, - tags: list[str] | None = None, - groups: list[str] | None = None, - ) -> dict[str, Any]: + async def abulk_update_whats_app_contacts(self, action: str, contact_ids: list[str], *, tags: list[str] | None = None, groups: list[str] | None = None) -> dict[str, Any]: """Bulk update contacts (async)""" payload = self._build_payload( action=action, @@ -708,9 +465,7 @@ async def abulk_update_whats_app_contacts( ) return await self._client._apost("/v1/whatsapp/contacts/bulk", data=payload) - async def abulk_delete_whats_app_contacts( - self, contact_ids: list[str] - ) -> dict[str, Any]: + async def abulk_delete_whats_app_contacts(self, contact_ids: list[str]) -> dict[str, Any]: """Bulk delete contacts (async)""" return await self._client._adelete("/v1/whatsapp/contacts/bulk") @@ -721,9 +476,7 @@ async def aget_whats_app_groups(self, account_id: str) -> dict[str, Any]: ) return await self._client._aget("/v1/whatsapp/groups", params=params) - async def arename_whats_app_group( - self, account_id: str, old_name: str, new_name: str - ) -> dict[str, Any]: + async def arename_whats_app_group(self, account_id: str, old_name: str, new_name: str) -> dict[str, Any]: """Rename group (async)""" payload = self._build_payload( account_id=account_id, @@ -732,9 +485,7 @@ async def arename_whats_app_group( ) return await self._client._apost("/v1/whatsapp/groups", data=payload) - async def adelete_whats_app_group( - self, account_id: str, group_name: str - ) -> dict[str, Any]: + async def adelete_whats_app_group(self, account_id: str, group_name: str) -> dict[str, Any]: """Delete group (async)""" return await self._client._adelete("/v1/whatsapp/groups") @@ -745,18 +496,7 @@ async def aget_whats_app_templates(self, account_id: str) -> dict[str, Any]: ) return await self._client._aget("/v1/whatsapp/templates", params=params) - async def acreate_whats_app_template( - self, - account_id: str, - name: str, - category: str, - language: str, - *, - components: list[dict[str, Any]] | None = None, - library_template_name: str | None = None, - library_template_body_inputs: dict[str, Any] | None = None, - library_template_button_inputs: list[dict[str, Any]] | None = None, - ) -> dict[str, Any]: + async def acreate_whats_app_template(self, account_id: str, name: str, category: str, language: str, *, components: list[Any] | None = None, library_template_name: str | None = None, library_template_body_inputs: dict[str, Any] | None = None, library_template_button_inputs: list[dict[str, Any]] | None = None) -> dict[str, Any]: """Create template (async)""" payload = self._build_payload( account_id=account_id, @@ -770,48 +510,29 @@ async def acreate_whats_app_template( ) return await self._client._apost("/v1/whatsapp/templates", data=payload) - async def aget_whats_app_template( - self, template_name: str, account_id: str - ) -> dict[str, Any]: + async def aget_whats_app_template(self, template_name: str, account_id: str) -> dict[str, Any]: """Get template (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._aget( - f"/v1/whatsapp/templates/{template_name}", params=params - ) + return await self._client._aget(f"/v1/whatsapp/templates/{template_name}", params=params) - async def aupdate_whats_app_template( - self, template_name: str, account_id: str, components: list[dict[str, Any]] - ) -> dict[str, Any]: + async def aupdate_whats_app_template(self, template_name: str, account_id: str, components: list[Any]) -> dict[str, Any]: """Update template (async)""" payload = self._build_payload( account_id=account_id, components=components, ) - return await self._client._apatch( - f"/v1/whatsapp/templates/{template_name}", data=payload - ) + return await self._client._apatch(f"/v1/whatsapp/templates/{template_name}", data=payload) - async def adelete_whats_app_template( - self, template_name: str, account_id: str - ) -> dict[str, Any]: + async def adelete_whats_app_template(self, template_name: str, account_id: str) -> dict[str, Any]: """Delete template (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._adelete( - f"/v1/whatsapp/templates/{template_name}", params=params - ) + return await self._client._adelete(f"/v1/whatsapp/templates/{template_name}", params=params) - async def aget_whats_app_broadcasts( - self, - account_id: str, - *, - status: str | None = None, - limit: int | None = 50, - skip: int | None = 0, - ) -> dict[str, Any]: + async def aget_whats_app_broadcasts(self, account_id: str, *, status: str | None = None, limit: int | None = 50, skip: int | None = 0) -> dict[str, Any]: """List broadcasts (async)""" params = self._build_params( account_id=account_id, @@ -821,15 +542,7 @@ async def aget_whats_app_broadcasts( ) return await self._client._aget("/v1/whatsapp/broadcasts", params=params) - async def acreate_whats_app_broadcast( - self, - account_id: str, - name: str, - template: dict[str, Any], - *, - description: str | None = None, - recipients: list[dict[str, Any]] | None = None, - ) -> dict[str, Any]: + async def acreate_whats_app_broadcast(self, account_id: str, name: str, template: dict[str, Any], *, description: str | None = None, recipients: list[dict[str, Any]] | None = None) -> dict[str, Any]: """Create broadcast (async)""" payload = self._build_payload( account_id=account_id, @@ -852,61 +565,36 @@ async def asend_whats_app_broadcast(self, broadcast_id: str) -> dict[str, Any]: """Send broadcast (async)""" return await self._client._apost(f"/v1/whatsapp/broadcasts/{broadcast_id}/send") - async def aschedule_whats_app_broadcast( - self, broadcast_id: str, scheduled_at: datetime | str - ) -> dict[str, Any]: + async def aschedule_whats_app_broadcast(self, broadcast_id: str, scheduled_at: datetime | str) -> dict[str, Any]: """Schedule broadcast (async)""" payload = self._build_payload( scheduled_at=scheduled_at, ) - return await self._client._apost( - f"/v1/whatsapp/broadcasts/{broadcast_id}/schedule", data=payload - ) + return await self._client._apost(f"/v1/whatsapp/broadcasts/{broadcast_id}/schedule", data=payload) - async def acancel_whats_app_broadcast_schedule( - self, broadcast_id: str - ) -> dict[str, Any]: + async def acancel_whats_app_broadcast_schedule(self, broadcast_id: str) -> dict[str, Any]: """Cancel scheduled broadcast (async)""" - return await self._client._adelete( - f"/v1/whatsapp/broadcasts/{broadcast_id}/schedule" - ) - - async def aget_whats_app_broadcast_recipients( - self, - broadcast_id: str, - *, - status: str | None = None, - limit: int | None = 100, - skip: int | None = 0, - ) -> dict[str, Any]: + return await self._client._adelete(f"/v1/whatsapp/broadcasts/{broadcast_id}/schedule") + + async def aget_whats_app_broadcast_recipients(self, broadcast_id: str, *, status: str | None = None, limit: int | None = 100, skip: int | None = 0) -> dict[str, Any]: """List recipients (async)""" params = self._build_params( status=status, limit=limit, skip=skip, ) - return await self._client._aget( - f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients", params=params - ) + return await self._client._aget(f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients", params=params) - async def aadd_whats_app_broadcast_recipients( - self, broadcast_id: str, recipients: list[dict[str, Any]] - ) -> dict[str, Any]: + async def aadd_whats_app_broadcast_recipients(self, broadcast_id: str, recipients: list[dict[str, Any]]) -> dict[str, Any]: """Add recipients (async)""" payload = self._build_payload( recipients=recipients, ) - return await self._client._apatch( - f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients", data=payload - ) + return await self._client._apatch(f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients", data=payload) - async def aremove_whats_app_broadcast_recipients( - self, broadcast_id: str, phones: list[str] - ) -> dict[str, Any]: + async def aremove_whats_app_broadcast_recipients(self, broadcast_id: str, phones: list[str]) -> dict[str, Any]: """Remove recipients (async)""" - return await self._client._adelete( - f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients" - ) + return await self._client._adelete(f"/v1/whatsapp/broadcasts/{broadcast_id}/recipients") async def aget_whats_app_business_profile(self, account_id: str) -> dict[str, Any]: """Get business profile (async)""" @@ -915,18 +603,7 @@ async def aget_whats_app_business_profile(self, account_id: str) -> dict[str, An ) return await self._client._aget("/v1/whatsapp/business-profile", params=params) - async def aupdate_whats_app_business_profile( - self, - account_id: str, - *, - about: str | None = None, - address: str | None = None, - description: str | None = None, - email: str | None = None, - websites: list[str] | None = None, - vertical: str | None = None, - profile_picture_handle: str | None = None, - ) -> dict[str, Any]: + async def aupdate_whats_app_business_profile(self, account_id: str, *, about: str | None = None, address: str | None = None, description: str | None = None, email: str | None = None, websites: list[str] | None = None, vertical: str | None = None, profile_picture_handle: str | None = None) -> dict[str, Any]: """Update business profile (async)""" payload = self._build_payload( account_id=account_id, @@ -949,25 +626,17 @@ async def aget_whats_app_display_name(self, account_id: str) -> dict[str, Any]: params = self._build_params( account_id=account_id, ) - return await self._client._aget( - "/v1/whatsapp/business-profile/display-name", params=params - ) + return await self._client._aget("/v1/whatsapp/business-profile/display-name", params=params) - async def aupdate_whats_app_display_name( - self, account_id: str, display_name: str - ) -> dict[str, Any]: + async def aupdate_whats_app_display_name(self, account_id: str, display_name: str) -> dict[str, Any]: """Request display name change (async)""" payload = self._build_payload( account_id=account_id, display_name=display_name, ) - return await self._client._apost( - "/v1/whatsapp/business-profile/display-name", data=payload - ) + return await self._client._apost("/v1/whatsapp/business-profile/display-name", data=payload) - async def alist_whats_app_group_chats( - self, account_id: str, *, limit: int | None = 25, after: str | None = None - ) -> dict[str, Any]: + async def alist_whats_app_group_chats(self, account_id: str, *, limit: int | None = 25, after: str | None = None) -> dict[str, Any]: """List active groups (async)""" params = self._build_params( account_id=account_id, @@ -976,14 +645,7 @@ async def alist_whats_app_group_chats( ) return await self._client._aget("/v1/whatsapp/wa-groups", params=params) - async def acreate_whats_app_group_chat( - self, - account_id: str, - subject: str, - *, - description: str | None = None, - join_approval_mode: str | None = None, - ) -> dict[str, Any]: + async def acreate_whats_app_group_chat(self, account_id: str, subject: str, *, description: str | None = None, join_approval_mode: str | None = None) -> dict[str, Any]: """Create group (async)""" payload = self._build_payload( account_id=account_id, @@ -993,109 +655,67 @@ async def acreate_whats_app_group_chat( ) return await self._client._apost("/v1/whatsapp/wa-groups", data=payload) - async def aget_whats_app_group_chat( - self, group_id: str, account_id: str - ) -> dict[str, Any]: + async def aget_whats_app_group_chat(self, group_id: str, account_id: str) -> dict[str, Any]: """Get group info (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._aget( - f"/v1/whatsapp/wa-groups/{group_id}", params=params - ) + return await self._client._aget(f"/v1/whatsapp/wa-groups/{group_id}", params=params) - async def aupdate_whats_app_group_chat( - self, - group_id: str, - account_id: str, - *, - subject: str | None = None, - description: str | None = None, - join_approval_mode: str | None = None, - ) -> dict[str, Any]: + async def aupdate_whats_app_group_chat(self, group_id: str, account_id: str, *, subject: str | None = None, description: str | None = None, join_approval_mode: str | None = None) -> dict[str, Any]: """Update group settings (async)""" payload = self._build_payload( subject=subject, description=description, join_approval_mode=join_approval_mode, ) - return await self._client._apost( - f"/v1/whatsapp/wa-groups/{group_id}", data=payload - ) + return await self._client._apost(f"/v1/whatsapp/wa-groups/{group_id}", data=payload) - async def adelete_whats_app_group_chat( - self, group_id: str, account_id: str - ) -> dict[str, Any]: + async def adelete_whats_app_group_chat(self, group_id: str, account_id: str) -> dict[str, Any]: """Delete group (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._adelete( - f"/v1/whatsapp/wa-groups/{group_id}", params=params - ) + return await self._client._adelete(f"/v1/whatsapp/wa-groups/{group_id}", params=params) - async def aadd_whats_app_group_participants( - self, group_id: str, account_id: str, phone_numbers: list[str] - ) -> dict[str, Any]: + async def aadd_whats_app_group_participants(self, group_id: str, account_id: str, phone_numbers: list[str]) -> dict[str, Any]: """Add participants (async)""" payload = self._build_payload( phone_numbers=phone_numbers, ) - return await self._client._apost( - f"/v1/whatsapp/wa-groups/{group_id}/participants", data=payload - ) + return await self._client._apost(f"/v1/whatsapp/wa-groups/{group_id}/participants", data=payload) - async def aremove_whats_app_group_participants( - self, group_id: str, account_id: str, phone_numbers: list[str] - ) -> dict[str, Any]: + async def aremove_whats_app_group_participants(self, group_id: str, account_id: str, phone_numbers: list[str]) -> dict[str, Any]: """Remove participants (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._adelete( - f"/v1/whatsapp/wa-groups/{group_id}/participants", params=params - ) + return await self._client._adelete(f"/v1/whatsapp/wa-groups/{group_id}/participants", params=params) - async def acreate_whats_app_group_invite_link( - self, group_id: str, account_id: str - ) -> dict[str, Any]: + async def acreate_whats_app_group_invite_link(self, group_id: str, account_id: str) -> dict[str, Any]: """Create invite link (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._apost( - f"/v1/whatsapp/wa-groups/{group_id}/invite-link", params=params - ) + return await self._client._apost(f"/v1/whatsapp/wa-groups/{group_id}/invite-link", params=params) - async def alist_whats_app_group_join_requests( - self, group_id: str, account_id: str - ) -> dict[str, Any]: + async def alist_whats_app_group_join_requests(self, group_id: str, account_id: str) -> dict[str, Any]: """List join requests (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._aget( - f"/v1/whatsapp/wa-groups/{group_id}/join-requests", params=params - ) + return await self._client._aget(f"/v1/whatsapp/wa-groups/{group_id}/join-requests", params=params) - async def aapprove_whats_app_group_join_requests( - self, group_id: str, account_id: str, phone_numbers: list[str] - ) -> dict[str, Any]: + async def aapprove_whats_app_group_join_requests(self, group_id: str, account_id: str, phone_numbers: list[str]) -> dict[str, Any]: """Approve join requests (async)""" payload = self._build_payload( phone_numbers=phone_numbers, ) - return await self._client._apost( - f"/v1/whatsapp/wa-groups/{group_id}/join-requests", data=payload - ) + return await self._client._apost(f"/v1/whatsapp/wa-groups/{group_id}/join-requests", data=payload) - async def areject_whats_app_group_join_requests( - self, group_id: str, account_id: str, phone_numbers: list[str] - ) -> dict[str, Any]: + async def areject_whats_app_group_join_requests(self, group_id: str, account_id: str, phone_numbers: list[str]) -> dict[str, Any]: """Reject join requests (async)""" params = self._build_params( account_id=account_id, ) - return await self._client._adelete( - f"/v1/whatsapp/wa-groups/{group_id}/join-requests", params=params - ) + return await self._client._adelete(f"/v1/whatsapp/wa-groups/{group_id}/join-requests", params=params) diff --git a/src/late/resources/_generated/whatsapp_phone_numbers.py b/src/late/resources/_generated/whatsapp_phone_numbers.py index 136d137..d248261 100644 --- a/src/late/resources/_generated/whatsapp_phone_numbers.py +++ b/src/late/resources/_generated/whatsapp_phone_numbers.py @@ -23,21 +23,17 @@ def __init__(self, client: BaseClient) -> None: def _build_params(self, **kwargs: Any) -> dict[str, Any]: """Build query parameters, filtering None values.""" - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - return {to_camel(k): v for k, v in kwargs.items() if v is not None} def _build_payload(self, **kwargs: Any) -> dict[str, Any]: """Build request payload, filtering None values.""" from datetime import datetime - def to_camel(s: str) -> str: parts = s.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) - result: dict[str, Any] = {} for k, v in kwargs.items(): if v is None: @@ -48,9 +44,7 @@ def to_camel(s: str) -> str: result[to_camel(k)] = v return result - def get_whats_app_phone_numbers( - self, *, status: str | None = None, profile_id: str | None = None - ) -> dict[str, Any]: + def get_whats_app_phone_numbers(self, *, status: str | None = None, profile_id: str | None = None) -> dict[str, Any]: """List phone numbers""" params = self._build_params( status=status, @@ -73,9 +67,7 @@ def release_whats_app_phone_number(self, phone_number_id: str) -> dict[str, Any] """Release phone number""" return self._client._delete(f"/v1/whatsapp/phone-numbers/{phone_number_id}") - async def aget_whats_app_phone_numbers( - self, *, status: str | None = None, profile_id: str | None = None - ) -> dict[str, Any]: + async def aget_whats_app_phone_numbers(self, *, status: str | None = None, profile_id: str | None = None) -> dict[str, Any]: """List phone numbers (async)""" params = self._build_params( status=status, @@ -88,18 +80,12 @@ async def apurchase_whats_app_phone_number(self, profile_id: str) -> dict[str, A payload = self._build_payload( profile_id=profile_id, ) - return await self._client._apost( - "/v1/whatsapp/phone-numbers/purchase", data=payload - ) + return await self._client._apost("/v1/whatsapp/phone-numbers/purchase", data=payload) async def aget_whats_app_phone_number(self, phone_number_id: str) -> dict[str, Any]: """Get phone number (async)""" return await self._client._aget(f"/v1/whatsapp/phone-numbers/{phone_number_id}") - async def arelease_whats_app_phone_number( - self, phone_number_id: str - ) -> dict[str, Any]: + async def arelease_whats_app_phone_number(self, phone_number_id: str) -> dict[str, Any]: """Release phone number (async)""" - return await self._client._adelete( - f"/v1/whatsapp/phone-numbers/{phone_number_id}" - ) + return await self._client._adelete(f"/v1/whatsapp/phone-numbers/{phone_number_id}") diff --git a/src/late/resources/tools.py b/src/late/resources/tools.py index cdf1376..2019e9e 100644 --- a/src/late/resources/tools.py +++ b/src/late/resources/tools.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING -from late.models import ( +from late.models.responses import ( CaptionResponse, DownloadResponse, HashtagCheckResponse, diff --git a/tests/test_exhaustive.py b/tests/test_exhaustive.py index d283c6e..28c68b0 100644 --- a/tests/test_exhaustive.py +++ b/tests/test_exhaustive.py @@ -271,13 +271,11 @@ def test_status_enum_values(self): assert Status.FAILED.value == "failed" def test_type_enum_values(self): - """Test Type enum has expected values.""" - from late.models import Type + """Test MediaItem type field accepts media types.""" + from late.models._generated.models import MediaItem - assert hasattr(Type, "IMAGE") - assert hasattr(Type, "VIDEO") - assert Type.IMAGE.value == "image" - assert Type.VIDEO.value == "video" + assert MediaItem(type="image").type.value == "image" + assert MediaItem(type="video").type.value == "video" def test_tiktok_settings_creation(self): """Test TikTokPlatformData model creation.""" @@ -294,11 +292,10 @@ def test_tiktok_settings_creation(self): def test_media_item_creation(self): """Test MediaItem with type enum.""" - from late.models import Type from late.models._generated.models import MediaItem - item = MediaItem(type=Type.IMAGE) - assert item.type == Type.IMAGE + item = MediaItem(type="image") + assert item.type.value == "image" # ============================================================================ diff --git a/uv.lock b/uv.lock index 6a8b4ed..10879a9 100644 --- a/uv.lock +++ b/uv.lock @@ -1905,7 +1905,7 @@ wheels = [ [[package]] name = "zernio-sdk" -version = "1.3.25" +version = "1.3.35" source = { editable = "." } dependencies = [ { name = "httpx" },