# Fabric notebooks and pipelines

Run a notebook and read what it wrote, run a pipeline and refresh a report, and let Fabric read your own systems.

This page will show you how to work with Microsoft Fabric notebooks and pipelines in Python:

- Running a notebook and reading its results
- Running a pipeline and refreshing a report
- Letting a notebook read your own systems

In Fabric nomenclature, running a notebook, running a pipeline and refreshing a report's data are all jobs.

Such a job runs in Fabric as a background task which may potentially take minutes to complete, so there are two functions to use when working with jobs.

- [`conn.run_job`](https://zato.io/docs/dev/examples/cloud/fabric/api/jobs.html#run_job) starts the job and returns its ID
- [`conn.wait_for_job`](https://zato.io/docs/dev/examples/cloud/fabric/api/jobs.html#wait_for_job) waits until the job is done, raising an exception if it failed

## Connection and IDs {#connection-and-ids}

The Fabric connection and the workspace and lakehouse IDs are the same as in the [tutorial](https://zato.io/docs/dev/examples/cloud/fabric/tutorial.html).

You'll also need the ID of the item to run (e.g. of a job), which is in the browser's address bar when the item is open in Fabric:

| Item | In the address bar after | Used on this page |
| --- | --- | --- |
| Notebook | `/synapsenotebooks/` | `ba7b1e23-b680-4fe7-a982-2a979b125604` |
| Pipeline | `/pipelines/` | `bf4868aa-05c8-4213-ab4d-8dcbe3189767` |
| A report's semantic model | `/datasets/` | `e040a801-73bd-4b01-b036-d490c7122941` |

## Running a notebook and reading its results {#running-a-notebook-and-reading-its-results}

In this case, a notebook writes to the `reminder_candidates` table and a service reads what the notebook wrote.

The service below will:

- Start the notebook
- Wait until it is done
- Read the table the notebook wrote with [`conn.query`](https://zato.io/docs/dev/examples/cloud/fabric/api/queries.html#query)

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class SendReminders(Service):

    name = 'fabric.send-reminders'

    def handle(self):

        workspace_id = '262ddd3d-3d0d-4495-b80f-6dddda1e23a1'
        lakehouse_id = '73811955-064d-481a-a7e9-f9c563124f9e'
        notebook_id = 'ba7b1e23-b680-4fe7-a982-2a979b125604'

        # Get a Fabric connection ..
        conn = self.microsoft.fabric['Zato Fabric']

        # .. start the notebook ..
        job_id = conn.run_job(workspace_id, notebook_id, 'RunNotebook')

        # .. wait until it is done ..
        conn.wait_for_job(workspace_id, notebook_id, job_id)

        # .. read the table it wrote ..
        sql = """
        select appointment_id, location, starts_at
        from reminder_candidates
        order by starts_at
        """
        rows = conn.query(workspace_id, lakehouse_id, sql)

        # .. log each row ..
        for row in rows:
            appointment_id = row['appointment_id']
            location = row['location']
            self.logger.info(f'Reminder -> {appointment_id} at {location}')

        # .. and tell our caller how many there were.
        self.response.payload = {'reminders': len(rows)}
```

After invoking the service you'll see:

```json
{"reminders": 9}
```

## Running a pipeline and refreshing a report {#running-a-pipeline-and-refreshing-a-report}

Here, a pipeline writes the `occupancy` table that a Power BI report reads, and the report's data is refreshed once the pipeline is done.

The service below will:

- Run the pipeline and wait for it
- Refresh the report's semantic model and wait for it too
- Return when both are done

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class RefreshOccupancyReport(Service):

    name = 'fabric.refresh-occupancy-report'

    def handle(self):

        workspace_id = '262ddd3d-3d0d-4495-b80f-6dddda1e23a1'
        pipeline_id = 'bf4868aa-05c8-4213-ab4d-8dcbe3189767'
        dataset_id = 'e040a801-73bd-4b01-b036-d490c7122941'

        # Get a Fabric connection ..
        conn = self.microsoft.fabric['Zato Fabric']

        # .. run the pipeline and wait for it ..
        job_id = conn.run_job(workspace_id, pipeline_id, 'Pipeline')
        pipeline = conn.wait_for_job(workspace_id, pipeline_id, job_id)

        # .. refresh the report's data and wait for that too ..
        job_id = conn.run_job(
          workspace_id,
          dataset_id,
          'DefaultSemanticModelRefresh')

        refresh = conn.wait_for_job(workspace_id, dataset_id, job_id)

        # .. and tell our caller when each of them ended.
        self.response.payload = {
            'pipeline_ended': pipeline['endTimeUtc'],
            'refresh_ended': refresh['endTimeUtc'],
        }
```

After invoking the service you'll see:

```json
{"pipeline_ended": "2026-09-02T02:34:11Z", "refresh_ended": "2026-09-02T02:36:40Z"}
```

If the pipeline fails, `conn.wait_for_job` raises an exception with Fabric's reason and the refresh does not start.

## Letting a notebook read your own systems {#letting-a-notebook-read-your-own-systems}

In this example, a notebook needs data from a database in your own network, so it will invoke a [REST API channel](https://zato.io/docs/dev/examples/rest.html) exposing a service to that notebook.

The service will in turn look up the data inside your on-prem database and return it to the notebook.

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class GetAppointments(Service):

    name = 'fabric.get-appointments'
    input = 'since'

    def handle(self):

        # The query to run ..
        sql = """
        select appointment_id, location, starts_at, status
        from appointments
        where updated_at >= :since
        order by starts_at
        """

        # .. the value for it, from our input ..
        input = self.request.input
        params = {
            'since': input.since,
        }

        # .. get a connection to the local database ..
        conn = self.out.sql['Appointments DB']

        # .. run the query ..
        rows = conn.execute(sql, params)

        # .. and return the rows to our caller.
        self.response.payload = rows
```

Now in the notebook, a cell can invoke the channel and load the rows into a DataFrame:

```python
# In a Microsoft Fabric notebook
import requests

url = 'https://api.example.com/api/appointments'
headers = {'X-API-Key': '<the key>'}
params = {'since': '2026-08-31T00:00:00Z'}

response = requests.get(url, headers=headers, params=params)
appointments = response.json()

frame = spark.createDataFrame(appointments)
display(frame)
```

## See also {#see-also}

- [Your first Fabric integration](https://zato.io/docs/dev/examples/cloud/fabric/tutorial.html) - The app registration, the connection and where the IDs come from
- [Fabric API - Jobs](https://zato.io/docs/dev/examples/cloud/fabric/api/jobs.html) - conn.run\_job, conn.wait\_for\_job, conn.get\_job and conn.cancel\_job, job types and statuses
- [Writing data to Fabric](https://zato.io/docs/dev/examples/cloud/fabric/writing-data.html) - The nightly load the pipeline runs after

## Learn more {#learn-more}

- [Development documentation](https://zato.io/docs/dev/) - The complete reference for writing services
- [Requests and responses](https://zato.io/docs/dev/request-response/) - What a service receives, what it returns and how both are structured
- [Integration examples](https://zato.io/docs/dev/examples/) - Ready-made code for the systems you are likely to connect to
- [IDE and debugging](https://zato.io/docs/dev/ide/) - Write services in the Dashboard or in your own editor
- [Data models](https://zato.io/docs/dev/model/) - Declare inputs and outputs and have them validated for you
- [In-depth API tutorial](https://zato.io/tutorials/main/01.html) - The full platform tutorial, from installation to production patterns
