# Safra Partner API reference

Base URL: https://safraway.com/api/partner/v1
Version: 2026-08-21
Auth: `Authorization: Bearer sk_live_…`
OpenAPI: https://docs.safraway.com/openapi.json

## Reference

### GET /openapi.json

OpenAPI specification

Public JSON document for this API. No authentication. Also mirrored at docs.safraway.com/openapi.json.

Scope: `none`

Response:

```json
{ "openapi": "3.1.0", "info": { "title": "Safra Partner API", "version": "2026-08-21" } }
```

### GET /cities

List trip cities

Cities you can use as fromCityId / toCityId when publishing trips and schedules.

Scope: `trips.read`

Response:

```json
{ "cities": [{ "id": "…", "name": "Damascus", "nameAr": "دمشق" }] }
```

### GET /places

Search places

Search pickup and drop-off places (P-codes) inside a city for fromPlacePcode / toPlacePcode.

Scope: `trips.read`

Query:
- `q` (string): Search text. Optional; empty returns a short default list.
- `cityId` (string): Restrict results to one city.

## Company

### GET /company

Get the authenticated company

The company is implied by the API key. Paths never include a company id.

Scope: `company.read`

Response:

```json
{ "company": { "id": "…", "name": "…", "verified": true } }
```

### PATCH /company

Update company profile

Partial update of public company fields such as description.

Scope: `company.write`

Body:
- `description` (string): Public company description.

Request body:

```json
{ "description": "Damascus–Aleppo coaches" }
```

### POST /company/logo

Upload company logo

Send a base64 image payload. Replaces the current logo.

Scope: `company.write`

Body:
- `image` (string, required): Base64-encoded image.

### DELETE /company/logo

Remove company logo

Deletes the current logo file.

Scope: `company.write`

## Members

### GET /members

List members

Drivers, staff, admins, and the owner attached to the company.

Scope: `members.read`

### POST /members

Add a member by phone

The phone must already belong to a Safra user. Roles: owner cannot be assigned this way.

Scope: `members.write`

Body:
- `phone` (string, required): E.164, e.g. +9639…
- `role` (string, required): admin | staff | driver

Request body:

```json
{ "phone": "+963944000000", "role": "driver" }
```

### PATCH /members/{member}

Update a member role

Change the company role for an existing member.

Scope: `members.write`

Path parameters:
- `member` (uuid): User id of the member.

Body:
- `role` (string, required): admin | staff | driver

### DELETE /members/{member}

Remove a member

Detaches the user from the company. Does not delete their Safra account.

Scope: `members.write`

Path parameters:
- `member` (uuid): User id of the member.

## Fleet

### GET /vehicles

List vehicles

Company fleet used when publishing trips.

Scope: `fleet.read`

### POST /vehicles

Create a vehicle

Adds a vehicle to the company fleet.

Scope: `fleet.write`

Body:
- `make` (string, required): e.g. Toyota
- `model` (string, required): e.g. HiAce
- `seats` (integer, required): 1–20
- `year` (integer): 1980–2100
- `color` (string): Optional
- `plate` (string): Optional plate number
- `fuelType` (string): Optional
- `transmission` (string): Optional

Request body:

```json
{ "make": "Toyota", "model": "HiAce", "seats": 14, "year": 2018 }
```

### PATCH /vehicles/{vehicle}

Update a vehicle

Partial update of fleet fields.

Scope: `fleet.write`

Path parameters:
- `vehicle` (uuid): Vehicle id.

### DELETE /vehicles/{vehicle}

Delete a vehicle

Fails if the vehicle is still attached to published trips.

Scope: `fleet.write`

Path parameters:
- `vehicle` (uuid): Vehicle id.

## Trips

### GET /trips

List trips

Company trips, newest departure first.

Scope: `trips.read`

Query:
- `status` (string): Filter by trip status.
- `fromCityId` (string): Origin city id.
- `toCityId` (string): Destination city id.
- `page` (integer): Page number, default 1.
- `perPage` (integer): Page size, default 25, max 100.

### POST /trips

Publish a trip

Creates a published company trip. The assigned driver must be a company member. vehicleId is required.

Scope: `trips.write`

Body:
- `vehicleId` (uuid, required): Fleet vehicle. Alias: carId.
- `driverId` (uuid): Defaults to the key’s actor.
- `fromCityId` (string, required): Origin city id (e.g. damascus).
- `toCityId` (string, required): Destination city id (e.g. tartus).
- `departureAt` (datetime, required): ISO-8601 departure.
- `pricePerSeat` (integer, required): Fare per seat (minor units as used in dashboard).
- `totalSeats` (integer): Defaults to the vehicle’s seats.
- `fromPlacePcode` (string): Optional pickup place.
- `toPlacePcode` (string): Optional drop-off place.
- `notes` (string): Public trip notes.

