TocDoc
A Ruby gem for interacting with the (unofficial) Doctolib API. A thin, Faraday-based client with configurable defaults, model-driven resource querying, and a clean error hierarchy.
Heads-up: Doctolib™ does not publish a public API. This gem reverse-engineers the endpoints used by the Doctolib™ website. Behaviour may change at any time without notice. This project is for entertainment purposes only. Doctolib is a trademark of Doctolib. This project is not affiliated with, endorsed by, or sponsored by Doctolib.
Contents
- Installation
- Quick start
- Configuration
- Module-level
- Per-client
- All options
- ENV variables
- Endpoints
- Availabilities
- Search
- Response objects
- Pagination
- Error handling
- Development
- Generating documentation
- Contributing
- Code of Conduct
- License
Installation
Add the gem to your Gemfile:
gem 'toc_doc'then run:
bundle installor install it directly:
gem install toc_docQuick start
require 'toc_doc'
collection = TocDoc::Availability.where(
visit_motive_ids: 7_767_829,
agenda_ids: 1_101_600,
practice_ids: 377_272,
telehealth: false
)
collection.total # => 5
collection.next_slot # => "2026-02-28T10:00:00.000+01:00"
collection.each do |avail|
puts "#{avail.date}: #{avail.slots.map { |s| s.strftime('%H:%M') }.join(', ')}"
endConfiguration
Module-level configuration
Set options once at startup and every subsequent call will share them:
TocDoc.configure do |config|
config.api_endpoint = 'https://www.doctolib.de' # target country
config.per_page = 10
end
TocDoc::Availability.where(visit_motive_ids: 123, agenda_ids: 456)Calling TocDoc.reset! restores all options to their defaults.
Use TocDoc.options to inspect the current configuration hash.
Per-client configuration
Instantiate independent clients with different options and query via TocDoc::Availability.where:
# Germany
TocDoc.configure { |c| c.api_endpoint = 'https://www.doctolib.de'; c.per_page = 3 }
TocDoc::Availability.where(visit_motive_ids: 123, agenda_ids: 456)
# Reset and switch to Italy
TocDoc.reset!
TocDoc.configure { |c| c.api_endpoint = 'https://www.doctolib.it' }
TocDoc::Availability.where(visit_motive_ids: 789, agenda_ids: 101)Alternatively, use TocDoc::Client directly for lower-level access ().
client = TocDoc::Client.new(api_endpoint: 'https://www.doctolib.de', per_page: 5)
client.get('/availabilities.json', query: { visit_motive_ids: '123', agenda_ids: '456', start_date: Date.today.to_s, limit: 5 })All configuration options
| Option | Default | Description |
|---|---|---|
api_endpoint |
https://www.doctolib.fr |
Base URL. Change to .de / .it for other countries. |
user_agent |
TocDoc Ruby Gem 1.3.0 |
User-Agent header sent with every request. |
default_media_type |
application/json |
Accept and Content-Type headers. |
per_page |
15 |
Default number of availability dates per request (capped at 15). |
middleware |
Retry + RaiseError + JSON + adapter | Full Faraday middleware stack. Override to customise completely. |
connection_options |
{} |
Options passed directly to Faraday.new. |
Environment variable overrides
All primary options can be set via environment variables before the gem is loaded:
| Variable | Option |
|---|---|
TOCDOC_API_ENDPOINT |
api_endpoint |
TOCDOC_USER_AGENT |
user_agent |
TOCDOC_MEDIA_TYPE |
default_media_type |
TOCDOC_PER_PAGE |
per_page |
TOCDOC_RETRY_MAX |
Maximum Faraday retry attempts (default 3) |
Endpoints
Availabilities
Retrieve open appointment slots for a given visit motive and agenda.
TocDoc::Availability.where(
visit_motive_ids: visit_motive_id, # Integer, String, or Array
agenda_ids: agenda_id, # Integer, String, or Array
start_date: Date.today, # Date or String (default: today)
limit: 5, # override per_page for this call
# any extra keyword args are forwarded verbatim as query params:
practice_ids: 377_272,
telehealth: false
)TocDoc.availabilities(...) is a module-level shortcut with the same signature.
Multiple IDs are accepted as arrays; the gem serialises them with the dash-separated format Doctolib expects:
TocDoc::Availability.where(
visit_motive_ids: [7_767_829, 7_767_830],
agenda_ids: [1_101_600, 1_101_601]
)
# → GET /availabilities.json?visit_motive_ids=7767829-7767830&agenda_ids=1101600-1101601&…Return value: a TocDoc::Availability::Collection (see Response objects).
Search
Query the Doctolib autocomplete endpoint to look up practitioners, organizations, and specialities.
result = TocDoc::Search.where(query: 'dentiste')
result.profiles # => [#<TocDoc::Profile::Practitioner ...>, ...]
result.specialities # => [#<TocDoc::Speciality ...>, ...]Pass type: to receive a filtered array directly:
# Only specialities
TocDoc::Search.where(query: 'cardio', type: 'speciality')
# => [#<TocDoc::Speciality name="Cardiologue">, ...]
# Only practitioners
TocDoc::Search.where(query: 'dupont', type: 'practitioner')
# => [#<TocDoc::Profile::Practitioner ...>, ...]Valid type: values: 'profile' (all profiles), 'practitioner', 'organization', 'speciality'.
TocDoc.search(...) is a module-level shortcut with the same signature.
Return value: a TocDoc::Search::Result when type: is omitted, or a filtered Array otherwise (see Response objects).
Response objects
All API responses are wrapped in lightweight Ruby objects that provide
dot-notation access and a #to_h round-trip helper.
TocDoc::Availability::Collection
Returned by TocDoc::Availability.where; also accessible via the TocDoc.availabilities module-level shortcut.
Implements Enumerable, yielding TocDoc::Availability instances that have at least one slot.
| Method | Type | Description |
|---|---|---|
#total |
Integer |
Total number of available slots across all dates. |
#next_slot |
String | nil |
ISO 8601 datetime of the nearest available slot. nil when none remain. |
#each |
— | Yields each TocDoc::Availability that has at least one slot (excludes empty-slot dates). |
#raw_availabilities |
Array<TocDoc::Availability> |
All date entries, including those with no slots. |
#to_h |
Hash |
Plain-hash representation (only dates with slots in the availabilities key). |
TocDoc::Availability
Represents a single availability date entry. Each element yielded by the collection.
| Method | Type | Description |
|---|---|---|
#date |
Date |
Parsed date object. |
#slots |
Array<DateTime> |
Parsed datetime objects for each bookable slot on that date. |
#to_h |
Hash |
Plain-hash representation. |
Example:
collection = TocDoc::Availability.where(visit_motive_ids: 123, agenda_ids: 456)
collection.total # => 5
collection.next_slot # => "2026-02-28T10:00:00.000+01:00"
collection.first.date # => #<Date: 2026-02-28>
collection.first.slots # => [#<DateTime: 2026-02-28T10:00:00+01:00>, ...]
collection.to_h
# => {
# "total" => 5,
# "next_slot" => "2026-02-28T10:00:00.000+01:00",
# "availabilities" => [{ "date" => "2026-02-28", "slots" => [...] }, ...]
# }TocDoc::Search::Result
Returned by TocDoc::Search.where when type: is omitted.
| Method | Type | Description |
|---|---|---|
#profiles |
Array<TocDoc::Profile::Practitioner, TocDoc::Profile::Organization> |
All profile results, typed via Profile.build. |
#specialities |
Array<TocDoc::Speciality> |
All speciality results. |
#filter_by_type(type) |
Array |
Narrows results to 'profile', 'practitioner', 'organization', or 'speciality'. |
TocDoc::Profile
Represents a search profile result (practitioner or organization). Use Profile.build(attrs) to obtain the correctly typed subclass instance.
| Method | Type | Description |
|---|---|---|
Profile.build(attrs) |
Profile::Practitioner | Profile::Organization |
Factory: returns Practitioner when owner_type is "Account", Organization otherwise. |
#practitioner? |
Boolean |
true when this is a Profile::Practitioner. |
#organization? |
Boolean |
true when this is a Profile::Organization. |
TocDoc::Profile::Practitioner and TocDoc::Profile::Organization are thin subclasses that inherit dot-notation attribute access from TocDoc::Resource.
TocDoc::Speciality
Represents a speciality returned by the autocomplete endpoint. Inherits dot-notation attribute access from TocDoc::Resource.
| Method | Type | Description |
|---|---|---|
#value |
Integer |
Numeric speciality identifier. |
#slug |
String |
URL-friendly identifier. |
#name |
String |
Human-readable speciality name. |
Example:
result = TocDoc::Search.where(query: 'dermato')
result.profiles.first.class # => TocDoc::Profile::Practitioner
result.profiles.first.practitioner? # => true
result.profiles.first.name # => "Dr. Jane Smith"
result.specialities.first.slug # => "dermatologue"
result.specialities.first.name # => "Dermatologue"Pagination
The Doctolib availability endpoint is window-based: each request returns up to
limit dates starting from start_date.
Automatic next-slot resolution
TocDoc::Availability.where automatically follows next_slot once: if the
first API response contains a next_slot key (indicating no available slots in
the requested window), a second request is issued transparently from that date
before the collection is returned.
Manual window advancement
To fetch additional date windows, call TocDoc::Availability.where again with a
later start_date:
first_page = TocDoc::Availability.where(
visit_motive_ids: 7_767_829,
agenda_ids: 1_101_600,
start_date: Date.today
)
if first_page.any?
next_start = first_page.raw_availabilities.last.date + 1
next_page = TocDoc::Availability.where(
visit_motive_ids: 7_767_829,
agenda_ids: 1_101_600,
start_date: next_start
)
endError handling
All errors raised by TocDoc inherit from TocDoc::Error < StandardError,
so you can rescue the whole hierarchy with a single clause:
begin
TocDoc::Availability.where(visit_motive_ids: 0, agenda_ids: 0)
rescue TocDoc::Error => e
puts "Doctolib error: #{e.message}"
endThe default middleware stack also includes Faraday::Response::RaiseError for
HTTP-level failures, and a Faraday::Retry::Middleware that automatically
retries (up to 3 times, with exponential back-off) on:
429 Too Many Requests500 Internal Server Error502 Bad Gateway503 Service Unavailable504 Gateway Timeout- network timeouts
Development
Clone the repository and install dependencies:
git clone https://github.com/01max/toc_doc.git
cd toc_doc
bin/setupRun the test suite:
bundle exec rake spec
# or
bundle exec rspecRun the linter:
bundle exec rubocopOpen an interactive console with the gem loaded:
bin/consoleInstall the gem locally:
bundle exec rake installAdding new endpoints
- Create
lib/toc_doc/models/<resource>.rbwith a model class inheriting fromTocDoc::Resource. Add a class-level.where(or equivalent) query method that callsTocDoc.client.get/.postto issue requests. - If the endpoint is paginated, create
lib/toc_doc/models/<resource>/collection.rbwith anEnumerablecollection class (seeTocDoc::Availability::Collectionfor the pattern). - Require the new files from
lib/toc_doc/models.rb. - Add specs under
spec/toc_doc/models/.
Generating documentation
The codebase uses YARD for API documentation. All public
methods are annotated with @param, @return, and @example tags.
Generate the HTML docs:
bundle exec yard docThe output is written to doc/. To browse it locally:
bundle exec yard server
# → http://localhost:8808To check documentation coverage without generating files:
bundle exec yard statsContributing
Bug reports and pull requests are welcome on GitHub at https://github.com/01max/toc_doc. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
Code of Conduct
Everyone interacting in the TocDoc project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
License
The gem is available as open source under the terms of the GNU General Public v3 License.