omnisocials 0.2.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cfdc77673c3c6885b9098af3daf33b7393329ed3b6501fd04d28e813585d97b0
4
- data.tar.gz: 9753cf8ccae71e85a0414fa137d1b201f678c8a7a018edacb2a74df4353dc822
3
+ metadata.gz: 0200cd0b7aa77ce95c037b0f5fd93a9e80b7bfdc589194b53c80084bb560b165
4
+ data.tar.gz: 37e48405dd0871201467442b940d2bc1dabd8057deddd81f903e25250bdb5cbc
5
5
  SHA512:
6
- metadata.gz: 7d8390a9fb03fb151002f79d55706c43b29506d604fe84a9c4707cbe233166ebaab7ad4d2a552e1db14de26a8eed0e8244879daf148f2f83bf3a83d5fe7d8449
7
- data.tar.gz: 0376b9f5a46d25a6cfc9ca718f46ffd45d2cbe1deecdd9430f1bc2ac2b7a45e1299b5b93d54e8dcf3cf98f17f32b379c56eaed9fffb1699aa203493d27d5a4b3
6
+ metadata.gz: 886f262e3a7eb8c2164f75845e8c67c3e5d86c2869974ffb290b69d8ebeb29b4f29cba11130d1722152236c3b865d9651194709fef35a24bc15b9eba4fe798a9
7
+ data.tar.gz: '078aba4be97a9fbcceef92fc3587059ea6e7cef7d760da25949bbc5eff37cc1ea3ff4fb59d47b8b5ec2ad12b532b5e7f4dace15572df90b8a42cffb57e8e75e2'
data/README.md CHANGED
@@ -143,6 +143,26 @@ end
143
143
 
144
144
  From `enforce_from` (2026-08-14) the balance is checked at publish time, but credits are only deducted after the post successfully publishes (a failed publish is never charged). If the balance can't cover it, only the X target fails (other platforms publish normally); top up in the dashboard under Settings -> Organisation -> Billing -> Credits, then call `posts.retry`. Posts without links, analytics, and media on X stay free. There is no API endpoint for credits — they are managed in the dashboard.
145
145
 
146
+ Separately, from 2026-08-14 `posts.create` / `posts.update` / `posts.publish` on X can refuse the request itself, up front, with a `402` and error code `x_credits_insufficient` (`error.details` carries `credits_required`, `credits_balance`, and `credits_reserved`) when reserving this post's cost would push the company's total reserved credits past its balance:
147
+
148
+ ```ruby
149
+ begin
150
+ client.posts.create(
151
+ content: "Read the full story: https://example.com/post",
152
+ channels: ["x"],
153
+ scheduled_at: "2026-08-20T09:00:00Z"
154
+ )
155
+ rescue OmniSocials::APIError => e
156
+ if e.code == "x_credits_insufficient"
157
+ puts "Not enough reserved credits: #{e.body["error"]["details"]}"
158
+ else
159
+ raise
160
+ end
161
+ end
162
+ ```
163
+
164
+ Drafts are never gated, and posts scheduled to publish before 2026-08-14 are never gated either.
165
+
146
166
  ### Other post operations
147
167
 
148
168
  ```ruby
@@ -310,6 +330,47 @@ client.posts.create(
310
330
  )
311
331
  ```
312
332
 
333
+ ## Inbox
334
+
335
+ Read and reply to social inbox conversations (DMs, comments, mentions) across connected platforms. Requires an API key with the opt-in `inbox:read` / `inbox:write` scopes. The list endpoints are cursor-paginated (unlike the offset pagination used elsewhere): page while `pagination["has_more"]` is true by passing the previous response's `pagination["next_cursor"]` as `cursor`.
336
+
337
+ ```ruby
338
+ conversations = client.inbox.list_conversations(platform: "instagram", unread: true, limit: 20)
339
+ conversations["data"].each do |conversation|
340
+ puts "#{conversation["platform"]}: #{conversation["preview"]}"
341
+ end
342
+
343
+ conversation_id = conversations["data"][0]["id"]
344
+ messages = client.inbox.get_messages(conversation_id)
345
+ messages["data"].each do |message|
346
+ puts "#{message["direction"]}: #{message["text"]}" # direction is "incoming" or "outgoing"
347
+ end
348
+
349
+ client.inbox.mark_read(conversation_id)
350
+ client.inbox.reply(conversation_id, text: "Thanks for reaching out!")
351
+ ```
352
+
353
+ `platform` accepts `"instagram"`, `"facebook"`, `"linkedin"`, `"tiktok"`, or `"x"`; `type` accepts `"dm"`, `"comment"`, or `"mention"`. TikTok replies are comments only and capped at 150 characters. Conversation ids are URL-encoded for you, so pass them exactly as returned - LinkedIn ids contain `":"` and `"()"` (e.g. `"linkedin_comment_urn:li:activity:123"`).
354
+
355
+ Replying to an X DM costs 2 prepaid credits, debited from the company balance before the send and automatically refunded if the send fails:
356
+
357
+ ```ruby
358
+ begin
359
+ client.inbox.reply(conversation_id, text: "On it, thanks!")
360
+ rescue OmniSocials::APIError => e
361
+ case e.code
362
+ when "insufficient_credits"
363
+ puts "Not enough credits to send this reply (need 2)."
364
+ when "x_inbox_suspended"
365
+ puts "X inbox is suspended - top up and re-enable it in the dashboard."
366
+ else
367
+ raise
368
+ end
369
+ end
370
+ ```
371
+
372
+ `x_inbox_suspended` fires once a workspace's X inbox has auto-suspended after its credit balance hit zero; topping up and re-enabling it in the dashboard resumes delivery, but DMs that arrived while suspended are not recovered.
373
+
313
374
  ## Webhooks
