posterly-mcp-server 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ export const getBrandTool = {
3
+ name: 'get_brand',
4
+ description: 'Get one brand/client by ID. Returns its workspace, source, linked legacy brand group if present, and the number of social accounts assigned to it.',
5
+ inputSchema: z.object({
6
+ brand_id: z.string().describe('The brand ID to look up (from list_brands).'),
7
+ }),
8
+ async execute(client, input) {
9
+ const result = await client.getBrand(input.brand_id);
10
+ const brand = result.brand;
11
+ const lines = [
12
+ `Brand: ${brand.name}`,
13
+ `• ID: ${brand.id}`,
14
+ `• Source: ${brand.source}`,
15
+ `• Workspace: ${brand.workspace_id || 'N/A'}`,
16
+ `• Accounts assigned: ${brand.account_count ?? 0}`,
17
+ ];
18
+ if (brand.workspace_client_id)
19
+ lines.push(`• Workspace client ID: ${brand.workspace_client_id}`);
20
+ if (brand.legacy_brand_group_id)
21
+ lines.push(`• Legacy brand group ID: ${brand.legacy_brand_group_id}`);
22
+ if (brand.created_at)
23
+ lines.push(`• Created: ${brand.created_at}`);
24
+ if (brand.updated_at)
25
+ lines.push(`• Updated: ${brand.updated_at}`);
26
+ return lines.join('\n');
27
+ },
28
+ };
@@ -0,0 +1,32 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ export declare const getPostAnalyticsTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: z.ZodObject<{
7
+ account_id: z.ZodNumber;
8
+ from: z.ZodOptional<z.ZodString>;
9
+ to: z.ZodOptional<z.ZodString>;
10
+ limit: z.ZodOptional<z.ZodNumber>;
11
+ offset: z.ZodOptional<z.ZodNumber>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ account_id: number;
14
+ limit?: number | undefined;
15
+ offset?: number | undefined;
16
+ from?: string | undefined;
17
+ to?: string | undefined;
18
+ }, {
19
+ account_id: number;
20
+ limit?: number | undefined;
21
+ offset?: number | undefined;
22
+ from?: string | undefined;
23
+ to?: string | undefined;
24
+ }>;
25
+ execute(client: PosterlyClient, input: {
26
+ account_id: number;
27
+ from?: string;
28
+ to?: string;
29
+ limit?: number;
30
+ offset?: number;
31
+ }): Promise<string>;
32
+ };
@@ -0,0 +1,65 @@
1
+ import { z } from 'zod';
2
+ export const getPostAnalyticsTool = {
3
+ name: 'get_post_analytics',
4
+ description: 'Get per-post engagement metrics (likes, comments, reach, impressions, saves, shares, plays) for a connected social account. Supports Instagram, LinkedIn, and Google Business Profile. Returns the most recent posts first.',
5
+ inputSchema: z.object({
6
+ account_id: z
7
+ .number()
8
+ .describe('The social account ID (from list_accounts)'),
9
+ from: z
10
+ .string()
11
+ .optional()
12
+ .describe('Start date (ISO date, e.g. 2026-03-19). Defaults to 30 days ago.'),
13
+ to: z
14
+ .string()
15
+ .optional()
16
+ .describe('End date (ISO date). Defaults to today.'),
17
+ limit: z
18
+ .number()
19
+ .min(1)
20
+ .max(200)
21
+ .optional()
22
+ .describe('Number of posts to return (default 50, max 200)'),
23
+ offset: z.number().min(0).optional().describe('Pagination offset'),
24
+ }),
25
+ async execute(client, input) {
26
+ const result = await client.getPostAnalytics(input);
27
+ const { account, range, posts, total } = result;
28
+ if (posts.length === 0) {
29
+ return `No analytics found for @${account.username} (${account.platform}) between ${range.from} and ${range.to}.`;
30
+ }
31
+ const lines = [
32
+ `Post analytics for @${account.username} (${account.platform})`,
33
+ `Range: ${range.from} → ${range.to} • Showing ${posts.length} of ${total}`,
34
+ '',
35
+ ];
36
+ for (const p of posts) {
37
+ const postedAt = p.posted_at
38
+ ? new Date(p.posted_at).toLocaleString()
39
+ : 'unknown date';
40
+ const caption = p.caption_snippet
41
+ ? p.caption_snippet.length > 60
42
+ ? `${p.caption_snippet.slice(0, 60)}…`
43
+ : p.caption_snippet
44
+ : '(no caption)';
45
+ const metrics = [
46
+ `${p.likes} likes`,
47
+ `${p.comments} comments`,
48
+ `${p.reach.toLocaleString()} reach`,
49
+ ];
50
+ if (p.impressions)
51
+ metrics.push(`${p.impressions.toLocaleString()} impressions`);
52
+ if (p.saved)
53
+ metrics.push(`${p.saved} saved`);
54
+ if (p.shares)
55
+ metrics.push(`${p.shares} shares`);
56
+ if (p.plays)
57
+ metrics.push(`${p.plays.toLocaleString()} plays`);
58
+ lines.push(`• [${postedAt}] ${caption}`);
59
+ lines.push(` ${metrics.join(' • ')}`);
60
+ if (p.permalink)
61
+ lines.push(` ${p.permalink}`);
62
+ }
63
+ return lines.join('\n');
64
+ },
65
+ };
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ export declare const listBrandAccountsTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: z.ZodObject<{
7
+ brand_id: z.ZodString;
8
+ }, "strip", z.ZodTypeAny, {
9
+ brand_id: string;
10
+ }, {
11
+ brand_id: string;
12
+ }>;
13
+ execute(client: PosterlyClient, input: {
14
+ brand_id: string;
15
+ }): Promise<string>;
16
+ };
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ export const listBrandAccountsTool = {
3
+ name: 'list_brand_accounts',
4
+ description: 'List the connected social accounts assigned to a brand/client. Use this when a user refers to a brand name rather than a raw account handle.',
5
+ inputSchema: z.object({
6
+ brand_id: z.string().describe('The brand ID to inspect (from list_brands).'),
7
+ }),
8
+ async execute(client, input) {
9
+ const accounts = await client.listBrandAccounts(input.brand_id);
10
+ if (accounts.length === 0) {
11
+ return 'No social accounts are currently assigned to this brand.';
12
+ }
13
+ const lines = accounts.map((account) => `• ${account.platform} — @${account.username} (ID: ${account.id}${account.workspace_id ? `, ws: ${account.workspace_id}` : ''})`);
14
+ return `Brand accounts (${accounts.length}):\n${lines.join('\n')}`;
15
+ },
16
+ };
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ import type { PosterlyClient } from '../lib/api-client.js';
3
+ export declare const listBrandsTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: z.ZodObject<{
7
+ workspace_id: z.ZodOptional<z.ZodString>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ workspace_id?: string | undefined;
10
+ }, {
11
+ workspace_id?: string | undefined;
12
+ }>;
13
+ execute(client: PosterlyClient, input: {
14
+ workspace_id?: string;
15
+ }): Promise<string>;
16
+ };
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ export const listBrandsTool = {
3
+ name: 'list_brands',
4
+ description: 'List brands/clients the caller can access. Returns each brand ID, name, workspace ID, source, and how many social accounts are currently assigned to it.',
5
+ inputSchema: z.object({
6
+ workspace_id: z
7
+ .string()
8
+ .optional()
9
+ .describe('Filter to brands in a specific workspace (get IDs via whoami).'),
10
+ }),
11
+ async execute(client, input) {
12
+ const brands = await client.listBrands({ workspace_id: input.workspace_id });
13
+ if (brands.length === 0) {
14
+ return input.workspace_id
15
+ ? 'No brands found in this workspace.'
16
+ : 'No brands found. Create brands in the posterly dashboard first.';
17
+ }
18
+ const lines = brands.map((brand) => {
19
+ const suffix = [
20
+ `ID: ${brand.id}`,
21
+ brand.workspace_id ? `ws: ${brand.workspace_id}` : null,
22
+ `accounts: ${brand.account_count}`,
23
+ brand.source,
24
+ ].filter(Boolean).join(', ');
25
+ return `• ${brand.name} (${suffix})`;
26
+ });
27
+ return `Brands (${brands.length}):\n${lines.join('\n')}`;
28
+ },
29
+ };
@@ -6,22 +6,26 @@ export declare const listPostsTool: {
6
6
  inputSchema: z.ZodObject<{
7
7
  status: z.ZodOptional<z.ZodString>;
8
8
  platform: z.ZodOptional<z.ZodString>;
9
+ account_id: z.ZodOptional<z.ZodString>;
9
10
  limit: z.ZodOptional<z.ZodNumber>;
10
11
  workspace_id: z.ZodOptional<z.ZodString>;
11
12
  }, "strip", z.ZodTypeAny, {
12
13
  workspace_id?: string | undefined;
13
14
  status?: string | undefined;
14
15
  platform?: string | undefined;
16
+ account_id?: string | undefined;
15
17
  limit?: number | undefined;
16
18
  }, {
17
19
  workspace_id?: string | undefined;
18
20
  status?: string | undefined;
19
21
  platform?: string | undefined;
22
+ account_id?: string | undefined;
20
23
  limit?: number | undefined;
21
24
  }>;
22
25
  execute(client: PosterlyClient, input: {
23
26
  status?: string;
24
27
  platform?: string;
28
+ account_id?: string;
25
29
  limit?: number;
26
30
  workspace_id?: string;
27
31
  }): Promise<string>;
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  export const listPostsTool = {
3
3
  name: 'list_posts',
4
- description: 'List upcoming or recent posts. Filter by status (scheduled, published, failed, draft), platform, or workspace_id. If workspace_id is omitted, posts across every workspace the caller is a member of are returned.',
4
+ description: 'List upcoming or recent posts. Filter by status (scheduled, published, failed, draft), platform, account_id, or workspace_id. If workspace_id is omitted, posts across every workspace the caller is a member of are returned.',
5
5
  inputSchema: z.object({
6
6
  status: z
7
7
  .string()
@@ -11,12 +11,16 @@ export const listPostsTool = {
11
11
  .string()
12
12
  .optional()
13
13
  .describe('Filter by platform: instagram, twitter, linkedin, etc.'),
14
+ account_id: z
15
+ .string()
16
+ .optional()
17
+ .describe('Filter to a specific social account ID (from list_accounts).'),
14
18
  limit: z
15
19
  .number()
16
20
  .min(1)
17
- .max(50)
21
+ .max(100)
18
22
  .optional()
19
- .describe('Number of posts to return (default 10, max 50)'),
23
+ .describe('Number of posts to return (default 20, max 100)'),
20
24
  workspace_id: z
21
25
  .string()
22
26
  .optional()
@@ -25,7 +29,7 @@ export const listPostsTool = {
25
29
  async execute(client, input) {
26
30
  const { posts, total } = await client.listPosts({
27
31
  ...input,
28
- limit: input.limit || 10,
32
+ limit: input.limit || 20,
29
33
  });
30
34
  if (posts.length === 0) {
31
35
  return 'No posts found matching your criteria.';
package/package.json CHANGED
@@ -1,8 +1,27 @@
1
1
  {
2
2
  "name": "posterly-mcp-server",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "MCP server for posterly — schedule social media posts from Claude Desktop",
5
5
  "license": "MIT",
6
+ "homepage": "https://www.poster.ly/mcp",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/awpthorp/posterly.git",
10
+ "directory": "mcp-server"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/awpthorp/posterly/issues"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "posterly",
19
+ "social-media",
20
+ "scheduling",
21
+ "claude",
22
+ "cursor",
23
+ "chatgpt"
24
+ ],
6
25
  "type": "module",
7
26
  "bin": {
8
27
  "posterly-mcp": "./dist/index.js"
package/src/index.ts CHANGED
@@ -4,6 +4,10 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
5
  import { PosterlyClient } from './lib/api-client.js';
6
6
  import { listAccountsTool } from './tools/list-accounts.js';
7
+ import { listBrandsTool } from './tools/list-brands.js';
8
+ import { getBrandTool } from './tools/get-brand.js';
9
+ import { listBrandAccountsTool } from './tools/list-brand-accounts.js';
10
+ import { getBrandProfileTool } from './tools/get-brand-profile.js';
7
11
  import { createPostTool } from './tools/create-post.js';
8
12
  import { findSlotTool } from './tools/find-slot.js';
9
13
  import { listPostsTool } from './tools/list-posts.js';
@@ -13,10 +17,12 @@ import { updatePostTool } from './tools/update-post.js';
13
17
  import { deletePostTool } from './tools/delete-post.js';
14
18
  import { whoamiTool } from './tools/whoami.js';
15
19
  import { generateImageTool } from './tools/generate-image.js';
20
+ import { getAccountAnalyticsTool } from './tools/get-account-analytics.js';
21
+ import { getPostAnalyticsTool } from './tools/get-post-analytics.js';
16
22
 
17
23
  const server = new McpServer({
18
24
  name: 'posterly',
19
- version: '0.4.0',
25
+ version: '0.6.0',
20
26
  });
21
27
 
22
28
  let client: PosterlyClient;
@@ -57,6 +63,62 @@ server.tool(
57
63
  }
58
64
  );
59
65
 
66
+ server.tool(
67
+ listBrandsTool.name,
68
+ listBrandsTool.description,
69
+ listBrandsTool.inputSchema.shape,
70
+ async (input) => {
71
+ try {
72
+ const text = await listBrandsTool.execute(client, input as any);
73
+ return { content: [{ type: 'text' as const, text }] };
74
+ } catch (err: any) {
75
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
76
+ }
77
+ }
78
+ );
79
+
80
+ server.tool(
81
+ getBrandTool.name,
82
+ getBrandTool.description,
83
+ getBrandTool.inputSchema.shape,
84
+ async (input) => {
85
+ try {
86
+ const text = await getBrandTool.execute(client, input as any);
87
+ return { content: [{ type: 'text' as const, text }] };
88
+ } catch (err: any) {
89
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
90
+ }
91
+ }
92
+ );
93
+
94
+ server.tool(
95
+ listBrandAccountsTool.name,
96
+ listBrandAccountsTool.description,
97
+ listBrandAccountsTool.inputSchema.shape,
98
+ async (input) => {
99
+ try {
100
+ const text = await listBrandAccountsTool.execute(client, input as any);
101
+ return { content: [{ type: 'text' as const, text }] };
102
+ } catch (err: any) {
103
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
104
+ }
105
+ }
106
+ );
107
+
108
+ server.tool(
109
+ getBrandProfileTool.name,
110
+ getBrandProfileTool.description,
111
+ getBrandProfileTool.inputSchema.shape,
112
+ async (input) => {
113
+ try {
114
+ const text = await getBrandProfileTool.execute(client, input as any);
115
+ return { content: [{ type: 'text' as const, text }] };
116
+ } catch (err: any) {
117
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
118
+ }
119
+ }
120
+ );
121
+
60
122
  server.tool(
61
123
  createPostTool.name,
62
124
  createPostTool.description,
@@ -169,6 +231,34 @@ server.tool(
169
231
  }
170
232
  );
171
233
 
234
+ server.tool(
235
+ getAccountAnalyticsTool.name,
236
+ getAccountAnalyticsTool.description,
237
+ getAccountAnalyticsTool.inputSchema.shape,
238
+ async (input) => {
239
+ try {
240
+ const text = await getAccountAnalyticsTool.execute(client, input as any);
241
+ return { content: [{ type: 'text' as const, text }] };
242
+ } catch (err: any) {
243
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
244
+ }
245
+ }
246
+ );
247
+
248
+ server.tool(
249
+ getPostAnalyticsTool.name,
250
+ getPostAnalyticsTool.description,
251
+ getPostAnalyticsTool.inputSchema.shape,
252
+ async (input) => {
253
+ try {
254
+ const text = await getPostAnalyticsTool.execute(client, input as any);
255
+ return { content: [{ type: 'text' as const, text }] };
256
+ } catch (err: any) {
257
+ return { content: [{ type: 'text' as const, text: `Error: ${err.message}` }], isError: true };
258
+ }
259
+ }
260
+ );
261
+
172
262
  // Start the server
173
263
  async function main() {
174
264
  const transport = new StdioServerTransport();
@@ -10,6 +10,43 @@ export interface Account {
10
10
  workspace_id?: string | null;
11
11
  }
12
12
 
13
+ export interface Brand {
14
+ id: string;
15
+ name: string;
16
+ workspace_id?: string | null;
17
+ workspace_client_id?: string | null;
18
+ legacy_brand_group_id?: string | null;
19
+ created_at?: string | null;
20
+ updated_at?: string | null;
21
+ source: 'canonical' | 'legacy';
22
+ account_count: number;
23
+ }
24
+
25
+ export interface BrandProfile {
26
+ id: string | null;
27
+ brand_group_id: string | null;
28
+ workspace_client_id: string | null;
29
+ brand_name: string;
30
+ tone_of_voice?: string | null;
31
+ audience?: string | null;
32
+ brand_values?: string | null;
33
+ do_donts?: unknown;
34
+ keywords?: unknown;
35
+ competitors?: unknown;
36
+ custom_instructions?: string | null;
37
+ visual_guidelines?: unknown;
38
+ logo_url?: string | null;
39
+ brand_story?: string | null;
40
+ example_posts?: unknown;
41
+ topics_to_cover?: unknown;
42
+ topics_to_avoid?: unknown;
43
+ voice_examples?: unknown;
44
+ last_context_refresh_at?: string | null;
45
+ created_at?: string | null;
46
+ updated_at?: string | null;
47
+ source: 'canonical' | 'legacy';
48
+ }
49
+
13
50
  export interface Post {
14
51
  id: number;
15
52
  content: string;
@@ -45,6 +82,77 @@ export interface Whoami {
45
82
  workspaces: Workspace[];
46
83
  }
47
84
 
85
+ export interface AccountAnalyticsSummary {
86
+ current_followers: number;
87
+ followers_change: number;
88
+ total_reach: number | null;
89
+ total_views: number | null;
90
+ total_accounts_engaged: number | null;
91
+ total_follows_gained: number;
92
+ total_follows_lost: number;
93
+ engagement_rate: number;
94
+ engagement_rate_by_followers: number;
95
+ total_website_clicks: number | null;
96
+ total_call_clicks: number | null;
97
+ total_direction_requests: number | null;
98
+ total_conversations: number | null;
99
+ total_bookings: number | null;
100
+ }
101
+
102
+ export interface AccountAnalyticsSnapshot {
103
+ date: string;
104
+ followers: number | null;
105
+ following: number | null;
106
+ media_count: number | null;
107
+ reach: number | null;
108
+ views: number | null;
109
+ profile_views: number | null;
110
+ accounts_engaged: number | null;
111
+ follows_gained: number | null;
112
+ follows_lost: number | null;
113
+ website_clicks: number | null;
114
+ call_clicks: number | null;
115
+ direction_requests: number | null;
116
+ conversations: number | null;
117
+ bookings: number | null;
118
+ }
119
+
120
+ export interface AccountAnalyticsResponse {
121
+ account: { id: number; platform: string; username: string };
122
+ range: { from: string; to: string };
123
+ summary: AccountAnalyticsSummary;
124
+ snapshots: AccountAnalyticsSnapshot[];
125
+ }
126
+
127
+ export interface PostAnalyticsRow {
128
+ id: number | null;
129
+ platform_media_id: string;
130
+ platform: string;
131
+ posted_at: string | null;
132
+ likes: number;
133
+ comments: number;
134
+ impressions: number;
135
+ reach: number;
136
+ saved: number;
137
+ shares: number;
138
+ plays: number;
139
+ total_interactions: number;
140
+ media_type: string | null;
141
+ media_url: string | null;
142
+ permalink: string | null;
143
+ caption_snippet: string | null;
144
+ synced_at: string;
145
+ }
146
+
147
+ export interface PostAnalyticsResponse {
148
+ account: { id: number; platform: string; username: string };
149
+ range: { from: string; to: string };
150
+ posts: PostAnalyticsRow[];
151
+ total: number;
152
+ limit: number;
153
+ offset: number;
154
+ }
155
+
48
156
  export class PosterlyClient {
49
157
  private baseUrl: string;
50
158
  private apiKey: string;
@@ -99,9 +207,31 @@ export class PosterlyClient {
99
207
  return data.accounts;
100
208
  }
101
209
 
210
+ async listBrands(params?: { workspace_id?: string }): Promise<Brand[]> {
211
+ const searchParams = new URLSearchParams();
212
+ if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
213
+ const qs = searchParams.toString();
214
+ const data = await this.request<{ brands: Brand[] }>('GET', `/brands${qs ? `?${qs}` : ''}`);
215
+ return data.brands;
216
+ }
217
+
218
+ async getBrand(id: string): Promise<{ brand: Brand }> {
219
+ return this.request('GET', `/brands/${encodeURIComponent(id)}`);
220
+ }
221
+
222
+ async listBrandAccounts(id: string): Promise<Account[]> {
223
+ const data = await this.request<{ accounts: Account[] }>('GET', `/brands/${encodeURIComponent(id)}/accounts`);
224
+ return data.accounts;
225
+ }
226
+
227
+ async getBrandProfile(id: string): Promise<{ brand_profile: BrandProfile }> {
228
+ return this.request('GET', `/brands/${encodeURIComponent(id)}/profile`);
229
+ }
230
+
102
231
  async listPosts(params?: {
103
232
  status?: string;
104
233
  platform?: string;
234
+ account_id?: string;
105
235
  limit?: number;
106
236
  offset?: number;
107
237
  workspace_id?: string;
@@ -109,6 +239,7 @@ export class PosterlyClient {
109
239
  const searchParams = new URLSearchParams();
110
240
  if (params?.status) searchParams.set('status', params.status);
111
241
  if (params?.platform) searchParams.set('platform', params.platform);
242
+ if (params?.account_id) searchParams.set('account_id', params.account_id);
112
243
  if (params?.limit) searchParams.set('limit', String(params.limit));
113
244
  if (params?.offset) searchParams.set('offset', String(params.offset));
114
245
  if (params?.workspace_id) searchParams.set('workspace_id', params.workspace_id);
@@ -198,6 +329,34 @@ export class PosterlyClient {
198
329
  return data.slots;
199
330
  }
200
331
 
332
+ async getAccountAnalytics(params: {
333
+ account_id: number;
334
+ from?: string;
335
+ to?: string;
336
+ }): Promise<AccountAnalyticsResponse> {
337
+ const searchParams = new URLSearchParams();
338
+ searchParams.set('account_id', String(params.account_id));
339
+ if (params.from) searchParams.set('from', params.from);
340
+ if (params.to) searchParams.set('to', params.to);
341
+ return this.request('GET', `/analytics/accounts?${searchParams.toString()}`);
342
+ }
343
+
344
+ async getPostAnalytics(params: {
345
+ account_id: number;
346
+ from?: string;
347
+ to?: string;
348
+ limit?: number;
349
+ offset?: number;
350
+ }): Promise<PostAnalyticsResponse> {
351
+ const searchParams = new URLSearchParams();
352
+ searchParams.set('account_id', String(params.account_id));
353
+ if (params.from) searchParams.set('from', params.from);
354
+ if (params.to) searchParams.set('to', params.to);
355
+ if (params.limit) searchParams.set('limit', String(params.limit));
356
+ if (params.offset) searchParams.set('offset', String(params.offset));
357
+ return this.request('GET', `/analytics/posts?${searchParams.toString()}`);
358
+ }
359
+
201
360
  async getSignedUploadUrl(
202
361
  filename: string,
203
362
  contentType: string,