---
updatedAt: 2025-11-02T04:18:55.000Z
agentTools:
  projectIndex: https://developers.zenbooker.com/llms.txt
---

# Build a custom booking flow

Learn how to build a complete end-to-end booking experience using the Zenbooker API. This guide walks through the four-step process: territory identification, job definition, timeslot selection, and booking creation.

## Prerequisites

All API requests require an Authorization header with your bearer token:

```
Authorization: Bearer <YOUR_API_KEY>
```

## Overview of the booking process

<Cards columns={2}>
  <Card title="Step 1: Territory Check" icon="map-marker-alt">
    Identify the service territory responsible for the customer's location
  </Card>

  <Card title="Step 2: Define Job" icon="clipboard-list">
    Determine the job scope and calculate total duration in minutes
  </Card>

  <Card title="Step 3: Find Timeslots" icon="calendar-alt">
    Query available times for the territory
  </Card>

  <Card title="Step 4: Create Booking" icon="check-circle">
    Submit the complete job with all gathered information
  </Card>
</Cards>

***

## Step 1: Identify the service territory

The first and most critical step is determining if you can service a customer's location, and if so, which specific service territory is responsible for it. This identification is needed because all subsequent steps—availability checking, scheduling, and staff assignment—are territory-specific.

Every booking starts with location validation. This step determines:

* Whether you service the area
* Which territory (branch/team) handles it

Use the [service\_area\_check](https://developers.zenbooker.com/reference/check-service-area) endpoint for location lookup and routing:

```bash
curl -G "https://api.zenbooker.com/v1/scheduling/service_area_check" \
  --data-urlencode "postal_code=20901" \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

<Accordion title="Understanding the Response" icon="info-circle">
  ```json
  {
    "in_service_area": true,
    "service_territory": {
      "id": "1586239489452x571449227260704600", // Store this for getting timeslots and booking later
      "name": "Silver Spring Team",             // Territory name
      "timezone": "America/New_York"
    },
    "customer_location": {
      "coordinates": {  // Store this to pass to the timeslots endpoint later (optional)
        "lat": 39.0191,
        "lng": -77.0076    
      }
    }
  }
  ```

  **Key fields to store:**

  * `service_territory.id` - Required for timeslots and booking
  * `customer_location.coordinates` - Optional but improves scheduling accuracy
</Accordion>

> **💡 Why territories matter**
>
> Territories are geographic boundaries that represent distinct operational units with their own schedules, providers, and rules. The same time might be available in one territory but not another.

***

## Step 2: Define the job and duration

Once you know which territory handles the work, define what the work entails. The goal is determining the job's total duration in minutes, required for finding available time slots.

<Tabs>
  <Tab title="Option A: Service Catalog">
    For customers choosing from existing services and add-ons, first [fetch your list of services](https://developers.zenbooker.com/reference/retrieve-a-list-of-services-1#/):

    ```bash
    curl "https://api.zenbooker.com/v1/services?is_visible=true" \
      -H "Authorization: Bearer <YOUR_API_KEY>"
    ```

    The response includes detailed service information with base duration and configurable options that can add additional time.

    **Duration Calculation:** Calculate total duration by summing the service base\_duration plus each selected option's duration.

    **Example:** Base service (120 min) + Large TV option (15 min) = 135 minutes total

    <Accordion title="Service Response Structure" icon="code">
      ```json
      {
        "results": [
          {
            "service_id": "1760300167598x745029266608947700",
            "name": "Home Theater TV Mounting & Setup",
            "base_duration": 120,
            "base_price": "149.00",
            "sections": [
              {
                "section_id": "1760300168129x855444251360474400",
                "section_type": "price_modifier",
                "title": "TV size (select one)",
                "input_type": "radio_buttons",
                "options": [
                  {
                    "option_id": "1760300168599x299965067990447740",
                    "name": "Up to 49-inch TV",
                    "price": "0.00",
                    "duration": 0
                  },
                  {
                    "option_id": "1760300168685x597574803337188900",
                    "name": "50–65-inch TV",
                    "price": "30.00",
                    "duration": 15
                  }
                ]
              }
            ]
          }
        ]
      }
      ```
    </Accordion>
  </Tab>

  <Tab title="Option B: Custom Services">
    Choose this for simpler integrations where you define service details dynamically. This approach works well for custom service builders and dynamic pricing models.

    **With this approach:**

    * No API call needed in this step
    * Simply determine your duration (e.g., 60 minutes)
    * Set arbitrary service details (name, duration, price) later when creating the job

    **Best for:** When you've built your own service configuration flow and just need to pass the final price and duration to Zenbooker.
  </Tab>
</Tabs>

***

## Step 3: Find available timeslots

[Query the schedule](https://developers.zenbooker.com/reference/retrieve-timeslots#/) for your identified territory using the duration from Step 2:

```bash
curl -G "https://api.zenbooker.com/v1/scheduling/timeslots" \
  --data-urlencode "territory=1586239489452x571449227260704600" \
  --data-urlencode "date=2025-10-28" \
  --data-urlencode "duration=135" \
  --data-urlencode "lat=39.0191" \
  --data-urlencode "lng=-77.0076" \
  -H "Authorization: Bearer <YOUR_API_KEY>"
```

<Accordion title="Key Parameters" icon="cog">
  **Required Parameters:**

  * `territory` - Territory ID from Step 1
  * `date` - Start date in YYYY-MM-DD format
  * `duration` - Job duration in minutes

  **Optional Parameters:**

  * `days` - Number of days to return (default: 7)
  * `lat/lng` - Customer coordinates for accurate scheduling
</Accordion>

<Accordion title="Response Structure" icon="calendar">
  ```json
  {
    "territory_id": "1586239489452x571449227260704600",
    "timezone": "America/New_York",
    "days": [
      {
        "date": "2025-10-28",
        "timeslots": [
          {
            "start": "2025-10-28T13:00:00.000Z",
            "end": "2025-10-28T17:00:00.000Z",
            "type": "arrival_window",
            "formatted": "9:00 AM - 1:00 PM",
            "id": "slot_1761051600_1761066000"
          }
        ]
      }
    ]
  }
  ```

  Present these options to customers and capture the selected timeslot `id`.
</Accordion>

***

## Step 4: Create the job

With all necessary information gathered, [create the job](https://developers.zenbooker.com/reference/create-a-job#/) with a single POST request. The `services` parameter accepts both catalog services and custom services.

```bash
curl -X POST "https://api.zenbooker.com/v1/jobs" \
  -H "Authorization: Bearer <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "territory_id": "1586239489452x571449227260704600",
    "timeslot_id": "slot_1761051600_1761066000",
    "customer": {
      "name": "Jane Doe",
      "email": "jane.doe@example.com",
      "phone": "555-123-4567"
    },
    "address": {
      "line1": "123 Main St",
      "city": "Silver Spring",
      "state": "MD",
      "postal_code": "20901"
    },
    "services": [
      {
        "service_id": "1760300167598x745029266608947700",
        "selections": [
          {
            "section_id": "1760300168129x855444251360474400",
            "selected_options": [{
              "option_id": "1760300168685x597574803337188900"
            }]
          }
        ]
      },
      {
        "custom_service": {
          "name": "Haul away old TV",
          "price": 50.00,
          "duration": 15,
          "taxable": false
        }
      }
    ],
    "sms_notifications": true,
    "email_notifications": true
  }'
