crystal-api-ovh
= ovh-api :toc: left :toc-title: Table of Contents :toclevels: 3 :source-highlighter: rouge :icons: font
Pure-Crystal client (stdlib only) for the https://api.ovh.com/console/[OVHcloud v1 API]. Covers the endpoints needed to provision dedicated servers: SSH keys, install + task polling, reverse DNS.
[NOTE]
ovh-api is a sister project in the Aloli Crystal ecosystem alongside https://github.com/aloli-crystal/crystal-beryl[crystal-beryl] (agentless FreeBSD configuration) and https://github.com/aloli-crystal/crystal-silex[crystal-silex] (pure-Crystal FAT12 / cloud-init NoCloud writer). Zero external dependencies: only HTTP::Client, OpenSSL, JSON from the Crystal stdlib.
== Why ovh-api?
OVH's official clients exist in Python, Node, Go, PHP and Java, but not Crystal. Existing Crystal wrappers are abandoned or pull in third-party HTTP shards.
ovh-api computes the X-Ovh-Signature locally (SHA-1), uses stdlib only, and exposes an idiomatic Crystal API that is easy to test by injecting a fake HTTP transport.
== Features
- Pure Crystal: no runtime dependencies (stdlib
HTTP::Client,OpenSSL,JSON). - OVH signature: local
$1$ + SHA1(secret+ck+method+url+body+timestamp). - Clock offset:
/auth/timequeried once and cached for the process lifetime. - Multi-endpoint routing:
:eu(default),:ca,:us, Kimsufi, SoYouStart. - Injectable transport: abstract
HttpTransport, trivial stub for specs. - Error taxonomy:
AuthenticationError,NotFound,RateLimited,ApiError(exposeserrorCode+http_status).
== Scope
[cols="1,3"] |=== | ovh-api does | ovh-api does not (yet)
| SSH keys (/me/sshKey): list, get, create, delete. | Full account management (/me/*).
| Dedicated servers: list, info, reinstall, tasks, boot/netboot/reboot, prepare_rescue. | Ordering (/order/cart) — planned for v0.3.
| Reverse DNS (/ip/{ip}/reverse): list, get, set, delete. | VPS, public cloud, CDN, web hosting.
| Full signing, clock sync, 401/403/404/429 handling. | OAuth 2.0 app flow (API Keys remain OVH's official scheme). |===
== Installation
In your shard.yml:
[source,yaml]
dependencies: ovh-api: github: aloli-crystal/crystal-ovh-api version: ~> 0.2
Then shards install.
== Quick start
[source,crystal]
require "ovh-api"
client = OvhApi::Client.new( application_key: ENV["OVH_APPLICATION_KEY"], application_secret: ENV["OVH_APPLICATION_SECRET"], consumer_key: ENV["OVH_CONSUMER_KEY"], endpoint: :eu, )
SSH keys
client.ssh_keys.list # => ["laptop", "desktop"] client.ssh_keys.create(name: "laptop", key: "ssh-ed25519 AAAA...")
Dedicated servers
servers = client.dedicated_servers.list # => ["ns1.ip-1-2-3.eu"] info = client.dedicated_servers.info(servers.first)
Reinstall Debian 12
task = client.dedicated_servers.reinstall( service_name: "ns1.ip-1-2-3.eu", template: "debian12_64", hostname: "web01.aloli.fr", ssh_key_name: "laptop", )
Poll
loop do status = client.dedicated_servers.task( service_name: "ns1.ip-1-2-3.eu", task_id: task.id, ) break if status.done? sleep 30.seconds end
Reverse DNS
client.ips.set_reverse(ip: "192.0.2.10", reverse: "web01.aloli.fr.")
Full runnable example in link:examples/provision_debian.cr[examples/provision_debian.cr].
== OVH rescue orchestration
Since v0.2.1, ovh-api exposes the netboot / reboot endpoints plus a high-level helper prepare_rescue that chains three steps to swap a dedicated server into rescue mode with an SSH key injected:
. Find the UEFI-compatible rescue bootId via GET /dedicated/server/\*/boot then GET .../boot/\{id\}. . PUT /dedicated/server/\* with {"bootId": <rescueId>, "rescueSshKey": "<ssh-key-name>"} (one call — rescueSshKey is a field on the server object, not a separate option). . POST /dedicated/server/\*/reboot → Task.
[source,crystal]
The key must already exist in /me/sshKey.
client.ssh_keys.list.includes?("laptop") # => true
task = client.dedicated_servers.prepare_rescue( service_name: "ns1.ip-1-2-3.eu", ssh_key_name: "laptop", ) puts "Rescue task #{task.id} (#{task.status})"
loop do task = client.dedicated_servers.task( service_name: "ns1.ip-1-2-3.eu", task_id: task.id, ) break if task.done? sleep 15.seconds end
The underlying calls are exposed individually as well (boots, boot, set_boot (avec rescue_ssh_key optionnel), reboot) if you need a different orchestrator (e.g. switching to ipxeCustomerScript).
== Getting OVH credentials
. Create an application at https://eu.api.ovh.com/createApp/[eu.api.ovh.com/createApp] → you get application_key and application_secret. . Generate a consumer key with the required scopes at https://eu.api.ovh.com/createToken/[eu.api.ovh.com/createToken]: + [source]
GET /me/sshKey POST /me/sshKey GET /dedicated/server GET /dedicated/server/* GET /dedicated/installationTemplate POST /dedicated/server//reinstall GET /dedicated/server//task GET /dedicated/server//task/ GET /ip//reverse POST /ip//reverse GET /dedicated/server//boot GET /dedicated/server//boot/* PUT /dedicated/server/*
POST /dedicated/server/*/reboot
. Validate the URL returned by OVH (SMS or OVH password manager). The consumer key becomes active.
== Running against the real API
Specs use a FakeTransport (stdlib-only) with no network calls. For an integration run against the real OVH API:
[source,sh]
export OVH_APPLICATION_KEY=... export OVH_APPLICATION_SECRET=... export OVH_CONSUMER_KEY=... crystal run examples/provision_debian.cr
Without OVH_CONFIRM=yes, the example stops before launching the reinstall: it just lists keys, servers and templates.
== Status
Early stage (v0.2 — provisioning + rescue mode covered).
- link:ARCHITECTURE.adoc[ARCHITECTURE.adoc] — signing, routing, error taxonomy.
- link:CHANGELOG.adoc[CHANGELOG.adoc] — changelog.
== License
MIT. See link:LICENSE[LICENSE].
crystal-api-ovh
- 0
- 0
- 0
- 1
- 0
- about 4 hours ago
- April 19, 2026
MIT License
Mon, 20 Apr 2026 15:27:44 GMT