A generated Ruby transport gem for the entire OVH REST API (all 70 top-level resources), produced from OVH's published schemas.
ovh-api is the thin, machine-generated HTTP layer. Business ergonomics and quirk
absorption belong in a hand-written wrapper on top (ovh-client, à la dolibarr-client
over dolibarr-api). OVH's request-signing auth is not baked in here — it is injected
as a middleware through the generator's auth seam (see Authentication below).
OVH does not publish OpenAPI. Its API is described in legacy Swagger 1.2
(per-resource API Declarations, no swaggerVersion, no security definitions), which
openapi-generator cannot ingest. The pipeline bridges that gap:
versions/raw/*.json pinned OVH 1.2 declarations (70 resources)
└─ scripts/convert.mjs
├─ union-merge colliding models OVH redefines shared types (service.*, order.*, …)
│ per product; enums → union of values, structs →
│ union of properties (all optional)
├─ swagger-converter 1.2 → 2.0, merges all 70 resources into one doc
├─ 3 structural fixups
│ • dangling $refs OVH scalar types (password, ipBlock, …) declared
│ as undefined models; array aliases (Foo[]); maps
│ • multi-body params 1.2 emits one `body` param per POST field; 2.0
│ allows only one → merged into a single object
│ • missing path params `{id}` in the template but undeclared (e.g.
│ /telephony …/{queueId}/agent)
└─ swagger2openapi 2.0 → 3.0
└─ versions/ovh.oas3.json → fed to openapi-generator (-g ruby-nextgen)
The last full run: 70 resources → 4728 paths, 5268 schemas, OpenAPI 3.0.0,
openapi-generator validate clean (0 errors; only harmless "unused model" warnings).
Two models remain mapped to a free-form object (lossy but harmless): T (a generic
placeholder) and coreTypes.AccountId:string.
mise run fetch # refresh versions/raw/*.json from api.ovh.com (overwrites pinned schemas)
mise run convert # versions/raw → versions/ovh.oas3.json
mise run generate # generate the gem into lib/** (-g ruby-nextgen)
mise run build # convert + generate + format
mise run dev:spec # run the generated spec suiteversions/raw/*.json are committed so the build is reproducible without hitting OVH.
Run mise run fetch only to deliberately refresh the pinned schemas.
Three pipeline artifacts are git-ignored, so a fresh clone needs mise run convert before
mise run generate (the latter fails fast with a message if the spec is missing):
| Artifact | Why it is ignored |
|---|---|
versions/ovh.oas3.json |
Intermediate spec (~5 MB), regenerated deterministically from versions/raw/*.json. Committing it would add unreadable multi-MB diffs on every re-pin, duplicating information the committed inputs already carry. |
scripts/node_modules/ |
Installed dependencies. |
scripts/package-lock.json |
Deliberate: see the caveat below. |
Reproducibility caveat: scripts/package.json pins the two converters exactly
(swagger-converter 1.5.1, swagger2openapi 2.9.4), but with no committed lockfile their
transitive dependencies float. Two npm install runs at different dates can therefore
produce a byte-different versions/ovh.oas3.json with no versioned file having changed.
This does not affect CI — it lints and tests the committed lib/** and never regenerates —
but a regeneration is only reproducible to the extent that npm resolves the same transitive
tree. Commit scripts/package-lock.json if that guarantee is ever needed.
The gem is generated with ruby-nextgen, which ships in the published
openapi-generator release pinned as OPENAPI_GENERATOR_VERSION in mise.toml (currently
7.24.0). No fork and no local Maven build are involved.
Locally, install the generator from Homebrew:
brew install openapi-generatorCI has no Homebrew formula, so regenerate.yml downloads the pinned jar from Maven Central
and exports OPENAPI_GENERATOR_CMD=java -jar …. Either way mise run generate cross-checks
the generator's reported version against the pin and fails loudly on a mismatch, rather
than silently emitting different code. To move to a newer generator, bump the pin — that is
the only place the version lives.
OVH signs each request: headers X-Ovh-Application, X-Ovh-Consumer, X-Ovh-Timestamp,
and X-Ovh-Signature = $1$ + SHA1(`app_secret + consumer_key + METHOD + full_url + body
- timestamp
). This is a per-request signature, **not** an OpenAPI security scheme, so it is injected through theruby-nextgen` middleware seam rather than generated:
require "digest/sha1"
class OvhSignature < Faraday::Middleware
def initialize(app, app_key:, app_secret:, consumer_key:)
super(app)
@app_key, @app_secret, @consumer_key = app_key, app_secret, consumer_key
end
def on_request(env)
ts = Time.now.to_i.to_s
sig = "$1$" + Digest::SHA1.hexdigest(
[@app_secret, @consumer_key, env.method.to_s.upcase, env.url.to_s, env.body.to_s, ts].join("+"))
env.request_headers.merge!(
"X-Ovh-Application" => @app_key, "X-Ovh-Consumer" => @consumer_key,
"X-Ovh-Timestamp" => ts, "X-Ovh-Signature" => sig)
end
end
client = Ovh::Api::Client.new do |config|
config.use(OvhSignature, app_key: ENV["OVH_APP_KEY"], app_secret: ENV["OVH_APP_SECRET"],
consumer_key: ENV["OVH_CONSUMER_KEY"])
endversions/raw/ pinned OVH 1.2 schemas (source of truth, committed)
versions/resources.txt the 70 top-level resource paths
scripts/convert.mjs conversion pipeline (1.2 → OpenAPI 3.0)
mise.toml fetch / convert / generate / build tasks
lib/ generated gem — DO NOT edit by hand