```

<Tabs>
  <Tab title="Catalog Service">
    How you represent a customer booking an existing service with selected options:

    ```json
    {
      "service_id": "1760300167598x745029266608947700",
      "selections": [
        {
          "section_id": "1760300168129x855444251360474400",
          "selected_options": [
            {
              "option_id": "1760300168685x597574803337188900",
              "quantity": 1,
              "comments": "Handle with care"
            }
          ]
        }
      ]
    }
    ```
  </Tab>

  <Tab title="Custom Service">
    How you can define custom services for a job with arbitrary price, name, and description:

    ```json
    {
      "custom_service": {
        "name": "Emergency repair consultation",
        "description": "Initial assessment and quote",
        "price": 75.00,
        "duration": 45,
        "taxable": true
      }
    }
    ```
  </Tab>
</Tabs>

<Accordion title="Additional Options" icon="settings">
  **Assignment Methods:**

  * `assignment_method`: `"auto"` or `"offer"` for provider assignment

  **Notifications:**

  * `sms_notifications`: Enable SMS updates for the customer
  * `email_notifications`: Enable email updates for the customer
</Accordion>

<Accordion title="Success Response" icon="check-circle">
  A successful `201 Created` response confirms the booking:

  ```json
  {
    "job_id": "1761023232848x682214498988670500",
    "status": "scheduled",
    "customer_id": "1759877773365x180935398640097200",
    "timeslot": {
      "type": "arrival_window",
      "start": "2025-10-28T13:00:00.000Z",
      "end": "2025-10-28T17:00:00.000Z"
    },
    "pricing": {
      "subtotal": "199.00",
      "tax_amount": "15.92",
      "total": "214.92"
    }
  }
  ```
</Accordion>

***

## Best practices

* Cache service catalog data when appropriate
* Include customer coordinates when requesting timeslots if your scheduling settings include things like [distance](https://help.zenbooker.com/en/articles/9468087-limit-distance-between-jobs) or [drive time](https://help.zenbooker.com/en/articles/12454885-drive-time-aware-scheduling) limits between jobs.
* Request timeslots for multiple days in one request by setting the `days` parameter.

***

**Your booking flow is complete!** The customer is scheduled, and you can display confirmation details with the job ID for future reference.