{"id":28047871,"url":"https://github.com/openai/openai-ruby","last_synced_at":"2026-05-16T00:10:44.858Z","repository":{"id":288190723,"uuid":"943458341","full_name":"openai/openai-ruby","owner":"openai","description":null,"archived":false,"fork":false,"pushed_at":"2025-05-07T14:10:57.000Z","size":5508,"stargazers_count":146,"open_issues_count":5,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-08T09:47:33.318Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-03-05T18:31:42.000Z","updated_at":"2025-05-08T03:39:14.000Z","dependencies_parsed_at":"2025-04-16T03:06:37.404Z","dependency_job_id":"cbcb3d50-50ec-4534-bba6-b411795a6f43","html_url":"https://github.com/openai/openai-ruby","commit_stats":null,"previous_names":["openai/openai-ruby"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openai%2Fopenai-ruby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openai%2Fopenai-ruby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openai%2Fopenai-ruby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openai%2Fopenai-ruby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openai","download_url":"https://codeload.github.com/openai/openai-ruby/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253166475,"owners_count":21864467,"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":[],"created_at":"2025-05-11T21:05:01.840Z","updated_at":"2026-05-16T00:10:44.852Z","avatar_url":"https://github.com/openai.png","language":"Ruby","funding_links":[],"categories":["Libraries \u0026 SDKs","Ruby","Openai","AI and LLMs"],"sub_categories":["Official SDKs"],"readme":"# OpenAI Ruby API library\n\nThe OpenAI Ruby library provides convenient access to the OpenAI REST API from any Ruby 3.2.0+ application. It ships with comprehensive types \u0026 docstrings in Yard, RBS, and RBI – [see below](https://github.com/openai/openai-ruby#Sorbet) for usage with Sorbet. The standard library's `net/http` is used as the HTTP transport, with connection pooling via the `connection_pool` gem.\n\n## Documentation\n\nDocumentation for releases of this gem can be found [on RubyDoc](https://gemdocs.org/gems/openai).\n\nThe REST API documentation can be found on [platform.openai.com](https://platform.openai.com/docs).\n\n## Installation\n\nTo use this gem, install via Bundler by adding the following to your application's `Gemfile`:\n\n\u003c!-- x-release-please-start-version --\u003e\n\n```ruby\ngem \"openai\", \"~\u003e 0.63.0\"\n```\n\n\u003c!-- x-release-please-end --\u003e\n\n## Usage\n\n```ruby\nrequire \"bundler/setup\"\nrequire \"openai\"\n\nopenai = OpenAI::Client.new(\n  api_key: ENV[\"OPENAI_API_KEY\"] # This is the default and can be omitted\n)\n\nchat_completion = openai.chat.completions.create(messages: [{role: \"user\", content: \"Say this is a test\"}], model: \"gpt-5.2\")\n\nputs(chat_completion)\n```\n\n### Streaming\n\nWe provide support for streaming responses using Server-Sent Events (SSE).\n\n```ruby\nstream = openai.responses.stream(\n  input: \"Write a haiku about OpenAI.\",\n  model: \"gpt-5.2\"\n)\n\nstream.each do |event|\n  puts(event.type)\nend\n```\n\n### Pagination\n\nList methods in the OpenAI API are paginated.\n\nThis library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:\n\n```ruby\npage = openai.fine_tuning.jobs.list(limit: 20)\n\n# Fetch single item from page.\njob = page.data[0]\nputs(job.id)\n\n# Automatically fetches more pages as needed.\npage.auto_paging_each do |job|\n  puts(job.id)\nend\n```\n\nAlternatively, you can use the `#next_page?` and `#next_page` methods for more granular control working with pages.\n\n```ruby\nif page.next_page?\n  new_page = page.next_page\n  puts(new_page.data[0].id)\nend\n```\n\n### File uploads\n\nRequest parameters that correspond to file uploads can be passed as raw contents, a [`Pathname`](https://rubyapi.org/3.2/o/pathname) instance, [`StringIO`](https://rubyapi.org/3.2/o/stringio), or more.\n\n```ruby\nrequire \"pathname\"\n\n# Use `Pathname` to send the filename and/or avoid paging a large file into memory:\nfile_object = openai.files.create(file: Pathname(\"input.jsonl\"), purpose: \"fine-tune\")\n\n# Alternatively, pass file contents or a `StringIO` directly:\nfile_object = openai.files.create(file: File.read(\"input.jsonl\"), purpose: \"fine-tune\")\n\nputs(file_object.id)\n\n# Or, to control the filename and/or content type:\nimage = OpenAI::FilePart.new(Pathname('dog.jpg'), content_type: 'image/jpeg')\nedited = openai.images.edit(\n  prompt: \"make this image look like a painting\",\n  model: \"gpt-image-1\",\n  size: '1024x1024',\n  image: image\n)\n\nputs(edited.data.first)\n```\n\nNote that you can also pass a raw `IO` descriptor, but this disables retries, as the library can't be sure if the descriptor is a file or pipe (which cannot be rewound).\n\n## Workload Identity Authentication\n\nFor secure, automated environments like cloud-managed Kubernetes, Azure, and GCP, you can use workload identity authentication with short-lived tokens from cloud identity providers instead of long-lived API keys.\n\n`client_id` remains available as an optional parameter for token exchange setups that require an explicit OAuth client ID.\n\n### Kubernetes Service Account\n\n```ruby\nrequire \"openai\"\n\n# Configure Kubernetes service account provider\nprovider = OpenAI::Auth::SubjectTokenProviders::K8sServiceAccountTokenProvider.new\n\nworkload_identity = OpenAI::Auth::WorkloadIdentity.new(\n  identity_provider_id: ENV[\"IDENTITY_PROVIDER_ID\"], # This is the default and can be omitted\n  service_account_id: ENV[\"SERVICE_ACCOUNT_ID\"], # This is the default and can be omitted\n  provider: provider\n)\n\nclient = OpenAI::Client.new(\n  workload_identity: workload_identity,\n)\n\nresponse = client.chat.completions.create(\n  messages: [{role: \"user\", content: \"Hello!\"}],\n  model: \"gpt-5.2\"\n)\n```\n\n### Azure Managed Identity\n\n```ruby\nprovider = OpenAI::Auth::SubjectTokenProviders::AzureManagedIdentityTokenProvider.new\n\nworkload_identity = OpenAI::Auth::WorkloadIdentity.new(\n  identity_provider_id: ENV[\"IDENTITY_PROVIDER_ID\"], # This is the default and can be omitted\n  service_account_id: ENV[\"SERVICE_ACCOUNT_ID\"], # This is the default and can be omitted\n  provider: provider\n)\n\nclient = OpenAI::Client.new(\n  workload_identity: workload_identity,\n)\n```\n\n### GCP Metadata Server\n\n```ruby\nprovider = OpenAI::Auth::SubjectTokenProviders::GCPIDTokenProvider.new\n\nworkload_identity = OpenAI::Auth::WorkloadIdentity.new(\n  identity_provider_id: ENV[\"IDENTITY_PROVIDER_ID\"], # This is the default and can be omitted\n  service_account_id: ENV[\"SERVICE_ACCOUNT_ID\"], # This is the default and can be omitted\n  provider: provider\n)\n\nclient = OpenAI::Client.new(\n  workload_identity: workload_identity,\n)\n```\n\n### Custom Token Providers\n\nYou can implement custom token providers by including the `OpenAI::Auth::SubjectTokenProvider` module:\n\n```ruby\nclass CustomProvider\n  include OpenAI::Auth::SubjectTokenProvider\n\n  def token_type\n    OpenAI::Auth::TokenType::JWT\n  end\n\n  def get_token\n    \"custom-token\"\n  end\nend\n\nprovider = CustomProvider.new\n\nworkload_identity = OpenAI::Auth::WorkloadIdentity.new(\n  identity_provider_id: ENV[\"IDENTITY_PROVIDER_ID\"], # This is the default and can be omitted\n  service_account_id: ENV[\"SERVICE_ACCOUNT_ID\"], # This is the default and can be omitted\n  provider: provider\n)\n\nclient = OpenAI::Client.new(\n  workload_identity: workload_identity,\n  organization: ENV[\"OPENAI_ORG_ID\"],\n  project: ENV[\"OPENAI_PROJECT_ID\"]\n)\n```\n\n## Webhook Verification\n\nVerifying webhook signatures is _optional but encouraged_.\n\nFor more information about webhooks, see [the API docs](https://platform.openai.com/docs/guides/webhooks).\n\n### Parsing webhook payloads\n\nFor most use cases, you will likely want to verify the webhook and parse the payload at the same time. To achieve this, we provide the method `client.webhooks.unwrap`, which parses a webhook request and verifies that it was sent by OpenAI. This method will raise an error if the signature is invalid.\n\nNote that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). The `unwrap` method will parse this JSON for you into an event object after verifying the webhook was sent from OpenAI.\n\n```ruby\nrequire 'sinatra'\nrequire 'openai'\n\n# Set up the client with webhook secret from environment variable\nclient = OpenAI::Client.new(webhook_secret: ENV['OPENAI_WEBHOOK_SECRET'])\n\npost '/webhook' do\n  request_body = request.body.read\n  \n  begin\n    event = client.webhooks.unwrap(request_body, request.env)\n    \n    case event.type\n    when 'response.completed'\n      puts \"Response completed: #{event.data}\"\n    when 'response.failed'\n      puts \"Response failed: #{event.data}\"\n    else\n      puts \"Unhandled event type: #{event.type}\"\n    end\n    \n    status 200\n    'ok'\n  rescue StandardError =\u003e e\n    puts \"Invalid signature: #{e}\"\n    status 400\n    'Invalid signature'\n  end\nend\n```\n\n### Verifying webhook payloads directly\n\nIn some cases, you may want to verify the webhook separately from parsing the payload. If you prefer to handle these steps separately, we provide the method `client.webhooks.verify_signature` to _only verify_ the signature of a webhook request. Like `unwrap`, this method will raise an error if the signature is invalid.\n\nNote that the `body` parameter must be the raw JSON string sent from the server (do not parse it first). You will then need to parse the body after verifying the signature.\n\n```ruby\nrequire 'sinatra'\nrequire 'json'\nrequire 'openai'\n\n# Set up the client with webhook secret from environment variable\nclient = OpenAI::Client.new(webhook_secret: ENV['OPENAI_WEBHOOK_SECRET'])\n\npost '/webhook' do\n  request_body = request.body.read\n  \n  begin\n    client.webhooks.verify_signature(request_body, request.env)\n    \n    # Parse the body after verification\n    event = JSON.parse(request_body)\n    puts \"Verified event: #{event}\"\n    \n    status 200\n    'ok'\n  rescue StandardError =\u003e e\n    puts \"Invalid signature: #{e}\"\n    status 400\n    'Invalid signature'\n  end\nend\n```\n\n### [Structured outputs](https://platform.openai.com/docs/guides/structured-outputs) and function calling\n\nThis SDK ships with helpers in `OpenAI::BaseModel`, `OpenAI::ArrayOf`, `OpenAI::EnumOf`, and `OpenAI::UnionOf` to help you define the supported JSON schemas used in making structured outputs and function calling requests.\n\n\u003cdetails\u003e\n\u003csummary\u003eSnippet\u003c/summary\u003e\n\n```ruby\n# Participant model with an optional last_name and an enum for status\nclass Participant \u003c OpenAI::BaseModel\n  required :first_name, String\n  required :last_name, String, nil?: true\n  required :status, OpenAI::EnumOf[:confirmed, :unconfirmed, :tentative]\nend\n\n# CalendarEvent model with a list of participants.\nclass CalendarEvent \u003c OpenAI::BaseModel\n  required :name, String\n  required :date, String\n  required :participants, OpenAI::ArrayOf[Participant]\nend\n\n\nclient = OpenAI::Client.new\n\nresponse = client.responses.create(\n  model: \"gpt-5.2\",\n  input: [\n    {role: :system, content: \"Extract the event information.\"},\n    {\n      role: :user,\n      content: \u003c\u003c~CONTENT\n        Alice Shah and Lena are going to a science fair on Friday at 123 Main St. in San Diego.\n        They have also invited Jasper Vellani and Talia Groves - Jasper has not responded and Talia said she is thinking about it.\n      CONTENT\n    }\n  ],\n  text: CalendarEvent\n)\n\nresponse\n  .output\n  .flat_map { _1.content }\n  # filter out refusal responses\n  .grep_v(OpenAI::Models::Responses::ResponseOutputRefusal)\n  .each do |content|\n    # parsed is an instance of `CalendarEvent`\n    pp(content.parsed)\n  end\n```\n\n\u003c/details\u003e\n\nSee the [examples](https://github.com/openai/openai-ruby/tree/main/examples) directory for more usage examples for helper usage.\n\nTo make the equivalent request using raw JSON schema format, you would do the following:\n\n\u003cdetails\u003e\n\u003csummary\u003eSnippet\u003c/summary\u003e\n\n```ruby\nresponse = client.responses.create(\n  model: \"gpt-5.2\",\n  input: [\n    {role: :system, content: \"Extract the event information.\"},\n    {\n      role: :user,\n      content: \"...\"\n    }\n  ],\n  text: {\n    format: {\n      type: :json_schema,\n      name: \"CalendarEvent\",\n      strict: true,\n      schema: {\n        type: \"object\",\n        properties: {\n          name: {type: \"string\"},\n          date: {type: \"string\"},\n          participants: {\n            type: \"array\",\n            items: {\n              type: \"object\",\n              properties: {\n                first_name: {type: \"string\"},\n                last_name: {type: %w[string null]},\n                status: {type: \"string\", enum: %w[confirmed unconfirmed tentative]}\n              },\n              required: %w[first_name last_name status],\n              additionalProperties: false\n            }\n          }\n        },\n        required: %w[name date participants],\n        additionalProperties: false\n      }\n    }\n  }\n)\n```\n\n\u003c/details\u003e\n\n### Handling errors\n\nWhen the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 4xx or 5xx response), a subclass of `OpenAI::Errors::APIError` will be thrown:\n\n```ruby\nbegin\n  job = openai.fine_tuning.jobs.create(model: \"gpt-4o\", training_file: \"file-abc123\")\nrescue OpenAI::Errors::APIConnectionError =\u003e e\n  puts(\"The server could not be reached\")\n  puts(e.cause)  # an underlying Exception, likely raised within `net/http`\nrescue OpenAI::Errors::RateLimitError =\u003e e\n  puts(\"A 429 status code was received; we should back off a bit.\")\nrescue OpenAI::Errors::APIStatusError =\u003e e\n  puts(\"Another non-200-range status code was received\")\n  puts(e.status)\nend\n```\n\nError codes are as follows:\n\n| Cause            | Error Type                 |\n| ---------------- | -------------------------- |\n| HTTP 400         | `BadRequestError`          |\n| HTTP 401         | `AuthenticationError`      |\n| HTTP 403         | `PermissionDeniedError`    |\n| HTTP 404         | `NotFoundError`            |\n| HTTP 409         | `ConflictError`            |\n| HTTP 422         | `UnprocessableEntityError` |\n| HTTP 429         | `RateLimitError`           |\n| HTTP \u003e= 500      | `InternalServerError`      |\n| Other HTTP error | `APIStatusError`           |\n| Timeout          | `APITimeoutError`          |\n| Network error    | `APIConnectionError`       |\n\n### Retries\n\nCertain errors will be automatically retried 2 times by default, with a short exponential backoff.\n\nConnection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, \u003e=500 Internal errors, and timeouts will all be retried by default.\n\nYou can use the `max_retries` option to configure or disable this:\n\n```ruby\n# Configure the default for all requests:\nopenai = OpenAI::Client.new(\n  max_retries: 0 # default is 2\n)\n\n# Or, configure per-request:\nopenai.chat.completions.create(\n  messages: [{role: \"user\", content: \"How can I get the name of the current day in JavaScript?\"}],\n  model: \"gpt-5.2\",\n  request_options: {max_retries: 5}\n)\n```\n\n### Timeouts\n\nBy default, requests will time out after 600 seconds. You can use the timeout option to configure or disable this:\n\n```ruby\n# Configure the default for all requests:\nopenai = OpenAI::Client.new(\n  timeout: nil # default is 600\n)\n\n# Or, configure per-request:\nopenai.chat.completions.create(\n  messages: [{role: \"user\", content: \"How can I list all files in a directory using Python?\"}],\n  model: \"gpt-5.2\",\n  request_options: {timeout: 5}\n)\n```\n\nOn timeout, `OpenAI::Errors::APITimeoutError` is raised.\n\nNote that requests that time out are retried by default.\n\n## Advanced concepts\n\n### BaseModel\n\nAll parameter and response objects inherit from `OpenAI::Internal::Type::BaseModel`, which provides several conveniences, including:\n\n1. All fields, including unknown ones, are accessible with `obj[:prop]` syntax, and can be destructured with `obj =\u003e {prop: prop}` or pattern-matching syntax.\n\n2. Structural equivalence for equality; if two API calls return the same values, comparing the responses with == will return true.\n\n3. Both instances and the classes themselves can be pretty-printed.\n\n4. Helpers such as `#to_h`, `#deep_to_h`, `#to_json`, and `#to_yaml`.\n\n### Making custom or undocumented requests\n\n#### Undocumented properties\n\nYou can send undocumented parameters to any endpoint, and read undocumented response properties, like so:\n\nNote: the `extra_` parameters of the same name overrides the documented parameters.\n\n```ruby\nchat_completion =\n  openai.chat.completions.create(\n    messages: [{role: \"user\", content: \"How can I get the name of the current day in JavaScript?\"}],\n    model: \"gpt-5.2\",\n    request_options: {\n      extra_query: {my_query_parameter: value},\n      extra_body: {my_body_parameter: value},\n      extra_headers: {\"my-header\": value}\n    }\n  )\n\nputs(chat_completion[:my_undocumented_property])\n```\n\n#### Undocumented request params\n\nIf you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` under the `request_options:` parameter when making a request, as seen in the examples above.\n\n#### Undocumented endpoints\n\nTo make requests to undocumented endpoints while retaining the benefit of auth, retries, and so on, you can make requests using `client.request`, like so:\n\n```ruby\nresponse = client.request(\n  method: :post,\n  path: '/undocumented/endpoint',\n  query: {\"dog\": \"woof\"},\n  headers: {\"useful-header\": \"interesting-value\"},\n  body: {\"hello\": \"world\"}\n)\n```\n\n### Concurrency \u0026 connection pooling\n\nThe `OpenAI::Client` instances are threadsafe, but are only fork-safe when there are no in-flight HTTP requests.\n\nEach instance of `OpenAI::Client` has its own HTTP connection pool with a default size of 99. As such, we recommend instantiating the client once per application in most settings.\n\nWhen all available connections from the pool are checked out, requests wait for a new connection to become available, with queue time counting towards the request timeout.\n\nUnless otherwise specified, other classes in the SDK do not have locks protecting their underlying data structure.\n\n## Sorbet\n\nThis library provides comprehensive [RBI](https://sorbet.org/docs/rbi) definitions and has no dependency on sorbet-runtime.\n\nYou can provide typesafe request parameters like so:\n\n```ruby\nopenai.chat.completions.create(\n  messages: [OpenAI::Chat::ChatCompletionUserMessageParam.new(content: \"Say this is a test\")],\n  model: \"gpt-5.2\"\n)\n```\n\nOr, equivalently:\n\n```ruby\n# Hashes work, but are not typesafe:\nopenai.chat.completions.create(messages: [{role: \"user\", content: \"Say this is a test\"}], model: \"gpt-5.2\")\n\n# You can also splat a full Params class:\nparams = OpenAI::Chat::CompletionCreateParams.new(\n  messages: [OpenAI::Chat::ChatCompletionUserMessageParam.new(content: \"Say this is a test\")],\n  model: \"gpt-5.2\"\n)\nopenai.chat.completions.create(**params)\n```\n\n### Enums\n\nSince this library does not depend on `sorbet-runtime`, it cannot provide [`T::Enum`](https://sorbet.org/docs/tenum) instances. Instead, we provide \"tagged symbols\" instead, which is always a primitive at runtime:\n\n```ruby\n# :in_memory\nputs(OpenAI::Chat::CompletionCreateParams::PromptCacheRetention::IN_MEMORY)\n\n# Revealed type: `T.all(OpenAI::Chat::CompletionCreateParams::PromptCacheRetention, Symbol)`\nT.reveal_type(OpenAI::Chat::CompletionCreateParams::PromptCacheRetention::IN_MEMORY)\n```\n\nEnum parameters have a \"relaxed\" type, so you can either pass in enum constants or their literal value:\n\n```ruby\n# Using the enum constants preserves the tagged type information:\nopenai.chat.completions.create(\n  prompt_cache_retention: OpenAI::Chat::CompletionCreateParams::PromptCacheRetention::IN_MEMORY,\n  # …\n)\n\n# Literal values are also permissible:\nopenai.chat.completions.create(\n  prompt_cache_retention: :in_memory,\n  # …\n)\n```\n\n## Versioning\n\nThis package follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions. As the library is in initial development and has a major version of `0`, APIs may change at any time.\n\nThis package considers improvements to the (non-runtime) `*.rbi` and `*.rbs` type definitions to be non-breaking changes.\n\n## Requirements\n\nRuby 3.2.0 or higher.\n\n## Contributing\n\nSee [the contributing documentation](https://github.com/openai/openai-ruby/tree/main/CONTRIBUTING.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenai%2Fopenai-ruby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenai%2Fopenai-ruby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenai%2Fopenai-ruby/lists"}