@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/dist/index.js CHANGED
@@ -816,7 +816,7 @@ var Posts = class {
816
816
  /**
817
817
  * List posts
818
818
  *
819
- * Returns a paginated list of posts.
819
+ * 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.
820
820
  */
821
821
  static listPosts(options) {
822
822
  return (options?.client ?? client).get({
@@ -978,7 +978,7 @@ var Media = class {
978
978
  /**
979
979
  * Upload media
980
980
  *
981
- * Upload one media file with multipart form data. The returned media ID can then be attached to posts.
981
+ * 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.
982
982
  */
983
983
  static uploadMedia(options) {
984
984
  return (options.client ?? client).post({
@@ -1004,6 +1004,39 @@ var Media = class {
1004
1004
  ...options
1005
1005
  });
1006
1006
  }
1007
+ /**
1008
+ * Create media upload
1009
+ *
1010
+ * Upload a file of up to 4 GB (500 MB on the Free plan) straight to storage:
1011
+ *
1012
+ * 1. Call this endpoint with `filename`, `contentType` and `fileSize`. You get back an `id` and an `uploadUrl`.
1013
+ * 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.
1014
+ * 3. Optional: call Complete media upload to add the file to your media library now.
1015
+ * 4. Put the `id` in `mediaIds` when you create or update a post.
1016
+ */
1017
+ static createMediaUpload(options) {
1018
+ return (options.client ?? client).post({
1019
+ security: [{ scheme: "bearer", type: "http" }],
1020
+ url: "/media/uploads",
1021
+ ...options,
1022
+ headers: {
1023
+ "Content-Type": "application/json",
1024
+ ...options.headers
1025
+ }
1026
+ });
1027
+ }
1028
+ /**
1029
+ * Complete media upload
1030
+ *
1031
+ * 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.
1032
+ */
1033
+ static completeMediaUpload(options) {
1034
+ return (options.client ?? client).post({
1035
+ security: [{ scheme: "bearer", type: "http" }],
1036
+ url: "/media/uploads/{id}/complete",
1037
+ ...options
1038
+ });
1039
+ }
1007
1040
  };
1008
1041
 
1009
1042
  // src/generated/zod.gen.ts
@@ -1023,6 +1056,7 @@ var zApiErrorCode = z.enum([
1023
1056
  "post_not_editable",
1024
1057
  "media_not_found",
1025
1058
  "media_in_use",
1059
+ "media_upload_incomplete",
1026
1060
  "storage_limit_exceeded",
1027
1061
  "free_media_limit_exceeded",
1028
1062
  "free_pending_media_limit_exceeded",
@@ -1033,6 +1067,9 @@ var zApiErrorCode = z.enum([
1033
1067
  "invalid_file_extension",
1034
1068
  "social_accounts_not_found",
1035
1069
  "social_accounts_expired",
1070
+ "social_account_limit_exceeded",
1071
+ "platform_temporarily_unavailable",
1072
+ "platform_permission_missing",
1036
1073
  "platform_validation_failed",
1037
1074
  "posting_limits_exceeded",
1038
1075
  "scheduled_at_required",
@@ -1042,10 +1079,12 @@ var zApiErrorCode = z.enum([
1042
1079
  "workspace_not_found",
1043
1080
  "workspace_forbidden",
1044
1081
  "workspace_owner_required",
1082
+ "workspace_limit_exceeded",
1045
1083
  "workspace_account_not_found",
1046
1084
  "workspace_account_conflict",
1047
1085
  "workspace_member_not_found",
1048
1086
  "workspace_member_conflict",
1087
+ "team_member_limit_exceeded",
1049
1088
  "workspace_invitation_invalid",
1050
1089
  "workspace_invitation_expired"
1051
1090
  ]);
@@ -1160,6 +1199,9 @@ var zYouTubePlatformSpecificSettings = z.object({
1160
1199
  })),
1161
1200
  playlistId: z.optional(z.string().register(z.globalRegistry, {
1162
1201
  description: "Playlist ID to add the video to after upload."
1202
+ })),
1203
+ thumbnailMediaId: z.optional(z.uuid().register(z.globalRegistry, {
1204
+ 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`."
1163
1205
  }))
1164
1206
  }).register(z.globalRegistry, {
1165
1207
  description: "YouTube-specific upload settings, such as description and privacy."
@@ -1254,6 +1296,56 @@ var zTwitterPlatformSpecificSettings = z.object({
1254
1296
  }).register(z.globalRegistry, {
1255
1297
  description: "X (Twitter) specific post settings. All fields are optional."
1256
1298
  });
1299
+ var zPostSource = z.enum(["PUBLISHQ", "IMPORTED"]).register(z.globalRegistry, {
1300
+ 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."
1301
+ });
1302
+ var zPostAnalytics = z.object({
1303
+ views: z.int().check(z.gte(0)).register(z.globalRegistry, {
1304
+ description: "Views the platform reports for this post."
1305
+ }),
1306
+ likes: z.int().check(z.gte(0)).register(z.globalRegistry, {
1307
+ description: "Likes the platform reports for this post."
1308
+ }),
1309
+ comments: z.int().check(z.gte(0)).register(z.globalRegistry, {
1310
+ description: "Comments the platform reports for this post."
1311
+ }),
1312
+ shares: z.int().check(z.gte(0)).register(z.globalRegistry, {
1313
+ description: "Shares the platform reports for this post. 0 on platforms that do not disclose it (including YouTube)."
1314
+ }),
1315
+ extraMetrics: z.record(z.string(), z.int().check(z.gte(0))).register(z.globalRegistry, {
1316
+ 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."
1317
+ }),
1318
+ syncedAt: z.iso.datetime().register(z.globalRegistry, {
1319
+ description: "When these numbers were last recorded. Metrics are refreshed in the background roughly once a day, and only stored when they change."
1320
+ })
1321
+ });
1322
+ var zPublishFailure = z.object({
1323
+ kind: z.enum([
1324
+ "RECONNECT",
1325
+ "MEDIA",
1326
+ "ACCOUNT_STATE",
1327
+ "TRANSIENT",
1328
+ "MAYBE_PUBLISHED",
1329
+ "UNKNOWN"
1330
+ ]).register(z.globalRegistry, {
1331
+ 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`."
1332
+ }),
1333
+ title: z.string().register(z.globalRegistry, {
1334
+ description: "Short label for this failure."
1335
+ }),
1336
+ whatHappened: z.string().register(z.globalRegistry, {
1337
+ description: "One sentence about what the platform did."
1338
+ }),
1339
+ whatToDo: z.string().register(z.globalRegistry, {
1340
+ description: "One sentence naming the next action."
1341
+ }),
1342
+ canRetry: z.boolean().register(z.globalRegistry, {
1343
+ description: "Whether retrying can succeed. False when the platform is blocking the account, since the same request will be refused the same way."
1344
+ }),
1345
+ checkPlatformFirst: z.boolean().register(z.globalRegistry, {
1346
+ 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."
1347
+ })
1348
+ });
1257
1349
  var zSocialPlatform = z.enum([
1258
1350
  "TWITTER",
1259
1351
  "INSTAGRAM",
@@ -1278,8 +1370,8 @@ var zPostOverrides = z.object({
1278
1370
  thumbnailTimestamp: z.optional(z.int().check(z.gte(0)).register(z.globalRegistry, {
1279
1371
  description: "Video thumbnail timestamp in milliseconds. Applies to Instagram and TikTok."
1280
1372
  })),
1281
- mediaIds: z.optional(z.array(z.uuid()).register(z.globalRegistry, {
1282
- description: "Replacement media for this account. Completely replaces the post-level media."
1373
+ mediaIds: z.optional(z.array(z.uuid()).check(z.maxLength(35)).register(z.globalRegistry, {
1374
+ description: "Replacement media for this account. Completely replaces the post-level media. Up to 35."
1283
1375
  }))
1284
1376
  });
1285
1377
  var zPlatformSpecificSettings = z.union([
@@ -1327,6 +1419,9 @@ var zPlatformSpecificSettings = z.union([
1327
1419
  })),
1328
1420
  playlistId: z.optional(z.string().register(z.globalRegistry, {
1329
1421
  description: "Playlist ID to add the video to after upload."
1422
+ })),
1423
+ thumbnailMediaId: z.optional(z.uuid().register(z.globalRegistry, {
1424
+ 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`."
1330
1425
  }))
1331
1426
  }).register(z.globalRegistry, {
1332
1427
  description: "YouTube-specific upload settings, such as description and privacy."
@@ -1471,6 +1566,18 @@ var zPostAccount = z.object({
1471
1566
  errorMessage: z.optional(z.string().register(z.globalRegistry, {
1472
1567
  description: "Error message from the platform if publishing failed for this account."
1473
1568
  })),
1569
+ thumbnail: z.optional(z.object({
1570
+ status: z.enum(["APPLIED", "FAILED"]),
1571
+ errorMessage: z.optional(z.string())
1572
+ }).register(z.globalRegistry, {
1573
+ 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."
1574
+ })),
1575
+ failure: z.optional(z.intersection(zPublishFailure, z.unknown().register(z.globalRegistry, {
1576
+ 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."
1577
+ }))),
1578
+ analytics: z.optional(z.intersection(zPostAnalytics, z.unknown().register(z.globalRegistry, {
1579
+ description: "Latest metrics recorded for this delivery. Present only for published posts on platforms with analytics support, once a background sync has run."
1580
+ }))),
1474
1581
  postOverrides: z.optional(zPostOverrides),
1475
1582
  platformSpecificSettings: z.optional(zPlatformSpecificSettings)
1476
1583
  });
@@ -1482,6 +1589,7 @@ var zPost = z.object({
1482
1589
  description: "The shared text content of the post."
1483
1590
  }),
1484
1591
  status: zPostStatus,
1592
+ source: zPostSource,
1485
1593
  errorMessage: z.optional(z.string().register(z.globalRegistry, {
1486
1594
  description: "Human-readable error summary when one or more accounts failed."
1487
1595
  })),
@@ -1523,8 +1631,8 @@ var zCreatePostRequest = z.object({
1523
1631
  content: z._default(z.optional(z.string().register(z.globalRegistry, {
1524
1632
  description: "The text content of the post, shared across all accounts unless overridden. Optional when media is attached (platform-dependent)."
1525
1633
  })), ""),
1526
- mediaIds: z.optional(z.array(z.uuid()).register(z.globalRegistry, {
1527
- description: "Media to attach to the post. Shared across all accounts unless an account provides its own mediaIds in postOverrides."
1634
+ mediaIds: z.optional(z.array(z.uuid()).check(z.maxLength(35)).register(z.globalRegistry, {
1635
+ 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."
1528
1636
  })),
1529
1637
  thumbnailTimestamp: z.optional(z.int().check(z.gte(0)).register(z.globalRegistry, {
1530
1638
  description: "Default video thumbnail timestamp in milliseconds for Instagram and TikTok. Can be overridden per account."
@@ -1533,7 +1641,7 @@ var zCreatePostRequest = z.object({
1533
1641
  description: "Accounts to post to. Each entry can optionally include content overrides and platform-specific settings."
1534
1642
  }),
1535
1643
  scheduledAt: z.optional(z.iso.datetime().register(z.globalRegistry, {
1536
- description: "Schedule the post for a future date. If omitted and publishNow is false or absent, the post is saved as a draft."
1644
+ 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."
1537
1645
  })),
1538
1646
  publishNow: z.optional(z.boolean().register(z.globalRegistry, {
1539
1647
  description: "Publish immediately. Cannot be combined with scheduledAt."
@@ -1541,27 +1649,43 @@ var zCreatePostRequest = z.object({
1541
1649
  }).register(z.globalRegistry, {
1542
1650
  description: "Create a post across one or more connected social accounts."
1543
1651
  });
1652
+ var zUpdatePostOverrides = z.object({
1653
+ content: z.nullish(z.string()),
1654
+ mediaIds: z.nullish(z.array(z.uuid()).check(z.maxLength(35))),
1655
+ thumbnailTimestamp: z.nullish(z.int().check(z.gte(0)))
1656
+ }).register(z.globalRegistry, {
1657
+ description: "Override the post-level content, media or thumbnail timestamp for this specific account."
1658
+ });
1659
+ var zUpdatePostAccount = z.object({
1660
+ accountId: z.uuid().register(z.globalRegistry, {
1661
+ description: "ID of a connected social account to post to."
1662
+ }),
1663
+ postOverrides: z.optional(zUpdatePostOverrides),
1664
+ platformSpecificSettings: z.optional(z.intersection(zPlatformSpecificSettings, z.unknown().register(z.globalRegistry, {
1665
+ 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."
1666
+ })))
1667
+ });
1544
1668
  var zUpdatePostRequest = z.object({
1545
1669
  content: z.optional(z.string().register(z.globalRegistry, {
1546
1670
  description: "New shared text content for the post."
1547
1671
  })),
1548
1672
  scheduledAt: z.optional(z.iso.datetime().register(z.globalRegistry, {
1549
- description: "New scheduled date for the post. Must be at least 1 minute in the future."
1673
+ 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."
1550
1674
  })),
1551
- mediaIds: z.optional(z.array(z.uuid()).register(z.globalRegistry, {
1552
- 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."
1675
+ mediaIds: z.optional(z.array(z.uuid()).check(z.maxLength(35)).register(z.globalRegistry, {
1676
+ 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."
1553
1677
  })),
1554
1678
  thumbnailTimestamp: z.optional(z.int().check(z.gte(0)).register(z.globalRegistry, {
1555
1679
  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."
1556
1680
  })),
1557
- accounts: z.optional(z.array(zCreatePostAccount).check(z.minLength(1)).register(z.globalRegistry, {
1558
- 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."
1681
+ accounts: z.optional(z.array(zUpdatePostAccount).check(z.minLength(1)).register(z.globalRegistry, {
1682
+ 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."
1559
1683
  })),
1560
1684
  publishNow: z.optional(z.boolean().register(z.globalRegistry, {
1561
1685
  description: "Publish an existing draft immediately. Cannot be combined with scheduledAt."
1562
1686
  }))
1563
1687
  }).register(z.globalRegistry, {
1564
- 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."
1688
+ 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."
1565
1689
  });
1566
1690
  var zSuccessMessage = z.object({
1567
1691
  deleted: z.literal(true),
@@ -1658,6 +1782,28 @@ var zMediaListResponse = z.object({
1658
1782
  var zUploadMediaRequest = z.object({
1659
1783
  file: z.string()
1660
1784
  });
1785
+ var zCreateMediaUploadRequest = z.object({
1786
+ filename: z.string().check(z.minLength(1), z.maxLength(1024)).register(z.globalRegistry, {
1787
+ description: "Name of the file you are uploading."
1788
+ }),
1789
+ contentType: z.string().check(z.minLength(1)).register(z.globalRegistry, {
1790
+ 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."
1791
+ }),
1792
+ fileSize: z.int().check(z.gt(0)).register(z.globalRegistry, {
1793
+ 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."
1794
+ })
1795
+ });
1796
+ var zMediaUpload = z.object({
1797
+ id: z.uuid().register(z.globalRegistry, {
1798
+ description: "Media ID of the file. Attach it to posts through mediaIds once the file is uploaded."
1799
+ }),
1800
+ uploadUrl: z.url().register(z.globalRegistry, {
1801
+ 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."
1802
+ }),
1803
+ expiresAt: z.iso.datetime().register(z.globalRegistry, {
1804
+ description: "When uploadUrl stops accepting the file, 1 hour after it is created."
1805
+ })
1806
+ });
1661
1807
  var zTikTokCreatorInfoResponse = z.object({
1662
1808
  privacyLevelOptions: z.array(z.string()).register(z.globalRegistry, {
1663
1809
  description: "Privacy levels available for this account. Use one of these values as `privacyLevel` in `platformSpecificSettings`."
@@ -1690,7 +1836,13 @@ var zListPostsQuery = z.object({
1690
1836
  })), 1),
1691
1837
  limit: z._default(z.optional(z.int().check(z.gt(0), z.lte(100)).register(z.globalRegistry, {
1692
1838
  description: "Page size. Max 100."
1693
- })), 20)
1839
+ })), 20),
1840
+ includeImported: z._default(z.optional(z.boolean().register(z.globalRegistry, {
1841
+ description: "Also return posts imported from a connected platform (posts with source IMPORTED). Defaults to false: only posts created through PublishQ are returned."
1842
+ })), false),
1843
+ onlyImported: z._default(z.optional(z.boolean().register(z.globalRegistry, {
1844
+ description: "Return ONLY posts imported from a connected platform. Cannot be combined with includeImported."
1845
+ })), false)
1694
1846
  });
1695
1847
  var zListPostsResponse = zPostsResponse;
1696
1848
  var zCreatePostBody = zCreatePostRequest;
@@ -1761,6 +1913,12 @@ var zGetMediaPath = z.object({
1761
1913
  id: z.uuid()
1762
1914
  });
1763
1915
  var zGetMediaResponse = zMedia;
1916
+ var zCreateMediaUploadBody = zCreateMediaUploadRequest;
1917
+ var zCreateMediaUploadResponse = zMediaUpload;
1918
+ var zCompleteMediaUploadPath = z.object({
1919
+ id: z.uuid()
1920
+ });
1921
+ var zCompleteMediaUploadResponse = zMedia;
1764
1922
 
1765
1923
  // src/index.ts
1766
1924
  var rawErrorBodies = /* @__PURE__ */ new WeakMap();
@@ -1862,12 +2020,92 @@ function createWorkspacesNamespace(client2) {
1862
2020
  get: (id) => preserveApiError(Workspaces.getWorkspace({ client: client2, path: { id } }))
1863
2021
  };
1864
2022
  }
2023
+ var UPLOAD_MIME_TYPE_BY_EXTENSION = {
2024
+ jpg: "image/jpeg",
2025
+ jpeg: "image/jpeg",
2026
+ png: "image/png",
2027
+ gif: "image/gif",
2028
+ webp: "image/webp",
2029
+ mp4: "video/mp4",
2030
+ webm: "video/webm",
2031
+ mov: "video/quicktime",
2032
+ avi: "video/x-msvideo"
2033
+ };
2034
+ function isUninformativeType(type) {
2035
+ return type === "" || type === "application/octet-stream";
2036
+ }
2037
+ function withInferredContentType(file) {
2038
+ const name = "name" in file ? file.name : void 0;
2039
+ if (!name || !isUninformativeType(file.type)) return file;
2040
+ const extension = name.split(".").pop()?.toLowerCase();
2041
+ const type = extension ? UPLOAD_MIME_TYPE_BY_EXTENSION[extension] : void 0;
2042
+ if (!type) return file;
2043
+ return new File([file], name, { type });
2044
+ }
2045
+ var STORAGE_UPLOAD_THRESHOLD_BYTES = 90 * 1024 * 1024;
2046
+ async function uploadThroughStorage(client2, file) {
2047
+ const created = await preserveApiError(
2048
+ Media.createMediaUpload({
2049
+ client: client2,
2050
+ body: {
2051
+ filename: "name" in file && file.name ? file.name : "upload",
2052
+ contentType: file.type,
2053
+ fileSize: file.size
2054
+ }
2055
+ })
2056
+ );
2057
+ if (!created.data) return created;
2058
+ const response = await fetch(created.data.uploadUrl, {
2059
+ method: "PUT",
2060
+ headers: { "Content-Type": file.type },
2061
+ body: file
2062
+ });
2063
+ if (!response.ok) {
2064
+ return {
2065
+ data: void 0,
2066
+ error: {
2067
+ error: {
2068
+ status: response.status,
2069
+ message: "Storage refused the file upload",
2070
+ mediaId: created.data.id,
2071
+ storageResponse: await response.text()
2072
+ }
2073
+ },
2074
+ request: new Request(created.data.uploadUrl, { method: "PUT" }),
2075
+ response
2076
+ };
2077
+ }
2078
+ return preserveApiError(
2079
+ Media.completeMediaUpload({ client: client2, path: { id: created.data.id } })
2080
+ );
2081
+ }
1865
2082
  function createMediaNamespace(client2) {
1866
2083
  return {
1867
2084
  /** List your uploaded and generated media files. Filter by id, type, source. Wraps `Media.listMedia`. */
1868
2085
  list: (query) => preserveApiError(Media.listMedia({ client: client2, query })),
1869
- /** Upload one media file (multipart) and get back a media ID to attach to posts. Wraps `Media.uploadMedia`. */
1870
- upload: (body) => preserveApiError(Media.uploadMedia({ client: client2, body })),
2086
+ /**
2087
+ * Upload one media file and get back a media ID to attach to posts. Wraps
2088
+ * `Media.uploadMedia`, or for files over `STORAGE_UPLOAD_THRESHOLD_BYTES`
2089
+ * runs `createUpload`, sends the file to storage and returns the finished
2090
+ * media from `completeUpload`. Both return the same `Media` object.
2091
+ *
2092
+ * The file's content type is filled in from its name when it has none, so a
2093
+ * file read off disk with `new File([buffer], 'photo.jpg')` is not sent as
2094
+ * `application/octet-stream` and refused. See `withInferredContentType`.
2095
+ */
2096
+ upload: (body) => {
2097
+ const file = withInferredContentType(body.file);
2098
+ if (file.size > STORAGE_UPLOAD_THRESHOLD_BYTES) {
2099
+ return uploadThroughStorage(client2, file);
2100
+ }
2101
+ return preserveApiError(
2102
+ Media.uploadMedia({ client: client2, body: { ...body, file } })
2103
+ );
2104
+ },
2105
+ /** 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`. */
2106
+ createUpload: (body) => preserveApiError(Media.createMediaUpload({ client: client2, body })),
2107
+ /** 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`. */
2108
+ completeUpload: (id) => preserveApiError(Media.completeMediaUpload({ client: client2, path: { id } })),
1871
2109
  /** Get one media file by ID. Wraps `Media.getMedia`. */
1872
2110
  get: (id) => preserveApiError(Media.getMedia({ client: client2, path: { id } }))
1873
2111
  };
@@ -1878,14 +2116,21 @@ export {
1878
2116
  Media as Media2,
1879
2117
  Posts,
1880
2118
  PublishQ,
2119
+ STORAGE_UPLOAD_THRESHOLD_BYTES,
1881
2120
  Workspaces,
1882
2121
  index_default as default,
2122
+ withInferredContentType,
1883
2123
  zAccount,
1884
2124
  zAccountsResponse,
1885
2125
  zApiError,
1886
2126
  zApiErrorCode,
1887
2127
  zApiKeyScope,
1888
2128
  zBlueskyPlatformSpecificSettings,
2129
+ zCompleteMediaUploadPath,
2130
+ zCompleteMediaUploadResponse,
2131
+ zCreateMediaUploadBody,
2132
+ zCreateMediaUploadRequest,
2133
+ zCreateMediaUploadResponse,
1889
2134
  zCreatePostAccount,
1890
2135
  zCreatePostBody,
1891
2136
  zCreatePostRequest,
@@ -1918,13 +2163,17 @@ export {
1918
2163
  zMediaListResponse,
1919
2164
  zMediaSource,
1920
2165
  zMediaType,
2166
+ zMediaUpload,
1921
2167
  zPagination,
1922
2168
  zPlatformSpecificSettings,
1923
2169
  zPost,
1924
2170
  zPostAccount,
2171
+ zPostAnalytics,
1925
2172
  zPostOverrides,
2173
+ zPostSource,
1926
2174
  zPostStatus,
1927
2175
  zPostsResponse,
2176
+ zPublishFailure,
1928
2177
  zRemovePostAccountPath,
1929
2178
  zRemovePostAccountResponse,
1930
2179
  zSocialPlatform,
@@ -1933,7 +2182,9 @@ export {
1933
2182
  zTikTokCreatorInfoResponse,
1934
2183
  zTikTokPlatformSpecificSettings,
1935
2184
  zTwitterPlatformSpecificSettings,
2185
+ zUpdatePostAccount,
1936
2186
  zUpdatePostBody,
2187
+ zUpdatePostOverrides,
1937
2188
  zUpdatePostPath,
1938
2189
  zUpdatePostRequest,
1939
2190
  zUpdatePostResponse,