Approving out-of-policy bookings
Route an out-of-policy booking to a supervisor, track the decision, and submit an exception.
When a booking violates corporate policy (over budget, wrong cabin class, an
unapproved supplier), it is routed for supervisor review before it can be
confirmed. This flow works the same way across flights, stays, trains,
and cars — {vertical} below is one of those four. All requests require the
consumer HMAC and a traveler bearer token.
A booking that needs approval carries needsApproval: true and an
approvalStatus block on its detail response — see
Bookings and trips § Full booking object.
Request approval
POST /v1/{vertical}/bookings/{booking_id}/approval-requests submits the
booking for review, capturing a snapshot of the policy violations that
triggered it.
curl -X POST "$TRAVELTECH_BASE_URL/v1/flights/bookings/018f.../approval-requests" \
-H "TravelTech-Consumer: $CONSUMER" \
-H "TravelTech-Timestamp: $TIMESTAMP" \
-H "TravelTech-Signature: $SIGNATURE" \
-H "Authorization: Bearer $TRAVELER_JWT" \
-H "Content-Type: application/json" \
-d '{"violations":[{"ruleType":"budget_cap","severity":"block","explanation":"Price 450 EUR exceeds budget cap of 300 EUR.","context":{"cap":"300.00","actual":"450.00"}}],"travelerId":"trvlr-1"}'import httpx
from your_signing_module import sign # see Authentication for the full algorithm
method, path = "POST", "/v1/flights/bookings/018f.../approval-requests"
body = (
b'{"violations":[{"ruleType":"budget_cap","severity":"block",'
b'"explanation":"Price 450 EUR exceeds budget cap of 300 EUR.",'
b'"context":{"cap":"300.00","actual":"450.00"}}],"travelerId":"trvlr-1"}'
)
timestamp = str(int(time.time()))
response = httpx.post(
f"{BASE_URL}{path}",
content=body,
headers={
"TravelTech-Consumer": CONSUMER,
"TravelTech-Timestamp": timestamp,
"TravelTech-Signature": sign(method, path, body, timestamp),
"Authorization": f"Bearer {traveler_jwt}",
"Content-Type": "application/json",
},
)const method = "POST";
const path = "/v1/flights/bookings/018f.../approval-requests";
const body = JSON.stringify({
violations: [
{
ruleType: "budget_cap",
severity: "block",
explanation: "Price 450 EUR exceeds budget cap of 300 EUR.",
context: { cap: "300.00", actual: "450.00" },
},
],
travelerId: "trvlr-1",
});
const timestamp = String(Math.floor(Date.now() / 1000));
await fetch(`${BASE_URL}${path}`, {
method,
body,
headers: {
"TravelTech-Consumer": CONSUMER,
"TravelTech-Timestamp": timestamp,
"TravelTech-Signature": sign(method, path, body, timestamp),
Authorization: `Bearer ${travelerJwt}`,
"Content-Type": "application/json",
},
});ruleType is one of budget_cap, cabin_class, advance_purchase,
approved_supplier, carbon_limit, duration_constraint,
destination_restriction, refundable_required, fare_basis_blocklist.
severity is warning or block. violations requires at least one entry.
Response (201):
{
"id": "7e4b9c1a-2f3d-4e5a-9b8c-1d2e3f4a5b6c",
"bookingId": "018f2c1a-9b3e-7c4d-8a1f-3e5b7c9d1a2f",
"vertical": "flight",
"accountId": "acc-018f2c1a",
"travelerId": "trvlr-1",
"status": "pending",
"violationsSnapshot": [
{ "ruleType": "budget_cap", "severity": "block", "explanation": "Price 450 EUR exceeds budget cap of 300 EUR.", "context": { "cap": "300.00", "actual": "450.00" } }
],
"justification": null,
"approverNotes": null,
"legacyApprovalId": null,
"createdAt": "2026-02-10T09:00:00Z",
"updatedAt": "2026-02-10T09:00:00Z"
}Submitting with no bearer-token-carried account identity returns 403:
{
"error": {
"code": "FORBIDDEN",
"message": "Account identity required.",
"details": [],
"recoveryAction": "BLOCKED"
}
}Track the decision
GET /v1/{vertical}/bookings/{booking_id}/approval-requests lists every
approval request for the booking, newest first — poll this endpoint (or watch
the booking detail's approvalStatus.state) to learn the supervisor's
decision. status moves through:
| Status | Meaning |
|---|---|
pending | Awaiting the supervisor's decision. |
approved | Approved — the booking can proceed to confirmation. |
rejected | Rejected — the booking cannot be confirmed as submitted. |
exception_pending | A traveler exception justification is awaiting review. |
exception_approved | The exception was accepted. |
exception_rejected | The exception was declined. |
The approve/reject decision itself is made by the supervisor through their
approval workflow, not by an endpoint the traveler's client calls — this API
surfaces the outcome, it does not let a caller set it directly.
Submit an exception
If the traveler wants to proceed despite a block-severity violation, POST /v1/{vertical}/bookings/{booking_id}/approval-requests/{approval_id}/exception
attaches a justification and moves the request to exception_pending. Only a
pending request accepts an exception.
{ "justification": "Direct flight required for a same-day return; the connecting option adds 6 hours." }Submitting an exception against a request that is not pending (for example,
already approved) returns 409:
{
"error": {
"code": "CONFLICT",
"message": "Approval request is not pending.",
"details": [],
"recoveryAction": "CONTACT_SUPPORT"
}
}An unknown approval_id returns 404 with code: RESOURCE_NOT_FOUND.
See the API Reference for the complete request and
response schemas.
Reporting issues
This page is maintained by the BizAway integration team. To request changes or
report an error, contact your BizAway integration contact. Edits made directly
in the ReadMe dashboard are overwritten on the next publish.
Updated 8 days ago