314
375
 
315
376
  Subscribe to `post.scheduled`, `post.published`, and `post.failed` events:
@@ -23,9 +23,10 @@ module OmniSocials
23
23
  # GET /inbox/conversations - list conversations, newest activity first.
24
24
  #
25
25
  # All filters are optional: platform ("instagram", "facebook",
26
- # "linkedin"), type ("dm", "comment", "mention"), unread (only
27
- # conversations with unread messages), limit (1-100), and cursor (an
28
- # opaque cursor from a previous response's pagination["next_cursor"]).
26
+ # "linkedin", "tiktok", "x"), type ("dm", "comment", "mention"),
27
+ # unread (only conversations with unread messages), limit (1-100), and
28
+ # cursor (an opaque cursor from a previous response's
29
+ # pagination["next_cursor"]).
29
30
  def list_conversations(platform: nil, type: nil, unread: nil, limit: nil, cursor: nil)
30
31
  @client.request(
31
32
  "GET", "/inbox/conversations",
@@ -59,7 +60,16 @@ module OmniSocials
59
60
  #
60
61
  # `text` is required. Optionally attach a single media asset by public
61
62
  # URL with `attachment_url` plus `attachment_type` ("image", "video",
62
- # "audio", or "file"). Returns the created outbound message.
63
+ # "audio", or "file"). Returns the created outgoing message.
64
+ #
65
+ # Replying to an X DM costs 2 prepaid credits, debited from the
66
+ # company balance before the send and automatically refunded if the
67
+ # send fails. Two 402 error codes are specific to this call:
68
+ # "insufficient_credits" when the balance can't cover the 2 credits,
69
+ # and "x_inbox_suspended" when the workspace's X inbox was
70
+ # auto-suspended after the balance hit zero (top up and re-enable it
71
+ # in the dashboard to resume; DMs that arrived while suspended are
72
+ # not recovered).
63
73
  def reply(conversation_id, text:, attachment_url: nil, attachment_type: nil)
64
74
  body = Internal.drop_nil(
65
75
  {
@@ -57,6 +57,14 @@ module OmniSocials
57
57
  # and credits_balance: X's link-post fee is passed through as prepaid
58
58
  # credits, debited at publish time (from 2026-08-14). Credits are
59
59
  # managed in the dashboard, not the API.
60
+ #
61
+ # Separately, from 2026-08-14 this call (and #update / #publish) can
62
+ # refuse an X link post up front with a 402 and error code
63
+ # "x_credits_insufficient" (details: credits_required, credits_balance,
64
+ # credits_reserved) when reserving this post's cost would push the
65
+ # company's total reserved credits past its balance. Drafts are never
66
+ # gated, and posts scheduled to publish before 2026-08-14 are never
67
+ # gated either.
60
68
  def create(content:, channels: nil, scheduled_at: nil, media_ids: nil,
61
69
  media_urls: nil, type: nil, source: nil, link_url: nil,
62
70
  link_title: nil, link_description: nil, link_thumbnail_url: nil,
@@ -65,7 +73,7 @@ module OmniSocials
65
73
  hashtag_platforms: nil, pinterest: nil, youtube: nil,
66
74
  instagram: nil, facebook: nil, linkedin: nil,
67
75
  linkedin_page: nil, tiktok: nil, x: nil, bluesky: nil,
68
- mastodon: nil, google_business: nil)
76
+ mastodon: nil, google_business: nil, linkedin_poll: nil)
69
77
  body = create_body(
70
78
  content: content, channels: channels, scheduled_at: scheduled_at,
71
79
  media_ids: media_ids, media_urls: media_urls, type: type,
@@ -78,13 +86,14 @@ module OmniSocials
78
86
  youtube: youtube, instagram: instagram, facebook: facebook,
79
87
  linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
80
88
  x: x, bluesky: bluesky, mastodon: mastodon,
81
- google_business: google_business
89
+ google_business: google_business, linkedin_poll: linkedin_poll
82
90
  )
83
91
  @client.request("POST", "/posts/create", json: body)
84
92
  end
85
93
 
86
94
  # POST /posts/create-and-publish - create and publish immediately.
87
- # See #create for the "warnings" array on X link posts.
95
+ # See #create for the "warnings" array and the 402
96
+ # "x_credits_insufficient" credit gate on X link posts.
88
97
  def create_and_publish(content:, channels: nil, media_ids: nil,
89
98
  media_urls: nil, type: nil, source: nil,
90
99
  link_url: nil, link_title: nil, link_description: nil,
@@ -95,7 +104,7 @@ module OmniSocials
95
104
  pinterest: nil, youtube: nil, instagram: nil,
96
105
  facebook: nil, linkedin: nil, linkedin_page: nil,
97
106
  tiktok: nil, x: nil, bluesky: nil, mastodon: nil,
98
- google_business: nil)
107
+ google_business: nil, linkedin_poll: nil)
99
108
  body = create_body(
100
109
  content: content, channels: channels, scheduled_at: nil,
101
110
  media_ids: media_ids, media_urls: media_urls, type: type,
@@ -108,7 +117,7 @@ module OmniSocials
108
117
  youtube: youtube, instagram: instagram, facebook: facebook,
109
118
  linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
110
119
  x: x, bluesky: bluesky, mastodon: mastodon,
111
- google_business: google_business
120
+ google_business: google_business, linkedin_poll: linkedin_poll
112
121
  )
113
122
  @client.request("POST", "/posts/create-and-publish", json: body)
114
123
  end
@@ -119,12 +128,15 @@ module OmniSocials
119
128
  # x: { "thread_parts" => nil } still clears an X thread (reverts the
120
129
  # post to single-tweet mode). The same applies to bluesky and mastodon
121
130
  # thread parts.
131
+ #
132
+ # See #create for the 402 "x_credits_insufficient" credit gate that
133
+ # can also refuse an update to a scheduled X link post.
122
134
  def update(post_id, content: nil, scheduled_at: nil, channels: nil,
123
135
  media_ids: nil, media_urls: nil, type: nil, location_id: nil,
124
136
  collaborators: nil, user_tags: nil, pinterest: nil,
125
137
  youtube: nil, instagram: nil, facebook: nil, linkedin: nil,
126
138
  linkedin_page: nil, tiktok: nil, x: nil, bluesky: nil,
127
- mastodon: nil, google_business: nil)
139
+ mastodon: nil, google_business: nil, linkedin_poll: nil)
128
140
  body = Internal.drop_nil(
129
141
  {
130
142
  "content" => content,
@@ -146,7 +158,8 @@ module OmniSocials
146
158
  "x" => x,
147
159
  "bluesky" => bluesky,
148
160
  "mastodon" => mastodon,
149
- "google_business" => google_business
161
+ "google_business" => google_business,
162
+ "linkedin_poll" => linkedin_poll
150
163
  }
151
164
  )
