Getting started
You will need three things from your DriveCars contact before any of this
works: a client id, a client secret, and the list of products your
account is enabled for. Ask for all three at once — the third one is the part
people forget, and it is the difference between a working integration and a
403 you cannot fix from your side.
The sandbox is a different world from production
Every example on this site uses https://{your-base-url} as the API base URL.
You receive two at onboarding: a sandbox one, which everything here describes,
and a production one at go-live.
Sandbox credentials are not production credentials, and a sandbox token presented to production is simply an invalid token. You get a separate client id and secret for production at go-live, along with the production base URL — which is the one config value you change. Nothing else about your integration moves.
Step 1 — get a token
Every /v1 call carries an access token. You mint one by presenting your
client credentials to POST /oauth/token. This is
RFC 6749 §4.4
client credentials — there is no end user in the flow, no redirect, no consent
screen. Your server is proving it is itself.
curl -sS -X POST https://{your-base-url}/oauth/token \
-u "$DRIVECARS_CLIENT_ID:$DRIVECARS_CLIENT_SECRET" \
-d grant_type=client_credentials{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "rentals:quote rentals:book bookings:read"
}That is the whole handshake. Note the token endpoint lives at the API root,
not under /v1 — /oauth/token, not /v1/oauth/token.
-u sends your credentials as HTTP Basic, which is what the RFC prefers. If
Basic is awkward in your stack, client_id and client_secret in the form body
work identically:
curl -sS -X POST https://{your-base-url}/oauth/token \
-d grant_type=client_credentials \
-d client_id="$DRIVECARS_CLIENT_ID" \
-d client_secret="$DRIVECARS_CLIENT_SECRET"A token lasts one hour (expires_in: 3600). Cache it for slightly less than
that and mint a new one when it lapses — do not mint one per request, and do
not try to refresh it. There is no refresh token in this grant; you just ask
for another.
Authentication covers scopes, what each failure means, and why a bad credential never tells you which half was wrong.
Step 2 — prove the token works
curl -sS https://{your-base-url}/v1/me \
-H "Authorization: Bearer $TOKEN"{
"id": 12,
"slug": "acme-travel",
"name": "Acme Travel",
"status": "active",
"mor_mode": "partner_invoiced",
"scopes": ["rentals:quote", "rentals:book", "bookings:read"]
}GET /v1/me needs no scope of its own — any valid API token can read its own
identity, the same way an OAuth userinfo endpoint can. Which makes it the right
first call: if it answers, your credentials, your token and your Authorization
header are all correct, and anything that fails next is about the request, not
about you.
scopes here is what this token holds, not everything your account could
ever be granted. If a scope you expected is missing, see
Authentication.
Step 3 — know the shape of the flow
A rental is a reserve-then-book flow:
coverage → availability → reserve → book → view → cancelThe reserve step creates a draft booking. It is a real step in the product — the customer’s details and the price are fixed there — but it holds no inventory and expires after about 30 minutes. Car rentals walks the whole thing; the next step is the condensed version.
Step 4 — the whole rental path, end to end
Coverage first, so your country picker only offers markets that exist:
curl -sS "https://{your-base-url}/v1/coverage" \
-H "Authorization: Bearer $TOKEN"Then availability. Both dates are required and date_to must be strictly after
date_from:
curl -sS -G "https://{your-base-url}/v1/rentals/availability" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "location=casablanca" \
--data-urlencode "date_from=2026-11-02" \
--data-urlencode "date_to=2026-11-06"Every vehicle comes back with a real dated total for that stay — not a
per-day figure you multiply — and a slug you pass straight back as
car_slug. Check use_polling on the response before you do anything else;
some searches finish in a follow-up call. Car rentals
unpacks both.
Reserve, which creates a draft and holds nothing:
curl -sS -X POST "https://{your-base-url}/v1/rentals/reservations" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"car_slug": "dacia-logan-casablanca",
"date_from": "2026-11-02",
"date_to": "2026-11-06",
"customer": {
"first_name": "Amina",
"last_name": "Berrada",
"email": "amina@example.com"
}
}'Then book it, once your own payment has actually gone through:
curl -sS -X POST \
"https://{your-base-url}/v1/rentals/reservations/DC-8F2K91/book" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "payment_reference": "pi_3QhK2xLm00001", "expected_total": 1840 }'payment_reference is your own PSP’s reference for the charge. You are the
merchant of record on this leg — DriveCars never touched that money, and this
string is the only handle either of us has for reconciling it later.
The four things that cost partners money
Each of these has been learned expensively at least once. They are stated plainly here and again in the guide where they bite.
Idempotency-Key is required on the calls that create things. Not
optional, not best-effort — omit it on any of the four creating POSTs and you
get 400 idempotency_key_required before anything happens. See
Idempotency.
A rental reservation does not hold a car. It is a draft booking with a
~30-minute expiry. Another customer’s search still sees that car and another
customer’s booking can still take it. Never render “we’re holding this for
you” off the back of a 201 from reserve.
Ownership answers 404, never 403. Ask for a reference that belongs to
another partner and the API says it does not exist. A 403 would confirm the
reference is real, which turns every read endpoint into an oracle for
enumerating bookings by walking references and reading status codes. So the
cost of that design lands on you here: a 404 on a reference you are sure you
created means you are reading it with the wrong credential, and no error
message will ever tell you that.
403 product_not_enabled is commercial, not a bug. It means your account
was never enabled for that product. Your code is fine, the URL is fine, and
retrying will never help. Contact your account manager. It is deliberately
distinct from 403 insufficient_scope, which you can fix yourself by minting
a credential with the missing scope.
Where to go next
- Authentication — scopes, token lifetime, every auth failure.
- Car rentals — the reserve-then-book flow in full.
- Errors — one envelope, and what each
codemeans. - API Reference — every endpoint and field, with a request builder.