# Payhawk → Google Drive receipt sync (`apify/payhawk-google-drive-receipt-sync`) Actor

Automatically archive Payhawk expense receipts (PDFs) to a Google Drive folder. Incremental, idempotent, schedule-friendly — every new receipt lands in Drive without duplicates.

- **URL**: https://apify.com/apify/payhawk-google-drive-receipt-sync.md
- **Developed by:** [Apify](https://apify.com/apify) (Apify)
- **Categories:** Automation
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

### Payhawk → Google Drive Receipt Sync

Automatically archive Payhawk expense receipts (PDFs) to a Google Drive folder. Set it on a schedule and only new receipts get uploaded, without duplicates.

### What is the Payhawk Google Drive Receipt Sync?

The Actor takes every expense receipt PDF from your Payhawk account and saves it to a Google Drive folder of your choice. Set it up once, run it on a schedule, and you stop downloading PDFs one expense at a time. Every new receipt your colleagues add to Payhawk shows up in Drive, ready to hand off to your accountant, feed into your bookkeeping system, or keep on file for long-term archival.

Behind the scenes, the Actor uses Payhawk's official Developer API to pick up only the receipts that haven't been exported yet, drops them into Drive, and marks them as exported in Payhawk. The same receipt never gets uploaded twice.

### What it can do?

- **Sync only what's new.** Each run picks up the receipts that haven't been exported yet and skips everything you've already moved.
- **Never upload the same receipt twice.** Files use a predictable name based on the expense ID, so if a receipt is already in Drive, the Actor leaves it alone.
- **Handle expenses with multiple receipt pages.** When an expense has two or more attachments, each one ends up in Drive as its own numbered file.
- **Keep Payhawk's record straight.** Once a receipt is in Drive, the Actor marks the expense as exported in Payhawk, so your team can see at a glance which receipts have already been sent.
- **Test before you flip the switch.** Dry-run mode walks through the whole sync without writing anything to Drive, so you can confirm permissions, folders, and credentials are right.
- **Hand back a full audit log.** Every run produces a dataset where each row shows what happened to one expense: success, skip, missing receipt, or error.
- **Schedule, monitor, and connect.** Running on Apify means you can set the sync to run daily, watch for failures, plug into your other tools, and pull results from the Apify API whenever you need them.

### How does it work?

Every run follows the same five steps:

1. **Asks Payhawk for new expenses.** The Actor pulls the list of expenses that haven't been exported yet and are older than the sync lag (no lag by default), page by page.
2. **Downloads each receipt.** For every expense it finds, the Actor grabs each attached PDF.
3. **Uploads the PDFs to Drive.** Receipts go into the Google Drive folder you configured. The folder has to live inside a Shared Drive so the service account has permission to write to it.
4. **Marks the expense as exported in Payhawk.** The Actor writes an entry to Payhawk's export history, including a link back to the file in Drive.
5. **Keeps a local memory of what's already been synced.** Each processed expense ID is stored in an Apify key-value store as a safety net against accidentally uploading the same receipt twice.

### How much will it cost?

The Actor runs on Apify and bills by [compute units](https://docs.apify.com/platform/actors/running/usage-and-resources#compute-units). Since most of each run is just waiting for Payhawk and Google Drive to answer, the work isn't compute-heavy. A run handling a few hundred receipts uses a small fraction of one compute unit, so the free Apify tier is usually enough to try the Actor on a real month of expenses.

The Payhawk and Google Drive APIs the Actor talks to are both free to use within their published rate limits. Payhawk caps its developer API at 15 requests per second, well above anything the sync gets close to.

### How to set up Payhawk to Google Drive Receipt Sync

#### Step 1: Get your Payhawk API key

In Payhawk, go to **Settings → Integrations → API → Create key**. Copy the key (it is sent in the `X-Payhawk-ApiKey` header). The account ID is shown alongside the key and also appears in any Payhawk URL.

Put the values into `payhawkApiKey` and `payhawkAccountId`.

#### Step 2: Create a Google service account

Service accounts are how a long-running Actor can write to Drive without an interactive Google login.

1. In the [Google Cloud Console](https://console.cloud.google.com/): **IAM & Admin → Service Accounts → Create service account**. Any name works (e.g. `payhawk-sync`).
2. Open the new service account → **Keys → Add key → Create new key → JSON**. Save the downloaded JSON; you will paste it into `googleServiceAccount`.
3. In **APIs & Services → Library**, search for and **enable the Google Drive API** for the project.

#### Step 3: Create a Shared Drive folder and share it with the service account

**This step is required.** Service accounts have no storage quota in a personal *My Drive*. Uploads to a regular My Drive folder fail with `Service Accounts do not have storage quota`. A Shared Drive is the supported way to give a service account a place to write.

1. In [Google Drive](https://drive.google.com/), open the left sidebar → **Shared drives → + New**. Name the drive anything you like (for example, `Payhawk receipts`).
2. Inside the Shared Drive, create a folder for the receipts.
3. Open the Shared Drive → **Manage members** → add your service account's email (`xxx@<project>.iam.gserviceaccount.com`, found in the JSON you downloaded) with the **Content manager** role.
4. Open the destination folder and copy the ID from the URL: `drive.google.com/drive/folders/<id>`. Paste it into `driveFolderId`.

#### Step 4: Run with dry-run enabled

For the first run, set `dryRun` to `true`. The Actor will list expenses and download receipts but will not write to Drive or Payhawk. Confirm the logs look right (e.g. the expected number of expenses are found), then flip `dryRun` to `false` and run for real.

#### Step 5: Schedule it

Once a real run succeeds, create an Apify schedule at the cadence you want (daily and hourly are common choices).

### Input

The Actor takes the following inputs. See the **Input** tab for the full schema with descriptions and secret handling.

- `payhawkApiKey`: Payhawk API key.
- `payhawkAccountId`: Payhawk account ID.
- `googleServiceAccount`: Service account JSON for Google Drive access.
- `driveFolderId`: Destination Drive folder ID. Must live inside a Shared Drive.
- `exportedInto` (optional): Label written to Payhawk's export history. Defaults to `"Google Drive"`.
- `syncLagDays` (optional): Only sync expenses created at least this many days ago, giving your team time to attach or fix a receipt first. Defaults to `0` (sync immediately with no lag). Set e.g. `7` to wait a week.
- `dryRun` (optional): When `true`, the Actor skips all writes. Defaults to `false`.

### Output

The Actor writes one dataset row per processed expense, with the upload status and a link to the file in Drive. You can download the dataset in JSON, CSV, Excel, HTML, RSS, or XML, the same as any Apify Actor.

**Status values:** `uploaded`, `reused` (file already existed in Drive), `skipped-already-synced` (already in local dedupe state), `no-receipt` (expense had no attached document), `missing-id` (malformed expense with no ID), and `error`.

### FAQ

**Why does the upload fail with "Service Accounts do not have storage quota"?**

The destination folder is in a personal My Drive instead of a Shared Drive. Service accounts can be granted access to a My Drive folder, but they cannot own files there, and Drive will refuse the upload. Move the folder into a Shared Drive (see Step 3 above) and use that folder ID instead.

**Can I sync to a personal My Drive folder?**

Not with a service account. The alternative is OAuth domain-wide delegation, which makes the service account impersonate a Google Workspace user, so uploaded files use that user's quota. This requires a Google Workspace administrator to grant the delegation. Most teams find creating a Shared Drive easier.

**Will running the Actor twice create duplicate files in Drive?**

No. The Actor checks for an existing file with the same `payhawk-<expenseId>.pdf` name in the destination folder before uploading, and it only requests expenses that haven't already been exported into this destination. As an extra safety net, synced expense IDs are tracked in a named key-value store.

**What happens to expenses without an attached receipt?**

They are reported as `no-receipt` in the dataset and skipped. The expense is not marked as exported in Payhawk, so it will be re-evaluated on the next run, giving your team a chance to upload the receipt.

**What if someone adds or changes a receipt after the expense is created?**

That is what the `syncLagDays` input is for. By default it is `0`, so expenses sync immediately. Set it to, say, `7` and the Actor will only sync expenses created at least that many days ago, giving your team a window to attach or correct a receipt before it is exported to Drive. Note that once an expense has been synced, changing its receipt in Payhawk later will not re-upload it, because the file already exists in Drive under the same name.

**Is this a two-way sync?**

No. The Actor syncs one-way, from Payhawk to Google Drive. Files deleted from Drive are not re-uploaded on the next run, because the expense has already been marked as exported in Payhawk.

**How do I change the label that appears in Payhawk's export history?**

Set the `exportedInto` input. It defaults to `"Google Drive"`. During testing, change it to something like `"Test sync"` so test runs are clearly distinguishable from real ones in Payhawk's UI.

**How do I run the Actor locally?**

Clone the repo, run `npm install`, put your inputs in `storage/key_value_stores/default/INPUT.json`, then run `npx apify run`. Use `dryRun: true` to validate the setup without touching Drive or Payhawk.

# Actor input Schema

## `payhawkApiKey` (type: `string`):

API key used to authenticate against the Payhawk API. Create it in Payhawk under Settings → Integrations → API.
## `payhawkAccountId` (type: `string`):

Your Payhawk account ID — the `accountId` path segment shown alongside the API key.
## `googleServiceAccount` (type: `object`):

Service account credentials JSON. The service account email must be added as a Content manager of the target Shared Drive (service accounts cannot upload to a personal My Drive).
## `driveFolderId` (type: `string`):

ID of the destination folder inside a Shared Drive. It's the trailing segment of the folder URL: drive.google.com/drive/folders/<id>.
## `exportedInto` (type: `string`):

Value written to Payhawk's export history `exportedInto` field — visible in the Payhawk UI to indicate where the receipt was sent. Override for testing (e.g. 'Test sync') before switching to your production label.
## `syncLagDays` (type: `integer`):

Only sync expenses created at least this many days ago. This gives your team time to attach or correct a receipt before it is synced to Drive. Defaults to 0 (sync immediately with no lag); set e.g. 7 to wait a week.
## `dryRun` (type: `boolean`):

When enabled, the Actor lists expenses and downloads receipts but does NOT upload to Drive or write back to Payhawk. Use for the first test run.

## Actor input object example

```json
{
  "exportedInto": "Google Drive",
  "syncLagDays": 0,
  "dryRun": false
}
````

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("apify/payhawk-google-drive-receipt-sync").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("apify/payhawk-google-drive-receipt-sync").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call apify/payhawk-google-drive-receipt-sync --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=apify/payhawk-google-drive-receipt-sync",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Payhawk → Google Drive receipt sync",
        "description": "Automatically archive Payhawk expense receipts (PDFs) to a Google Drive folder. Incremental, idempotent, schedule-friendly — every new receipt lands in Drive without duplicates.",
        "version": "0.0",
        "x-build-id": "D8lLJgNoZ59gq7XSx"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/apify~payhawk-google-drive-receipt-sync/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-apify-payhawk-google-drive-receipt-sync",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/apify~payhawk-google-drive-receipt-sync/runs": {
            "post": {
                "operationId": "runs-sync-apify-payhawk-google-drive-receipt-sync",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/apify~payhawk-google-drive-receipt-sync/run-sync": {
            "post": {
                "operationId": "run-sync-apify-payhawk-google-drive-receipt-sync",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "payhawkApiKey",
                    "payhawkAccountId",
                    "googleServiceAccount",
                    "driveFolderId"
                ],
                "properties": {
                    "payhawkApiKey": {
                        "title": "Payhawk API key",
                        "type": "string",
                        "description": "API key used to authenticate against the Payhawk API. Create it in Payhawk under Settings → Integrations → API."
                    },
                    "payhawkAccountId": {
                        "title": "Payhawk account ID",
                        "type": "string",
                        "description": "Your Payhawk account ID — the `accountId` path segment shown alongside the API key."
                    },
                    "googleServiceAccount": {
                        "title": "Google service account JSON",
                        "type": "object",
                        "description": "Service account credentials JSON. The service account email must be added as a Content manager of the target Shared Drive (service accounts cannot upload to a personal My Drive)."
                    },
                    "driveFolderId": {
                        "title": "Google Drive folder ID",
                        "type": "string",
                        "description": "ID of the destination folder inside a Shared Drive. It's the trailing segment of the folder URL: drive.google.com/drive/folders/<id>."
                    },
                    "exportedInto": {
                        "title": "Exported-into label",
                        "type": "string",
                        "description": "Value written to Payhawk's export history `exportedInto` field — visible in the Payhawk UI to indicate where the receipt was sent. Override for testing (e.g. 'Test sync') before switching to your production label.",
                        "default": "Google Drive"
                    },
                    "syncLagDays": {
                        "title": "Sync lag (days)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Only sync expenses created at least this many days ago. This gives your team time to attach or correct a receipt before it is synced to Drive. Defaults to 0 (sync immediately with no lag); set e.g. 7 to wait a week.",
                        "default": 0
                    },
                    "dryRun": {
                        "title": "Dry run",
                        "type": "boolean",
                        "description": "When enabled, the Actor lists expenses and downloads receipts but does NOT upload to Drive or write back to Payhawk. Use for the first test run.",
                        "default": false
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