152
165
  @client.request("PATCH", "/posts/#{post_id}", json: body)
@@ -158,6 +171,8 @@ module OmniSocials
158
171
  end
159
172
 
160
173
  # POST /posts/{id}/publish - publish a draft or scheduled post now.
174
+ # See #create for the 402 "x_credits_insufficient" credit gate that
175
+ # can also refuse publishing a scheduled X link post.
161
176
  def publish(post_id)
162
177
  @client.request("POST", "/posts/#{post_id}/publish")
163
178
  end
@@ -182,7 +197,7 @@ module OmniSocials
182
197
  hashtag_set_id:, hashtag_placement:, hashtag_platforms:,
183
198
  pinterest:, youtube:, instagram:, facebook:, linkedin:,
184
199
  linkedin_page:, tiktok:, x:, bluesky:, mastodon:,
185
- google_business:)
200
+ google_business:, linkedin_poll:)
186
201
  Internal.drop_nil(
187
202
  {
188
203
  "content" => content,
@@ -213,7 +228,8 @@ module OmniSocials
213
228
  "x" => x,
214
229
  "bluesky" => bluesky,
215
230
  "mastodon" => mastodon,
216
- "google_business" => google_business
231
+ "google_business" => google_business,
232
+ "linkedin_poll" => linkedin_poll
217
233
  }
218
234
  )
219
235
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OmniSocials
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omnisocials
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OmniSocials
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-09 00:00:00.000000000 Z
11
+ date: 2026-08-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Schedule and publish social media posts, upload media, and read analytics
14
14
  across Instagram, Facebook, LinkedIn, YouTube, TikTok, X, Pinterest, Bluesky, Threads,