Request body:

```json
{
  "vehicleId": "…",
  "driverId": "…",
  "fromCityId": "damascus",
  "toCityId": "tartus",
  "departureAt": "2026-09-01T06:00:00+03:00",
  "pricePerSeat": 25000,
  "totalSeats": 12
}
```

### GET /trips/{trip}

Get a trip

Single trip belonging to the authenticated company.

Scope: `trips.read`

Path parameters:
- `trip` (uuid): Trip id.

### PATCH /trips/{trip}

Update a trip

Partial update of a published trip.

Scope: `trips.write`

Path parameters:
- `trip` (uuid): Trip id.

### DELETE /trips/{trip}

Cancel a trip

Cancels the trip. Existing bookings follow dashboard cancellation rules.

Scope: `trips.write`

Path parameters:
- `trip` (uuid): Trip id.

### POST /trips/{trip}/complete

Mark trip completed

Closes the trip after it has run.

Scope: `trips.write`

Path parameters:
- `trip` (uuid): Trip id.

### GET /trips/{trip}/manifest

Passenger manifest

Approved and boarded passengers for dispatch and boarding.

Scope: `trips.read`

Path parameters:
- `trip` (uuid): Trip id.

## Schedules

### GET /schedules

List recurring schedules

Templates that publish trips on a recurrence (daily, weekly, …).

Scope: `schedules.read`

### POST /schedules

Create a schedule

Same trip fields as publish, plus recurrence and time-of-day. City ids are strings such as damascus / aleppo.

Scope: `schedules.write`

Body:
- `vehicleId` (uuid, required): Fleet vehicle.
- `driverId` (uuid, required): Company member who drives.
- `fromCityId` (string, required): Origin city id.
- `toCityId` (string, required): Destination city id.
- `pricePerSeat` (integer, required): Fare per seat.
- `recurrence` (string, required): daily | weekly | interval
- `weekdays` (integer[]): Required for weekly (0=Sun … 6=Sat).
- `departureTime` (string, required): Local HH:MM.
- `startsOn` (date): First occurrence date (YYYY-MM-DD).
- `horizonDays` (integer): How far ahead to publish trips.

Request body:

```json
{
  "vehicleId": "…",
  "driverId": "…",
  "fromCityId": "damascus",
  "toCityId": "aleppo",
  "pricePerSeat": 30000,
  "totalSeats": 10,
  "recurrence": "weekly",
  "weekdays": [1, 3, 5],
  "departureTime": "07:30",
  "startsOn": "2026-09-01",
  "horizonDays": 14
}
```

### GET /schedules/{schedule}

Get a schedule

Includes exceptions when loaded.

Scope: `schedules.read`

Path parameters:
- `schedule` (uuid): Schedule id.

### PATCH /schedules/{schedule}

Update a schedule

Partial update — send only fields to change. Omitted fields keep their current values.

Scope: `schedules.write`

Path parameters:
- `schedule` (uuid): Schedule id.

Body:
- `pricePerSeat` (integer): New fare.
- `departureTime` (string): Local HH:MM.
- `weekdays` (integer[]): Weekly days.
- `horizonDays` (integer): Publish horizon.
- `notes` (string): Optional notes.

Request body:

```json
{ "pricePerSeat": 31000, "departureTime": "08:00" }
```

### DELETE /schedules/{schedule}

Delete a schedule

Stops future generation. Already published trips are not deleted.

Scope: `schedules.write`

Path parameters:
- `schedule` (uuid): Schedule id.

### POST /schedules/{schedule}/toggle

Enable or pause a schedule

Flip the schedule active flag.

Scope: `schedules.write`

Path parameters:
- `schedule` (uuid): Schedule id.

### POST /schedules/{schedule}/exceptions

Upsert a schedule exception

Skip or override a specific date (holiday, extra departure).

Scope: `schedules.write`

Path parameters:
- `schedule` (uuid): Schedule id.

### DELETE /schedules/{schedule}/exceptions/{exception}

Delete a schedule exception

Removes a date override.

Scope: `schedules.write`

Path parameters:
- `schedule` (uuid): Schedule id.
- `exception` (uuid): Exception id.

## Bookings

### GET /bookings

List bookings

Bookings on this company’s trips, newest first.

Scope: `bookings.read`

Query:
- `status` (string): requested | approved | rejected | cancelled | …
- `tripId` (uuid): Limit to one trip.
- `page` (integer): Page number.
- `perPage` (integer): Default 25, max 100.

### POST /bookings

Create booking by phone

Finds or creates a passenger from the phone number and places a requested booking. source is partner_api. Auto-approve is not implied.

