Shared

Ask for free migration help (admin)

post/api/data/support

The button on the proactive support banner. Stamps support_requested_at on one of this org's data jobs and pages us (console line plus SUPPORT_WEBHOOK_URL, if set).

Deliberately small: it does not open a ticket in another system, email the customer, or promise a response time. The durable record is the row, the superadmin queue reads it, and a human replies.

Scoped with an explicit .eq('company_id') on top of the id, because this route holds a service-role client and so has to spell out the tenancy that RLS would otherwise enforce. A zero-row update is a PostgREST 204 rather than an error, so the returned row (not the absence of an error) is what tells a real request from one naming somebody else's job.

Authorization

sessionCookie
sb-qxrvgfkjyvbngipqvslu-auth-token<token>

The dashboard's Supabase Auth session cookie, set at sign-in. Large sessions are split across numbered chunks (…auth-token.0, .1), so treat this as a cookie family rather than one name.

Every request re-validates it against the Auth server (getUser()), never by decoding the cookie locally: a JWT nothing has checked is not a credential. Tenancy is then read from the verified app_metadata.company_id claim and enforced by row-level security; it is never read from request input, on any route, ever.

role (admin / staff) is deliberately not in RLS. It gates specific actions in route code, the operations marked admin below, so hiding a button in the UI is cosmetic only, and a route's own check is the enforcement.

In: cookie

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/api/data/support" \  -H "Content-Type: application/json" \  -d '{    "jobId": "9d222c6d-893e-4e79-8201-3c9ca16a0f39"  }'
{  "ok": true,  "requestedAt": "2019-08-24T14:15:22Z"}

Commit an import (admin) POST

Stores the file in the private `data-imports` bucket, records the mapping the operator CONFIRMED, and either does the work inline (50 rows or fewer) or enqueues a `data_job` for the worker. The mapping is stored rather than re-derived, because the auto-mapper is a heuristic that will change: a job re-run six months from now must apply what a human approved, not what this build would guess today. The file is parsed here as well as in the runner. That is not waste: it is the only way to know the row count before choosing inline-vs-queued, and it means an unreadable file is a 400 the operator sees rather than a queued job failing somewhere they have to go looking for. **Not gated on billing status**, same reasoning as `/api/data/preview`.

Manually adjust a loyalty member's points balance (admin) PATCH

A birthday bonus, a correction, a chargeback clawback: the ONE trigger the loyalty wallet-card push engine has today (migration 0161). There is no automatic points-accrual formula for completed bookings anywhere in this product yet, so nothing else calls this. `delta` is a signed, non-zero integer added to `points_balance` via `book_loyalty_adjust_points`, a `security definer` RPC rather than a plain PostgREST update: PostgREST cannot express `points_balance = points_balance + delta` atomically, and a read-then-write from this route would race under concurrent adjustments. The RPC leans on `book_loyalty_members.points_balance >= 0` (migration 0160) as the single source of truth for the floor, so a delta that would take the balance negative is rejected by the database, not by application logic re-implementing the same rule. Every adjustment is recorded in `book_loyalty_point_adjustments` (company, member, delta, an optional `reason`, and who made it) for a durable audit trail: redeemable points are worth being able to explain. **No `Idempotency-Key`, unlike `/api/payments/refunds`.** That machinery exists there because a retry calls an external payment processor that could double-charge with no way to detect the duplicate. A double-submitted delta has no such blast radius: it is one UPDATE against this database, its result is returned in this same response, and it is trivially correctable with the opposite delta. On success, enqueues `loyalty_pass_push` so both wallets pick up the new balance: Apple via a silent APNs wake-up that makes the device re-fetch its pass, Google by re-patching the `LoyaltyObject` directly. That enqueue is best-effort and never fails this request: a queue hiccup must not turn a successful points adjustment into a client-visible error. Not engine-gated: a loyalty member hangs off `book_customers`, a table both engines already share, and carries no engine column at all.