Skip to content

vehicledb ​

← All modules

39 endpoints.

POST /api/v1/vdb/auth/request-otp ​

Request Otp

Send a sign-in OTP to an existing account's mobile number.

Request body

json
{
  "properties": {
    "mobile_number": {
      "type": "string",
      "maxLength": 32,
      "minLength": 6,
      "title": "Mobile Number"
    }
  },
  "type": "object",
  "required": [
    "mobile_number"
  ],
  "title": "MobileIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vdb/auth/signup ​

Signup

Stage a new account and send its verification OTP.

Nothing is billable until the OTP is verified, but unlike the bench (which held the whole signup in a Redis blob keyed by tmp_id) the app-user row is created here, unverified. A half-finished signup is then visible and recoverable instead of evaporating after 5 minutes.

Request body

json
{
  "properties": {
    "mobile_number": {
      "type": "string",
      "maxLength": 32,
      "minLength": 6,
      "title": "Mobile Number"
    },
    "name": {
      "type": "string",
      "maxLength": 255,
      "minLength": 1,
      "title": "Name"
    },
    "email": {
      "type": "string",
      "format": "email",
      "title": "Email"
    },
    "company": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 255
        },
        {
          "type": "null"
        }
      ],
      "title": "Company"
    },
    "reseller_code": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 64
        },
        {
          "type": "null"
        }
      ],
      "title": "Reseller Code"
    }
  },
  "type": "object",
  "required": [
    "mobile_number",
    "name",
    "email"
  ],
  "title": "SignupIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vdb/auth/verify-otp ​

Verify Otp

Verify an OTP and return the session token plus the account profile.

On a first verification for a signed-up user this also creates the books customer and grants the promo credit — deferred to here so an abandoned signup never leaves an empty customer behind in the books.

Request body

json
{
  "properties": {
    "mobile_number": {
      "type": "string",
      "maxLength": 32,
      "minLength": 6,
      "title": "Mobile Number"
    },
    "code": {
      "type": "string",
      "maxLength": 10,
      "minLength": 4,
      "title": "Code"
    }
  },
  "type": "object",
  "required": [
    "mobile_number",
    "code"
  ],
  "title": "VerifyOtpIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vdb/auth/sign-out-everywhere ​

Sign Out Everywhere

Invalidate every outstanding token for this account.

Parameters

NameInRequiredTypeDescription
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/account ​

Get Account

Parameters

NameInRequiredTypeDescription
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/account/statement ​

Get Account Statement

Paged credit-wallet statement.

Parameters

NameInRequiredTypeDescription
from_datequeryno
to_datequeryno
pagequerynointeger
limitquerynointeger
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vdb/credits/spend ​

Spend Credits

Debit the wallet for a lookup the client performed outside the proxy.

Nothing calls this any more. Its reason for existing was the Lightstone flow, which was deleted 2026-08-13, and the app stopped calling it the same day — having it debit alongside /vdb/valuation's own charge is what made one check cost two credits. /vdb/valuation charges as part of the call, which is the safer shape: the charge and the thing charged for are one transaction, where this endpoint can only trust the client's word that a lookup happened.

Left in place solely because builds 4.2.0(3) and (4) are on testers' handsets and do call it; removing it would 404 their check rather than just overcharge it. Delete once nothing in the field is older than 4.2.0(5).

Parameters

NameInRequiredTypeDescription
authorizationheaderno

Request body

json
{
  "properties": {
    "qty": {
      "type": "number",
      "maximum": 1000,
      "exclusiveMinimum": 0,
      "title": "Qty"
    },
    "report_type": {
      "type": "string",
      "maxLength": 64,
      "title": "Report Type",
      "default": "vin"
    },
    "reference": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 255
        },
        {
          "type": "null"
        }
      ],
      "title": "Reference"
    }
  },
  "type": "object",
  "required": [
    "qty"
  ],
  "title": "SpendIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/vehicles ​

List Vehicles

Paginated vehicle reference list. Public — browsing costs no credits.

Parameters

NameInRequiredTypeDescription
searchqueryno
makequeryno
modelqueryno
sub_typequeryno
yearqueryno
pagequerynointeger
limitquerynointeger

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/vehicles/dropdowns ​

Vehicle Dropdowns

Distinct filter values for the cascading make/model/variant/year pickers.

