REST API v1

REST API Reference

Complete reference for the ParkOnce REST API. Authenticate, manage vehicles, look up parking locations, and start/stop sessions programmatically.

Base URL https://parkonce.ca

🔒 Authentication

Most endpoints require a Bearer token in the Authorization header. Obtain a token via POST /api/auth/login.

📦 Request Format

All request bodies must be JSON. Set Content-Type: application/json on every POST/PUT request.

✅ Response Format

All responses return JSON with a success boolean. Errors include a message string explaining what went wrong.

💰 Pricing

ParkOnce adds a 2.5% service fee on top of the location's hourly rate. Costs are returned in cents (amount_cents).

Endpoints marked Auth Required expect an HTTP header: Authorization: Bearer <token>. The token is returned by the login and register endpoints and does not expire automatically.
🔐

Authentication

6 endpoints
POST /api/auth/register Create a new account

Creates a new user account. Returns a JWT token and user object on success. Passwords must be at least 6 characters.

Request Body

FieldTypeRequiredDescription
emailstringrequiredUser email address
passwordstringrequiredMin 6 characters
namestringoptionalDisplay name
phonestringoptionalPhone number

Response

JSON
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "user": {
    "id": 42,
    "email": "user@example.com",
    "name": "Alex Kim",
    "phone": "604-555-0100"
  }
}
JSON
{
  "success": false,
  "message": "Account already exists. Try logging in."
}
POST /api/auth/login Sign in to an account

Authenticates a user and returns a JWT bearer token. Store this token and include it on all authenticated requests.

Request Body

FieldTypeRequired
emailstringrequired
passwordstringrequired

Response · 200

JSON
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "user": {
    "id": 42,
    "email": "user@example.com",
    "name": "Alex Kim",
    "phone": "604-555-0100"
  }
}
GET /api/auth/me Get current user profile 🔒 Auth

Returns the profile of the authenticated user. No request body needed.

Response · 200

JSON
{
  "success": true,
  "user": {
    "id": 42,
    "email": "user@example.com",
    "name": "Alex Kim",
    "phone": "604-555-0100"
  }
}
PUT /api/auth/profile Update name and phone 🔒 Auth

Request Body (partial update)

FieldType
namestring
phonestring

Response · 200

JSON
{
  "success": true,
  "user": { "id": 42, "name": "New Name", ... }
}
POST /api/auth/forgot-password Request a password reset email

Sends a password reset link to the provided email address. Always responds with success regardless of whether the account exists (security by design).

Request Body

FieldTypeRequired
emailstringrequired

Response · 200

JSON
{
  "success": true,
  "message": "If an account exists for that email, we've sent a reset link."
}
POST /api/auth/reset-password Set a new password using a reset token

Request Body

FieldTypeRequiredDescription
tokenstringrequiredToken from reset email link
passwordstringrequiredNew password (min 6 chars)

Response · 200

JSON
{
  "success": true,
  "message": "Password updated. You can now log in."
}
📍

Locations

4 endpoints
GET /api/locations/map All lots with coordinates and availability

Returns all active parking locations with lat/lng coordinates and real-time availability percentages. Used to populate the map view.

Response · 200

JSON
{
  "success": true,
  "locations": [
    {
      "id": 1,
      "location_code": "VAN-1042",
      "name": "Robson St Lot",
      "address": "800 Robson St, Vancouver",
      "rate_cents_per_hour": 300,
      "max_duration_minutes": 120,
      "lat": 49.2827,
      "lng": -123.1207,
      "availability_pct": 62,
      "provider_name": "PayByPhone",
      "provider_slug": "paybyphone"
    }
  ]
}
GET /api/locations/lookup?code=VAN-1042 Look up a location by code

Looks up a parking location by its location code (found on meters or parking signs).

Query Parameters

ParamTypeRequiredDescription
codestringrequiredLocation code from the meter/sign

Response · 200

JSON
{
  "success": true,
  "location": {
    "id": 1,
    "location_code": "VAN-1042",
    "name": "Robson St Lot",
    "address": "800 Robson St",
    "rate_cents_per_hour": 300,
    "max_duration_minutes": 120,
    "provider_name": "PayByPhone",
    "provider_slug": "paybyphone"
  }
}
GET /api/locations/:code/rate-options Available duration/price options

Returns available parking duration options and their costs for a given location. For PayByPhone and Passport locations, rates come directly from the provider's live API. For all other providers, durations are derived from the location's hourly rate.

Path Parameters

ParamTypeDescription
codestringLocation code

Response · 200

JSON
{
  "success": true,
  "rate_options": [
    {
      "duration_minutes": 30,
      "label": "30 min",
      "amount_cents": 154,
      "rate_option_id": "7001-30"
    }
  ],
  "location": { "name": "Robson St Lot", ... },
  "source": "live"
}
GET /api/locations/:code/restrictions Parking restrictions for a location

Returns time limits, no-return rules, and other parking restrictions for a location.

Response · 200

JSON
{
  "success": true,
  "restrictions": {
    "max_stay_minutes": 120,
    "no_return_minutes": null,
    "time_limit_enforced": true
  }
}
🅿️

Parking Sessions

5 endpoints
POST /api/parking/start Start a parking session 🔒 Auth

Starts a parking session at a given location. Charges the user's default payment method. ParkOnce routes the transaction to the appropriate provider (PayByPhone, Passport, INRIX, or stub). A 2.5% service fee is added to the base rate. The user must have a saved payment method before starting a session.