Scope: `bookings.write`

Body:
- `tripId` (uuid, required): Must belong to this company.
- `seats` (integer, required): 1–20
- `phone` (string, required): Passenger phone.
- `fullName` (string): Used when creating a new passenger.
- `passengerNote` (string): Optional note, max 200.
- `groupName` (string): Optional group label, max 80.

Request body:

```json
{
  "tripId": "…",
  "seats": 2,
  "phone": "+963944000000",
  "fullName": "Maha N."
}
```

Response:

```json
{ "booking": { "id": "…", "status": "requested", "source": "partner_api", "seats": 2 } }
```

### GET /bookings/{booking}

Get a booking

Includes trip, passenger, and payments when available.

Scope: `bookings.read`

Path parameters:
- `booking` (uuid): Booking id.

### PATCH /bookings/{booking}

Approve, reject, or cancel

status must be one of approved, rejected, cancelled.

Scope: `bookings.write`

Path parameters:
- `booking` (uuid): Booking id.

Body:
- `status` (string, required): approved | rejected | cancelled

Request body:

```json
{ "status": "approved" }
```

### POST /bookings/{booking}/boarded

Mark boarded

Passenger boarded. Sets boardedAt.

Scope: `bookings.write`

Path parameters:
- `booking` (uuid): Booking id.

### POST /bookings/{booking}/no-show

Mark no-show

Passenger did not board.

Scope: `bookings.write`

Path parameters:
- `booking` (uuid): Booking id.

### GET /bookings/{booking}/ticket

Get ticket payload

Company-view ticket data for printing or display.

Scope: `bookings.read`

Path parameters:
- `booking` (uuid): Booking id.

## Payments

### GET /payments

List payments

Read-only. You cannot create Ziina charges from the Partner API.

Scope: `payments.read`

### GET /payments/{payment}

Get a payment

Single payment on a company booking.

Scope: `payments.read`

Path parameters:
- `payment` (uuid): Payment id.

### GET /payments/{payment}/invoice.pdf

Download invoice PDF

Binary PDF. Send the same Bearer token.

Scope: `payments.read`

Path parameters:
- `payment` (uuid): Payment id.

### GET /payouts

List payouts

Company payout records.

Scope: `payments.read`

## Reports

### GET /dashboard

Dashboard snapshot

High-level counts for the company home.

Scope: `reports.read`

### GET /reports

Reports payload

Aggregated booking and revenue figures.

Scope: `reports.read`

### GET /reports/export

Export reports

Download a bookings/trips/revenue export. type and format are required query params.

Scope: `reports.read`

Query:
- `type` (string, required): bookings | trips | revenue | drivers | routes | fleet | reviews
- `format` (string, required): csv | xlsx | pdf

## Reviews

### GET /reviews

List reviews

Reviews left on this company’s trips.

Scope: `company.read`

## Webhooks

### GET /webhooks

List webhook endpoints

Configured HTTPS endpoints. Signing secrets are never returned after create.

Scope: `webhooks.read`

### POST /webhooks

Create a webhook endpoint

Returns the endpoint plus secret once. Store the secret; it cannot be retrieved later. Do not include webhook.test in events — that event is only sent by the test ping.

Scope: `webhooks.write`

Body:
- `url` (string, required): HTTPS URL, max 2048.
- `events` (string[], required): At least one selectable event (booking.*, trip.*, schedule.*, payment.updated). webhook.test is not allowed here.

Request body:

```json
{
  "url": "https://crm.example.com/safra/webhooks",
  "events": ["booking.created", "booking.updated", "trip.cancelled"]
}
```

Response:

```json
{ "endpoint": { "id": "…", "url": "…", "events": ["booking.created"] }, "secret": "whsec_…" }
```

### PATCH /webhooks/{endpoint}

Update a webhook endpoint

Change URL, events, or active flag.

Scope: `webhooks.write`

Path parameters:
- `endpoint` (uuid): Endpoint id.

Body:
- `url` (string): HTTPS URL.
- `events` (string[]): Replace the event list.
- `active` (boolean): Pause without deleting.

### DELETE /webhooks/{endpoint}

Delete a webhook endpoint

Stops future deliveries.

Scope: `webhooks.write`

Path parameters:
- `endpoint` (uuid): Endpoint id.

### POST /webhooks/{endpoint}/test

Queue a test ping

Enqueues event webhook.test with a small payload.

Scope: `webhooks.write`

Path parameters:
- `endpoint` (uuid): Endpoint id.

### GET /webhooks/{endpoint}/deliveries

List deliveries

Recent delivery attempts for debugging.

Scope: `webhooks.read`

Path parameters:
- `endpoint` (uuid): Endpoint id.
