Vacation incentives engine — mobile API
This document is the integration contract for native apps to use the same vacation incentive / redeem flow as the web portal: catalog + provider-backed issuance, plus the user vacation pool ledger (POS credits, etc.).
Base URL: your portal origin, e.g. https://<tenant-domain> (or the shared API host if you call a central domain).
API prefix: https://<host>/api
Stable (versioned) surface: https://<host>/api/v1/... — prefer this for new mobile work.
Index: docs/api/README.md · Authentication · Hotels (tenant engine) · Developer UI in the app.
All JSON APIs should send:
Accept: application/json
(Without it, unauthenticated calls may return an HTML login redirect instead of401JSON.)
Authentication (Laravel Sanctum)
Mobile clients obtain a Bearer token and send it on protected routes:
Authorization: Bearer <access_token>
Issue token
| Method | Path | Auth |
|---|---|---|
POST |
/api/v1/auth/token |
No |
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | User email |
password |
string | Yes | Account password |
device_name |
string | Yes | Device label (stored with the token; e.g. iPhone 16, Pixel 9) |
Success (200):
{
"access_token": "1|…",
"token_type": "Bearer",
"user": {
"id": 1,
"name": "Jane",
"email": "jane@example.com",
"role": "user",
"tenant_id": 2,
"tenant": { "id": 2, "name": "Agency", "slug": "agency" }
}
}
Errors: 422 validation (wrong password, inactive account, etc.).
Rate limit: 10 requests per minute per IP (by default for this route).
Current user (optional)
| Method | Path | Auth |
|---|---|---|
GET |
/api/v1/auth/user |
Bearer |
Returns profile fields including vacation_pool_balance (string decimal).
Revoke current token (logout on device)
| Method | Path | Auth |
|---|---|---|
POST |
/api/v1/auth/revoke |
Bearer |
Success (200): { "ok": true }
Tenant / branding context
The backend must know which tenant (organization / white-label) the request applies to, so incentive issuer settings and optional destination card images resolve correctly.
Ways to pass tenant (same as other public APIs, e.g. hotel search):
- Query or JSON body:
tenant_id(integer,tenants.id) - Query or body:
tenant— id, slug, or domain string - Header:
X-Tenant— same astenant(id, slug, or domain) - If the app calls the tenant’s own domain and that domain is stored on
tenants.domain, the host is used. - If the user is signed in,
tenant_idon the user is the default for non–super-admins.
Rule for normal users (not super admin): they can only redeem for their own user.tenant_id. If the resolved tenant (from X-Tenant / tenant_id) does not match, the API responds 403.
Super admin: can redeem under the tenant resolved from the request (or their own tenant_id as fallback).
Endpoints
1) List vacation destinations (public)
| Method | Path | Auth |
|---|---|---|
GET |
/api/v1/deals/incentives/destinations |
No |
Query parameters:
| Param | Type | Required | Description |
|---|---|---|---|
country |
string | Yes | ISO 3166-1 alpha-2, e.g. US |
tenant_id |
int | No | If set, optional per-destination image_url may be returned from tenant marketing settings |
Success (200):
{
"data": [
{ "id": "123", "name": "Orlando … - 4 Days / 3 Nights", "image_url": "https://…" }
],
"meta": {
"source": "live",
"master_configured": true,
"issuer_auth_configured": true
}
}
data[].idis thedestination_idto send when redeeming.meta.sourceindicates whether the list came from a live provider fetch, static fallback, etc.
Errors: 422 if country missing or invalid.
Legacy (unversioned) alias: GET /api/deals/incentives/destinations (same handler).
2) Issue / redeem vacation (protected)
| Method | Path | Auth |
|---|---|---|
POST |
/api/v1/deals/incentives/issue-vacation |
Bearer |
Body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
destination_id |
string | Yes | From GET …/destinations → data[].id |
full_name |
string | Yes | Traveler / recipient name |
email |
string | Yes | Contact email |
country |
string | No | 2-letter country for validation; default US |
phone |
string | No | Phone |
message |
string | No | Short message to issuer |
tenant_id |
int | No | See “Tenant / branding context” |
Success (200):
{
"ok": true,
"transaction_id": 42,
"status": "sent",
"http_status": 200,
"error": null
}
okistrueonly when the incentive issuer accepted the redeem.
Domain errors (200/422 with ok: false): destination not allowed for country, issuer misconfiguration, remote validation, etc. Check error and status (failed, sent, …).
HTTP: 401 if not authenticated, 403 if tenant mismatch (non–super-admin), 422 validation.
Legacy (unversioned) alias: POST /api/deals/incentives/issue-vacation (also requires auth:sanctum and Accept: application/json for API clients).
Web note: the browser may use POST /deals/incentives/issue-vacation with session auth (not necessarily Sanctum). Mobile should use the /api/v1/... path with Bearer tokens.
3) Vacation pool (protected)
| Method | Path | Auth |
|---|---|---|
GET |
/api/v1/deals/incentives/vacation-pool |
Bearer |
Success (200):
{
"data": {
"vacation_pool_balance": "150.00",
"entries": [
{
"id": 9,
"amount": "150.00",
"currency": "USD",
"source": "pos_sale",
"description": "POS sale #1001",
"created_at": "2026-04-29T12:00:00+00:00"
}
]
}
}
vacation_pool_balance is a running total; entries are recent ledger lines (e.g. POS credits). Redeeming a vacation is recorded separately in issuer transaction logs (backend); pool display is for credits from sales and similar sources.
Server-side implementation map (for maintainers)
| Concern | Location |
|---|---|
| Destination list + images | App\Http\Controllers\Api\MarketingboostDealsController |
| Redeem (issue) | App\Http\Controllers\Api\MarketingboostIssueController + MarketingboostIssueEngine |
| Mobile token + user | App\Http\Controllers\Api\MobileAuthController |
| Vacation pool JSON | App\Http\Controllers\Api\VacationIncentiveController |
| Tenant resolution (API) | App\Http\Controllers\Concerns\ResolvesApiTenant (aligned with availability/booking) |
| User model tokens | Laravel\Sanctum\HasApiTokens on App\Models\User |
| Token storage | personal_access_tokens (Sanctum migration) |
Changelog (internal)
- 2026-04-29: Sanctum for mobile,
/api/v1/...routes,vacation-poolread, tenant resolution on redeem without browser session,config/auth.phpsanctumguard withusersprovider.
Related docs in repo
docs/tenancy.md— tenant model and resolution patterns where relevant.
If you add OpenAPI/Swagger later, this file can be migrated to a openapi.yaml and kept in sync with the same route names.