V2 · Account

Auth

Obtain an access token and learn how V2 normalises request bodies.

The V2 auth endpoints are how partner integrations exchange an email + password for a Sanctum personal-access token. They are the only V2 routes that do not require an Authorization header.

Log in

POST /api/v2/login

Validates the credentials against Laravel's auth guard. On success it issues a Sanctum personal-access token via auth()->user()->createToken('authToken')->plainTextToken and returns the user record alongside the plaintext token.

Body parameters

Validated inline in App\Http\Controllers\Api\V2\AuthController::login() (not via a Form Request):

Field Type Rules Description
email string required, email The user's email address.
password string required The user's password.

Responses

  • 200{ "user": { ... }, "access_token": "..." }. Send Authorization: Bearer {access_token} on subsequent V2 requests.
  • 401 Unauthorized{ "message": "Invalid Credentials" } when auth()->attempt() fails.
  • 422 Unprocessable Entity — standard Laravel validation error envelope when email or password is missing or malformed.

Register

POST /api/v2/register

RegisterRequest uses the App\Http\Requests\Api\V2\Concerns\DeniesAccess trait, so the controller is never reached: the request authorisation always fails and the endpoint returns 403 Forbidden with body { "message": "Forbidden" }. User registration must be performed inside FinDesk's web app.

V2 input normalisation (NormalizeApiInput)

Every authenticated V2 request (everything except POST /api/v2/login and POST /api/v2/register) passes through App\Http\Middleware\NormalizeApiInput after auth:sanctum and the api_version:v2 middleware.

The middleware recursively walks the request body ($request->all()) and converts string keys to snake_case via Str::snake(). URL parameters and headers are left untouched.

In practice this means:

  • FirstName, firstName, and first_name all reach the validator as first_name.
  • Nested objects are normalised at every depth — a Contact.PhoneNumbers[0].PhoneType key in the body becomes contact.phone_numbers.0.phone_type.
  • Order matters when casings collide. If both personName and person_name appear in the same payload, whichever comes later in the JSON wins, because both normalise to person_name. Servers generally append the snake-case variant last, so the snake form typically wins in practice.

This middleware exists so partner integrations that emit camelCase or PascalCase JSON can talk to V2 without rewriting their serialisers. The V1 routes do not include it — see Versioning for the full list of V1 → V2 changes.

V2 differences — V2 wraps every authenticated request with NormalizeApiInput; V1 does not. The login endpoint itself is unauthenticated, so it bypasses the middleware and consumes email / password as-is. The register endpoint is wired but disabled by DeniesAccess.