Request Body

FieldTypeRequiredDescription
location_codestringrequiredLocation code from the sign/meter
vehicle_idintegeroptional*ID of a saved vehicle
license_platestringoptional*One-time plate if no vehicle_id
duration_minutesintegerrequiredDesired parking duration
rate_option_idstringoptionalRate option ID from /rate-options

* Either vehicle_id or license_plate is required.

Response · 200

JSON
{
  "success": true,
  "session": {
    "id": 501,
    "status": "active",
    "start_time": "2026-04-21T14:00:00Z",
    "end_time": "2026-04-21T15:00:00Z",
    "duration_minutes": 60,
    "amount_cents": 308,
    "base_amount_cents": 300,
    "fee_cents": 8,
    "provider_session_id": "pbp-1713700800000-a1b2c3",
    "provider_mode": "sandbox"
  }
}
GET /api/parking/active Get the current active session 🔒 Auth

Returns the user's currently active parking session, if any. Returns null for session if no active session exists.

Response · 200

JSON
{
  "success": true,
  "session": {
    "id": 501,
    "status": "active",
    "end_time": "2026-04-21T15:00:00Z",
    "location_name": "Robson St Lot",
    "license_plate": "ABC1234"
  }
}
POST /api/parking/:id/extend Extend an active session 🔒 Auth

Request Body

FieldTypeRequiredDescription
duration_minutesintegerrequiredAdditional minutes to add

Response · 200

JSON
{
  "success": true,
  "session": {
    "id": 501,
    "end_time": "2026-04-21T16:00:00Z",
    "duration_minutes": 120,
    "amount_cents": 616
  }
}
POST /api/parking/:id/stop Stop an active session early 🔒 Auth

Stops an active parking session before its scheduled end time. No request body required.

Response · 200

JSON
{
  "success": true,
  "session": {
    "id": 501,
    "status": "stopped",
    "stopped_at": "2026-04-21T14:42:00Z"
  }
}
GET /api/parking/history Past parking sessions 🔒 Auth

Returns a paginated list of the user's completed and stopped parking sessions, newest first.

Response · 200

JSON
{
  "success": true,
  "sessions": [
    {
      "id": 498,
      "status": "completed",
      "start_time": "2026-04-20T10:00:00Z",
      "end_time": "2026-04-20T11:00:00Z",
      "duration_minutes": 60,
      "amount_cents": 308,
      "location_name": "Robson St Lot",
      "license_plate": "ABC1234"
    }
  ]
}
🚗

Vehicles

4 endpoints
GET /api/vehicles List saved vehicles 🔒 Auth

Response · 200

JSON
{
  "success": true,
  "vehicles": [
    {
      "id": 7,
      "license_plate": "ABC1234",
      "province": "BC",
      "make": "Toyota",
      "model": "Corolla",
      "color": "Silver",
      "nickname": "Daily Driver",
      "is_default": true
    }
  ]
}
POST /api/vehicles Add a vehicle (max 5) 🔒 Auth

Request Body

FieldTypeRequired
license_platestringrequired
provincestringoptional
makestringoptional
modelstringoptional
colorstringoptional
nicknamestringoptional
is_defaultbooleanoptional

Response · 200

JSON
{
  "success": true,
  "vehicle": { "id": 8, "license_plate": "XYZ9999", ... }
}
PUT /api/vehicles/:id/default Set a vehicle as default 🔒 Auth

Sets the specified vehicle as the user's default. Unsets all other vehicles. No request body needed.

Response · 200

JSON
{
  "success": true,
  "vehicle": { "id": 7, "is_default": true, ... }
}
DELETE /api/vehicles/:id Remove a vehicle 🔒 Auth

Deletes a saved vehicle. Will fail if the vehicle has an active parking session.

Response · 200

JSON
{ "success": true }
💳

Payment Methods

3 endpoints
GET /api/payment-methods List saved payment methods 🔒 Auth

Response · 200

JSON
{
  "success": true,
  "payment_methods": [
    {
      "id": 3,
      "card_last4": "4242",
      "card_brand": "visa",
      "is_default": true
    }
  ]
}
POST /api/payment-methods Add a payment method 🔒 Auth

Request Body

FieldTypeRequiredDescription
card_last4stringrequiredLast 4 digits of card
card_brandstringoptionale.g. "visa", "mastercard"
is_defaultbooleanoptionalSet as default

Response · 200

JSON
{
  "success": true,
  "payment_method": { "id": 4, "card_last4": "1234", ... }
}
DELETE /api/payment-methods/:id Remove a payment method 🔒 Auth

Response · 200

JSON
{ "success": true }
🔌

Providers

2 endpoints
GET /api/providers List all active parking providers

Response · 200

JSON
{
  "success": true,
  "providers": [
    {
      "id": 1,
      "name": "PayByPhone",
      "slug": "paybyphone",
      "logo_emoji": "📱",
      "is_active": true
    }
  ]
}
GET /api/providers/status Integration status for all providers

Returns the live connection status for each provider integration (PayByPhone, Passport, INRIX). Useful for diagnosing provider availability.

Response · 200

JSON
{
  "success": true,
  "providers": {
    "paybyphone": { "status": "live", "mode": "sandbox" },
    "passport": { "status": "live", "mode": "sandbox" },
    "inrix": { "status": "live", "mode": "sandbox" }
  }
}
© 2026 ParkOnce Home Open App Partners