Getting started

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 → cancel

The 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 code means.
  • API Reference — every endpoint and field, with a request builder.