{"id":16431970,"url":"https://github.com/davidcelis/threads-api","last_synced_at":"2025-03-21T04:32:10.823Z","repository":{"id":245081406,"uuid":"817411135","full_name":"davidcelis/threads-api","owner":"davidcelis","description":"🧵 A Ruby client for Meta's Threads API","archived":false,"fork":false,"pushed_at":"2024-10-28T18:32:30.000Z","size":43,"stargazers_count":8,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-09T22:39:41.789Z","etag":null,"topics":["facebook","instagram","meta","threads","threads-api"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidcelis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-19T16:42:05.000Z","updated_at":"2024-08-23T07:27:58.000Z","dependencies_parsed_at":"2024-06-19T17:46:12.454Z","dependency_job_id":null,"html_url":"https://github.com/davidcelis/threads-api","commit_stats":null,"previous_names":["davidcelis/threads-api"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidcelis%2Fthreads-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidcelis%2Fthreads-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidcelis%2Fthreads-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidcelis%2Fthreads-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidcelis","download_url":"https://codeload.github.com/davidcelis/threads-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243950792,"owners_count":20373664,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["facebook","instagram","meta","threads","threads-api"],"created_at":"2024-10-11T08:35:17.217Z","updated_at":"2025-03-21T04:32:10.531Z","avatar_url":"https://github.com/davidcelis.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Threads API\n\n`threads-api` is a Ruby client for [Threads](https://developers.facebook.com/docs/threads), providing a simple interface for interacting with its API endpoints after the OAuth2 handshake is initialized.\n\n## Installation\n\nInstall the gem and add it to your application's Gemfile by executing:\n\n```sh\n$ bundle add threads-api\n```\n\nIf your'e not using bundler to manage your dependencies, you can install the gem manually:\n\n```sh\n$ gem install threads-api\n```\n\n## Usage\n\nThe Threads API uses OAuth2 for authentication. While this client won't initialize the handshake for you (which requires a web server to direct the user to Facebook and accept a callback), it will allow you to _finish_ the handshake by exchanging the OAuth2 code from your callback for an access token:\n\n```ruby\nclient = Threads::API::OAuth2::Client.new(client_id: ENV[\"THREADS_CLIENT_ID\"], client_secret: ENV[\"THREADS_CLIENT_SECRET\"])\nresponse = client.access_token(code: params[:code], redirect_uri: \"https://example.com/threads/oauth/callback\")\n\n# Save the access token and user ID for future requests.\naccess_token = response.access_token\nuser_id = response.user_id\n```\n\nThe access token returned by this initial exchange is short-lived and only valid for one hour. You can exchange it for a long-lived access token by calling `exchange_access_token`:\n\n```ruby\nresponse = client.exchange_access_token(access_token)\n\n# Save the long-lived access token for future requests.\naccess_token = response.access_token\nexpires_at = Time.now + response.expires_in\n```\n\nLong-lived access tokens are valid for 60 days. After one day (but before the token expires), you can refresh them by calling `refresh_access_token`:\n\n```ruby\nresponse = client.refresh_access_token(access_token)\n\n# Save the refreshed access token for future requests.\naccess_token = response.access_token\nexpires_at = Time.now + response.expires_in\n```\n\nOnce you have a valid access token, whether it's short-lived or long-lived, you can use it to make requests to the Threads API using a `Threads::API::Client`:\n\n```ruby\nclient = Threads::API::Client.new(access_token)\n```\n\n## Reading Threads\n\nTo read threads for a user:\n\n```ruby\n# List recent threads for a user.\nresponse = client.list_threads # Defaults to the authenticated user\nresponse = client.list_threads(user_id: \"7770386109746442\")\n\n# By default, the Threads API returns 25 threads at a time. You can paginate through them like so:\nnext_page = client.list_threads(after: response.after_cursor) # or\nprevious_page = client.list_threads(before: response.before_cursor)\n\n# Get a specific thread by ID.\nthread = client.get_thread(\"18050206876707110\") # Defaults to the authenticated user\nthread = client.get_thread(\"18050206876707110\", user_id: \"7770386109746442\")\n```\n\n`Threads::API::Client#list_threads` accepts the following options:\n\n* `user_id` - The ID of the user whose threads you want to read. Defaults to `\"me\"`, the authenticated user.\n* `fields` - An Array (or comma-separated String) of fields to include in the response. By default, all documented fields are requested. See the [Threads API documentation](https://developers.facebook.com/docs/threads/threads-media#fields) for a list of available fields.\n* `since` - An ISO 8601 date string. Only threads published after this date will be returned.\n* `until` - An ISO 8601 date string. Only threads published before this date will be returned.\n* `before` - A cursor string returned by a previous request for pagination.\n* `after` - A cursor string returned by a previous request for pagination.\n* `limit` - The number of threads to return. Defaults to `25`, with a maximum of `100`.\n\n`Threads::API::Client#get_thread` accepts only the `user_id` and `fields` options.\n\n## Reading profiles\n\nTo get a user's profile:\n\n```ruby\nprofile = client.get_profile(\"7770386109746442\")\n```\n\n`Threads::API::Client#get_profile` accepts a `fields` option, which is an Array (or comma-separated String) of fields to include in the response. By default, all documented fields are requested. See the [Threads API documentation](https://developers.facebook.com/docs/threads/threads-profiles#fields) for a list of available fields.\n\n## Posting to Threads\n\nPosting to Threads is, at the very least, a two-step process. Threads requires that you first create a container for the media you want to post, then explicitly publishing that container as a thread. However, more steps are involved if you want to post multiple media items in a single thread.\n\n### Creating the Thread\n\nThe first step in posting to Threads is to create a \"media container\", even if your post is text-only.\n\n```ruby\n# Create a text-only post\nclient.create_thread(text: \"Hello, world!\")\n\n# Create a post with a photo or video\nclient.create_thread(type: \"IMAGE\", image_url: \"https://example.com/image.jpg\", text: \"Some optional text\")\nclient.create_thread(type: \"VIDEO\", video_url: \"https://example.com/video.mp4\", text: \"Some optional text\")\n\n# Reply to one of your own threads\nclient.create_thread(text: \"Hello, world!\", reply_to_id: \"18050206876707110\")\n\n# Control who can reply to your thread. Defaults to \"everyone\".\nclient.create_thread(text: \"Hello, world!\", reply_control: \"accounts_you_follow\") # or \"mentioned_only\"\n```\n\n### Publishing the Thread\n\nOnce you've created a media container, you can publish it as a thread:\n\n```ruby\npending_thread = client.create_thread(text: \"Hello, world!\")\nclient.publish_thread(pending_thread.id)\n```\n\nAccording to Meta, you may need to wait before attempting to publish a thread, especially if the thread contains images or videos. They suggest checking the status of the pending thread before attempting to publish it:\n\n```ruby\npending_thread = client.create_thread(text: \"Hello, world!\")\npending_thread = client.get_thread_status(pending_thread.id)\n\nwhile pending_thread.in_progress?\n  # Wait a bit (they recommend checking only once per minute) and try again\n  sleep 60\n  pending_thread = client.get_thread_status(pending_thread.id)\nend\n\nif pending_thread.finished?\n  client.publish_thread(pending_thread.id)\nelsif pending_thread.errored?\n  # Handle the error\nelse\n  # Unpublished threads expire after 24 hours.\n  pending_thread.expired?\n\n  # If you've already published the thread, the status will be \"PUBLISHED\".\n  pending_thread.published?\nend\n```\n\n### Posting multiple photos and/or videos\n\nThreads allows you to post a combination of up to 10 photos and/or videos in a single thread. To do so, you must first create a media container for each photo or video you want to post, then create a media container for the thread itself, and finally publish the thread.\n\n```ruby\n# Create carousel items for each photo or video you want to post\nimage1 = client.create_carousel_item(type: \"IMAGE\", image_url: \"https://example.com/image1.jpg\")\nimage2 = client.create_carousel_item(type: \"IMAGE\", image_url: \"https://example.com/image2.jpg\")\nvideo1 = client.create_carousel_item(type: \"VIDEO\", video_url: \"https://example.com/video1.mp4\")\nvideo2 = client.create_carousel_item(type: \"VIDEO\", video_url: \"https://example.com/video2.mp4\")\n\n# Create the media container for the thread itself\npending_thread = client.create_carousel_thread(text: \"Some optional text\", children: [image1.id, image2.id, video1.id, video2.id])\n\n# Publish the thread\nclient.publish_thread(pending_thread.id)\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/davidcelis/threads-api. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/davidcelis/threads-api/blob/main/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the `threads-api` project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/davidcelis/threads-api/blob/main/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidcelis%2Fthreads-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidcelis%2Fthreads-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidcelis%2Fthreads-api/lists"}