Parameters

NameInRequiredTypeDescription
searchqueryno
makequeryno
modelqueryno
sub_typequeryno
yearqueryno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vdb/contact ​

Contact Form

Record the in-app "Contact Us" message, then email it to the helpdesk.

Recorded FIRST, deliberately. This used to be a send and nothing else, so a message lived exactly as long as one SMTP call: a failure returned 502 and the customer's words were gone, with nothing to retry from and no way to count what had been lost.

So a failed send is no longer a failed submission. The row is saved either way and the caller is told the message is in — asking someone to retype it because our mailbox is down would only produce duplicates of a row we already hold. delivered=False rows are what to look at when someone asks whether anything is arriving; nothing sweeps them automatically yet.

Request body

json
{
  "properties": {
    "full_name": {
      "type": "string",
      "maxLength": 255,
      "minLength": 1,
      "title": "Full Name"
    },
    "phone_number": {
      "type": "string",
      "maxLength": 32,
      "minLength": 3,
      "title": "Phone Number"
    },
    "email": {
      "type": "string",
      "format": "email",
      "title": "Email"
    },
    "message": {
      "type": "string",
      "maxLength": 5000,
      "minLength": 1,
      "title": "Message"
    }
  },
  "type": "object",
  "required": [
    "full_name",
    "phone_number",
    "email",
    "message"
  ],
  "title": "ContactIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/valuation/makes ​

Valuation Makes

Parameters

NameInRequiredTypeDescription
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/valuation/guides ​

Valuation Guides

Parameters

NameInRequiredTypeDescription
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/valuation/models ​

Valuation Models

Parameters

NameInRequiredTypeDescription
mmMakequeryyesstring
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/valuation/years ​

Valuation Years

Parameters

NameInRequiredTypeDescription
mmCodequeryyesstring
nGuidequeryyesstring
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/valuation/extras ​

Valuation Extras

The factory-fitted extras offered on one vehicle-year. Free, like the rest of the reference cascade — only /valuation/values costs a credit.

Parameters

NameInRequiredTypeDescription
mmCodequeryyesstring
mmYearqueryyesstring
guidequeryyesstring
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/valuation/mileage ​

Valuation Mileage

The kilometres each mileage band stands for on a car of this year.

Parameters

NameInRequiredTypeDescription
mmYearqueryyesstring
guidequeryyesstring
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/valuation/values ​

Valuation Values

Run a valuation. Costs one credit, including when the answer is "no value for that vehicle" — see the service's module docstring.

Parameters

NameInRequiredTypeDescription
vinqueryno
regqueryno
mmCodequeryno
mmYearqueryno
guidequeryno
conditionqueryno
mileagequeryno
optionsqueryno
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/vehicle/accident-report ​

Accident Report

Previous claims registered against a VIN. Costs R100.

GET rather than POST although upstream is a POST: from this side it is a read — it creates nothing the caller can refer to afterwards, and the app's other paid search (/vdb/valuation/values) is a GET too. The charge is a side effect of the read in both cases, and making one of them a POST purely because the vendor's own wire protocol is a POST would leak their transport into our API.

Parameters

NameInRequiredTypeDescription
vinqueryyesstring
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vdb/payments/paystack/checkout ​

Paystack Checkout

Open a Paystack checkout for a wallet top-up.

The app opens the returned url. It holds no keys, and it does not say whose wallet to credit — the customer comes from the caller's own session, so a tampered request can only ever top up the account that made it.

Parameters

NameInRequiredTypeDescription
authorizationheaderno

Request body

json
{
  "properties": {
    "amount": {
      "type": "number",
      "maximum": 1000000,
      "exclusiveMinimum": 0,
      "title": "Amount"
    },
    "credits": {
      "anyOf": [
        {
          "type": "integer"
        },
        {
          "type": "null"
        }
      ],
      "title": "Credits",
      "deprecated": true
    }
  },
  "type": "object",
  "required": [
    "amount"
  ],
  "title": "PaystackCheckoutIn",
  "description": "A wallet top-up.\n\n``amount`` is rands, and is the whole request: since 2026-08-20 a top-up\ncredits the wallet with exactly what was paid (\"R1 paid = R1 in wallet\"),\nso there is no second quantity to disagree with it.\n\n``credits`` is accepted and ignored, for handsets running a build from\nbefore the conversion — they send both, and rejecting the field outright\nwould 422 every top-up from an install that has not updated."
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vdb/payments/paystack/verify ​

Paystack Verify

Confirm a payment and apply the top-up. Safe to call repeatedly.

This is the PRIMARY settlement path, not a fallback: the app calls it when it comes back to the foreground after checkout. Webhooks have a history of not reaching this estate, and Paystack — unlike PayFast — lets us ask.

Parameters

NameInRequiredTypeDescription
referencequeryyesstring
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/payments/paystack/return ​

Paystack Return

Where Paystack sends the payer's browser afterwards.

Settles here rather than trusting the app to call verify, so closing the browser at the wrong moment doesn't strand a paid-for top-up. Unauthenticated by necessity — it is a browser redirect carrying no session — which is why it credits strictly by the reference we issued and never by anything in the query string beyond it.

Parameters

NameInRequiredTypeDescription
referencequeryno
trxrefqueryno

200 — Successful Response

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vdb/payments/payfast/notify ​

Payfast Notify

PayFast ITN — disabled. Records, never credits. Always 200.

200 — Successful Response

json
{}

POST /api/v1/vdb/payments/apple/notify ​

Apple Notify

Apple IAP credit purchase. Always 200.

Accepts either the caller's own vdb_app bearer token (what the app sends — it is signed in when it buys) or the legacy shared key header. The token is strictly better: it is per-user and revocable, it tells us who to credit without trusting a field in the body, and it means the app no longer ships a shared secret in its binary. The key path stays only for handsets still running the pre-migration build.

Parameters

NameInRequiredTypeDescription
keyheaderno
authorizationheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vdb/credits/pricing ​

Credits Pricing

What each report costs. Public — it is a price list, and the buy page needs it before anyone has signed in.

The route keeps its /credits/ path although nothing is called a credit any more: it is compiled into every handset already in the field, and renaming it would 404 the price list for those installs rather than merely showing them a stale one.

tiers is still sent, and is deliberately empty. A build from before the rand conversion reads that key and, finding nothing usable, keeps its own compiled-in fallback bundle prices — which is wrong but harmless (its Buy button opens the website, which quotes the real price). Sending the two service prices under tiers instead would be actively worse: that shape is {credits, amount, per_credit}, so an old build would render "30 credits — R30".

200 — Successful Response

json
{}

GET /api/v1/vdb/buy-credits ​

Buy Credits Page

Permanent redirect to the website's credit bundles.

KEPT as a redirect rather than deleted outright, deliberately. The URL is compiled into every VehicleDB build already on a handset (pricing_screen.dart's buyCreditUrl) and has been printed in the app for customers to read off a screen — deleting the route would 404 the only top-up path those installs have, for as long as people run them. A redirect costs nothing and cannot strand anybody.

308 rather than 301/302: the redirect target is a different origin and the old page was reached by GET only, so the method-preserving code is the honest one and browsers cache it the same way.

200 — Successful Response

json
{}

GET /api/v1/vehicledb/wallets ​

Wallets

The credit wallets this login can search against, with live balances.

The search page reads this first: it is what puts the balance in the header, what decides whether an account picker is needed at all (one wallet — the normal case, and the only one the app has — needs none), and what lets the page say "you have no credits" up front instead of after a customer has filled in a VIN.

scoped: false is the superadmin all-organizations view: a real answer ("you are not looking at a customer"), not an empty list of wallets.

200 — Successful Response

json
{}

GET /api/v1/vehicledb/valuation/makes ​

Valuation Makes

200 — Successful Response

json
{}

GET /api/v1/vehicledb/valuation/guides ​

Valuation Guides

200 — Successful Response

json
{}

GET /api/v1/vehicledb/valuation/models ​

Valuation Models

Parameters

NameInRequiredTypeDescription
mmMakequeryyesstring

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vehicledb/valuation/years ​

Valuation Years

Parameters

NameInRequiredTypeDescription
mmCodequeryyesstring
nGuidequeryyesstring

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vehicledb/valuation/extras ​

Valuation Extras

Parameters

NameInRequiredTypeDescription
mmCodequeryyesstring
mmYearqueryyesstring
guidequeryyesstring

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vehicledb/valuation/mileage ​

Valuation Mileage

Parameters

NameInRequiredTypeDescription
mmYearqueryyesstring
guidequeryyesstring

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vehicledb/valuation/values ​

Valuation Values

Run a valuation from the portal. Costs one credit, on the same terms as the app — including the "no book value" case, which is charged because TransUnion bills us for the search either way. See app.apps.vehicledb.services.valuation.

Parameters

NameInRequiredTypeDescription
vinqueryno
regqueryno
mmCodequeryno
mmYearqueryno
guidequeryno
conditionqueryno
mileagequeryno
optionsqueryno
customer_idqueryno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vehicledb/vehicle/accident-report ​

Accident Report

Run an Accident Report from the portal — previous claims against a VIN.

Added 2026-09-10 (owner: "add accident reports"). Until now this search existed on the handset alone: app.apps.vehicledb.services.accident was written as a service precisely so a second front door could be added without the two drifting on price, refund rules or logging, and this is that door. It is four lines for the same reason the valuation one is — everything that decides what a customer is charged lives in the service, and both doors reach it.

Costs R100 and is charged the same way as the app: debited before the upstream call, refunded only when no search happened (transport failure, non-200, or the empty 200 this vendor answers with when it swallows an error). A clean history is NOT refunded — "no claims on record" is the answer the customer bought, and Imagin8 bills us for it either way.

GET, matching the app's own /vdb/vehicle/accident-report: from this side it is a read whose charge is a side effect, exactly like /valuation/values above.

Parameters

NameInRequiredTypeDescription
vinqueryyesstring
customer_idqueryno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vehicledb/pricing ​

Pricing

What each paid lookup costs, in rands.

Served rather than hardcoded in the SPA because the two prices have moved once already (the wallet counted "credits" until the 2026-09-08 conversion and the search page still said "1 credit" for a R30 lookup for two days afterwards), and because ACCIDENT_CHARGE has a second copy in the website catalogue that already has to be kept in step by hand. One more hand-copied price in a frontend build is the one that stays wrong longest — a deploy is not needed to notice this one is stale, only a refresh.

200 — Successful Response

json
{}

GET /api/v1/vehicledb/vehicles ​

List Vehicles

Paginated vehicle reference list. Browsing costs no credits.

Default page size is 100, not the app's 500: a handset pulls a big page once and filters it locally offline, while this backs a table that pages on demand over a connection nobody is paying by the megabyte.

Parameters

NameInRequiredTypeDescription
searchqueryno
makequeryno
modelqueryno
sub_typequeryno
yearqueryno
pagequerynointeger
limitquerynointeger

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/vehicledb/vehicles/dropdowns ​

Vehicle Dropdowns

Distinct filter values for the cascading make/model/variant/year pickers.

Parameters

NameInRequiredTypeDescription
searchqueryno
makequeryno
modelqueryno
sub_typequeryno
yearqueryno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vehicledb/auth/request-otp ​

Portal Request Otp

Send a sign-in OTP, exactly as the app's own /vdb/auth/request-otp does.

Same service, same rate limit, same adopt-a-books-customer path — so a number that works on a handset works here, and one that doesn't gets the same answer rather than a second, subtly different one.

Request body

json
{
  "properties": {
    "mobile_number": {
      "type": "string",
      "maxLength": 32,
      "minLength": 6,
      "title": "Mobile Number"
    }
  },
  "type": "object",
  "required": [
    "mobile_number"
  ],
  "title": "MobileIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/vehicledb/auth/verify-otp ​

Portal Verify Otp

Verify the OTP and return a PORTAL session.

This is the one place the portal's sign-in differs from the app's: the app mints a vdb_app JWT for its own API, while the portal needs the ordinary User-backed session that the SPA, the grids and customer_scope all authenticate with. Everything up to that point — the OTP, the account lookup, the books-customer link, the promo credit — is the shared service.

Request body

json
{
  "properties": {
    "mobile_number": {
      "type": "string",
      "maxLength": 32,
      "minLength": 6,
      "title": "Mobile Number"
    },
    "code": {
      "type": "string",
      "maxLength": 10,
      "minLength": 4,
      "title": "Code"
    }
  },
  "type": "object",
  "required": [
    "mobile_number",
    "code"
  ],
  "title": "VerifyOtpIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

Lubb One Documentation