@publishq/sdk 0.0.1 → 0.0.3

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.
package/README.md CHANGED
@@ -40,7 +40,7 @@ for (const account of data?.accounts ?? []) {
40
40
  ## Authentication
41
41
 
42
42
  Pass your API key (starts with `pq_live_`). Create one in the PublishQ web app
43
- under **Settings → API Keys** and select the scopes you need.
43
+ under **API Keys** and select the scopes you need.
44
44
 
45
45
  ```ts
46
46
  const publishq = new PublishQ({ apiKey: 'pq_live_...' });
@@ -79,7 +79,7 @@ await publishq.posts.removeAccount('<post-id>', '<account-id>');
79
79
  // List connected social accounts (filter by workspace / platform)
80
80
  const { data } = await publishq.accounts.list({ platform: ['TIKTOK'] });
81
81
 
82
- // TikTok requires fetching creator info before posting — the privacyLevel you
82
+ // TikTok requires fetching creator info before posting. The privacyLevel you
83
83
  // set must be one of the returned privacyLevelOptions.
84
84
  const { data: info } = await publishq.accounts.getTikTokCreatorInfo('<account-id>');
85
85
  ```
@@ -95,7 +95,7 @@ await publishq.workspaces.get('<workspace-id>');
95
95
  ### Media
96
96
 
97
97
  ```ts
98
- // Upload a file, then attach the returned media id to a post via `mediaIds`
98
+ // Upload a file of up to 4 GB, then attach the returned media id to a post via `mediaIds`.
99
99
  const { data: media } = await publishq.media.upload({ file });
100
100
  await publishq.media.list({ mediaTypes: ['IMAGE'] });
101
101
  await publishq.media.get('<media-id>');
@@ -114,7 +114,7 @@ returns `403`.
114
114
  | `workspaces:read` | `workspaces.list`, `workspaces.get` |
115
115
  | `workspaces:write` | `workspaces.create` |
116
116
  | `media:read` | `media.list`, `media.get` |
117
- | `media:write` | `media.upload` |
117
+ | `media:write` | `media.upload`, `media.createUpload`, `media.completeUpload` |
118
118
 
119
119
  ## Configuration
120
120
 
@@ -128,8 +128,8 @@ const publishq = new PublishQ({
128
128
 
129
129
  ## Related
130
130
 
131
- - [`@publishq/cli`](https://www.npmjs.com/package/@publishq/cli) — command-line interface built on this SDK.
132
- - [`@publishq/mcp`](https://www.npmjs.com/package/@publishq/mcp) — Model Context Protocol server for Claude Desktop and other AI agents.
131
+ - [`@publishq/cli`](https://www.npmjs.com/package/@publishq/cli): command-line interface built on this SDK.
132
+ - [`@publishq/mcp`](https://www.npmjs.com/package/@publishq/mcp): Model Context Protocol server for Claude Desktop and other AI agents.
133
133
  - [API reference](https://publishq.com/docs)
134
134
 
135
135
  ## License
package/dist/index.cjs CHANGED
@@ -34,14 +34,21 @@ __export(index_exports, {
34
34
  Media2: () => Media,
35
35
  Posts: () => Posts,
36
36
  PublishQ: () => PublishQ,
37
+ STORAGE_UPLOAD_THRESHOLD_BYTES: () => STORAGE_UPLOAD_THRESHOLD_BYTES,
37
38
  Workspaces: () => Workspaces,
38
39
  default: () => index_default,
40
+ withInferredContentType: () => withInferredContentType,
39
41
  zAccount: () => zAccount,
40
42
  zAccountsResponse: () => zAccountsResponse,
41
43
  zApiError: () => zApiError,
42
44
  zApiErrorCode: () => zApiErrorCode,
43
45
  zApiKeyScope: () => zApiKeyScope,
44
46
  zBlueskyPlatformSpecificSettings: () => zBlueskyPlatformSpecificSettings,
47
+ zCompleteMediaUploadPath: () => zCompleteMediaUploadPath,
48
+ zCompleteMediaUploadResponse: () => zCompleteMediaUploadResponse,
49
+ zCreateMediaUploadBody: () => zCreateMediaUploadBody,
50
+ zCreateMediaUploadRequest: () => zCreateMediaUploadRequest,
51
+ zCreateMediaUploadResponse: () => zCreateMediaUploadResponse,
45
52
  zCreatePostAccount: () => zCreatePostAccount,
46
53
  zCreatePostBody: () => zCreatePostBody,
47
54
  zCreatePostRequest: () => zCreatePostRequest,
@@ -74,13 +81,17 @@ __export(index_exports, {
74
81
  zMediaListResponse: () => zMediaListResponse,
75
82
  zMediaSource: () => zMediaSource,
76
83
  zMediaType: () => zMediaType,
84
+ zMediaUpload: () => zMediaUpload,
77
85
  zPagination: () => zPagination,
78
86
  zPlatformSpecificSettings: () => zPlatformSpecificSettings,
79
87
  zPost: () => zPost,
80
88
  zPostAccount: () => zPostAccount,
89
+ zPostAnalytics: () => zPostAnalytics,
81
90
  zPostOverrides: () => zPostOverrides,
91
+ zPostSource: () => zPostSource,
82
92
  zPostStatus: () => zPostStatus,
83
93
  zPostsResponse: () => zPostsResponse,
94
+ zPublishFailure: () => zPublishFailure,
84
95
  zRemovePostAccountPath: () => zRemovePostAccountPath,
85
96
  zRemovePostAccountResponse: () => zRemovePostAccountResponse,
86
97
  zSocialPlatform: () => zSocialPlatform,
@@ -89,7 +100,9 @@ __export(index_exports, {
89
100
  zTikTokCreatorInfoResponse: () => zTikTokCreatorInfoResponse,
90
101
  zTikTokPlatformSpecificSettings: () => zTikTokPlatformSpecificSettings,
91
102
  zTwitterPlatformSpecificSettings: () => zTwitterPlatformSpecificSettings,
103
+ zUpdatePostAccount: () => zUpdatePostAccount,
92
104
  zUpdatePostBody: () => zUpdatePostBody,
105
+ zUpdatePostOverrides: () => zUpdatePostOverrides,
93
106
  zUpdatePostPath: () => zUpdatePostPath,
94
107
  zUpdatePostRequest: () => zUpdatePostRequest,
95
108
  zUpdatePostResponse: () => zUpdatePostResponse,
@@ -920,7 +933,7 @@ var Posts = class {
920
933
  /**
921
934
  * List posts
922
935
  *
923
- * Returns a paginated list of posts.
936
+ * Returns a paginated list of the posts you created through PublishQ. Posts imported from a connected platform are excluded by default; pass includeImported=true to get both, or onlyImported=true for only the imported ones.
924
937
  */
925
938
  static listPosts(options) {
926
939
  return (options?.client ?? client).get({
@@ -1082,7 +1095,7 @@ var Media = class {
1082
1095
  /**
1083
1096
  * Upload media
1084
1097
  *
1085
- * Upload one media file with multipart form data. The returned media ID can then be attached to posts.
1098
+ * Upload a file smaller than 100 MB in a single request. It is the simplest way to upload: one call returns a media ID you can attach to posts. For bigger files, use Create media upload.
1086
1099
  */
1087
1100
  static uploadMedia(options) {
1088
1101
  return (options.client ?? client).post({
@@ -1108,6 +1121,39 @@ var Media = class {
1108
1121
  ...options
1109
1122
  });
1110
1123
  }
1124
+ /**
1125
+ * Create media upload
1126
+ *
1127
+ * Upload a file of up to 4 GB (500 MB on the Free plan) straight to storage:
1128
+ *
1129
+ * 1. Call this endpoint with `filename`, `contentType` and `fileSize`. You get back an `id` and an `uploadUrl`.
1130
+ * 2. `PUT` the file to `uploadUrl` with the same `Content-Type` header. Storage answers `200`, or `403` if the file is not exactly `fileSize` bytes.
1131
+ * 3. Optional: call Complete media upload to add the file to your media library now.
1132
+ * 4. Put the `id` in `mediaIds` when you create or update a post.
1133
+ */
1134
+ static createMediaUpload(options) {
1135
+ return (options.client ?? client).post({
1136
+ security: [{ scheme: "bearer", type: "http" }],
1137
+ url: "/media/uploads",
1138
+ ...options,
1139
+ headers: {
1140
+ "Content-Type": "application/json",
1141
+ ...options.headers
1142
+ }
1143
+ });
1144
+ }
1145
+ /**
1146
+ * Complete media upload
1147
+ *
1148
+ * Optional. Adds a file uploaded through Create media upload to your media library now and returns it. Without this call the file is added the first time you use its id in a post, and an upload nobody completes or uses within 1 day expires.
1149
+ */
1150
+ static completeMediaUpload(options) {
1151
+ return (options.client ?? client).post({
1152
+ security: [{ scheme: "bearer", type: "http" }],
1153
+ url: "/media/uploads/{id}/complete",
1154
+ ...options
1155
+ });
1156
+ }
1111
1157
  };
1112
1158
 
1113
1159
  // src/generated/zod.gen.ts
@@ -1127,6 +1173,7 @@ var zApiErrorCode = z.enum([
1127
1173
  "post_not_editable",
1128
1174
  "media_not_found",
1129
1175
  "media_in_use",
1176
+ "media_upload_incomplete",
1130
1177
  "storage_limit_exceeded",
1131
1178
  "free_media_limit_exceeded",
1132
1179
  "free_pending_media_limit_exceeded",
@@ -1137,6 +1184,9 @@ var zApiErrorCode = z.enum([
1137
1184
  "invalid_file_extension",
1138
1185
  "social_accounts_not_found",
1139
1186
  "social_accounts_expired",
1187
+ "social_account_limit_exceeded",
1188
+ "platform_temporarily_unavailable",
1189
+ "platform_permission_missing",
1140
1190
  "platform_validation_failed",
1141
1191
  "posting_limits_exceeded",
1142
1192
  "scheduled_at_required",
@@ -1146,10 +1196,12 @@ var zApiErrorCode = z.enum([
1146
1196
  "workspace_not_found",
1147
1197
  "workspace_forbidden",
1148
1198
  "workspace_owner_required",
1199
+ "workspace_limit_exceeded",
1149
1200
  "workspace_account_not_found",
1150
1201
  "workspace_account_conflict",
1151
1202
  "workspace_member_not_found",
1152
1203
  "workspace_member_conflict",
1204
+ "team_member_limit_exceeded",
1153
1205
  "workspace_invitation_invalid",
1154
1206
  "workspace_invitation_expired"
1155
1207
  ]);
@@ -1264,6 +1316,9 @@ var zYouTubePlatformSpecificSettings = z.object({
1264
1316
  })),
1265
1317
  playlistId: z.optional(z.string().register(z.globalRegistry, {
1266
1318
  description: "Playlist ID to add the video to after upload."
1319
+ })),
1320
+ thumbnailMediaId: z.optional(z.uuid().register(z.globalRegistry, {
1321
+ description: "ID of an image in your media library to set as the custom thumbnail of this video. JPEG or PNG, up to 50MB; YouTube recommends 1280x720 (16:9). Upload the image with the media endpoints first. The thumbnail is set right after the video uploads. If YouTube refuses it (custom thumbnails need a verified channel), the video still publishes and `thumbnail` on the account says why. To replace it on a draft or scheduled post, update the account with the new id; to remove it, send `thumbnailMediaId: null`."
1267
1322
  }))
1268
1323
  }).register(z.globalRegistry, {
1269
1324
  description: "YouTube-specific upload settings, such as description and privacy."
@@ -1358,6 +1413,56 @@ var zTwitterPlatformSpecificSettings = z.object({
1358
1413
  }).register(z.globalRegistry, {
1359
1414
  description: "X (Twitter) specific post settings. All fields are optional."
1360
1415
  });
1416
+ var zPostSource = z.enum(["PUBLISHQ", "IMPORTED"]).register(z.globalRegistry, {
1417
+ description: "Where the post came from. PUBLISHQ: created in PublishQ. IMPORTED: published directly on the platform and discovered during an analytics sync, so the post history stays complete."
1418
+ });
1419
+ var zPostAnalytics = z.object({
1420
+ views: z.int().check(z.gte(0)).register(z.globalRegistry, {
1421
+ description: "Views the platform reports for this post."
1422
+ }),
1423
+ likes: z.int().check(z.gte(0)).register(z.globalRegistry, {
1424
+ description: "Likes the platform reports for this post."
1425
+ }),
1426
+ comments: z.int().check(z.gte(0)).register(z.globalRegistry, {
1427
+ description: "Comments the platform reports for this post."
1428
+ }),
1429
+ shares: z.int().check(z.gte(0)).register(z.globalRegistry, {
1430
+ description: "Shares the platform reports for this post. 0 on platforms that do not disclose it (including YouTube)."
1431
+ }),
1432
+ extraMetrics: z.record(z.string(), z.int().check(z.gte(0))).register(z.globalRegistry, {
1433
+ description: "Metrics only this platform reports, keyed by metric name \u2014 Bluesky bookmarks, for example. Every metric the platform reports is always present, 0 included, so no key has to be guessed. Empty on platforms that expose nothing beyond the fields above, and a metric that already has its own field is never repeated here."
1434
+ }),
1435
+ syncedAt: z.iso.datetime().register(z.globalRegistry, {
1436
+ description: "When these numbers were last recorded. Metrics are refreshed in the background roughly once a day, and only stored when they change."
1437
+ })
1438
+ });
1439
+ var zPublishFailure = z.object({
1440
+ kind: z.enum([
1441
+ "RECONNECT",
1442
+ "MEDIA",
1443
+ "ACCOUNT_STATE",
1444
+ "TRANSIENT",
1445
+ "MAYBE_PUBLISHED",
1446
+ "UNKNOWN"
1447
+ ]).register(z.globalRegistry, {
1448
+ description: "What kind of action this failure needs. `RECONNECT`: the account has to be reconnected. `MEDIA`: the platform refused the attached file. `ACCOUNT_STATE`: the platform is blocking this account, so the fix is on the platform. `TRANSIENT`: the platform was unavailable and the same request can succeed unchanged. `MAYBE_PUBLISHED`: the platform reported an error for a post that may have gone out anyway. `UNKNOWN`: unrecognised, so read `errorMessage`."
1449
+ }),
1450
+ title: z.string().register(z.globalRegistry, {
1451
+ description: "Short label for this failure."
1452
+ }),
1453
+ whatHappened: z.string().register(z.globalRegistry, {
1454
+ description: "One sentence about what the platform did."
1455
+ }),
1456
+ whatToDo: z.string().register(z.globalRegistry, {
1457
+ description: "One sentence naming the next action."
1458
+ }),
1459
+ canRetry: z.boolean().register(z.globalRegistry, {
1460
+ description: "Whether retrying can succeed. False when the platform is blocking the account, since the same request will be refused the same way."
1461
+ }),
1462
+ checkPlatformFirst: z.boolean().register(z.globalRegistry, {
1463
+ description: "Whether the post may already be live. A publish is not idempotent and no platform accepts a deduplication key, so when this is true a retry can create a second copy: check the profile before retrying."
1464
+ })
1465
+ });
1361
1466
  var zSocialPlatform = z.enum([
1362
1467
  "TWITTER",
1363
1468
  "INSTAGRAM",
@@ -1382,8 +1487,8 @@ var zPostOverrides = z.object({
1382
1487
  thumbnailTimestamp: z.optional(z.int().check(z.gte(0)).register(z.globalRegistry, {
1383
1488
  description: "Video thumbnail timestamp in milliseconds. Applies to Instagram and TikTok."
1384
1489
  })),
1385
- mediaIds: z.optional(z.array(z.uuid()).register(z.globalRegistry, {
1386
- description: "Replacement media for this account. Completely replaces the post-level media."
1490
+ mediaIds: z.optional(z.array(z.uuid()).check(z.maxLength(35)).register(z.globalRegistry, {
1491
+ description: "Replacement media for this account. Completely replaces the post-level media. Up to 35."
1387
1492
  }))
1388
1493
  });
1389
1494
  var zPlatformSpecificSettings = z.union([
@@ -1431,6 +1536,9 @@ var zPlatformSpecificSettings = z.union([
1431
1536
  })),
1432
1537
  playlistId: z.optional(z.string().register(z.globalRegistry, {
1433
1538
  description: "Playlist ID to add the video to after upload."
1539
+ })),
1540
+ thumbnailMediaId: z.optional(z.uuid().register(z.globalRegistry, {
1541
+ description: "ID of an image in your media library to set as the custom thumbnail of this video. JPEG or PNG, up to 50MB; YouTube recommends 1280x720 (16:9). Upload the image with the media endpoints first. The thumbnail is set right after the video uploads. If YouTube refuses it (custom thumbnails need a verified channel), the video still publishes and `thumbnail` on the account says why. To replace it on a draft or scheduled post, update the account with the new id; to remove it, send `thumbnailMediaId: null`."
1434
1542
  }))
1435
1543
  }).register(z.globalRegistry, {
1436
1544
  description: "YouTube-specific upload settings, such as description and privacy."
@@ -1575,6 +1683,18 @@ var zPostAccount = z.object({
1575
1683
  errorMessage: z.optional(z.string().register(z.globalRegistry, {
1576
1684
  description: "Error message from the platform if publishing failed for this account."
1577
1685
  })),
1686
+ thumbnail: z.optional(z.object({
1687
+ status: z.enum(["APPLIED", "FAILED"]),
1688
+ errorMessage: z.optional(z.string())
1689
+ }).register(z.globalRegistry, {
1690
+ description: "Present on a published account whose post set a custom thumbnail: whether the platform applied it, and if not, why. The post itself is live either way."
1691
+ })),
1692
+ failure: z.optional(z.intersection(zPublishFailure, z.unknown().register(z.globalRegistry, {
1693
+ description: "The same failure read as an action: what happened and the one thing to do about it. Present whenever status is FAILED, including when the platform gave no reason. The wording is what the app and the failure email show, so a client does not have to interpret `errorMessage` itself."
1694
+ }))),
1695
+ analytics: z.optional(z.intersection(zPostAnalytics, z.unknown().register(z.globalRegistry, {
1696
+ description: "Latest metrics recorded for this delivery. Present only for published posts on platforms with analytics support, once a background sync has run."
1697
+ }))),
1578
1698
  postOverrides: z.optional(zPostOverrides),
1579
1699
  platformSpecificSettings: z.optional(zPlatformSpecificSettings)
1580
1700
  });
@@ -1586,6 +1706,7 @@ var zPost = z.object({
1586
1706
  description: "The shared text content of the post."
1587
1707
  }),
1588
1708
  status: zPostStatus,
1709
+ source: zPostSource,
1589
1710
  errorMessage: z.optional(z.string().register(z.globalRegistry, {
1590
1711
  description: "Human-readable error summary when one or more accounts failed."
1591
1712
  })),
@@ -1627,8 +1748,8 @@ var zCreatePostRequest = z.object({
1627
1748
  content: z._default(z.optional(z.string().register(z.globalRegistry, {
1628
1749
  description: "The text content of the post, shared across all accounts unless overridden. Optional when media is attached (platform-dependent)."
1629
1750
  })), ""),
1630
- mediaIds: z.optional(z.array(z.uuid()).register(z.globalRegistry, {
1631
- description: "Media to attach to the post. Shared across all accounts unless an account provides its own mediaIds in postOverrides."
1751
+ mediaIds: z.optional(z.array(z.uuid()).check(z.maxLength(35)).register(z.globalRegistry, {
1752
+ description: "Media to attach to the post, up to 35. Shared across all accounts unless an account provides its own mediaIds in postOverrides. Accepts IDs from Upload media and from Create media upload."
1632
1753
  })),
1633
1754
  thumbnailTimestamp: z.optional(z.int().check(z.gte(0)).register(z.globalRegistry, {
1634
1755
  description: "Default video thumbnail timestamp in milliseconds for Instagram and TikTok. Can be overridden per account."
@@ -1637,7 +1758,7 @@ var zCreatePostRequest = z.object({
1637
1758
  description: "Accounts to post to. Each entry can optionally include content overrides and platform-specific settings."
1638
1759
  }),
1639
1760
  scheduledAt: z.optional(z.iso.datetime().register(z.globalRegistry, {
1640
- description: "Schedule the post for a future date. If omitted and publishNow is false or absent, the post is saved as a draft."
1761
+ description: "Schedule the post for a future date. If omitted and publishNow is false or absent, the post is saved as a draft. Accepts UTC (2026-04-01T12:00:00Z) or an explicit UTC offset (2026-04-01T14:00:00+02:00); a time without zone information is rejected."
1641
1762
  })),
1642
1763
  publishNow: z.optional(z.boolean().register(z.globalRegistry, {
1643
1764
  description: "Publish immediately. Cannot be combined with scheduledAt."
@@ -1645,27 +1766,43 @@ var zCreatePostRequest = z.object({
1645
1766
  }).register(z.globalRegistry, {
1646
1767
  description: "Create a post across one or more connected social accounts."
1647
1768
  });
1769
+ var zUpdatePostOverrides = z.object({
1770
+ content: z.nullish(z.string()),
1771
+ mediaIds: z.nullish(z.array(z.uuid()).check(z.maxLength(35))),
1772
+ thumbnailTimestamp: z.nullish(z.int().check(z.gte(0)))
1773
+ }).register(z.globalRegistry, {
1774
+ description: "Override the post-level content, media or thumbnail timestamp for this specific account."
1775
+ });
1776
+ var zUpdatePostAccount = z.object({
1777
+ accountId: z.uuid().register(z.globalRegistry, {
1778
+ description: "ID of a connected social account to post to."
1779
+ }),
1780
+ postOverrides: z.optional(zUpdatePostOverrides),
1781
+ platformSpecificSettings: z.optional(z.intersection(zPlatformSpecificSettings, z.unknown().register(z.globalRegistry, {
1782
+ description: "Platform-specific settings to change. Only the settings you send change, and every setting you leave out keeps its current value. Send a setting as null to remove it. Nested objects merge the same way (JSON Merge Patch, RFC 7396). The resulting settings must still be valid for the platform, so a required setting cannot be removed."
1783
+ })))
1784
+ });
1648
1785
  var zUpdatePostRequest = z.object({
1649
1786
  content: z.optional(z.string().register(z.globalRegistry, {
1650
1787
  description: "New shared text content for the post."
1651
1788
  })),
1652
1789
  scheduledAt: z.optional(z.iso.datetime().register(z.globalRegistry, {
1653
- description: "New scheduled date for the post. Must be at least 1 minute in the future."
1790
+ description: "New scheduled date for the post. Must be at least 1 minute in the future. Accepts UTC (2026-04-15T14:00:00Z) or an explicit UTC offset (2026-04-15T16:00:00+02:00); a time without zone information is rejected."
1654
1791
  })),
1655
- mediaIds: z.optional(z.array(z.uuid()).register(z.globalRegistry, {
1656
- description: "Full replacement for the post media. If omitted, the existing media stays unchanged. If provided, it replaces the current media across all accounts in the post. Send an empty array to remove all media from the post."
1792
+ mediaIds: z.optional(z.array(z.uuid()).check(z.maxLength(35)).register(z.globalRegistry, {
1793
+ description: "Full replacement for the post media, up to 35. If omitted, the existing media stays unchanged. If provided, it replaces the current media across all accounts in the post. Send an empty array to remove all media from the post."
1657
1794
  })),
1658
1795
  thumbnailTimestamp: z.optional(z.int().check(z.gte(0)).register(z.globalRegistry, {
1659
1796
  description: "New video thumbnail timestamp in milliseconds for Instagram and TikTok media. Does not change which media files are attached. Combine with mediaIds if you want to replace media and set the new thumbnail timestamp in the same request."
1660
1797
  })),
1661
- accounts: z.optional(z.array(zCreatePostAccount).check(z.minLength(1)).register(z.globalRegistry, {
1662
- description: "Accounts whose content overrides or platform-specific settings should be updated, plus any additional connected accounts to add to the post. Existing accounts omitted from this array remain unchanged and must be removed through the account-specific delete endpoint."
1798
+ accounts: z.optional(z.array(zUpdatePostAccount).check(z.minLength(1)).register(z.globalRegistry, {
1799
+ description: "Accounts to change, plus any connected accounts to add to the post. For an account already in the post, only the fields you send change: omitted fields keep their current value and a field sent as null is removed. Existing accounts left out of this array stay unchanged; remove an account through the account-specific delete endpoint."
1663
1800
  })),
1664
1801
  publishNow: z.optional(z.boolean().register(z.globalRegistry, {
1665
1802
  description: "Publish an existing draft immediately. Cannot be combined with scheduledAt."
1666
1803
  }))
1667
1804
  }).register(z.globalRegistry, {
1668
- description: "Update a draft or scheduled post. Published and publishing posts cannot be updated. Media behavior is explicit: omit mediaIds to keep existing media unchanged, provide mediaIds to replace all current media, or provide an empty mediaIds array to remove all media."
1805
+ description: "Update a draft or scheduled post. Published and publishing posts cannot be updated. Only the fields you send change; everything you leave out keeps its current value. To remove an account setting, or an account's own text, media or thumbnail timestamp, send it as null. Media behavior is explicit: omit mediaIds to keep existing media unchanged, provide mediaIds to replace all current media, or provide an empty mediaIds array to remove all media. The post that results from the update is validated as a whole before anything is saved."
1669
1806
  });
1670
1807
  var zSuccessMessage = z.object({
1671
1808
  deleted: z.literal(true),
@@ -1762,6 +1899,28 @@ var zMediaListResponse = z.object({
1762
1899
  var zUploadMediaRequest = z.object({
1763
1900
  file: z.string()
1764
1901
  });
1902
+ var zCreateMediaUploadRequest = z.object({
1903
+ filename: z.string().check(z.minLength(1), z.maxLength(1024)).register(z.globalRegistry, {
1904
+ description: "Name of the file you are uploading."
1905
+ }),
1906
+ contentType: z.string().check(z.minLength(1)).register(z.globalRegistry, {
1907
+ description: "Content type of the file. One of: image/jpeg, image/png, image/gif, image/webp, video/mp4, video/webm, video/quicktime, video/x-msvideo."
1908
+ }),
1909
+ fileSize: z.int().check(z.gt(0)).register(z.globalRegistry, {
1910
+ description: "Exact size of the file in bytes. Your plan's file size limit and your storage are checked before you upload, and storage refuses a file of any other size. Each platform's own size limits are checked when you attach the media to a post."
1911
+ })
1912
+ });
1913
+ var zMediaUpload = z.object({
1914
+ id: z.uuid().register(z.globalRegistry, {
1915
+ description: "Media ID of the file. Attach it to posts through mediaIds once the file is uploaded."
1916
+ }),
1917
+ uploadUrl: z.url().register(z.globalRegistry, {
1918
+ description: "Signed storage URL. PUT the file to it with the same Content-Type. It goes to storage, not to the PublishQ API, so send no Authorization header."
1919
+ }),
1920
+ expiresAt: z.iso.datetime().register(z.globalRegistry, {
1921
+ description: "When uploadUrl stops accepting the file, 1 hour after it is created."
1922
+ })
1923
+ });
1765
1924
  var zTikTokCreatorInfoResponse = z.object({
1766
1925
  privacyLevelOptions: z.array(z.string()).register(z.globalRegistry, {
1767
1926
  description: "Privacy levels available for this account. Use one of these values as `privacyLevel` in `platformSpecificSettings`."
@@ -1794,7 +1953,13 @@ var zListPostsQuery = z.object({
1794
1953
  })), 1),
1795
1954
  limit: z._default(z.optional(z.int().check(z.gt(0), z.lte(100)).register(z.globalRegistry, {
1796
1955
  description: "Page size. Max 100."
1797
- })), 20)
1956
+ })), 20),
1957
+ includeImported: z._default(z.optional(z.boolean().register(z.globalRegistry, {
1958
+ description: "Also return posts imported from a connected platform (posts with source IMPORTED). Defaults to false: only posts created through PublishQ are returned."
1959
+ })), false),
1960
+ onlyImported: z._default(z.optional(z.boolean().register(z.globalRegistry, {
1961
+ description: "Return ONLY posts imported from a connected platform. Cannot be combined with includeImported."
1962
+ })), false)
1798
1963
  });
1799
1964
  var zListPostsResponse = zPostsResponse;
1800
1965
  var zCreatePostBody = zCreatePostRequest;
@@ -1865,6 +2030,12 @@ var zGetMediaPath = z.object({
1865
2030
  id: z.uuid()
1866
2031
  });
1867
2032
  var zGetMediaResponse = zMedia;
2033
+ var zCreateMediaUploadBody = zCreateMediaUploadRequest;
2034
+ var zCreateMediaUploadResponse = zMediaUpload;
2035
+ var zCompleteMediaUploadPath = z.object({
2036
+ id: z.uuid()
2037
+ });
2038
+ var zCompleteMediaUploadResponse = zMedia;
1868
2039
 
1869
2040
  // src/index.ts
1870
2041
  var rawErrorBodies = /* @__PURE__ */ new WeakMap();
@@ -1966,12 +2137,92 @@ function createWorkspacesNamespace(client2) {
1966
2137
  get: (id) => preserveApiError(Workspaces.getWorkspace({ client: client2, path: { id } }))
1967
2138
  };
1968
2139
  }
2140
+ var UPLOAD_MIME_TYPE_BY_EXTENSION = {
2141
+ jpg: "image/jpeg",
2142
+ jpeg: "image/jpeg",
2143
+ png: "image/png",
2144
+ gif: "image/gif",
2145
+ webp: "image/webp",
2146
+ mp4: "video/mp4",
2147
+ webm: "video/webm",
2148
+ mov: "video/quicktime",
2149
+ avi: "video/x-msvideo"
2150
+ };
2151
+ function isUninformativeType(type) {
2152
+ return type === "" || type === "application/octet-stream";
2153
+ }
2154
+ function withInferredContentType(file) {
2155
+ const name = "name" in file ? file.name : void 0;
2156
+ if (!name || !isUninformativeType(file.type)) return file;
2157
+ const extension = name.split(".").pop()?.toLowerCase();
2158
+ const type = extension ? UPLOAD_MIME_TYPE_BY_EXTENSION[extension] : void 0;
2159
+ if (!type) return file;
2160
+ return new File([file], name, { type });
2161
+ }
2162
+ var STORAGE_UPLOAD_THRESHOLD_BYTES = 90 * 1024 * 1024;
2163
+ async function uploadThroughStorage(client2, file) {
2164
+ const created = await preserveApiError(
2165
+ Media.createMediaUpload({
2166
+ client: client2,
2167
+ body: {
2168
+ filename: "name" in file && file.name ? file.name : "upload",
2169
+ contentType: file.type,
2170
+ fileSize: file.size
2171
+ }
2172
+ })
2173
+ );
2174
+ if (!created.data) return created;
2175
+ const response = await fetch(created.data.uploadUrl, {
2176
+ method: "PUT",
2177
+ headers: { "Content-Type": file.type },
2178
+ body: file
2179
+ });
2180
+ if (!response.ok) {
2181
+ return {
2182
+ data: void 0,
2183
+ error: {
2184
+ error: {
2185
+ status: response.status,
2186
+ message: "Storage refused the file upload",
2187
+ mediaId: created.data.id,
2188
+ storageResponse: await response.text()
2189
+ }
2190
+ },
2191
+ request: new Request(created.data.uploadUrl, { method: "PUT" }),
2192
+ response
2193
+ };
2194
+ }
2195
+ return preserveApiError(
2196
+ Media.completeMediaUpload({ client: client2, path: { id: created.data.id } })
2197
+ );
2198
+ }
1969
2199
  function createMediaNamespace(client2) {
1970
2200
  return {
1971
2201
  /** List your uploaded and generated media files. Filter by id, type, source. Wraps `Media.listMedia`. */
1972
2202
  list: (query) => preserveApiError(Media.listMedia({ client: client2, query })),
1973
- /** Upload one media file (multipart) and get back a media ID to attach to posts. Wraps `Media.uploadMedia`. */
1974
- upload: (body) => preserveApiError(Media.uploadMedia({ client: client2, body })),
2203
+ /**
2204
+ * Upload one media file and get back a media ID to attach to posts. Wraps
2205
+ * `Media.uploadMedia`, or for files over `STORAGE_UPLOAD_THRESHOLD_BYTES`
2206
+ * runs `createUpload`, sends the file to storage and returns the finished
2207
+ * media from `completeUpload`. Both return the same `Media` object.
2208
+ *
2209
+ * The file's content type is filled in from its name when it has none, so a
2210
+ * file read off disk with `new File([buffer], 'photo.jpg')` is not sent as
2211
+ * `application/octet-stream` and refused. See `withInferredContentType`.
2212
+ */
2213
+ upload: (body) => {
2214
+ const file = withInferredContentType(body.file);
2215
+ if (file.size > STORAGE_UPLOAD_THRESHOLD_BYTES) {
2216
+ return uploadThroughStorage(client2, file);
2217
+ }
2218
+ return preserveApiError(
2219
+ Media.uploadMedia({ client: client2, body: { ...body, file } })
2220
+ );
2221
+ },
2222
+ /** Get a storage upload URL for a file of up to 4 GB. Send the file there, then use the returned media ID in posts. Wraps `Media.createMediaUpload`. */
2223
+ createUpload: (body) => preserveApiError(Media.createMediaUpload({ client: client2, body })),
2224
+ /** Add a file sent to a `createUpload` URL to the media library now and return it. Optional: using the ID in a post does the same. Wraps `Media.completeMediaUpload`. */
2225
+ completeUpload: (id) => preserveApiError(Media.completeMediaUpload({ client: client2, path: { id } })),
1975
2226
  /** Get one media file by ID. Wraps `Media.getMedia`. */
1976
2227
  get: (id) => preserveApiError(Media.getMedia({ client: client2, path: { id } }))
1977
2228
  };
@@ -1983,13 +2234,20 @@ var index_default = PublishQ;
1983
2234
  Media2,
1984
2235
  Posts,
1985
2236
  PublishQ,
2237
+ STORAGE_UPLOAD_THRESHOLD_BYTES,
1986
2238
  Workspaces,
2239
+ withInferredContentType,
1987
2240
  zAccount,
1988
2241
  zAccountsResponse,
1989
2242
  zApiError,
1990
2243
  zApiErrorCode,
1991
2244
  zApiKeyScope,
1992
2245
  zBlueskyPlatformSpecificSettings,
2246
+ zCompleteMediaUploadPath,
2247
+ zCompleteMediaUploadResponse,
2248
+ zCreateMediaUploadBody,
2249
+ zCreateMediaUploadRequest,
2250
+ zCreateMediaUploadResponse,
1993
2251
  zCreatePostAccount,
1994
2252
  zCreatePostBody,
1995
2253
  zCreatePostRequest,
@@ -2022,13 +2280,17 @@ var index_default = PublishQ;
2022
2280
  zMediaListResponse,
2023
2281
  zMediaSource,
2024
2282
  zMediaType,
2283
+ zMediaUpload,
2025
2284
  zPagination,
2026
2285
  zPlatformSpecificSettings,
2027
2286
  zPost,
2028
2287
  zPostAccount,
2288
+ zPostAnalytics,
2029
2289
  zPostOverrides,
2290
+ zPostSource,
2030
2291
  zPostStatus,
2031
2292
  zPostsResponse,
2293
+ zPublishFailure,
2032
2294
  zRemovePostAccountPath,
2033
2295
  zRemovePostAccountResponse,
2034
2296
  zSocialPlatform,
@@ -2037,7 +2299,9 @@ var index_default = PublishQ;
2037
2299
  zTikTokCreatorInfoResponse,
2038
2300
  zTikTokPlatformSpecificSettings,
2039
2301
  zTwitterPlatformSpecificSettings,
2302
+ zUpdatePostAccount,
2040
2303
  zUpdatePostBody,
2304
+ zUpdatePostOverrides,
2041
2305
  zUpdatePostPath,
2042
2306
  zUpdatePostRequest,
2043
2307
  zUpdatePostResponse,