omnisocials 0.2.0 → 0.4.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: a8be79cfb8525cbb823f24a4a34d6cb3d51b193d864db80cc190bc0a7db45a18
4
+ data.tar.gz: ed95b24b6e722f29e9c7e94e9d4506c69dece3579f9e609966fa999d5d1893bf
5
5
  SHA512:
6
- metadata.gz: 7d8390a9fb03fb151002f79d55706c43b29506d604fe84a9c4707cbe233166ebaab7ad4d2a552e1db14de26a8eed0e8244879daf148f2f83bf3a83d5fe7d8449
7
- data.tar.gz: 0376b9f5a46d25a6cfc9ca718f46ffd45d2cbe1deecdd9430f1bc2ac2b7a45e1299b5b93d54e8dcf3cf98f17f32b379c56eaed9fffb1699aa203493d27d5a4b3
6
+ metadata.gz: 2138c0350aea5e6c3059bf5d71bc9cd050441ac11b8846d01d55f9c6bd88c83488cd4e47b5c17ba8e298f98c01d75e53dcf1e54845a89a2c92553f4e896a9386
7
+ data.tar.gz: 10177239235e7dcd623615ed273c4171b81c2819de790bdc636ff495a1dd3b177cc18011b4cf9ac67a1162358d711d927d38a07c7a00ed9d672a0fe551dc524d
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
  {
@@ -18,7 +18,9 @@ module OmniSocials
18
18
  @client = client
19
19
  end
20
20
 
21
- # GET /posts - list posts (status: draft, scheduled, posted, failed).
21
+ # GET /posts - list posts (status: draft, in_approval, scheduled, posting,
22
+ # posted, failed, warning; in_approval = waiting for a reviewer in an
23
+ # approval workflow).
22
24
  def list(status: nil, limit: nil, offset: nil)
23
25
  @client.request(
24
26
  "GET", "/posts",
@@ -57,6 +59,14 @@ module OmniSocials
57
59
  # and credits_balance: X's link-post fee is passed through as prepaid
58
60
  # credits, debited at publish time (from 2026-08-14). Credits are
59
61
  # managed in the dashboard, not the API.
62
+ #
63
+ # Separately, from 2026-08-14 this call (and #update / #publish) can
64
+ # refuse an X link post up front with a 402 and error code
65
+ # "x_credits_insufficient" (details: credits_required, credits_balance,
66
+ # credits_reserved) when reserving this post's cost would push the
67
+ # company's total reserved credits past its balance. Drafts are never
68
+ # gated, and posts scheduled to publish before 2026-08-14 are never
69
+ # gated either.
60
70
  def create(content:, channels: nil, scheduled_at: nil, media_ids: nil,
61
71
  media_urls: nil, type: nil, source: nil, link_url: nil,
62
72
  link_title: nil, link_description: nil, link_thumbnail_url: nil,
@@ -65,7 +75,7 @@ module OmniSocials
65
75
  hashtag_platforms: nil, pinterest: nil, youtube: nil,
66
76
  instagram: nil, facebook: nil, linkedin: nil,
67
77
  linkedin_page: nil, tiktok: nil, x: nil, bluesky: nil,
68
- mastodon: nil, google_business: nil)
78
+ mastodon: nil, google_business: nil, linkedin_poll: nil)
69
79
  body = create_body(
70
80
  content: content, channels: channels, scheduled_at: scheduled_at,
71
81
  media_ids: media_ids, media_urls: media_urls, type: type,
@@ -78,13 +88,14 @@ module OmniSocials
78
88
  youtube: youtube, instagram: instagram, facebook: facebook,
79
89
  linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
80
90
  x: x, bluesky: bluesky, mastodon: mastodon,
81
- google_business: google_business
91
+ google_business: google_business, linkedin_poll: linkedin_poll
82
92
  )
83
93
  @client.request("POST", "/posts/create", json: body)
84
94
  end
85
95
 
86
96
  # POST /posts/create-and-publish - create and publish immediately.
87
- # See #create for the "warnings" array on X link posts.
97
+ # See #create for the "warnings" array and the 402
98
+ # "x_credits_insufficient" credit gate on X link posts.
88
99
  def create_and_publish(content:, channels: nil, media_ids: nil,
89
100
  media_urls: nil, type: nil, source: nil,
90
101
  link_url: nil, link_title: nil, link_description: nil,
@@ -95,7 +106,7 @@ module OmniSocials
95
106
  pinterest: nil, youtube: nil, instagram: nil,
96
107
  facebook: nil, linkedin: nil, linkedin_page: nil,
97
108
  tiktok: nil, x: nil, bluesky: nil, mastodon: nil,
98
- google_business: nil)
109
+ google_business: nil, linkedin_poll: nil)
99
110
  body = create_body(
100
111
  content: content, channels: channels, scheduled_at: nil,
101
112
  media_ids: media_ids, media_urls: media_urls, type: type,
@@ -108,7 +119,7 @@ module OmniSocials
108
119
  youtube: youtube, instagram: instagram, facebook: facebook,
109
120
  linkedin: linkedin, linkedin_page: linkedin_page, tiktok: tiktok,
110
121
  x: x, bluesky: bluesky, mastodon: mastodon,
111
- google_business: google_business
122
+ google_business: google_business, linkedin_poll: linkedin_poll
112
123
  )
