{"openapi":"3.1.0","info":{"title":"JEK Platform API","version":"1.0.0","description":"REST API for the JEK platform. Field names are snake_case, timestamps are ISO 8601 UTC, money is expressed in integer minor units with an ISO 4217 currency, and phone numbers are E.164. Errors follow RFC 9457 (application/problem+json).","contact":{"name":"JEK Developer Support","url":"https://developer.jek.app"}},"servers":[{"url":"https://api.jek.app","description":"Production"},{"url":"https://api-staging.jek.app","description":"Staging"}],"security":[{"userBearer":[]}],"components":{"securitySchemes":{"userBearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"End-user session token."},"driverBearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Driver / device session token (Driver Pro, M1OS)."},"merchantBearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"adminBearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"wonderHmac":{"type":"apiKey","in":"header","name":"Authorization","description":"Legacy compatibility scheme: `Wonder-HMAC-SHA256 Credential=<appid>/<yyyyMMdd>/<service>, SignedHeaders=..., Signature=<hex>` plus `X-JEK-Timestamp` and `X-JEK-Device-Sn`."}},"schemas":{"SuccessEnvelope":{"type":"object","required":["data"],"properties":{"data":{"description":"Resource or list of resources."},"meta":{"$ref":"#/components/schemas/PageMeta"}}},"PageMeta":{"type":"object","properties":{"next_cursor":{"type":["string","null"],"description":"Cursor for the next page."},"page_size":{"type":"integer"},"total_count":{"type":"integer"}}},"Problem":{"type":"object","required":["type","title","status","code"],"properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"code":{"type":"string"},"errors":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"code":{"type":"string"},"message":{"type":"string"}}}}}}},"responses":{"Problem":{"description":"RFC 9457 problem details","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"paths":{"/api/v1/auth/signin/check":{"post":{"operationId":"postApiV1AuthSigninCheck","summary":"Check whether a phone has a Supabase account","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"International phone, e.g. +852 91234567"}},"required":["phone"]}}}}}},"/api/v1/auth/register":{"post":{"operationId":"postApiV1AuthRegister","summary":"Send Supabase signup OTP","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"International phone, e.g. +852 91234567"},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."}},"required":["phone"]}}}}}},"/api/v1/auth/login":{"post":{"operationId":"postApiV1AuthLogin","summary":"Sign in with Supabase phone and password","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"International phone, e.g. +852 91234567"},"password":{"type":"string","description":"New passwords require 8–128 characters and the Supabase password policy."}},"required":["phone","password"]}}}}}},"/api/v1/auth/forget-password":{"post":{"operationId":"postApiV1AuthForgetPassword","summary":"Send password recovery SMS through Supabase","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"International phone, e.g. +852 91234567"},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."}},"required":["phone"]}}}}}},"/api/v1/auth/forgot-password":{"post":{"operationId":"postApiV1AuthForgotPassword","summary":"Alias for forget-password","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"International phone, e.g. +852 91234567"},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."}},"required":["phone"]}}}}}},"/api/v1/auth/otp/send":{"post":{"operationId":"postApiV1AuthOtpSend","summary":"Send or resend a Supabase SMS OTP","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"International phone, e.g. +852 91234567"},"action":{"type":"string","description":"signin, signup, or pwdreset"},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."}},"required":["phone","action"]}}}}}},"/api/v1/auth/otp/verify":{"post":{"operationId":"postApiV1AuthOtpVerify","summary":"Verify SMS; signin returns session, signup/pwdreset return completion token","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"International phone, e.g. +852 91234567"},"action":{"type":"string","description":"signin, signup, or pwdreset"},"token":{"type":"string","description":"Opaque proof from the previous step; bound to product, device and purpose, expires after 10 minutes."},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."},"aut_code":{"type":"string"}},"required":["phone","action","token","aut_code"]}}}}}},"/api/v1/auth/verification":{"post":{"operationId":"postApiV1AuthVerification","summary":"Alias for otp/verify","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"International phone, e.g. +852 91234567"},"action":{"type":"string","description":"signin, signup, or pwdreset"},"token":{"type":"string","description":"Opaque proof from the previous step; bound to product, device and purpose, expires after 10 minutes."},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."},"aut_code":{"type":"string"}},"required":["phone","action","token","aut_code"]}}}}}},"/api/v1/auth/signup/completed":{"post":{"operationId":"postApiV1AuthSignupCompleted","summary":"Set name after phone verification; return Supabase session","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Opaque proof from the previous step; bound to product, device and purpose, expires after 10 minutes."},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."},"first_name":{"type":"string"},"last_name":{"type":"string"},"password":{"type":"string","description":"New passwords require 8–128 characters and the Supabase password policy."}},"required":["token","first_name","last_name"]}}}}}},"/api/v1/auth/phone/change/start":{"post":{"operationId":"postApiV1AuthPhoneChangeStart","summary":"Send a code to the number currently on the account","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/auth/phone/change/verify":{"post":{"operationId":"postApiV1AuthPhoneChangeVerify","summary":"Verify the current number's code; returns the proof the next step needs","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Opaque proof from the previous step; bound to product, device and purpose, expires after 10 minutes."},"aut_code":{"type":"string","description":"The digits from the SMS. Generated, checked and expired by Supabase."},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."}},"required":["token","aut_code"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/auth/phone/change/send":{"post":{"operationId":"postApiV1AuthPhoneChangeSend","summary":"Send a code to the new number; repeatable to resend or correct it","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Opaque proof from the previous step; bound to product, device and purpose, expires after 10 minutes."},"new_phone":{"type":"string","description":"The international number to move the account to. Must not already belong to an account."},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."}},"required":["token","new_phone"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/auth/phone/change/confirm":{"post":{"operationId":"postApiV1AuthPhoneChangeConfirm","summary":"Verify the new number's code; applies the change and returns the session","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Opaque proof from the previous step; bound to product, device and purpose, expires after 10 minutes."},"aut_code":{"type":"string","description":"The digits from the SMS. Generated, checked and expired by Supabase."},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."}},"required":["token","aut_code"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/auth/password/signin":{"post":{"operationId":"postApiV1AuthPasswordSignin","summary":"Alias for login","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string","description":"International phone, e.g. +852 91234567"},"password":{"type":"string","description":"New passwords require 8–128 characters and the Supabase password policy."}},"required":["phone","password"]}}}}}},"/api/v1/auth/password/reset":{"post":{"operationId":"postApiV1AuthPasswordReset","summary":"Spend recovery proof and update Supabase password","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Opaque proof from the previous step; bound to product, device and purpose, expires after 10 minutes."},"deviceId":{"type":"string","description":"Optional device identifier; if supplied, reuse it at every step."},"password":{"type":"string","description":"New passwords require 8–128 characters and the Supabase password policy."}},"required":["token","password"]}}}}}},"/api/v1/auth/session/refresh":{"post":{"operationId":"postApiV1AuthSessionRefresh","summary":"Rotate the Supabase session","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"refresh_token":{"type":"string"}},"required":["refresh_token"]}}}}}},"/api/v1/auth/session/logout":{"post":{"operationId":"postApiV1AuthSessionLogout","summary":"Revoke the current Supabase refresh session","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/auth/profile":{"get":{"operationId":"getApiV1AuthProfile","summary":"Read authenticated consumer profile","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"patch":{"operationId":"patchApiV1AuthProfile","summary":"Update consumer first and last name","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"}},"required":["first_name","last_name"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/auth/scopes":{"get":{"operationId":"getApiV1AuthScopes","summary":"Read the consumer's product memberships","description":"Native Supabase Auth. Responses use {code,message,data}. Success code=200; failures use HTTP 400/401/403/409/429/503. No legacy/Wonder consumer authentication.","tags":["Consumer Auth"],"parameters":[{"name":"X-User-Scope","in":"header","required":false,"description":"jek (default) or hk_taxi_go. Product membership; never driver/admin permissions.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/config/bootstrap":{"get":{"operationId":"getApiV1ConfigBootstrap","summary":"Client bootstrap configuration","description":"Public runtime config for web and native clients: API base, map keys scope, feature flags and channel branding.","tags":["Platform"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/wonder/qr-start":{"post":{"operationId":"postApiWonderQrStart","summary":"Start a Wonder QR sign-in session","description":"Returns a session id and QR payload to display. Poll /api/wonder/qr-poll until it resolves.","tags":["Platform"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/wonder/qr-poll":{"get":{"operationId":"getApiWonderQrPoll","summary":"Poll a Wonder QR sign-in session","tags":["Platform"],"parameters":[{"name":"id","in":"query","required":true,"description":"Session id from qr-start","schema":{"type":"string"}},{"name":"role","in":"query","required":false,"description":"admin | merchant | driver","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/agent/voice-extract":{"post":{"operationId":"postApiAgentVoiceExtract","summary":"Extract a ride request from a voice transcript","description":"AI helper used by the voice assistant to turn speech into structured pickup / dropoff / options.","tags":["Platform"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/legacy/{path}":{"post":{"operationId":"postApiLegacyByPath","summary":"Authenticated proxy to the legacy JEK backend (GET/POST/PUT/PATCH/DELETE)","description":"Transparent pass-through used while legacy Go/Node routes are being migrated. Signs Wonder gateway requests from the caller's session headers.","tags":["Platform"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/geo/suggest":{"post":{"operationId":"postApiV1GeoSuggest","summary":"Smart place suggestions for the Pickup and Dropoff fields","description":"Server-owned STG Ride location primitive. With an empty `input` it recommends places before the rider types — saved Home/Work/custom places, repeated and recent trips, and real nearby points of interest, ranked by distance, recency, frequency and weekday/time-of-day (docs/25 §3). With a non-empty `input` it merges provider autocomplete with the rider's own places, keeping exact saved matches on top. Every row carries a stable `reason` code (current_location, nearby_pickup, nearby_poi, saved_home, saved_work, saved_place, recent_pickup, recent_destination, frequent_pickup, frequent_destination, repeated_route, commute_pattern, autocomplete), a `source`, `distance_m` and a QA `score`; `meta.ranking_version` names the ranking. The frontend still shows the live GPS row itself, removes what is already selected and trims to 10. The rider comes from the bearer; no user id is read from the body.","tags":["Geo"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"results":[{"place":{"placeId":"opaque","text":"IFC Mall","fullText":"8 Finance Street, Central","lat":22.28552,"lng":114.15769,"provider":"google"},"reason":"nearby_pickup","source":"nearby_poi","distance_m":120,"score":0.91}],"meta":{"ranking_version":"ride-places-v1","generated_at":"2026-08-28T01:20:00Z","has_more":true,"reasons":{"nearby_pickup":1}}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"Pickup (initial, default) or Dropoff (destination)."},"input":{"type":"string","description":"What the rider typed. Empty means 'recommend'."},"current_location":{"type":"object","description":"{ lat, lng } of the device. Top-level lat/lng is also accepted."},"pickup":{"type":"object","description":"The selected pickup place ({ lat, lng } or { placeId }) — required for useful Dropoff recommendations. `pickup_place_id` is also accepted."},"selected_place_ids":{"type":"string","description":"Place ids already chosen on the screen; excluded from the answer."},"limit":{"type":"number","description":"Candidate limit, default 20, capped at 50."},"timezone":{"type":"string","description":"IANA zone for weekday/time ranking (default Asia/Hong_Kong). Server time is authoritative."},"session_token":{"type":"string","description":"Provider autocomplete session token, echoed in meta."},"is_home_page":{"type":"boolean","description":"With empty input: put the current address first (reason current_location — the 'Depart from' label reads results[0]) and the recommendations after it."}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/geo/place":{"post":{"operationId":"postApiV1GeoPlace","summary":"Resolve one place","description":"Server-owned STG Ride location primitive. The mobile client never calls the map or Wonder provider directly.","tags":["Geo"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/geo/reverse":{"post":{"operationId":"postApiV1GeoReverse","summary":"Reverse geocode coordinates","description":"Server-owned STG Ride location primitive. The mobile client never calls the map or Wonder provider directly.","tags":["Geo"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/geo/route":{"post":{"operationId":"postApiV1GeoRoute","summary":"Resolve a driving route","description":"Server-owned STG Ride location primitive. The mobile client never calls the map or Wonder provider directly.","tags":["Geo"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/geo/access-points":{"post":{"operationId":"postApiV1GeoAccessPoints","summary":"List pickup access points","description":"Server-owned STG Ride location primitive. The mobile client never calls the map or Wonder provider directly.","tags":["Geo"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/quotes":{"post":{"operationId":"postApiV1PassengerQuotes","summary":"Quote a trip","description":"Resolves Standard Ride places server-side, returns real Wonder quotes and persists the owned, expiring batch before responding.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/catalogs":{"get":{"operationId":"getApiV1PassengerCatalogs","summary":"Get ride cancellation and evaluation catalogs","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/nearby":{"post":{"operationId":"postApiV1PassengerNearby","summary":"List nearby taxis","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/serviceability":{"post":{"operationId":"postApiV1PassengerServiceability","summary":"Check pickup serviceability","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/cancel-preview":{"get":{"operationId":"getApiV1PassengerTripsByIdCancelPreview","summary":"Preview trip cancellation","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/location":{"post":{"operationId":"postApiV1PassengerTripsByIdLocation","summary":"Publish passenger location","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/vehicle-classes":{"post":{"operationId":"postApiV1PassengerTripsByIdVehicleClasses","summary":"Add trip vehicle classes","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/location-quote":{"post":{"operationId":"postApiV1PassengerTripsByIdLocationQuote","summary":"Quote a trip location change","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/location-confirm":{"post":{"operationId":"postApiV1PassengerTripsByIdLocationConfirm","summary":"Confirm a trip location change","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/evaluation":{"post":{"operationId":"postApiV1PassengerTripsByIdEvaluation","summary":"Evaluate a completed trip","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/related-activities":{"get":{"operationId":"getApiV1PassengerTripsByIdRelatedActivities","summary":"List related trip activities","description":"Authenticated passenger-trip operation used by the STG Standard Ride flow.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips":{"post":{"operationId":"postApiV1PassengerTrips","summary":"Create a trip","description":"Accepts batch_id and selected_quote_ids only. Locations and prices are re-read from the caller's unexpired server-owned quote batch.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"get":{"operationId":"getApiV1PassengerTrips","summary":"List my trips","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}":{"get":{"operationId":"getApiV1PassengerTripsById","summary":"Get a trip","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/cancel":{"post":{"operationId":"postApiV1PassengerTripsByIdCancel","summary":"Cancel a trip","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/pay":{"post":{"operationId":"postApiV1PassengerTripsByIdPay","summary":"Confirm trip payment and dispatch","description":"Records caller-supplied payment confirmation and then creates the Wonder taxi order. It does not charge a payment method.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/payments/charge":{"post":{"operationId":"postApiV1PaymentsCharge","summary":"Charge a payment method","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/ride-orders":{"get":{"operationId":"getApiV1RideOrders","summary":"List ride orders","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1RideOrders","summary":"Create a ride order","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/ride-orders/{id}/cancel":{"post":{"operationId":"postApiV1RideOrdersByIdCancel","summary":"Cancel a ride order","description":"For a Wonder order id, focus=false previews cancellation, focus=true confirms it, and submit_reason_only=true submits post-cancel feedback. Native UUID orders use the migration dispatcher.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"focus":{"type":"boolean"},"submit_reason_only":{"type":"boolean"},"cancel_type":{"type":"string"},"cancel_remark":{"type":"string"}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/standard-rides/{operation}":{"post":{"operationId":"postApiV1StandardRidesByOperation","summary":"Standard Ride compatibility operations","description":"Preserves the existing Standard Ride client contracts while moving behavior-compatible Wonder order creation, lifecycle, history, route, edit, rating, and Live Activity operations onto the stable API origin. Payment-coupled and map-search operations are intentionally excluded.","tags":["Passenger"],"parameters":[{"name":"operation","in":"path","required":true,"description":"quotes, nearby, pickup-serviceability, orders, live-activity/token, order-list, order-detail, locations-quote, locations-confirm, universal-transactions, related-activities, status, ride, router, evaluation-options, evaluate, or update-order","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides":{"post":{"operationId":"postApiV1MeRides","summary":"Request a ride (Make a Wish)","description":"Creates a ride request with optional wish text/audio, analysis metadata and tip. Send vehicle_class_ids to accept several classes at once and be matched by whichever driver turns up first: the selection is priced HERE and the passenger is charged the DEAREST of it, so the fare is covered whichever class arrives. The request's own `fare` is not consulted for a selection — a caller who named its own total would also get to decide which class counted as dearest. Send duration_min alongside it, or the reprice drops the per-minute component and lands under the quote you showed. Refuses a pickup whose service area is fulfilled by PARTNER capacity with 422 `PARTNER_FULFILMENT_REQUIRED`, carrying `fulfilment` and `fulfilment_area`: that market is served by a third party through Wonder, and this lane has no payment gateway to buy it with. Call /api/v1/ride/preflight first to learn which fulfilment applies.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pickup":{"type":"string"},"dropoff":{"type":"string"},"distance_km":{"type":"number"},"duration_min":{"type":"number","description":"Send it whenever per-minute rates apply, so the charge matches POST /api/v1/ride/quote."},"vehicle_class_ids":{"type":"string","description":"Classes this ride will accept, max 6. Priced here; the passenger is charged the dearest, and it becomes the ride's vehicle label. Widen later with POST /api/v1/me/rides/{id}/vehicle-classes."},"fare":{"type":"number","description":"What was displayed. Ignored when vehicle_class_ids is sent."},"order_type":{"type":"string"},"quote":{"type":"object","description":"The signed quote from the wish analysis — what a wish is charged against."}},"required":["pickup","dropoff"]}}}},"security":[{"userBearer":[]}]},"get":{"operationId":"getApiV1MeRides","summary":"List the caller's rides (account surface)","description":"Rides and wishes together, newest first. History is paged by an opaque keyset cursor: echo `next_cursor` back as `?cursor=` for the next page, and stop when it comes back null. `limit` defaults to 50 and caps at 100. `?active=1` answers only the orders still in flight and is deliberately NOT paged. A `cursor` this endpoint did not issue is refused with 400 `invalid_cursor` rather than silently restarting at the newest row.","tags":["Rides"],"parameters":[{"name":"active","in":"query","required":false,"description":"1|true|yes — in-flight orders only, unpaged.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1–100, default 50. History only.","schema":{"type":"number"}},{"name":"cursor","in":"query","required":false,"description":"Opaque; the previous page's next_cursor.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":[{"id":"…","order_type":"ride","status":"completed"}],"next_cursor":"MjAyNi0wOS0wMVQxMjowMDowMC4wMDBafGFiYw","has_more":true,"limit":50,"max_limit":100}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/messaging/seed-friends":{"post":{"operationId":"postApiV1MeMessagingSeedFriends","summary":"Seed demo conversations for the signed-in user","tags":["Consumer"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/da/wishes":{"get":{"operationId":"getApiV1DaWishes","summary":"List open wishes near the driver","description":"The driver's whole wish surface in one read: `data` is the open pool (no driver yet), `mine` is every wish the caller holds in any state, and `can_accept` says whether a claim from this driver would be allowed at all. Rows are sanitised for the driver — the passenger's `fare` and the `wish.pricing` pin are stripped — and only `mine` carries passenger contact details.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/da/wishes/{id}/accept":{"post":{"operationId":"postApiV1DaWishesByIdAccept","summary":"Accept a wish","description":"Claims a wish, first driver wins via a conditional update on `driver_id`. Returns `{ data, served_vehicle_class, refunded? }` where `data` is the full claimed wish row with `status: \"accepted\"` — clients render from it rather than re-reading the feed. Refuses with 403 when the driver is not eligible to take work or their active vehicle serves no class the order accepts, and 409 \"Wish already taken\" when another driver got there first. Unlike the list endpoint, the row returned here is RAW, so a driver-facing relay must strip `fare` and `wish.pricing` itself.","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/public/explore-merchants":{"get":{"operationId":"getApiPublicExploreMerchants","summary":"Merchants for the Explore map","tags":["Public"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/merchant-detail":{"get":{"operationId":"getApiPublicMerchantDetail","summary":"Public merchant profile","tags":["Public"],"parameters":[{"name":"slug","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/social-summary":{"post":{"operationId":"postApiPublicSocialSummary","summary":"AI summary of a merchant's public reviews","tags":["Public"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/dispatch/orders":{"post":{"operationId":"postApiPublicV1DispatchOrders","summary":"Create a dispatch order from a demand platform","description":"Token-authenticated integration endpoint. All channels funnel into the same supplier order queue.","tags":["Dispatch"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/dispatch/orders/{id}/cancel":{"post":{"operationId":"postApiPublicV1DispatchOrdersByIdCancel","summary":"Cancel a dispatch order","tags":["Dispatch"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/dispatch/tick":{"post":{"operationId":"postApiPublicV1DispatchTick","summary":"Dispatch engine tick (scheduler)","description":"Called by the scheduler to advance offers, expire stale ones and fall back to external supply.","tags":["Dispatch"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/supply/credentials":{"get":{"operationId":"getApiPublicV1SupplyCredentials","summary":"List supply platform credentials","tags":["Dispatch"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}},"post":{"operationId":"postApiPublicV1SupplyCredentials","summary":"Issue supply platform credentials","tags":["Dispatch"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/migration":{"post":{"operationId":"postApiPublicMigration","summary":"Run a migration step","description":"Token-authenticated (MIGRATION_ADMIN_TOKEN) migration runner used by the admin migration console.","tags":["Migration"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/migration-agent":{"post":{"operationId":"postApiPublicMigrationAgent","summary":"Migration agent batch worker","tags":["Migration"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}},"get":{"operationId":"getApiPublicMigrationAgent","summary":"Migration agent status","tags":["Migration"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/admin/status":{"get":{"operationId":"getApiAdminStatus","summary":"Backend health & role check","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/users":{"get":{"operationId":"getApiAdminUsers","summary":"List platform users","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/jek-users":{"get":{"operationId":"getApiAdminJekUsers","summary":"List legacy JEK users","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/roles":{"post":{"operationId":"postApiAdminRoles","summary":"Grant or revoke an admin role","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/claim":{"post":{"operationId":"postApiAdminClaim","summary":"Claim the first admin seat","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/access-requests":{"get":{"operationId":"getApiAdminAccessRequests","summary":"List admin access requests","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminAccessRequests","summary":"Approve or reject an access request","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/access-request":{"get":{"operationId":"getApiAdminAccessRequest","summary":"Get my access request status","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminAccessRequest","summary":"Submit an admin access request","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/share-session":{"post":{"operationId":"postApiAdminShareSession","summary":"Share the admin session across jek.app subdomains","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/merchants":{"get":{"operationId":"getApiAdminMerchants","summary":"List merchants","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/merchant-detail":{"get":{"operationId":"getApiAdminMerchantDetail","summary":"Get a merchant","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/hk-malls":{"get":{"operationId":"getApiAdminHkMalls","summary":"List HK malls","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/hk-mall-detail":{"get":{"operationId":"getApiAdminHkMallDetail","summary":"Get an HK mall","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/hk-food-permits-sync":{"post":{"operationId":"postApiAdminHkFoodPermitsSync","summary":"Sync HK food permit registry","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/michelin-hk":{"get":{"operationId":"getApiAdminMichelinHk","summary":"List Michelin HK entries","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/michelin-hk-detail":{"get":{"operationId":"getApiAdminMichelinHkDetail","summary":"Get a Michelin HK entry","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/michelin-sync":{"post":{"operationId":"postApiAdminMichelinSync","summary":"Sync Michelin data","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/michelin-synced":{"get":{"operationId":"getApiAdminMichelinSynced","summary":"Synced Michelin records","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/virtual-numbers":{"get":{"operationId":"getApiAdminVirtualNumbers","summary":"List virtual numbers","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminVirtualNumbers","summary":"Provision a virtual number","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"patch":{"operationId":"patchApiAdminVirtualNumbers","summary":"Update a virtual number","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/maps-config":{"get":{"operationId":"getApiAdminMapsConfig","summary":"Read maps provider configuration","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminMapsConfig","summary":"Update maps provider configuration","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/wonder-config":{"get":{"operationId":"getApiAdminWonderConfig","summary":"Read Wonder gateway configuration","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminWonderConfig","summary":"Update Wonder gateway configuration","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/consumer-chat-config":{"get":{"operationId":"getApiAdminConsumerChatConfig","summary":"Read consumer chat / AI configuration","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminConsumerChatConfig","summary":"Update consumer chat / AI configuration","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/metabase":{"get":{"operationId":"getApiAdminMetabase","summary":"Proxy Metabase datasets & questions","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/db-browse":{"post":{"operationId":"postApiAdminDbBrowse","summary":"Browse whitelisted database tables","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/migration":{"post":{"operationId":"postApiAdminMigration","summary":"Trigger a migration run","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/migration/diffs":{"get":{"operationId":"getApiAdminMigrationDiffs","summary":"Schema / row diffs between source and target","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/migration/flags":{"get":{"operationId":"getApiAdminMigrationFlags","summary":"Read migration feature flags","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminMigrationFlags","summary":"Update migration feature flags","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/migration/inventory":{"get":{"operationId":"getApiAdminMigrationInventory","summary":"Migration inventory snapshot","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminMigrationInventory","summary":"Refresh migration inventory","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/v1/da/me":{"get":{"operationId":"getApiV1DaMe","summary":"Driver profile & onboarding state","description":"Returns the signed-in driver, their vehicles, documents and an onboarding checklist. The profile is provisioned automatically on first call.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"driver":{"id":"…","display_name":"Chan Tai Man","status":"active","is_online":false},"onboarding":{"has_vehicle":true,"has_licence":true,"approved":true,"suspended":false,"status":"active","can_take_work":true}}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]},"patch":{"operationId":"patchApiV1DaMe","summary":"Update driver profile","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"display_name":{"type":"string"},"phone_e164":{"type":"string"}}}}}},"security":[{"driverBearer":[]}]}},"/api/v1/da/me/online":{"post":{"operationId":"postApiV1DaMeOnline","summary":"Go online / offline","description":"Only approved drivers with an active vehicle may go online.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"is_online":{"type":"boolean"}},"required":["is_online"]}}}},"security":[{"driverBearer":[]}]}},"/api/v1/da/vehicles":{"get":{"operationId":"getApiV1DaVehicles","summary":"List the driver's vehicles","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]},"post":{"operationId":"postApiV1DaVehicles","summary":"Register a vehicle","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"plate_number":{"type":"string"},"model_name":{"type":"string"},"color":{"type":"string"},"seats":{"type":"number"},"vehicle_type_code":{"type":"string","description":"Single-class spelling of vehicle_type_codes. Still accepted."},"vehicle_type_codes":{"type":"string","description":"Every class this vehicle is approved to serve, e.g. [\"Standard Taxi\",\"Premium Taxi\"]. On an order accepting several classes the driver is paid for the DEAREST of these the order also accepts, and cannot accept an order with none of them. A vehicle that declares nothing is unrestricted, exactly as before. vehicle_type_code is set to the first of the set. Max 8."}},"required":["plate_number"]}}}},"security":[{"driverBearer":[]}]}},"/api/v1/da/vehicles/{id}":{"patch":{"operationId":"patchApiV1DaVehiclesById","summary":"Update or activate a vehicle","description":"Send is_active=true to make this the vehicle used for new orders. Sending vehicle_type_codes REPLACES the approved class set rather than adding to it, so a class claimed in error can be taken back.","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"is_active":{"type":"boolean"},"plate_number":{"type":"string"},"model_name":{"type":"string"},"color":{"type":"string"},"seats":{"type":"number"},"vehicle_type_code":{"type":"string"},"vehicle_type_codes":{"type":"string"}}}}}},"security":[{"driverBearer":[]}]},"delete":{"operationId":"deleteApiV1DaVehiclesById","summary":"Remove a vehicle","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/da/documents/upload-url":{"post":{"operationId":"postApiV1DaDocumentsUploadUrl","summary":"Signed upload URL for a document","description":"Returns a short-lived URL; PUT the file to it, then register it with POST /api/v1/da/documents.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"doc_type":{"type":"string","description":"driving_licence | vehicle_registration | insurance | id_card | other"},"file_name":{"type":"string"}},"required":["doc_type","file_name"]}}}},"security":[{"driverBearer":[]}]}},"/api/v1/da/documents":{"post":{"operationId":"postApiV1DaDocuments","summary":"Submit a document for review","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"doc_type":{"type":"string"},"storage_path":{"type":"string"},"document_number":{"type":"string"},"expires_on":{"type":"string"}},"required":["doc_type","storage_path"]}}}},"security":[{"driverBearer":[]}]},"get":{"operationId":"getApiV1DaDocuments","summary":"List submitted documents","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/da/offers":{"get":{"operationId":"getApiV1DaOffers","summary":"Pending order offers","description":"Live offers for the signed-in driver. Empty while offline or when onboarding is incomplete.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/da/offers/{id}/accept":{"post":{"operationId":"postApiV1DaOffersByIdAccept","summary":"Accept an offer","description":"First accept wins; a losing caller receives 409 Order already taken.","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/da/offers/{id}/decline":{"post":{"operationId":"postApiV1DaOffersByIdDecline","summary":"Decline an offer","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}}}}},"security":[{"driverBearer":[]}]}},"/api/v1/da/orders/active":{"get":{"operationId":"getApiV1DaOrdersActive","summary":"Current in-flight order","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/da/orders/{id}/status":{"post":{"operationId":"postApiV1DaOrdersByIdStatus","summary":"Advance the order state","description":"accepted → arrived → in_trip → completed (cancel allowed before in_trip). Completing an order writes the earnings row and mirrors the state to the rider platform.","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"state":{"type":"string"},"reason":{"type":"string"}},"required":["state"]}}}},"security":[{"driverBearer":[]}]}},"/api/v1/da/orders":{"get":{"operationId":"getApiV1DaOrders","summary":"Order history","tags":["Driver Assistant"],"parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number"}},{"name":"offset","in":"query","required":false,"schema":{"type":"number"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/da/location":{"post":{"operationId":"postApiV1DaLocation","summary":"Location ping","description":"Updates the driver's live position and, when on a trip, the trail shown to the rider.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"},"heading":{"type":"number"},"speed_kph":{"type":"number"},"accuracy_m":{"type":"number"}},"required":["latitude","longitude"]}}}},"security":[{"driverBearer":[]}]}},"/api/v1/da/push-tokens":{"post":{"operationId":"postApiV1DaPushTokens","summary":"Register the driver's device for JEK pushes","description":"The driver's half of `/api/v1/me/push-tokens`. Without a row here `fanOutPush` has nothing to deliver to and a passenger's chat message never rings. Expo tokens only — Driver Pro's native builds are the only client, and its web build has no service worker or VAPID keys. Idempotent on the token itself (a handset that changes hands re-points to whoever registered last). The row is written under `da_drivers.user_id`, which is what conversation membership keys on; a driver with no messaging identity gets 409 `driver_has_no_identity`. `DELETE ?expo_token=` retires the caller's own row on sign-out.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expo_token":{"type":"string","description":"`ExponentPushToken[…]`"},"platform":{"type":"string","description":"`ios` or `android`"}},"required":["expo_token"]}}}},"security":[{"driverBearer":[]}]}},"/api/v1/da/earnings":{"get":{"operationId":"getApiV1DaEarnings","summary":"Earnings summary and line items","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/supplier/orders":{"post":{"operationId":"postApiV1SupplierOrders","summary":"Submit an order to the supplier pool","description":"Demand-platform intake (JEK, Uber, Grab, ComfortDelGro…). Creates a supplier order and broadcasts it to eligible online drivers. Authenticated with the X-Internal-Token dispatch token. Send your own order id as external_ref: a repeat POST with the same external_ref and demand_platform_code returns the existing order (200, idempotent:true) instead of dispatching a second driver, so a timed-out request is safe to retry.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pickup_address":{"type":"string"},"dropoff_address":{"type":"string"},"external_ref":{"type":"string","description":"Your order id. Idempotency key, with demand_platform_code."},"demand_platform_code":{"type":"string"},"channel_code":{"type":"string"},"pickup_latitude":{"type":"number"},"pickup_longitude":{"type":"number"},"dropoff_latitude":{"type":"number"},"dropoff_longitude":{"type":"number"},"vehicle_type_code":{"type":"string","description":"Legacy OMS class name, e.g. \"Standard Taxi\". Still accepted; it is the single-class spelling of vehicle_type_codes."},"vehicle_type_codes":{"type":"string","description":"Every class this order will accept, in your preference order, e.g. [\"Standard Taxi\",\"Express Taxi\"] — whichever driver turns up first takes it. Max 8. vehicle_type_code is set to the first of the set, so readers that show one class are unaffected. Widen it later with POST /api/v1/supplier/orders/{id}/vehicle-classes."},"fare_amount":{"type":"number","description":"Minor units (cents)"},"currency":{"type":"string","description":"Defaults to HKD."},"passenger_name":{"type":"string"},"passenger_phone_e164":{"type":"string"},"scheduled_at":{"type":"string"}},"required":["pickup_address","dropoff_address"]}}}},"security":[{"adminBearer":[]}]}},"/api/v1/supplier/orders/{id}":{"get":{"operationId":"getApiV1SupplierOrdersById","summary":"Read a supplier order back","description":"Reconciliation, not polling. Status callbacks are best-effort and unordered, so a demand platform that missed one needs to ask what happened. `{id}` accepts our order id OR your own external_ref, because a partner often keeps only its own reference. Authenticated with the X-Internal-Token dispatch token.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/v1/supplier/orders/{id}/cancel":{"post":{"operationId":"postApiV1SupplierOrdersByIdCancel","summary":"Cancel from the demand side","description":"Stands a driver down, including after they have accepted — without this a driver keeps going to a pickup nobody is waiting at. Allowed through in_trip; refuses a completed trip (that is a refund conversation); idempotent once terminal. Outstanding offers are expired in the same breath.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}}}}},"security":[{"adminBearer":[]}]}},"/api/v1/supplier/orders/{id}/vehicle-classes":{"post":{"operationId":"postApiV1SupplierOrdersByIdVehicleClasses","summary":"Accept more vehicle classes on a waiting order","description":"Widen an unmatched order instead of cancelling and re-booking, which loses its place in the queue, or placing a second order, which dispatches two drivers to one passenger. Widening only — classes are never removed, since a driver may already be heading over on one of them. The first class of the set stays the primary, so vehicle_type_code never changes under a partner who has already told their passenger what is coming. 409 once a driver has taken it, enforced again in the write so a driver accepting mid-request cannot have the order changed underneath them. Re-sending classes the order already accepts answers 200 with unchanged:true, so a timed-out call is safe to retry. `{id}` accepts our order id OR your own external_ref.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vehicle_type_codes":{"type":"string","description":"Classes to ADD, e.g. [\"Premium Taxi\"]. Merged with what the order already accepts; a bare string is accepted. Max 8 in total."}},"required":["vehicle_type_codes"]}}}},"security":[{"adminBearer":[]}]}},"/api/public/v1/health":{"get":{"operationId":"getApiPublicV1Health","summary":"Health probe","tags":["Platform"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"ok":true}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/merchants":{"get":{"operationId":"getApiPublicV1Merchants","summary":"List merchants","description":"Search & filter public merchant directory.","tags":["Merchants"],"parameters":[{"name":"q","in":"query","required":false,"description":"Free-text search","schema":{"type":"string"}},{"name":"cuisine","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1–200, default 50","schema":{"type":"number"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":[{"id":"…","slug":"eatatiment","name":"Eatatiment Restaurant and Bar","cuisine":"Fusion"}]}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/merchants/{slug}":{"get":{"operationId":"getApiPublicV1MerchantsBySlug","summary":"Merchant detail","tags":["Merchants"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/vouchers":{"get":{"operationId":"getApiPublicV1Vouchers","summary":"List vouchers","tags":["Promotions"],"parameters":[{"name":"merchant_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/promotions":{"get":{"operationId":"getApiPublicV1Promotions","summary":"Active promotions","tags":["Promotions"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/ride/vehicle-types":{"get":{"operationId":"getApiPublicV1RideVehicleTypes","summary":"Available vehicle types","description":"The rate card, already in the order a passenger should read it: Standard, Express, Meter, 85% Meter, Premium, 6-seater, Wheelchair, Extra Luggage, then anything else by name. Render the array as it arrives. A client that must re-sort may only sort by `sort_order` — the operator's own ordering from /admin/ride/vehicle-types, which this response already honours — never by name or price, which is a different running order for the app.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/ride/quote":{"post":{"operationId":"postApiV1RideQuote","summary":"Estimate ride fare","description":"Prices one vehicle type, or a whole selection. Send vehicle_class_ids to price several at once: the response adds `authorize`, the dearest of them, which is what the passenger pre-authorises so that whichever driver accepts, the fare is already covered. The selection is priced in one call on purpose — quoting each class separately would let the client decide which counted as dearest. A quote takes up to 24 ids — the whole rate card, so a price list is one call, no batching — while a booking accepts at most 6; the response echoes that as vehicle_class_limit. A class can have a MINIMUM FARE — 85% Meter Taxi is not offered below HKD 90, because a discounted short trip is a job no driver takes. On a trip that prices under it the class is dropped from `quotes` and listed in `withheld` with the floor it missed, so a price list drawn from `quotes` loses the row by itself. 400 BELOW_CLASS_MINIMUM when nothing clears its floor, or when vehicle_type_id named such a class; POST /api/v1/me/rides refuses the same class with the same code rather than dropping it, so a stale screen cannot book one.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"currency":"HKD","authorize":120,"dearest":{"vehicle_type_id":"…","name":"Express Taxi","fare":120,"currency":"HKD"},"quotes":[{"vehicle_type_id":"…","name":"Standard Taxi","fare":88,"currency":"HKD"}],"withheld":[{"vehicle_type_id":"…","name":"85% Meter Taxi","fare":74,"currency":"HKD","min_fare":90}],"vehicle_class_limit":6}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vehicle_type_id":{"type":"string","description":"One class. Omit when sending vehicle_class_ids."},"vehicle_class_ids":{"type":"string","description":"Several classes, priced together. Max 24 per quote; a booking accepts at most 6."},"distance_km":{"type":"number"},"duration_min":{"type":"number"}},"required":["distance_km"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/vehicle-classes":{"post":{"operationId":"postApiV1MeRidesByIdVehicleClasses","summary":"Accept more vehicle classes while waiting","description":"Widens what an unmatched ride will accept. Only the DIFFERENCE is charged — the passenger already authorised the previous dearest class, so a cheaper addition costs nothing and a dearer one costs the gap. Widening only: classes are never removed, because that could strand a driver already on the way. 409 once a driver has taken it.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{},"added":["…"],"charged":32,"authorized_total":152}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vehicle_class_ids":{"type":"string"}},"required":["vehicle_class_ids"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/boost":{"post":{"operationId":"postApiV1MeRidesByIdBoost","summary":"Add money to an unmatched order","description":"Raises the fare on an order no driver has taken. Wishes and ordinary rides both. Refused once a driver is attached.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number"}},"required":["amount"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/rides":{"get":{"operationId":"getApiV1Rides","summary":"Signed-in user's ride history","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me":{"get":{"operationId":"getApiV1Me","summary":"Current user + profile","tags":["Account"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/merchant/me":{"get":{"operationId":"getApiV1MerchantMe","summary":"Signed-in merchant record","tags":["Merchant Dashboard"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"merchantBearer":[]}]}},"/api/v1/merchant/vouchers":{"get":{"operationId":"getApiV1MerchantVouchers","summary":"Vouchers owned by signed-in merchant","tags":["Merchant Dashboard"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"merchantBearer":[]}]},"post":{"operationId":"postApiV1MerchantVouchers","summary":"Create voucher","tags":["Merchant Dashboard"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"merchantBearer":[]}]}},"/api/public/v1/merchants/{slug}/socials":{"get":{"operationId":"getApiPublicV1MerchantsBySlugSocials","summary":"Merchant social profiles","tags":["Merchants"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/chains/{id}":{"get":{"operationId":"getApiPublicV1ChainsById","summary":"Chain detail + member merchants","tags":["Merchants"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/maps-config":{"get":{"operationId":"getApiPublicV1MapsConfig","summary":"Public Google Maps browser key + tracking ID","tags":["Platform"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/v1/openapi":{"get":{"operationId":"getApiPublicV1Openapi","summary":"OpenAPI 3.1 spec (JSON)","tags":["Platform"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/me/activity":{"get":{"operationId":"getApiV1MeActivity","summary":"Recent transactions, rides, rewards","tags":["Account"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/merchant/onboard":{"post":{"operationId":"postApiV1MerchantOnboard","summary":"Claim / register a merchant profile","tags":["Merchant Dashboard"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/merchant/vouchers/{id}":{"patch":{"operationId":"patchApiV1MerchantVouchersById","summary":"Update voucher","tags":["Merchant Dashboard"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"merchantBearer":[]}]},"delete":{"operationId":"deleteApiV1MerchantVouchersById","summary":"Delete voucher","tags":["Merchant Dashboard"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"merchantBearer":[]}]}},"/api/v1/admin/merchants":{"get":{"operationId":"getApiV1AdminMerchants","summary":"List all merchants","tags":["Admin"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/v1/admin/users":{"get":{"operationId":"getApiV1AdminUsers","summary":"List all users + roles","tags":["Admin"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/v1/admin/query":{"post":{"operationId":"postApiV1AdminQuery","summary":"Generic table proxy (whitelisted tables only)","description":"All admin dashboard reads/writes go through this endpoint instead of talking to the database directly. Table names are whitelisted server-side; every request is admin-gated.","tags":["Admin"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"table":{"type":"string"},"op":{"type":"string"},"select":{"type":"string"},"filters":{"type":"string"},"values":{"type":"string"}},"required":["table","op"]}}}},"security":[{"adminBearer":[]}]}},"/api/v1/me/contacts":{"post":{"operationId":"postApiV1MeContacts","summary":"Bulk upload contacts from a JEK user's phone","description":"Accepts a batch of contacts (name + phone(s) + optional emails), normalizes each phone to E.164 using the provided default_country (e.g. \"852\"), and upserts them into jek_user_contacts scoped to the caller. Safe to call repeatedly — duplicates are merged by (user_id, phone_e164).","tags":["Contacts"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"inserted":123,"total":456}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"string"},"default_country":{"type":"string","description":"E.g. \"852\" for HK, \"853\" for Macau"},"source":{"type":"string","description":"\"phone\" | \"upload\" | free-form"}},"required":["contacts"]}}}},"security":[{"userBearer":[]}]},"get":{"operationId":"getApiV1MeContacts","summary":"List uploaded contacts with on-JEK match","description":"Returns the caller's uploaded contacts. Each contact carries an on_jek flag computed by matching phone_e164 against the JEK user directory server-side (via SECURITY DEFINER — no direct DB read from the client).","tags":["Contacts"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"total":456,"on_jek_count":87,"contacts":[{"id":"…","display_name":"Ada","phone":"+85268880042","emails":[],"source":"phone","on_jek":true,"uploaded_at":"…"}]}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MeContacts","summary":"Clear all uploaded contacts (soft-delete)","tags":["Contacts"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/referral":{"get":{"operationId":"getApiV1MeReferral","summary":"List the caller's invite codes (auto-creates a default)","description":"Returns every active referral code the user owns plus the default one, each with invite.jek.app URL and WhatsApp/SMS share links. First call for a user lazily allocates a random 8-char default code (collision-safe).","tags":["Contacts"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"default":{"id":"…","code":"K4TR9M2P","is_default":true,"label":"Default","visit_count":0,"signup_count":0,"invite_url":"https://invite.jek.app/?ref=K4TR9M2P"},"codes":[{"id":"…","code":"K4TR9M2P","is_default":true,"label":"Default"}],"code":"K4TR9M2P","invite_url":"https://invite.jek.app/?ref=K4TR9M2P"}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeReferral","summary":"Create an additional invite code (campaign / channel)","description":"Allocates a new random code owned by the caller. Useful for influencers or marketing campaigns that need to attribute installs per channel. Set make_default=true to promote it to the primary code.","tags":["Contacts"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","description":"Human label, e.g. 'Instagram'."},"campaign":{"type":"string"},"make_default":{"type":"boolean"}}}}}},"security":[{"userBearer":[]}]},"patch":{"operationId":"patchApiV1MeReferral","summary":"Promote a code to default, or soft-delete it","tags":["Contacts"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"action":{"type":"string"}},"required":["id","action"]}}}},"security":[{"userBearer":[]}]}},"/api/public/referral-lookup":{"get":{"operationId":"getApiPublicReferralLookup","summary":"Validate a referral code (public)","description":"Called by invite.jek.app to confirm an inbound ?ref=CODE is real before showing it to the visitor and setting the attribution cookie. Never returns the owner's identity.","tags":["Contacts"],"parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"valid":true,"code":"K4TR9M2P","label":"Default","campaign":null}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/public/referral-visit":{"post":{"operationId":"postApiPublicReferralVisit","summary":"Track a visit to invite.jek.app for attribution","description":"Called by the invite.jek.app landing page whenever it loads with a valid ?ref=CODE. Increments the code's visit_count and records platform (ios/android/web), source, referrer and user-agent.","tags":["Contacts"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"platform":{"type":"string"},"source":{"type":"string"},"referrer":{"type":"string"}},"required":["code"]}}}}}},"/api/v1/me/messaging/conversations":{"get":{"operationId":"getApiV1MeMessagingConversations","summary":"List the caller's conversations (inbox)","description":"Returns every conversation the user is a member of, ordered by last_message_at. Each entry carries last_message_preview, unread_count, member list, and type (dm/group/agent/support).","tags":["Messaging"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeMessagingConversations","summary":"Start a DM, group, or JEK Assistant thread","description":"Provide type='agent' for the auto-created JEK Assistant thread (idempotent — returns the existing one), user_id for a 1:1 DM with a JEK user, phone for a DM by phone number (creates the counterparty stub if needed), or type='group' with member_ids for a new group chat.","tags":["Messaging"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string"},"user_id":{"type":"string"},"phone":{"type":"string"},"title":{"type":"string"},"member_ids":{"type":"string"}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/messaging/conversations/{id}":{"get":{"operationId":"getApiV1MeMessagingConversationsById","summary":"Fetch conversation detail + message page","description":"Returns the conversation, its members, and the most recent `limit` messages (default 50). Pass `before` (ISO timestamp) for pagination to older messages.","tags":["Messaging"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"string"}},{"name":"before","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"patch":{"operationId":"patchApiV1MeMessagingConversationsById","summary":"Mark the conversation read (advances read cursor)","tags":["Messaging"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/messaging/conversations/{id}/messages":{"post":{"operationId":"postApiV1MeMessagingConversationsByIdMessages","summary":"Send a message (text, image, file, voice)","description":"Set kind='text' (default) for plain text. For attachments upload via POST /api/v1/me/messaging/attachments first, then send kind='image'|'file'|'voice' with the returned path in attachments. Mentioning @jek in a non-agent conversation triggers a JEK Assistant reply from the fan-out job.","tags":["Messaging"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string"},"kind":{"type":"string"},"reply_to_id":{"type":"string"},"attachments":{"type":"string"},"metadata":{"type":"object"}},"required":["body"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/messaging/conversations/{id}/typing":{"post":{"operationId":"postApiV1MeMessagingConversationsByIdTyping","summary":"Broadcast a typing indicator (3s TTL)","description":"Fires a lightweight realtime event other members receive within ~200ms. Client should debounce — call at most once every 3 seconds while the user is actively typing.","tags":["Messaging"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/messaging/attachments":{"post":{"operationId":"postApiV1MeMessagingAttachments","summary":"Request a signed upload URL for a chat attachment","description":"Returns a one-shot signed URL you can PUT the raw file to directly (never rehashed through the Worker). Returns the storage path to include in the subsequent POST /messages call. Bucket is private — read access is signed on demand via GET /attachments?path=...","tags":["Messaging"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"bucket":"chat-attachments","path":"<conv>/<user>/1729-abc-photo.jpg","upload_url":"https://…storage/v1/…?token=…","token":"…","read_url_expires_in":3600}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"conversation_id":{"type":"string"},"filename":{"type":"string"}},"required":["conversation_id","filename"]}}}},"security":[{"userBearer":[]}]},"get":{"operationId":"getApiV1MeMessagingAttachments","summary":"Sign a read URL for an existing attachment path","tags":["Messaging"],"parameters":[{"name":"path","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/messaging/candidates":{"get":{"operationId":"getApiV1MeMessagingCandidates","summary":"List uploaded contacts who are already on JEK","description":"Cross-references the caller's uploaded contacts with jek_account_users to power the New Chat picker. Returns [{ user_id, name, phone }].","tags":["Messaging"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/public/v1/push-config":{"get":{"operationId":"getApiPublicV1PushConfig","summary":"VAPID public key + service worker path (public)","description":"Browsers call this to obtain the VAPID applicationServerKey before subscribing to Web Push. Also returns the service worker path (/sw.js).","tags":["Push"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"vapid_public_key":"BFA…","service_worker":"/sw.js"}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/me/push-tokens":{"post":{"operationId":"postApiV1MePushTokens","summary":"Register a Web Push subscription or Expo push token","description":"For web, pass { platform: 'web', endpoint, p256dh, auth } from PushSubscription.toJSON(). For native, pass { platform: 'ios' | 'android', expo_token } from expo-notifications' getExpoPushTokenAsync(). Upsert is de-duped by endpoint / expo_token so it's safe to call on every app start.","tags":["Push"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string"},"endpoint":{"type":"string"},"p256dh":{"type":"string"},"auth":{"type":"string"},"expo_token":{"type":"string"}}}}}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MePushTokens","summary":"Unregister a push token (on sign-out or permission revoke)","tags":["Push"],"parameters":[{"name":"endpoint","in":"query","required":false,"schema":{"type":"string"}},{"name":"expo_token","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/legacy/svc/user/api/v2/signin/check":{"post":{"operationId":"postApiLegacySvcUserApiV2SigninCheck","summary":"Legacy signin/check (proxy to main.bindo.co)","description":"Thin proxy that forwards to https://main.bindo.co/svc/user/api/v2/signin/check with the same JSON body. Injects x-client-id / x-platform-id defaults when not provided. Returns whether the phone is already registered.","tags":["Legacy passthrough"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string"}},"required":["phone"]}}}}}},"/api/legacy/svc/user/api/v2/otp/send":{"post":{"operationId":"postApiLegacySvcUserApiV2OtpSend","summary":"Legacy otp/send (proxy)","tags":["Legacy passthrough"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string"},"action":{"type":"string"}},"required":["phone","action"]}}}}}},"/api/legacy/svc/user/api/v2/otp/verify":{"post":{"operationId":"postApiLegacySvcUserApiV2OtpVerify","summary":"Legacy otp/verify (proxy, persists user)","description":"Forwards to main.bindo.co/svc/user/api/v2/otp/verify. On success (code=200 with an access_token), the user is upserted into public.profiles on the JEK App Supabase so the admin dashboard sees every sign-in.","tags":["Legacy passthrough"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string"},"token":{"type":"string","description":"OTP session token from otp/send"},"aut_code":{"type":"string","description":"6-digit code the user typed"},"action":{"type":"string"}},"required":["phone","token","aut_code","action"]}}}}}},"/api/legacy/svc/user/api/v2/signup/completed":{"post":{"operationId":"postApiLegacySvcUserApiV2SignupCompleted","summary":"Legacy signup/completed (proxy, persists user)","description":"Forwards to main.bindo.co/svc/user/api/v2/signup/completed. On success, the new user (including first/last name) is upserted into public.profiles.","tags":["Legacy passthrough"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string","description":"Signup token from otp/verify"},"first_name":{"type":"string"},"last_name":{"type":"string"}},"required":["token","first_name","last_name"]}}}}}},"/api/legacy/user/b2c/me":{"get":{"operationId":"getApiLegacyUserB2cMe","summary":"Legacy /user/b2c/me (proxy)","description":"Forwards to main.bindo.co/user/b2c/me. Requires X-USER-ACCESS-TOKEN header (the legacy access token returned from otp/verify).","tags":["Legacy passthrough"],"parameters":[{"name":"X-USER-ACCESS-TOKEN","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/legacy/{...path}":{"post":{"operationId":"postApiLegacyByPath","summary":"Catch-all legacy proxy (any method)","description":"Any path under /api/legacy/* is forwarded verbatim to https://main.bindo.co/<same-path> with the request body, query string, and non-hop headers intact. Supports GET, POST, PUT, PATCH, DELETE and OPTIONS (CORS is open).","tags":["Legacy passthrough"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/me/saved-places":{"get":{"operationId":"getApiV1MeSavedPlaces","summary":"List the caller's saved places","description":"Home, Work and custom places, in the rider's display order. `meta.max_places` is the ceiling. Each place carries the normalized `{ placeId, text, fullText, lat, lng, provider }` that /api/v1/geo/place answers with, so it can be booked without a second resolve.","tags":["Saved Places"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":[{"id":"uuid","type":"home","label":"Home","place":{"placeId":"opaque","text":"One Victoria","fullText":"21 Shing Fung Road, Kai Tak","lat":22.3302,"lng":114.1992,"provider":"google"},"pickup_note":"Meet at the main entrance","sort_order":0,"version":3,"created_at":"2026-08-28T01:20:00Z","updated_at":"2026-08-28T01:25:00Z"}],"meta":{"total":1,"max_places":20}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeSavedPlaces","summary":"Save a Home, Work or custom place","description":"The place is resolved and normalized server-side from `placeId` (or accepted as a dropped pin when `place` carries lat, lng and a name); raw display text alone is refused with 400 `invalid_place`, and an id the provider cannot resolve with 422 `unresolvable_place`. One Home and one Work per rider: a second is 409 `home_already_exists` / `work_already_exists`. `pickup_note` is private until the rider books from this place.","tags":["Saved Places"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","description":"Default custom."},"label":{"type":"string","description":"Required for custom (max 60); defaults to Home / Work otherwise."},"placeId":{"type":"string","description":"A place id from /api/v1/geo/suggest or /api/v1/geo/place."},"place":{"type":"object","description":"Alternative to placeId: { lat, lng, text } for a map pin."},"pickup_note":{"type":"string","description":"Max 200 characters."}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/saved-places/{id}":{"get":{"operationId":"getApiV1MeSavedPlacesById","summary":"Read one saved place","tags":["Saved Places"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"patch":{"operationId":"patchApiV1MeSavedPlacesById","summary":"Edit a saved place","description":"Edit label, pickup_note (null clears), type, sort_order or the place itself (placeId / place). Send the `version` you last read: a stale one is 409 `version_conflict` and nothing is written. The response carries the new version.","tags":["Saved Places"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"number","description":"Optimistic-concurrency token from the last read."},"label":{"type":"string"},"pickup_note":{"type":"string"},"type":{"type":"string"},"placeId":{"type":"string"}}}}}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MeSavedPlacesById","summary":"Delete a saved place","description":"Soft delete, idempotent: a repeated delete answers the same `{ data: { id, deleted: true } }`. The place leaves future recommendations; historical trips are untouched.","tags":["Saved Places"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/saved-places/reorder":{"post":{"operationId":"postApiV1MeSavedPlacesReorder","summary":"Persist the display order","description":"Body `{ ids: [...] }` in the order to show. Places left out keep their relative order after the named ones. Answers the full list.","tags":["Saved Places"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"string"}},"required":["ids"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/emergency-contacts":{"get":{"operationId":"getApiV1MeEmergencyContacts","summary":"List the caller's emergency contacts","description":"`meta` carries `max_contacts` (configurable, default 5), `supported_channels` (today `sms`) and `verification_delivery`: `simulated` on the staging fulfilment env (X-JEK-Env: staging; no message is sent and the platform's fixed test code confirms) or `unavailable` where no SMS provider exists yet.","tags":["Emergency Contacts"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":[{"id":"uuid","name":"Alex Chan","phone":"+85291234567","relationship":"family","is_primary":true,"verification_status":"verified","notification_channels":["sms"],"share_trip_by_default":false,"version":2,"created_at":"2026-08-28T01:20:00Z","updated_at":"2026-08-28T01:25:00Z"}],"meta":{"total":1,"max_contacts":5,"supported_channels":["sms"],"verification_delivery":"simulated"}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeEmergencyContacts","summary":"Add an emergency contact","description":"The phone is normalized to E.164 (a bare local number is read as +852; send `default_country` to change that). Refused: an invalid number (400 `invalid_phone`), the rider's own number (400 `invalid_phone`), a number already saved (409 `duplicate_contact`), and one past the ceiling (409 `contact_limit_reached`). New contacts are `pending` until verified; nothing automated reaches them before that.","tags":["Emergency Contacts"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string","description":"E.164 preferred."},"relationship":{"type":"string"},"notification_channels":{"type":"string","description":"Only `sms` is accepted."},"share_trip_by_default":{"type":"boolean"}},"required":["name","phone"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/emergency-contacts/{id}":{"get":{"operationId":"getApiV1MeEmergencyContactsById","summary":"Read one emergency contact","tags":["Emergency Contacts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"patch":{"operationId":"patchApiV1MeEmergencyContactsById","summary":"Edit contact details or preferences","description":"Conditional on `version` (409 `version_conflict` when stale). Changing `phone` resets `verification_status` to `pending` — the consent belonged to the old number.","tags":["Emergency Contacts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"number"},"name":{"type":"string"},"phone":{"type":"string"},"relationship":{"type":"string"},"notification_channels":{"type":"string"},"share_trip_by_default":{"type":"boolean"}}}}}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MeEmergencyContactsById","summary":"Delete an emergency contact","description":"Soft delete, idempotent. The contact stops receiving anything immediately; if it was the primary, nobody is promoted in its place.","tags":["Emergency Contacts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/emergency-contacts/{id}/verification":{"post":{"operationId":"postApiV1MeEmergencyContactsByIdVerification","summary":"Send the verification / consent challenge","description":"Issues a 6-digit code valid for 10 minutes, at most once a minute per contact (429 `rate_limited`). 409 `already_verified` for a verified contact. 503 `notification_provider_unavailable` where no delivery exists (alpha/production today); on staging the send is simulated and `meta.delivery` says so.","tags":["Emergency Contacts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/emergency-contacts/{id}/verification/confirm":{"post":{"operationId":"postApiV1MeEmergencyContactsByIdVerificationConfirm","summary":"Confirm the verification code","description":"200 with `meta.outcome` verified; 400 `invalid_code` (with `attempts_remaining`, 5 per code); 409 `verification_not_started`; 410 `verification_expired` when the code lapsed or the attempts ran out.","tags":["Emergency Contacts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"}},"required":["code"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/emergency-contacts/{id}/set-primary":{"post":{"operationId":"postApiV1MeEmergencyContactsByIdSetPrimary","summary":"Make this the primary contact","description":"Zero or one primary per rider; the previous primary steps down. Idempotent.","tags":["Emergency Contacts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/profile-image":{"get":{"operationId":"getApiV1MeProfileImage","summary":"The caller's profile image","description":"`data.profile_image` is null when the caller has none in this app. The `url` is a signed, short-lived link into a private bucket — render it, do not store it; `expires_in` is its life in seconds, and re-reading this endpoint (or GET /api/v1/me, which carries the same object) mints a fresh one.","tags":["Profile"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"profile_image":{"url":"https://…/storage/v1/object/sign/profile-images/…","expires_in":3600,"content_type":"image/jpeg","byte_size":84213,"created_at":"2026-09-18T02:10:00Z","updated_at":"2026-09-18T04:30:00Z"}}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeProfileImage","summary":"Upload a profile image, or replace the current one","description":"Send `{ image }` as a data URL or bare base64, or a multipart body with an `image` part — the same pair the licence scan takes, so a client already doing one needs nothing new. This is BOTH the upload and the edit: there is one current image per rider per app, so a second POST replaces the first and the old object is deleted. 201 when the rider had none, 200 when one was replaced (`data.replaced` says the same thing). The format is read off the bytes, never off the declared content type: JPEG, PNG and WebP are stored, and HEIC — what an iPhone hands over by default — is refused 415 `image_format_unsupported` with `detected` and `accepted`, because nothing but iOS can display it and this server has no way to transcode. Over 5 MB decoded is 413 `image_too_large` carrying `max_bytes`; bytes that are not an image at all are 400 `invalid_image`; no image in the body is 400 `image_required`.","tags":["Profile"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"image":{"type":"string","description":"The photo: a data: URL or bare base64 in a JSON body, or the `image` part of a multipart/form-data body."}},"required":["image"]}}}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MeProfileImage","summary":"Remove the current profile image","description":"The row and the stored object both go. Idempotent: a rider with no image answers 200 with `removed: false` rather than 404, so a retry over a flaky connection never has to tell the two apart. Only this app's image is removed — the same account's picture in the other app is untouched.","tags":["Profile"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"profile_image":null,"removed":true}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/ride/rating-reasons":{"get":{"operationId":"getApiV1RideRatingReasons","summary":"Rating reason catalog (managed, versioned)","description":"The reason chips a finished Lane 1 trip offers, with a `catalog_version` derived from the catalog's content so a client can cache against it. Each entry carries `code`, `label_key` (a translation key the client resolves — the server never sends an English label, which would be wrong in three of the four shipped locales), an operator `label` override or null, and `sentiment`. Managed in `system_configs['ride.rating']` beside the rating windows; POST /api/v1/me/rides/{id}/rating validates against this same list, so an added reason is stored rather than dropped.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/ride/preflight":{"post":{"operationId":"postApiV1RidePreflight","summary":"May this trip be booked? (Lane 1 serviceability)","description":"The authoritative Lane 1 answer Standard Ride and JEK Wish both run before opening a quote/request screen. 200 = serviceable, with the summed route (`distance_m`, `duration_min`, stitched `points`). 422 `OUT_OF_SERVICE_AREA` = definitively unsupported — a waypoint outside every configured service area, or a leg the router says has no road; refuse, do not retry. 503 `ROUTING_UNAVAILABLE` = temporary, offer Retry (never shown as \"unavailable here\"). 401 `AUTH_REQUIRED`. Service areas come from `system_configs['ride.service_area']`, shared with /api/v1/passenger/serviceability. `dropoff` is optional: with a pickup alone this answers the area question only, which is what a landing screen needs. No supply probe — that would sign with Wonder DA, which Lane 1 exists to avoid. Also returns `fulfilment` (`jek` = our own fleet, `partner` = third-party capacity through Wonder for a market where JEK has no fleet) and `fulfilment_area`. THE CLIENT READS THIS, IT NEVER DECIDES IT: a JEK passenger books through the same surface wherever they are standing, and the server picks the supplier from each area's `fulfilment`, maintained at /admin/ride/service-areas. The PICKUP decides — a Hong Kong pickup bound for Shenzhen is still `jek`. On `partner` our own service areas stop constraining the other waypoints (they describe where OUR fleet drives), though an undrivable leg still refuses. `fulfilment` is null on a 422 whose pickup nobody serves.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"order_type":{"type":"string","description":"ride | wish (default ride)"},"pickup":{"type":"object","description":"{ lat, lng, label? }"},"dropoff":{"type":"object","description":"{ lat, lng, label? }; omit for a pickup-only area check"},"stops":{"type":"array","description":"Ordered intermediate waypoints, at most 4"}},"required":["pickup"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/ride/availability":{"post":{"operationId":"postApiV1RideAvailability","summary":"Live supply and pickup ETA (Lane 1)","description":"Is there a car for this pickup, which classes can serve it, and how long until one arrives — the third pre-booking question, beside /ride/preflight (may we drive here) and /ride/quote (what does it cost). Counts `da_drivers` that are online, approved, carrying a fix newer than 150s, inside the radius and not already on a job; a driver whose vehicle declares no dispatch class is UNRESTRICTED and counts for every class, exactly as the accept path reads it. Deliberately NOT /passenger/serviceability's probe, which signs with Wonder DA and counts street taxis that cannot take a jek_rides order. `supply.status`: `ok` (measured, cars) | `none` (measured, none) | `unavailable` (OUR outage — show a retry, never \"no cars\") | `not_applicable` (this area's `fulfilment` is partner/fleet, so our own fleet is not the answer). `eta` is the road route to the NEAREST car, falling back to the same straight-line estimate the live ride's `remaining_route` uses and labelled `source: estimate`; each class routes its own nearest eligible car, with parallel four-second timeouts and one call per distinct driver. Classes sharing a car share its ETA. A failed route falls back from that car's own coordinates with eta_source: estimate and eta_confidence: low. Never answers 5xx for an outage. This reports supply, it does NOT authorise a booking — /ride/preflight stays the coverage authority. Cached 10s on the rounded request.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"supply":{"status":"ok","count":3,"nearest_m":420,"radius_m":2500,"fix_max_age_s":150},"eta":{"pickup_eta_min":4,"duration_s":228,"distance_m":1310,"source":"provider"},"classes":[{"vehicle_type_id":"…","name":"Standard Taxi","available":true,"count":3,"nearest_m":420,"eta_min":4},{"vehicle_type_id":"…","name":"Premium Taxi","available":false,"count":0,"nearest_m":null,"eta_min":null}],"area":{"code":"hk","name":"Hong Kong"},"fulfilment":"jek","config_version":"default-2026-08-28","cached":false}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pickup":{"type":"object","description":"{ lat, lng }"},"vehicle_class_ids":{"type":"string","description":"Narrow to the classes on screen; omit for the whole card. Max 24."},"radius_m":{"type":"number","description":"200–20000; defaults to the service-area config's supply_radius_m."}},"required":["pickup"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/ride/nearby":{"post":{"operationId":"postApiV1RideNearby","summary":"Nearby cars on the map (Lane 1)","description":"The pins the where-to map draws before anything is assigned: JEK's OWN drivers around a point, from the same `da_drivers` probe /ride/availability counts (online, approved, fix newer than 150s, inside the radius, not on a job) — with no routing call, so a map may poll it every 12s. Deliberately NOT /passenger/nearby, which proxies Wonder DA's taxis/nearby with partner credentials: that is the lane a JEK rider is handed down OUTSIDE Hong Kong, and it returns street taxis that cannot take a jek_rides order. `vehicles[]` is nearest first, capped at `limits.max_vehicles` (supply.count stays the true number); each `id` is a pseudonym that is stable across polls and rotates daily — never a driver id. `supply.status` as on /ride/availability: `ok` | `none` (measured, empty street) | `unavailable` (OUR outage — not an empty street, not cached) | `not_applicable` (this area's fulfilment is partner/fleet; somebody else's cars serve it). Never 5xx for an outage. Reports supply, does not authorise a booking. Cached 10s on the rounded request.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"supply":{"status":"ok","count":3,"nearest_m":140,"radius_m":2500,"fix_max_age_s":150},"vehicles":[{"id":"9f2c1e7a4b3d5c60","lat":22.283,"lng":114.159,"distance_m":140,"fix_age_s":12},{"id":"0b7d4e2f9a1c3e58","lat":22.29,"lng":114.165,"distance_m":1100,"fix_age_s":40}],"area":{"code":"hk","name":"Hong Kong"},"fulfilment":"jek","config_version":"default-2026-08-28","cached":false,"limits":{"max_radius_m":20000,"max_vehicles":50}}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pickup":{"type":"object","description":"{ lat, lng }; `location` is accepted as an alias"},"radius_m":{"type":"number","description":"200–20000; defaults to the service-area config's supply_radius_m."}},"required":["pickup"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/ride/directions":{"post":{"operationId":"postApiV1RideDirections","summary":"Route geometry, distance & duration","description":"Server-side Google Directions proxy. Returns an encoded polyline plus distance (metres) and duration (seconds) for a pickup → destination pair. Used by the booking wizard so the browser never needs a Maps key.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"origin":{"type":"object","description":"{ lat, lng } or address string"},"destination":{"type":"object","description":"{ lat, lng } or address string"}},"required":["origin","destination"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/ride/wish-analysis":{"post":{"operationId":"postApiV1RideWishAnalysis","summary":"Analyse a passenger 'wish' request","description":"Runs the free-text (or transcribed voice) wish through the analysis model with the route context. Returns feasibility, whether it is really a plain ride, clarification questions, estimated extra time/cost and a suggested tip range.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"example":{"data":{"rideOnly":false,"feasible":true,"clarifications":["Which pharmacy branch should the driver use?"],"extra_minutes":18,"extra_cost":96,"suggested_tips":[20,50,100]}}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"wish":{"type":"string"},"pickup":{"type":"string"},"destination":{"type":"string"},"distance_km":{"type":"number"},"base_fare":{"type":"number"}},"required":["wish"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/cancel":{"post":{"operationId":"postApiV1MeRidesByIdCancel","summary":"Cancel the caller's own ride or wish order","description":"Marks the caller's own order cancelled and records the reason in the wish metadata. Returns 409 if the order is already completed/cancelled, if a driver has already accepted it (code WISH_ALREADY_CLAIMED), or if the status changed concurrently (code WISH_CHANGED). No refund transaction is written.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"Free-text cancel reason (max 200 chars)"}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/da/wishes/{id}/status":{"post":{"operationId":"postApiV1DaWishesByIdStatus","summary":"Advance a wish order's status","description":"Driver-side state transition for a wish the caller holds: accepted → arrived → in_trip → completed, with cancelled available until the trip starts. The body field is `state`, NOT `status`. Returns `{ data }` — the full updated wish row, which is what the driver app renders from rather than re-reading the feed. Refuses a disallowed move with 409 `{ error, allowed: [...] }` naming the transitions it would have accepted, 409 \"Wish changed concurrently\" if the status moved under the caller, and 404 for a wish the caller does not hold.","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"state":{"type":"string","description":"Target state: arrived | in_trip | completed | cancelled"},"reason":{"type":"string","description":"Free-text cancel reason (max 200 chars), recorded when state is `cancelled`"}},"required":["state"]}}}},"security":[{"driverBearer":[]}]}},"/api/v1/ride/wish-quote":{"post":{"operationId":"postApiV1RideWishQuote","summary":"Re-price an already-analysed wish","description":"Recomputes a signed fare quote when the vehicle class or route changes after /api/v1/ride/wish-analysis, without re-running the language model. Wish facts sent by the client are clamped server-side, so a cheaper wish cannot be forced.","tags":["Ride"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vehicleTypeId":{"type":"string"},"distanceKm":{"type":"number"},"durationMin":{"type":"number"},"analysis":{"type":"object","description":"Analysis payload returned by /api/v1/ride/wish-analysis"}},"required":["vehicleTypeId","distanceKm"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/driver-location":{"get":{"operationId":"getApiV1MeRidesByIdDriverLocation","summary":"Last known driver location for an order","description":"Rider-facing read of the assigned driver's most recent GPS ping. Poll while the order is accepted / in progress.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/da/wishes/{id}":{"get":{"operationId":"getApiV1DaWishesById","summary":"Wish order detail for the assigned driver","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/da/wishes/{id}/expense":{"get":{"operationId":"getApiV1DaWishesByIdExpense","summary":"List expense claims filed against a wish","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]},"post":{"operationId":"postApiV1DaWishesByIdExpense","summary":"File an expense claim for reimbursement","description":"Submits an out-of-pocket expense (amount, note, uploaded receipt path) for the wish the driver is running.","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number"},"receiptPath":{"type":"string","description":"Storage path returned by the upload-url endpoint"},"note":{"type":"string"}},"required":["amount"]}}}},"security":[{"driverBearer":[]}]}},"/api/v1/da/wishes/{id}/expense/upload-url":{"post":{"operationId":"postApiV1DaWishesByIdExpenseUploadUrl","summary":"Signed upload URL for an expense receipt","description":"Returns a short-lived signed URL into the driver-documents bucket for the receipt image.","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/supplier/quotes":{"post":{"operationId":"postApiV1SupplierQuotes","summary":"Fare quotes for a demand platform","description":"The quote counterpart to POST /api/v1/supplier/orders. Authenticates with the shared X-Internal-Token (a partner is a platform, not a signed-in rider) and answers in the caller's registered dialect — Wonder callers get {code,message,data:{quotes,batch_id}}, native callers get {data:{quotes,batch_id}}. Priced from the same rate card the booking uses; batch_id ties a later booking back to the fares it was offered. Quotes are honoured for 10 minutes. Distance/duration echo what the caller sent — this path does not geocode or route. The classes offered are the globally enabled ones NARROWED by this partner's allow list at /admin/ride/partners: a partner with no list is offered everything, one that is not sold a named class is answered unknown_class, and a list that leaves nothing answers 403 no_partner_classes (not 503 no_card, which means the rate card itself is empty).","tags":["Supply platform"],"parameters":[{"name":"x-internal-token","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vehicle_type_codes":{"type":"string","description":"Classes to price, in dispatch vocabulary"},"from":{"type":"object","description":"{ lat, lng, text? }"},"to":{"type":"object","description":"{ lat, lng, text? }"},"distance_km":{"type":"number"}},"required":["vehicle_type_codes","from","to"]}}}}}},"/api/v1/me/rides/{id}":{"get":{"operationId":"getApiV1MeRidesById","summary":"Full detail of one of the caller's rides","description":"The single read the trip screen polls. Scoped by id AND user_id — a ride belonging to someone else answers 404, never 403, so ids cannot be probed. The payload is additive: each feature (driver, cancellation, receipt, events, coupons…) contributes one sibling key, while `fare` stays a bare number for compatibility with older app builds.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/cancel-preview":{"get":{"operationId":"getApiV1MeRidesByIdCancelPreview","summary":"Preview cancelling this ride","description":"Answers 'what would happen if I cancelled right now' — whether it is allowed and what it would cost — using the same stored policy and evaluator as POST /cancel, so the preview cannot disagree with the charge. Writes nothing.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/contact":{"get":{"operationId":"getApiV1MeRidesByIdContact","summary":"Masked contact channels to the assigned driver","description":"How the passenger reaches the driver without either side learning the other's number: a pooled virtual number held for this ride, plus an in-app chat conversation. Offered only while the ride is live; an empty number pool returns 200 with available:false rather than ever exposing the driver's real mobile.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeRidesByIdContact","summary":"Open a masked contact channel","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","description":"phone | chat"}},"required":["channel"]}}}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MeRidesByIdContact","summary":"Release the ride's masked contact channel","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/destination":{"post":{"operationId":"postApiV1MeRidesByIdDestination","summary":"Change where a ride is going","description":"Re-rides the pricing server-side: the request names where to, never a fare. Only the difference between the old and new route's class price moves — boosts, surcharges and tips on top stay on top — and after a driver has claimed the ride the fare can only go up.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"destination":{"type":"object","description":"{ lat, lng, text? }"}},"required":["destination"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/driver":{"get":{"operationId":"getApiV1MeRidesByIdDriver","summary":"Live identity of the assigned driver","description":"Name, plate and rating of the driver working this ride, while they are working it. Gated exactly like /driver-location: caller's own ride only (404 otherwise), and the response goes dark when the trip ends. The permanent record stays on GET /api/v1/me/rides/{id}'s driver block.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/events":{"get":{"operationId":"getApiV1MeRidesByIdEvents","summary":"Incremental event feed for a ride","description":"Cursor-keyed replay of what has changed since the client last asked. The realtime nudge is a Supabase Realtime broadcast on `realtime.topic` (`order:ride:{id}`), published on every lifecycle transition; subscribe to it and call this with the cursor when it fires, or poll at `poll_after_ms` without it. Not SSE/websockets: the deployment is a Cloudflare Worker with no long-lived connections. Events carry `at` (when the row says it happened) beside `observed_at`. Also folds in `driver_location`, `remaining_route` (driver → pickup/drop-off, provider route or a labelled straight-line estimate), `timestamps`, and `actions` — including `can_cancel` from the same cancellation policy the detail and cancel endpoints use, and `can_end_trip`.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from the previous response","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/end":{"post":{"operationId":"postApiV1MeRidesByIdEnd","summary":"End a Wish / ride early (passenger)","description":"Only while `in_trip` — the same predicate the detail publishes as `actions.can_end_trip`. Idempotent: a repeat answers the same success with `already: true`. Writes `status=completed`, `completed_at` and `wish.earlyEnd`; the driver's trip count is bumped exactly as their own completion would (never on a test job) and both parties are notified. No money moves (Module A). 409 `not_in_trip` / `already_finished` / `RIDE_CHANGED`.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","description":"Max 200 chars"}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/share":{"get":{"operationId":"getApiV1MeRidesByIdShare","summary":"List the rider's trip-sharing links for a ride","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeRidesByIdShare","summary":"Create a secure trip-sharing link","description":"Mints a 256-bit token (returned once, stored hashed) and a public URL (`GET /api/public/v1/trip-shares/{token}`) that serves a minimised live view: phase, short pickup/drop-off labels, driver first name + plate + vehicle, the car's last position while the trip is active, lifecycle times. Never the rider's name/phone, the driver's phone, the fare or addresses beyond the labels. Default TTL 4h (`ttl_ms`, 10 min – 24 h), at most 5 live links per order, revocable. 409 `order_finished` / `too_many_links`.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ttl_ms":{"type":"number"}}}}}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MeRidesByIdShare","summary":"Revoke trip-sharing links","description":"Every live link on the ride, or one by `?id=`.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"id","in":"query","required":false,"description":"A single share id","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/public/v1/trip-shares/{token}":{"get":{"operationId":"getApiPublicV1TripSharesByToken","summary":"Open a shared trip link (public)","description":"Unauthenticated: the token is the credential. Serves a small HTML page to browsers (auto-refreshes while the trip is active) and the JSON `SharedTripView` to `Accept: application/json` / `?format=json`. Invalid, expired and revoked tokens all answer the same 404. Rate-limited per token.","tags":["Passenger"],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/me/rides/{id}/sos":{"post":{"operationId":"postApiV1MeRidesByIdSos","summary":"Trigger an SOS and notify emergency contacts","description":"Idempotent on `Idempotency-Key` (header or `idempotency_key` body): the same key returns the same event and sends nothing again; without one, presses within a minute collapse. Records the event, mints a share link, and sends one SMS to each VERIFIED emergency contact (optionally narrowed by `contact_ids`) through the configured SMS provider. Every attempt is a `notifications[]` row with a delivery status: `sent`/`failed` with the provider reference, `simulated` on staging without a provider, `skipped:notification_provider_unavailable` elsewhere without one, `skipped:contact_unverified`. 201 on create, 200 on a repeat.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"},"note":{"type":"string"},"contact_ids":{"type":"string"}}}}}},"security":[{"userBearer":[]}]},"get":{"operationId":"getApiV1MeRidesByIdSos","summary":"SOS events on a ride with delivery audit","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MeRidesByIdSos","summary":"Mark an SOS resolved","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"event_id","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/live-activity":{"post":{"operationId":"postApiV1MeRidesByIdLiveActivity","summary":"Register an iOS Live Activity for a ride","description":"Trip-scoped: the ActivityKit push token is bound to this ride only (unique per ride + token; a repeat re-registers). Updates are pushed on every lifecycle transition and an `end` on completion/cancellation/expiry, through APNs when `APNS_*` is configured — `meta.configured: false` means the registration is kept and nothing is faked. 409 `order_finished`.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"push_token":{"type":"string"},"activity_id":{"type":"string"},"platform":{"type":"string","description":"Default ios"}},"required":["push_token"]}}}},"security":[{"userBearer":[]}]},"get":{"operationId":"getApiV1MeRidesByIdLiveActivity","summary":"The caller's Live Activity registrations on a ride","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MeRidesByIdLiveActivity","summary":"End a ride's Live Activities","description":"All of them, or one by `?push_token=`. Sends ActivityKit `end` when APNs is configured.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"push_token","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/da/wishes/{id}/messages":{"get":{"operationId":"getApiV1DaWishesByIdMessages","summary":"Read the passenger chat thread for a held order (driver)","description":"The driver's side of the thread `POST /api/v1/me/rides/{id}/contact {channel:'chat'}` opens. Only for an order this driver holds (`driver_id`); `can_post` reflects the same liveness gate the passenger side applies. `viewer_id` is the caller's own messaging id — compare it with a message's `sender_id` to tell their messages from the passenger's; a client has no other way to learn what JEK calls it.","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"number"}},{"name":"before","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]},"post":{"operationId":"postApiV1DaWishesByIdMessages","summary":"Reply to the passenger (driver)","description":"Sends into the ride's thread, creating it if the passenger has not opened it yet. Only while the driver holds a live order — 409 `RIDE_NOT_LIVE` otherwise. The passenger is pushed the message like any other.","tags":["Driver Assistant"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","description":"Max 4000"}},"required":["body"]}}}},"security":[{"driverBearer":[]}]}},"/api/v1/passenger/trips/{id}/events":{"get":{"operationId":"getApiV1PassengerTripsByIdEvents","summary":"Resumable event feed for a Standard Ride","description":"A position in the trip's audit log (`jek_passenger_trip_events`): `?cursor=` is the last event the client saw, the answer is every passenger-visible event after it in log order, and the new cursor is the last one returned. `?refresh=1` first polls Wonder DA (the detail's refresh leg) so one call both advances the trip and reads what moved. The realtime nudge is the broadcast topic `order:trip:{id}`. Payloads are never spread — each event carries `{id,type,source,from,to,at,detail}`.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"refresh","in":"query","required":false,"description":"`1` to poll upstream first","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/end":{"post":{"operationId":"postApiV1PassengerTripsByIdEnd","summary":"End a Standard Ride early (passenger)","description":"Only while `in_progress` — the predicate the detail publishes as `capabilities.allow_early_end_trip`. Idempotent (`already: true` on a repeat). Completes the trip on JEK's side (`status`, `completed_at`, `early_end`) and records `trip.ended_early`; Wonder DA has no passenger-side end, so `upstream.ended` is false and `supply_status` keeps following the supplier's order without ever reopening the trip. 409 `trip_not_in_progress` / `trip_changed`.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string"}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/chat":{"post":{"operationId":"postApiV1PassengerTripsByIdChat","summary":"Open a chat thread with the driver of a Standard Ride","description":"The Wonder driver must first resolve to a Driver Pro profile (matched by Wonder id or phone; the outcome is remembered on the trip and is what `capabilities.allow_chat` / `allow_chat_reason` report). Opens the same thread model the Wish lane uses, so `/api/v1/me/messaging/*` and the driver's `/api/v1/da/wishes/{id}/messages` serve it. 409 `driver_not_on_jek` / `TRIP_NO_DRIVER` / `TRIP_NOT_LIVE`. `GET` reads the state without opening.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/cancel-reasons":{"get":{"operationId":"getApiV1PassengerTripsByIdCancelReasons","summary":"The managed cancellation-reason catalog for this trip's stage","description":"The same server-owned catalog the Wish lane uses (`system_configs['ride.cancellation']`), filtered to the trip's stage, with `catalog_version`. `POST …/cancel` accepts `reason_code` (or the legacy numeric `reason_id`); only catalog codes are stored as codes, anything else becomes a note. `GET /api/v1/passenger/catalogs` returns the whole catalog with the same version.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/share":{"post":{"operationId":"postApiV1PassengerTripsByIdShare","summary":"Create a trip-sharing link for a Standard Ride","description":"Same contract as `POST /api/v1/me/rides/{id}/share`; `GET` lists, `DELETE` revokes (`?id=` for one).","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ttl_ms":{"type":"number"}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/sos":{"post":{"operationId":"postApiV1PassengerTripsByIdSos","summary":"Trigger an SOS on a Standard Ride","description":"Same contract as `POST /api/v1/me/rides/{id}/sos`; `GET` lists events with delivery audit, `DELETE ?event_id=` resolves.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"},"note":{"type":"string"},"contact_ids":{"type":"string"}}}}}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/live-activity":{"post":{"operationId":"postApiV1PassengerTripsByIdLiveActivity","summary":"Register an iOS Live Activity for a Standard Ride","description":"Same contract as `POST /api/v1/me/rides/{id}/live-activity`, plus the token is bound upstream (Wonder DA `bind_apn_token`, best-effort, `upstream_bound`) once the supplier order exists so Wonder's own updates reach the same activity. `GET` lists, `DELETE ?push_token=` ends.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"push_token":{"type":"string"},"activity_id":{"type":"string"}},"required":["push_token"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/location":{"post":{"operationId":"postApiV1MeRidesByIdLocation","summary":"Publish the passenger's live position","description":"The mirror of /api/v1/da/location: the passenger shares their fix with the assigned driver, only while a driver is coming. Treated as a home address — caller's own ride only, live window enforced, DELETE stops sharing immediately.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"}},"required":["lat","lng"]}}}},"security":[{"userBearer":[]}]},"get":{"operationId":"getApiV1MeRidesByIdLocation","summary":"Read the passenger's shared position / visibility","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"delete":{"operationId":"deleteApiV1MeRidesByIdLocation","summary":"Stop sharing the passenger's position","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/rating":{"get":{"operationId":"getApiV1MeRidesByIdRating","summary":"Read the rating given for a trip","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeRidesByIdRating","summary":"Rate a completed trip","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rating":{"type":"number","description":"1–5"},"comment":{"type":"string"}},"required":["rating"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/receipt":{"get":{"operationId":"getApiV1MeRidesByIdReceipt","summary":"Itemised receipt for a ride","description":"An itemised account of what the ride cost and what was actually charged, assembled from the transaction rows and the sealed pricing pin stored at booking. It reports; it does not re-price — editing the rate card later cannot rewrite an issued receipt.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/tip":{"get":{"operationId":"getApiV1MeRidesByIdTip","summary":"Read the tip left on a trip","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeRidesByIdTip","summary":"Tip the driver after the trip","description":"Adds money for the driver once the trip is finished — the mirror image of /boost, which is only legal while the ride is still waiting. Same ownership check and payment ledger as boost.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","description":"Tip in major currency units (> 0)"}},"required":["amount"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/me/rides/{id}/payment-order":{"post":{"operationId":"postApiV1MeRidesByIdPaymentOrder","summary":"Open the ride's Wonder payment order (in-app SDK)","description":"Creates ONE Wonder payment order for the ride and returns its `number` plus the `client_secret` the in-app Wonder Payment SDK renders its payment sheet against — no card data, token or 3DS step touches this server. The fare is one `Charge` line and each voucher is a NEGATIVE `Charge` line beside it, so the passenger owes the discounted total and the order itself records why; `jek_rides.fare` is untouched because it is the driver's payout basis. Call it when the passenger is about to pay, not at booking: the client_secret expires in about two hours and Wonder offers no way to mint a second one for an existing order, so a repeat call answers with the order but `client_secret_available: false`. Idempotent by construction — the order is keyed at Wonder on the ride id, and Wonder refuses a duplicate reference, so one ride can never have two orders. 409 FREE_RIDE when a voucher covers the whole fare. Card payment is open on EVERY declared environment — dev and staging hit gateway-stg where no real money moves, while alpha and production share one payment appId and both bill Jek Limited's real merchant record, so alpha is not a rehearsal. Only two things close it: `JEK_RIDE_PAYMENT_ORDERS_ENABLED=0` as an emergency stop, and an environment that has not declared itself, which cannot name the gateway it would bill.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"get":{"operationId":"getApiV1MeRidesByIdPaymentOrder","summary":"Was the ride paid for? (and record it)","description":"Reads the merchant's own order back from Wonder and, when it reports the money arrived, records the fare leg in jek_payment_charges. THE ORDER IS THE AUTHORITY: a client saying the payment sheet succeeded is a claim, `correspondence_state` on Jek Limited's order is a fact. Safe to poll — the charge row is keyed on the ride and leg, so one payment is recorded once however many times this is called. Answers `{data: null}` when no order has been opened.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/me/coupons":{"get":{"operationId":"getApiV1MeCoupons","summary":"List the caller's ride coupons","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]},"post":{"operationId":"postApiV1MeCoupons","summary":"Price a coupon against a specific trip","description":"Server-side 'HK$230, less HK$40 = HK$190' for the booking screen: prices the selection from the rate card (the same priceSelection the quote and booking routes use) and applies the coupon rule. The request never carries a fare, so the discount cannot be chosen by the client.","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"coupon_id":{"type":"string"},"vehicleTypeId":{"type":"string"},"distanceKm":{"type":"number"}},"required":["coupon_id","vehicleTypeId","distanceKm"]}}}},"security":[{"userBearer":[]}]}},"/api/webhooks/wonder-payment":{"post":{"operationId":"postApiWebhooksWonderPayment","summary":"Wonder payment webhook (M1OS bills)","description":"Receives Wonder order events for M1OS payment links. Authenticated three ways: a Wonder-RSA-SHA256 signature over the exact body bytes (primary), the shared webhook secret, and an allow-listed callback path. Replays and unsigned bodies are refused. GET on the same path is a health probe.","tags":["Webhooks"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}}}},"/api/v1/geo/route_":{"post":{"operationId":"postApiV1GeoRoute","summary":"Route polyline, distance and ETA between two points","description":"Driving route used to render the booking map and feed distance into fare quotes. The path keeps its trailing underscore (the bare /route segment is reserved by the router).","tags":["Geo"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pickup":{"type":"object","description":"{ lat, lng }"},"dropoff":{"type":"object","description":"{ lat, lng }"}},"required":["pickup","dropoff"]}}}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/trips/{id}/{action}":{"post":{"operationId":"postApiV1PassengerTripsByIdByAction","summary":"Trip lifecycle action (driver and passenger)","description":"Multiplexed state-machine action endpoint. Passenger actions and driver actions (arrived, start, end) share the route; the caller's role decides which transitions are allowed.","tags":["Passenger"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"action","in":"path","required":true,"description":"e.g. arrived | start | end | rate","schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/passenger/transactions":{"get":{"operationId":"getApiV1PassengerTransactions","summary":"Passenger payment transactions","tags":["Passenger"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/v1/da/vehicle-catalogue":{"get":{"operationId":"getApiV1DaVehicleCatalogue","summary":"Vehicle catalogue for driver registration","description":"Makes / models / classes a driver can bind to their vehicle; the same catalogue M1OS servicing uses.","tags":["Driver Assistant"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"driverBearer":[]}]}},"/api/v1/standard-rides/{path}":{"post":{"operationId":"postApiV1StandardRidesByPath","summary":"Standard Ride wish/ride creation (Flutter-compatible)","description":"Splat route that mirrors the legacy Flutter client's standard-ride calls onto the JEK backend; accepts the Wonder session in the body for legacy clients.","tags":["Rides"],"parameters":[{"name":"path","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"userBearer":[]}]}},"/api/admin/ride-orders":{"get":{"operationId":"getApiAdminRideOrders","summary":"List ride & wish orders across the platform","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/wish-config":{"get":{"operationId":"getApiAdminWishConfig","summary":"Read wish pricing / premium configuration","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminWishConfig","summary":"Update wish pricing / premium configuration","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/wish-expense-claims":{"get":{"operationId":"getApiAdminWishExpenseClaims","summary":"List driver expense claims on wish orders","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminWishExpenseClaims","summary":"Approve or reject a wish expense claim","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/demand-callbacks":{"get":{"operationId":"getApiAdminDemandCallbacks","summary":"Inspect inbound demand-partner callbacks","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminDemandCallbacks","summary":"Register or replay a demand-partner callback","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/wonder-da-credentials":{"get":{"operationId":"getApiAdminWonderDaCredentials","summary":"Wonder DA partner credentials per gateway env (never the secret)","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminWonderDaCredentials","summary":"Set one env's Wonder DA partner ID / app ID / app secret","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"delete":{"operationId":"deleteApiAdminWonderDaCredentials","summary":"Drop one env's console-set Wonder DA credentials","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/wonder-beams-credentials":{"get":{"operationId":"getApiAdminWonderBeamsCredentials","summary":"Wonder beams service credentials per env (never the secret); ?probe=1 issues a real token","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminWonderBeamsCredentials","summary":"Set one env's beams app ID / app secret / gateway override","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"delete":{"operationId":"deleteApiAdminWonderBeamsCredentials","summary":"Drop one env's console-set beams credentials","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/wonder-beams-call":{"post":{"operationId":"postApiAdminWonderBeamsCall","summary":"Send one request through the Wonder gateway to /service/beams/*","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/wonder-beams-sms":{"post":{"operationId":"postApiAdminWonderBeamsSms","summary":"Send one SMS via beams notification/send_sms","description":"Composes and validates the documented body before a token is spent — `client_id` is filled in from the env's saved beams config unless the caller names one — then returns the upstream's status and body verbatim alongside the `x-request-id` the send carried — the handle Wonder traces a missing message by. A 4xx here means nothing left api.jek.app; beams' own refusal arrives as 200 with `ok: false`.","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/da-signin-config":{"get":{"operationId":"getApiAdminDaSigninConfig","summary":"Driver app sign-in + Sendbird config per Wonder env (never the sealed secrets)","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminDaSigninConfig","summary":"Set one env's sign-in credentials or Sendbird ids","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"delete":{"operationId":"deleteApiAdminDaSigninConfig","summary":"Drop one env's console-set sign-in credentials","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/fare-rules":{"get":{"operationId":"getApiAdminFareRules","summary":"Read the fare rate-rule catalogue","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminFareRules","summary":"Create or edit a fare rule","description":"Trip-kind guard is DB-enforced; `commissionable` marks the components commission is computed on (the Wonder settlement adapter reads it); renaming a rule or flipping a flag changes future pricing only, never issued receipts.","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/fare-rules/verify":{"post":{"operationId":"postApiAdminFareRulesVerify","summary":"Dry-run a route against the fare rules","description":"Read-only verification harness: send an encoded polyline (+ optional overrides) and get back what the engine would charge and why, without writing a rule. Used to validate toll geofence coordinates before they bill a passenger.","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/m1os-audit":{"get":{"operationId":"getApiAdminM1osAudit","summary":"Read the M1OS audit trail (read-only; append is server-side)","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/m1os-import":{"post":{"operationId":"postApiAdminM1osImport","summary":"Import M1OS catalogue rows from CSV or JSON","description":"Bulk insert of workers, locations and package pricing. Insert-only with upsert-ignore: re-running the same file is a no-op and nothing existing is ever updated in bulk. Reports per-table received/inserted/skipped/errors.","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/m1os-payment":{"post":{"operationId":"postApiAdminM1osPayment","summary":"Collect for an M1OS job (link | cash | cash_approve | cash_reject)","description":"Two rails from one record: `link` issues a Wonder H5 payment page; `cash` records a driver's claim to have collected, pending admin approval (`cash_approve` / `cash_reject`). `link` and `cash` are also open to the m1os_operation role (counter staff); approval actions stay admin-only.","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/m1os-settings":{"get":{"operationId":"getApiAdminM1osSettings","summary":"Read M1OS payment settings (webhook secret never returned)","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminM1osSettings","summary":"Update M1OS payment settings","description":"Per-environment Wonder config. wonder_env (staging|production) decides which RSA key signs and which Wonder host bills; leaving it on production while testing sends real bills.","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]}},"/api/admin/m1os-wonder-credentials":{"get":{"operationId":"getApiAdminM1osWonderCredentials","summary":"List per-environment Wonder credentials","description":"Returns the public key and fingerprint only — the private key never leaves the server.","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"security":[{"adminBearer":[]}]},"post":{"operationId":"postApiAdminM1osWonderCredentials","summary":"Register or rotate Wonder credentials for an environment","tags":["Admin console"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"}}}},"400":{"$ref":"#/components/responses/Problem"},"401":{"$ref":"#/components/responses/Problem"},"404":{"$ref":"#/components/responses/Problem"},"429":{"$ref":"#/components/responses/Problem"},"500":{"$ref":"#/components/responses/Problem"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"env":{"type":"string","description":"staging | production"}},"required":["env"]}}}},"security":[{"adminBearer":[]}]}}}}