Authentication and API keys
Every request is authenticated with a bearer token in the Authorization header:
GET /v1/me HTTP/1.1
Host: api.vehiso.com
Authorization: Bearer vh_live_sk_...
There are no sessions, cookies or CSRF tokens. The key identifies the dealer, so every dealer uses the same host, api.vehiso.com.
Key types
| Prefix | Type | Use it for | Data it reaches |
|---|---|---|---|
vh_live_sk_ | Secret | Servers, scripts, ETL jobs | The dealer's own data |
vh_live_pk_ | Publishable | Browser code on a bespoke website | The dealer's public data |
vh_test_sk_ | Secret (test) | Building and testing | The shared sandbox dealer |
vh_test_pk_ | Publishable (test) | Building and testing | The shared sandbox dealer |
vh_oat_ | OAuth access token | Partner apps | The dealer that authorised the app |
A key has the form vh_<environment>_<type>_<lookup>_<secret>. The fixed vh_ prefix lets secret scanners such as GitHub secret scanning recognise a leaked key.
A key is shown once, when it is created or rolled. Vehiso stores only a SHA-256 hash of its secret part, so a lost key cannot be recovered, only replaced.
Secret keys
Secret keys can be given any scope the key owner is allowed. Treat them like a password: keep them in a secrets manager or environment variable on a server.
A secret key must never be used from a browser. If a request made with a secret key carries an Origin header, the API refuses it with 403 secret_key_in_browser. A key that has been in browser code has been exposed to everyone who opened developer tools, so roll it and use a publishable key instead.
Secret keys need a paid Vehiso plan. On the Free plan, requests with a secret key are refused with 403 plan_not_entitled.
Publishable keys
Publishable keys are made to be embedded in a website's front-end code. They are fixed to a public subset of the API and cannot be given more:
vehicles:read, limited to public fields and stock that is visible on the websitebranches:readPOST /v1/public/enquiriesPOST /v1/public/valuation-requests
A publishable key must have an origin allowlist of at least one https://host[:port] entry. A browser request from an origin that is not on the list is refused with 403 origin_not_allowed. Publishable keys work on every plan, including Free.
See Building a website with a publishable key.
Test keys and the sandbox
Test keys (vh_test_sk_..., vh_test_pk_...) resolve to a shared sandbox dealer rather than your own dealership. Use them while you build: you can create vehicles, enquiries and customers freely without affecting real stock or real customers.
- The sandbox is shared by every developer, so do not put real personal data in it and do not rely on its contents staying as you left them.
- A test key acts as the sandbox's own user, not as one of your staff.
- A secret test key follows your own dealership's plan: it needs a paid plan just as a live secret key does.
- If the sandbox is unavailable, test keys are refused with
401 sandbox_unavailable.
Switching to production is a matter of swapping the test key for a live one. Nothing else about the request changes.
Who a key acts as
A key belongs to the dealership, but it acts as a person: its owner, by default the staff member who created it. Every request runs as that user, so branch restrictions, activity logs and business rules behave exactly as they would for that person in the DMS.
A key's effective permissions are its scopes and its owner's current permissions in the DMS:
- If the owner loses the DMS permission a scope needs, that scope stops working and the API answers
403 scope_not_permitted_for_owner. The rest of the key keeps working. - If the owner is deleted or banned, the whole key stops working with
401 key_owner_inactive.
The DMS shows these problems next to the key, and an administrator can move the key to another owner without changing the key itself. The permission each scope needs is listed on the Scopes page.
Optional restrictions
Each key can be narrowed further in the DMS:
| Restriction | Applies to | Effect |
|---|---|---|
| Branches | All keys | The key sees and writes only data for the chosen branches. Empty means all branches. |
| IP allowlist | Secret keys | Requests from any other IP address are refused with 403 ip_not_allowed. |
| Origin allowlist | Publishable keys (required) | Browser requests from any other origin are refused with 403 origin_not_allowed. |
| Expiry | All keys | After the expiry date the key is refused with 401 api_key_expired. |
An IP allowlist is a good idea for any secret key used from servers with fixed addresses.
Rolling and revoking keys
In Administration > Developers:
- Roll replaces a key's secret. The old key is revoked immediately and a copy with a new secret is shown once. Update wherever the key is stored straight away; requests with the old key are refused with
401 api_key_revoked. - Revoke disables a key permanently.
Roll a key whenever it may have been exposed: committed to a repository, pasted into a ticket, used in a browser, or held by someone who has left.
Authentication errors
| Status | code | Meaning |
|---|---|---|
| 401 | missing_api_key | No Authorization: Bearer header. |
| 401 | invalid_api_key | The key is malformed or unknown. |
| 401 | api_key_revoked | The key was revoked or rolled, or the OAuth grant was revoked. |
| 401 | api_key_expired | The key is past its expiry date. |
| 401 | key_owner_inactive | The key's owner was deleted or banned. |
| 403 | insufficient_scope | The key does not have the scope this endpoint needs. |
| 403 | scope_not_permitted_for_owner | The key has the scope, but its owner no longer has the permission behind it. |
| 403 | plan_not_entitled | Secret keys, webhooks and OAuth need a paid plan. |
| 403 | ip_not_allowed | The request came from an IP address outside the key's allowlist. |
| 403 | origin_not_allowed | The browser origin is not on the publishable key's allowlist. |
| 403 | secret_key_in_browser | A secret key was used from a browser. Roll it. |
The response body format is described in Requests and responses.