113
124
  @client.request("POST", "/posts/create-and-publish", json: body)
114
125
  end
@@ -119,12 +130,15 @@ module OmniSocials
119
130
  # x: { "thread_parts" => nil } still clears an X thread (reverts the
120
131
  # post to single-tweet mode). The same applies to bluesky and mastodon
121
132
  # thread parts.
133
+ #
134
+ # See #create for the 402 "x_credits_insufficient" credit gate that
135
+ # can also refuse an update to a scheduled X link post.
122
136
  def update(post_id, content: nil, scheduled_at: nil, channels: nil,
123
137
  media_ids: nil, media_urls: nil, type: nil, location_id: nil,
124
138
  collaborators: nil, user_tags: nil, pinterest: nil,
125
139
  youtube: nil, instagram: nil, facebook: nil, linkedin: nil,
126
140
  linkedin_page: nil, tiktok: nil, x: nil, bluesky: nil,
127
- mastodon: nil, google_business: nil)
141
+ mastodon: nil, google_business: nil, linkedin_poll: nil)
128
142
  body = Internal.drop_nil(
129
143
  {
130
144
  "content" => content,
@@ -146,7 +160,8 @@ module OmniSocials
146
160
  "x" => x,
147
161
  "bluesky" => bluesky,
148
162
  "mastodon" => mastodon,
149
- "google_business" => google_business
163
+ "google_business" => google_business,
164
+ "linkedin_poll" => linkedin_poll
150
165
  }
151
166
  )
152
167
  @client.request("PATCH", "/posts/#{post_id}", json: body)
@@ -158,6 +173,8 @@ module OmniSocials
158
173
  end
159
174
 
160
175
  # POST /posts/{id}/publish - publish a draft or scheduled post now.
176
+ # See #create for the 402 "x_credits_insufficient" credit gate that
177
+ # can also refuse publishing a scheduled X link post.
161
178
  def publish(post_id)
162
179
  @client.request("POST", "/posts/#{post_id}/publish")
163
180
  end
@@ -182,7 +199,7 @@ module OmniSocials
182
199
  hashtag_set_id:, hashtag_placement:, hashtag_platforms:,
183
200
  pinterest:, youtube:, instagram:, facebook:, linkedin:,
184
201
  linkedin_page:, tiktok:, x:, bluesky:, mastodon:,
185
- google_business:)
202
+ google_business:, linkedin_poll:)
186
203
  Internal.drop_nil(
187
204
  {
188
205
  "content" => content,
@@ -213,7 +230,8 @@ module OmniSocials
213
230
  "x" => x,
214
231
  "bluesky" => bluesky,
215
232
  "mastodon" => mastodon,
216
- "google_business" => google_business
233
+ "google_business" => google_business,
234
+ "linkedin_poll" => linkedin_poll
217
235
  }
218
236
  )
219
237
  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.4.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.4.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-22 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,