diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml
index d743d34dac5af..4bed63565f547 100644
--- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml
+++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml
@@ -892,3 +892,9 @@
dockerImageTag: 0.1.2
documentationUrl: https://docs.airbyte.io/integrations/sources/kustomer
sourceType: api
+- name: ZohoCRM
+ sourceDefinitionId: 4942d392-c7b5-4271-91f9-3b4f4e51eb3e
+ dockerRepository: airbyte/source-zoho-crm
+ dockerImageTag: 0.1.0
+ documentationUrl: https://docs.airbyte.com/integrations/sources/zoho-crm
+ sourceType: api
diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml
index ee4b76e90791c..f5e56ec0bb316 100644
--- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml
+++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml
@@ -9387,3 +9387,83 @@
supportsNormalization: false
supportsDBT: false
supported_destination_sync_modes: []
+- dockerImage: "airbyte/source-zoho-crm:0.1.0"
+ spec:
+ documentationUrl: "https://docs.airbyte.com/integrations/sources/zoho-crm"
+ connectionSpecification:
+ $schema: "http://json-schema.org/draft-07/schema#"
+ title: "Zoho Crm Configuration"
+ type: "object"
+ required:
+ - "client_id"
+ - "client_secret"
+ - "refresh_token"
+ - "environment"
+ - "dc_region"
+ - "edition"
+ additionalProperties: false
+ properties:
+ client_id:
+ type: "string"
+ title: "Client ID"
+ description: "OAuth2.0 Client ID"
+ airbyte_secret: true
+ client_secret:
+ type: "string"
+ title: "Client Secret"
+ description: "OAuth2.0 Client Secret"
+ airbyte_secret: true
+ refresh_token:
+ type: "string"
+ title: "Refresh Token"
+ description: "OAuth2.0 Refresh Token"
+ airbyte_secret: true
+ dc_region:
+ title: "Data Center Location"
+ type: "string"
+ description: "Please choose the region of your Data Center location. More\
+ \ info by this Link"
+ enum:
+ - "US"
+ - "AU"
+ - "EU"
+ - "IN"
+ - "CN"
+ - "JP"
+ environment:
+ title: "Environment"
+ type: "string"
+ description: "Please choose the environment"
+ enum:
+ - "Production"
+ - "Developer"
+ - "Sandbox"
+ start_datetime:
+ title: "Start Date"
+ type:
+ - "null"
+ - "string"
+ examples:
+ - "2000-01-01"
+ - "2000-01-01 13:00"
+ - "2000-01-01 13:00:00"
+ - "2000-01-01T13:00+00:00"
+ - "2000-01-01T13:00:00-07:00"
+ description: "ISO 8601, for instance: `YYYY-MM-DD`, `YYYY-MM-DD HH:MM:SS+HH:MM`"
+ format: "date-time"
+ edition:
+ title: "Zoho CRM Edition"
+ type: "string"
+ description: "Choose your Edition of Zoho CRM to determine API Concurrency\
+ \ Limits"
+ enum:
+ - "Free"
+ - "Standard"
+ - "Professional"
+ - "Enterprise"
+ - "Ultimate"
+ default: "Free"
+ supportsNormalization: false
+ supportsDBT: false
+ supported_destination_sync_modes: []
diff --git a/airbyte-integrations/connectors/source-zoho-crm/.dockerignore b/airbyte-integrations/connectors/source-zoho-crm/.dockerignore
new file mode 100644
index 0000000000000..e68f560f2ea70
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/.dockerignore
@@ -0,0 +1,6 @@
+*
+!Dockerfile
+!main.py
+!source_zoho_crm
+!setup.py
+!secrets
diff --git a/airbyte-integrations/connectors/source-zoho-crm/Dockerfile b/airbyte-integrations/connectors/source-zoho-crm/Dockerfile
new file mode 100644
index 0000000000000..d01642352c66d
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/Dockerfile
@@ -0,0 +1,38 @@
+FROM python:3.7.11-alpine3.14 as base
+
+# build and load all requirements
+FROM base as builder
+WORKDIR /airbyte/integration_code
+
+# upgrade pip to the latest version
+RUN apk --no-cache upgrade \
+ && pip install --upgrade pip \
+ && apk --no-cache add tzdata build-base
+
+
+COPY setup.py ./
+# install necessary packages to a temporary folder
+RUN pip install --prefix=/install .
+
+# build a clean environment
+FROM base
+WORKDIR /airbyte/integration_code
+
+# copy all loaded and built libraries to a pure basic image
+COPY --from=builder /install /usr/local
+# add default timezone settings
+COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime
+RUN echo "Etc/UTC" > /etc/timezone
+
+# bash is installed for more convenient debugging.
+RUN apk --no-cache add bash
+
+# copy payload code only
+COPY main.py ./
+COPY source_zoho_crm ./source_zoho_crm
+
+ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py"
+ENTRYPOINT ["python", "/airbyte/integration_code/main.py"]
+
+LABEL io.airbyte.version=0.1.0
+LABEL io.airbyte.name=airbyte/source-zoho-crm
diff --git a/airbyte-integrations/connectors/source-zoho-crm/README.md b/airbyte-integrations/connectors/source-zoho-crm/README.md
new file mode 100644
index 0000000000000..a56146be089f5
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/README.md
@@ -0,0 +1,132 @@
+# Zoho Crm Source
+
+This is the repository for the Zoho Crm source connector, written in Python.
+For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/zoho-crm).
+
+## Local development
+
+### Prerequisites
+**To iterate on this connector, make sure to complete this prerequisites section.**
+
+#### Minimum Python version required `= 3.7.0`
+
+#### Build & Activate Virtual Environment and install dependencies
+From this connector directory, create a virtual environment:
+```
+python -m venv .venv
+```
+
+This will generate a virtualenv for this module in `.venv/`. Make sure this venv is active in your
+development environment of choice. To activate it from the terminal, run:
+```
+source .venv/bin/activate
+pip install -r requirements.txt
+pip install '.[tests]'
+```
+If you are in an IDE, follow your IDE's instructions to activate the virtualenv.
+
+Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is
+used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`.
+If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything
+should work as you expect.
+
+#### Building via Gradle
+You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow.
+
+To build using Gradle, from the Airbyte repository root, run:
+```
+./gradlew :airbyte-integrations:connectors:source-zoho-crm:build
+```
+
+#### Create credentials
+**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/zoho-crm)
+to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_zoho_crm/spec.json` file.
+Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information.
+See `integration_tests/sample_config.json` for a sample config file.
+
+**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source zoho-crm test creds`
+and place them into `secrets/config.json`.
+
+### Locally running the connector
+```
+python main.py spec
+python main.py check --config secrets/config.json
+python main.py discover --config secrets/config.json
+python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json
+```
+
+### Locally running the connector docker image
+
+#### Build
+First, make sure you build the latest Docker image:
+```
+docker build . -t airbyte/source-zoho-crm:dev
+```
+
+You can also build the connector image via Gradle:
+```
+./gradlew :airbyte-integrations:connectors:source-zoho-crm:airbyteDocker
+```
+When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in
+the Dockerfile.
+
+#### Run
+Then run any of the connector commands as follows:
+```
+docker run --rm airbyte/source-zoho-crm:dev spec
+docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-zoho-crm:dev check --config /secrets/config.json
+docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-zoho-crm:dev discover --config /secrets/config.json
+docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-zoho-crm:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json
+```
+## Testing
+Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named.
+First install test dependencies into your virtual environment:
+```
+pip install .[tests]
+```
+### Unit Tests
+To run unit tests locally, from the connector directory run:
+```
+python -m pytest unit_tests
+```
+
+### Integration Tests
+There are two types of integration tests: Acceptance Tests (Airbyte's test suite for all source connectors) and custom integration tests (which are specific to this connector).
+#### Custom Integration tests
+Place custom tests inside `integration_tests/` folder, then, from the connector root, run
+```
+python -m pytest integration_tests
+```
+#### Acceptance Tests
+Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information.
+If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py.
+To run your integration tests with acceptance tests, from the connector root, run
+```
+python -m pytest integration_tests -p integration_tests.acceptance
+```
+To run your integration tests with docker
+
+### Using gradle to run tests
+All commands should be run from airbyte project root.
+To run unit tests:
+```
+./gradlew :airbyte-integrations:connectors:source-zoho-crm:unitTest
+```
+To run acceptance and custom integration tests:
+```
+./gradlew :airbyte-integrations:connectors:source-zoho-crm:integrationTest
+```
+
+## Dependency Management
+All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development.
+We split dependencies between two groups, dependencies that are:
+* required for your connector to work need to go to `MAIN_REQUIREMENTS` list.
+* required for the testing need to go to `TEST_REQUIREMENTS` list
+
+### Publishing a new version of the connector
+You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what?
+1. Make sure your changes are passing unit and integration tests.
+1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)).
+1. Create a Pull Request.
+1. Pat yourself on the back for being an awesome contributor.
+1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master.
diff --git a/airbyte-integrations/connectors/source-zoho-crm/__init__.py b/airbyte-integrations/connectors/source-zoho-crm/__init__.py
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-config.yml
new file mode 100644
index 0000000000000..e10ad5caeccdb
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-config.yml
@@ -0,0 +1,29 @@
+# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference)
+# for more information about how to configure these tests
+connector_image: airbyte/source-zoho-crm:dev
+tests:
+ spec:
+ - spec_path: "source_zoho_crm/spec.json"
+ connection:
+ - config_path: "secrets/config.json"
+ status: "succeed"
+ - config_path: "integration_tests/invalid_config.json"
+ status: "failed"
+ discovery:
+ - config_path: "secrets/config.json"
+ basic_read:
+ - config_path: "secrets/config.json"
+ configured_catalog_path: "integration_tests/configured_catalog.json"
+ empty_streams: []
+ expect_records:
+ path: "integration_tests/expected_records.json"
+ extra_fields: yes
+ exact_order: yes
+ extra_records: no
+ incremental:
+ - config_path: "secrets/config.json"
+ configured_catalog_path: "integration_tests/configured_catalog.json"
+ future_state_path: "integration_tests/abnormal_state.json"
+ full_refresh:
+ - config_path: "secrets/config.json"
+ configured_catalog_path: "integration_tests/configured_catalog.json"
diff --git a/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-docker.sh
new file mode 100644
index 0000000000000..c51577d10690c
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-docker.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env sh
+
+# Build latest connector image
+docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-)
+
+# Pull latest acctest image
+docker pull airbyte/source-acceptance-test:latest
+
+# Run
+docker run --rm -it \
+ -v /var/run/docker.sock:/var/run/docker.sock \
+ -v /tmp:/tmp \
+ -v $(pwd):/test_input \
+ airbyte/source-acceptance-test \
+ --acceptance-test-config /test_input
+
diff --git a/airbyte-integrations/connectors/source-zoho-crm/build.gradle b/airbyte-integrations/connectors/source-zoho-crm/build.gradle
new file mode 100644
index 0000000000000..c546a5dc1702e
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/build.gradle
@@ -0,0 +1,9 @@
+plugins {
+ id 'airbyte-python'
+ id 'airbyte-docker'
+ id 'airbyte-source-acceptance-test'
+}
+
+airbytePython {
+ moduleDirectory 'source_zoho_crm'
+}
diff --git a/airbyte-integrations/connectors/source-zoho-crm/integration_tests/__init__.py b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/__init__.py
new file mode 100644
index 0000000000000..46b7376756ec6
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/__init__.py
@@ -0,0 +1,3 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
diff --git a/airbyte-integrations/connectors/source-zoho-crm/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/abnormal_state.json
new file mode 100644
index 0000000000000..f78a6a2cefdde
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/abnormal_state.json
@@ -0,0 +1,62 @@
+{
+ "incremental_leads_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_notes_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_contacts_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_accounts_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_activities_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_tasks_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_calls_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_deals_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_events_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_vendors_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_products_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_quotes_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_sales__orders_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_purchase__orders_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_price__books_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_solutions_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_cases_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_invoices_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_attachments_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ },
+ "incremental_campaigns_zoho_crm_stream": {
+ "Modified_Time": "2220-03-07T11:30:00+00:00"
+ }
+}
diff --git a/airbyte-integrations/connectors/source-zoho-crm/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/acceptance.py
new file mode 100644
index 0000000000000..0347f2a0b143d
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/acceptance.py
@@ -0,0 +1,14 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+
+import pytest
+
+pytest_plugins = ("source_acceptance_test.plugin",)
+
+
+@pytest.fixture(scope="session", autouse=True)
+def connector_setup():
+ """This fixture is a placeholder for external resources that acceptance test might require."""
+ yield
diff --git a/airbyte-integrations/connectors/source-zoho-crm/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/configured_catalog.json
new file mode 100644
index 0000000000000..afbbdb9d30ef0
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/configured_catalog.json
@@ -0,0 +1,424 @@
+{
+ "streams": [
+ {
+ "stream": {
+ "name": "incremental_leads_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_notes_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_contacts_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_accounts_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_activities_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_tasks_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_calls_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_deals_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_events_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_vendors_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_products_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_quotes_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_sales__orders_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_purchase__orders_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_price__books_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_solutions_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_cases_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_invoices_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_attachments_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ },
+ {
+ "stream": {
+ "name": "incremental_campaigns_zoho_crm_stream",
+ "json_schema": {},
+ "supported_sync_modes": [
+ "full_refresh",
+ "incremental"
+ ],
+ "source_defined_cursor": true,
+ "default_cursor_field": [
+ "Modified_Time"
+ ],
+ "source_defined_primary_key": [
+ [
+ "id"
+ ]
+ ]
+ },
+ "sync_mode": "full_refresh",
+ "destination_sync_mode": "overwrite"
+ }
+ ]
+}
diff --git a/airbyte-integrations/connectors/source-zoho-crm/integration_tests/expected_records.json b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/expected_records.json
new file mode 100644
index 0000000000000..7d4ff6f73b31b
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/expected_records.json
@@ -0,0 +1,137 @@
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "789", "First_Name": "8", "Last_Name": "7", "Full_Name": "8 7", "Designation": null, "Email": "test@test.com", "Phone": null, "Fax": null, "Mobile": null, "Website": null, "Lead_Source": null, "Lead_Status": null, "Industry": null, "No_of_Employees": null, "Annual_Revenue": null, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": null, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-04T04:08:39-07:00", "Modified_Time": "2021-08-04T04:08:39-07:00", "Salutation": null, "Secondary_Email": null, "Twitter": null, "Last_Activity_Time": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": null, "City": null, "State": "Test Texas", "Zip_Code": null, "Country": null, "Description": null, "Record_Image": null}, "emitted_at": 1647947807561}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "456", "First_Name": "5", "Last_Name": "4", "Full_Name": "5 4", "Designation": null, "Email": "test@test.com", "Phone": null, "Fax": null, "Mobile": null, "Website": null, "Lead_Source": null, "Lead_Status": null, "Industry": null, "No_of_Employees": null, "Annual_Revenue": null, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": null, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-04T04:08:39-07:00", "Modified_Time": "2021-08-04T04:08:39-07:00", "Salutation": null, "Secondary_Email": null, "Twitter": null, "Last_Activity_Time": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": null, "City": null, "State": "Test Texas", "Zip_Code": null, "Country": null, "Description": null, "Record_Image": null}, "emitted_at": 1647947807563}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "123", "First_Name": "2", "Last_Name": "1", "Full_Name": "2 1", "Designation": null, "Email": "test@test.com", "Phone": null, "Fax": null, "Mobile": null, "Website": null, "Lead_Source": null, "Lead_Status": null, "Industry": null, "No_of_Employees": null, "Annual_Revenue": null, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": null, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-04T04:08:39-07:00", "Modified_Time": "2021-08-04T04:08:39-07:00", "Salutation": null, "Secondary_Email": null, "Twitter": null, "Last_Activity_Time": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": null, "City": null, "State": "Test Texas", "Zip_Code": null, "Country": null, "Description": null, "Record_Image": null}, "emitted_at": 1647947807564}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Villa Margarita", "First_Name": "Brian", "Last_Name": "Dolan", "Full_Name": "Brian Dolan", "Designation": null, "Email": "brian@villa.com", "Phone": null, "Fax": null, "Mobile": null, "Website": null, "Lead_Source": null, "Lead_Status": null, "Industry": null, "No_of_Employees": null, "Annual_Revenue": null, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": null, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-04T04:06:27-07:00", "Modified_Time": "2021-08-04T04:06:27-07:00", "Salutation": null, "Secondary_Email": null, "Twitter": null, "Last_Activity_Time": "2022-03-22T01:02:33-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": null, "City": null, "State": "Texas", "Zip_Code": null, "Country": null, "Description": null, "Record_Image": null}, "emitted_at": 1647947807565}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Airbyte Test", "First_Name": "Test", "Last_Name": "Airbyte", "Full_Name": "Test Airbyte", "Designation": null, "Email": "test@test.com", "Phone": null, "Fax": null, "Mobile": null, "Website": null, "Lead_Source": null, "Lead_Status": null, "Industry": null, "No_of_Employees": null, "Annual_Revenue": null, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": null, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-04T04:06:27-07:00", "Modified_Time": "2021-08-04T04:06:27-07:00", "Salutation": null, "Secondary_Email": null, "Twitter": null, "Last_Activity_Time": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": null, "City": null, "State": "Test Texas", "Zip_Code": null, "Country": null, "Description": null, "Record_Image": null}, "emitted_at": 1647947807565}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Villa Margarita", "First_Name": "Brian", "Last_Name": "Dolan", "Full_Name": "Brian Dolan", "Designation": null, "Email": "brian@villa.com", "Phone": null, "Fax": null, "Mobile": null, "Website": null, "Lead_Source": null, "Lead_Status": null, "Industry": null, "No_of_Employees": null, "Annual_Revenue": null, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": null, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-04T01:40:31-07:00", "Modified_Time": "2021-08-04T01:40:31-07:00", "Salutation": null, "Secondary_Email": null, "Twitter": null, "Last_Activity_Time": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": null, "City": null, "State": "Texas", "Zip_Code": null, "Country": null, "Description": null, "Record_Image": null}, "emitted_at": 1647947807566}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Zylker", "First_Name": "Paul", "Last_Name": "Daly", "Full_Name": "Paul Daly", "Designation": null, "Email": "p.daly@zylker.com", "Phone": null, "Fax": null, "Mobile": null, "Website": null, "Lead_Source": null, "Lead_Status": null, "Industry": null, "No_of_Employees": null, "Annual_Revenue": null, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": null, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-04T01:40:31-07:00", "Modified_Time": "2021-08-04T01:40:31-07:00", "Salutation": null, "Secondary_Email": null, "Twitter": null, "Last_Activity_Time": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": null, "City": null, "State": "Texas", "Zip_Code": null, "Country": null, "Description": null, "Record_Image": null}, "emitted_at": 1647947807567}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Rangoni Of Florence", "First_Name": "Christopher", "Last_Name": "Maclead (Sample)", "Full_Name": "Mr. Christopher Maclead (Sample)", "Designation": "VP Accounting", "Email": "christopher-maclead@gmail.com", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.rangoniofflorence.com", "Lead_Source": "Cold Call", "Lead_Status": "Lost Lead", "Industry": "Service Provider", "No_of_Employees": null, "Annual_Revenue": 850000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "christopher-maclead", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:11-07:00", "Salutation": "Mr.", "Secondary_Email": null, "Twitter": "christophermaclead_sample", "Last_Activity_Time": "2021-08-03T04:49:11-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "37275 St Rt 17m M", "City": "Middle Island", "State": "NY", "Zip_Code": "11953", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a61444b399fafde74f999941d09eaf27d26dfd9cb2d5f9d14cd8f32e93eed57e6c9beff2e4469036b27eab41ceef58b1e1"}, "emitted_at": 1647947807568}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Oh My Goodknits Inc", "First_Name": "Carissa", "Last_Name": "Kidman (Sample)", "Full_Name": "Ms. Carissa Kidman (Sample)", "Designation": "Director of Sales", "Email": "carissa-kidman@yahoo.com", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.truhlarandtruhlarattys.com", "Lead_Source": "Advertisement", "Lead_Status": "Contact in Future", "Industry": "Data/Telecom OEM", "No_of_Employees": null, "Annual_Revenue": 200000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "carissa-kidman", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:11-07:00", "Salutation": "Ms.", "Secondary_Email": null, "Twitter": "carissakidman_sample", "Last_Activity_Time": "2021-08-03T04:49:11-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "5 Boston Ave #88", "City": "Sioux Falls", "State": "SD", "Zip_Code": "57105", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6db5ed87724f1009b7ed8f88ef4798886acb084fb78989a68686677c279035b88c40394ac7617d1ea6d64dc54710c008e"}, "emitted_at": 1647947807569}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Kwik Kopy Printing", "First_Name": "James", "Last_Name": "Merced (Sample)", "Full_Name": "Mr. James Merced (Sample)", "Designation": "Biostatistician I", "Email": "james-merced@gmail.com", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.commercialpress.com", "Lead_Source": "Web Download", "Lead_Status": "Lost Lead", "Industry": "Large Enterprise", "No_of_Employees": null, "Annual_Revenue": 650000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "james-merced", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:11-07:00", "Salutation": "Mr.", "Secondary_Email": null, "Twitter": "jamesmerced_sample", "Last_Activity_Time": "2021-08-03T04:49:11-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "7 W Jackson Blvd", "City": "San Jose", "State": "CA", "Zip_Code": "95111", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a608617a7eb0d5f44bd86b787722e28c6ffb7bd489893462dee2cc53506bdec7d376f1548ffb17f07c57d9fb06a969ee5b"}, "emitted_at": 1647947807570}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Morlong Associates", "First_Name": "Tresa", "Last_Name": "Sweely (Sample)", "Full_Name": "Ms. Tresa Sweely (Sample)", "Designation": "Executive Secretary", "Email": "tresa-sweely@hotmail.com", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.morlongassociates.com", "Lead_Source": "Seminar Partner", "Lead_Status": "Contacted", "Industry": "Storage Equipment", "No_of_Employees": null, "Annual_Revenue": 190000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "tresa-sweely", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:11-07:00", "Salutation": "Ms.", "Secondary_Email": null, "Twitter": "tresasweely_sample", "Last_Activity_Time": "2022-03-22T01:20:17-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "7 Eads St", "City": "Chicago", "State": "IL", "Zip_Code": "60632", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6f61a649fccaf9efe2c2ab290ae9994aea3f4e9138c3f2b5a302a2e7eb51b8039f5915499ad5741b9e98f55bccdfa4de6"}, "emitted_at": 1647947807571}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Chapman", "First_Name": "Felix", "Last_Name": "Hirpara (Sample)", "Full_Name": "Mr. Felix Hirpara (Sample)", "Designation": "Computer Systems Analyst I", "Email": "felix-hirpara@cox.net", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.chapmanrosseesq.com", "Lead_Source": "Online Store", "Lead_Status": "Attempted to Contact", "Industry": "Non-management ISV", "No_of_Employees": null, "Annual_Revenue": 230000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "felix-hirpara", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": "Mr.", "Secondary_Email": null, "Twitter": "felixhirpara_sample", "Last_Activity_Time": "2021-08-03T04:49:10-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "3 Mcauley Dr", "City": "Ashland", "State": "OH", "Zip_Code": "44805", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6a119226d14d72aeb7882458c616a3e91315fd97c1bba0005dc95f1c61368851b55fd3ce80c2274d773392fc2ca07e744"}, "emitted_at": 1647947807571}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Printing Dimensions", "First_Name": "Kayleigh", "Last_Name": "Lace (Sample)", "Full_Name": "Ms. Kayleigh Lace (Sample)", "Designation": "Cost Accountant", "Email": "kayleigh-lace@yahoo.com", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.printingdimensions.com", "Lead_Source": "Partner", "Lead_Status": "Not Contacted", "Industry": "Optical Networking", "No_of_Employees": null, "Annual_Revenue": 270000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "kayleigh-lace", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": "Ms.", "Secondary_Email": null, "Twitter": "kayleighlace_sample", "Last_Activity_Time": "2021-08-03T04:49:10-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "34 Center St", "City": "Hamilton", "State": "OH", "Zip_Code": "45011", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6949a4674aa4fa405e67c6fc964baa8b5e1fb97155a858ae9b079924b39b2914c78e4dd91896205015dabe95281b00545"}, "emitted_at": 1647947807572}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Grayson", "First_Name": "Yvonne", "Last_Name": "Tjepkema (Sample)", "Full_Name": "Ms. Yvonne Tjepkema (Sample)", "Designation": "Office Assistant III", "Email": "yvonne-tjepkema@hotmail.com", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.feltzprintingservice.com", "Lead_Source": "External Referral", "Lead_Status": "Pre-Qualified", "Industry": "Government/Military", "No_of_Employees": null, "Annual_Revenue": 170000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "yvonne-tjepkema", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": "Ms.", "Secondary_Email": null, "Twitter": "yvonnetjepkema_sample", "Last_Activity_Time": "2022-03-22T01:01:18-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "639 Main St", "City": "Anchorage", "State": "AK", "Zip_Code": "99501", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6b032aa89fe8bab98bcc42ab238eb5de3940d6a779bfd16f0649df157fc29d493c60f9da0f1f3204cc6d67594936bc90a"}, "emitted_at": 1647947807573}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Buckley Miller & Wright", "First_Name": "Michael", "Last_Name": "Ruta (Sample)", "Full_Name": "Mr. Michael Ruta (Sample)", "Designation": "Computer Systems Analyst II", "Email": "michael-gruta@cox.net", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.buckleymillerwright.com", "Lead_Source": "Online Store", "Lead_Status": "Attempted to Contact", "Industry": "MSP (Management Service Provider)", "No_of_Employees": null, "Annual_Revenue": 850000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "michael-gruta", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": "Mr.", "Secondary_Email": null, "Twitter": "michaelgruta_sample", "Last_Activity_Time": "2021-08-03T04:49:10-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "98 Connecticut Ave Nw", "City": "Chagrin Falls", "State": "OH", "Zip_Code": "44023", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6c9fa47259d06208b9f3c70ccec7280c5e1f5f098cb2ba207182a8491359f1a2a958f6752dac217dcf7d9558f153310ef"}, "emitted_at": 1647947807573}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Dal Tile Corporation", "First_Name": "Theola", "Last_Name": "Frey (Sample)", "Full_Name": "Ms. Theola Frey (Sample)", "Designation": "Mechanical Systems Engineer", "Email": "theola-frey@frey.com", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.chanayjeffreyaesq.com", "Lead_Source": "Cold Call", "Lead_Status": "Contact in Future", "Industry": "Management ISV", "No_of_Employees": null, "Annual_Revenue": 200000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "theola-frey", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": "Ms.", "Secondary_Email": null, "Twitter": "theolafrey_sample", "Last_Activity_Time": "2022-03-22T01:00:11-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "4 B Blue Ridge Blvd", "City": "Brighton", "State": "MI", "Zip_Code": "48116", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a62b2bb26c4344447170e51d12d6d8f5bb8aa95c520b50fa731f7e4fb6a38c85ab172360ac770d61f9c96ac5ba31537924"}, "emitted_at": 1647947807574}
+{"stream": "incremental_leads_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Company": "Creative Business Systems", "First_Name": "Chau", "Last_Name": "Kitzman (Sample)", "Full_Name": "Mr. Chau Kitzman (Sample)", "Designation": "Junior Executive", "Email": "chau-kitzman@gmail.com", "Phone": "555-555-5555", "Fax": null, "Mobile": "555-555-5555", "Website": "http://www.bentonjohnbjr.com", "Lead_Source": "Advertisement", "Lead_Status": "Attempted to Contact", "Industry": "ERP", "No_of_Employees": null, "Annual_Revenue": 100000, "Rating": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Email_Opt_Out": false, "Skype_ID": "chau-kitzman", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:38-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": "Mr.", "Secondary_Email": null, "Twitter": "chaukitzman_sample", "Last_Activity_Time": "2021-08-03T04:48:39-07:00", "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Street": "6649 N Blue Gum St", "City": "New Orleans", "State": "LA", "Zip_Code": "70116", "Country": "United States", "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a67937d5beda27086e62ffafd52d86c34f090b8787e0a89472ff27487b1a7347f4ea3211d1b5543ec54c79f81336ded9b7"}, "emitted_at": 1647947807575}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Praesent eleifend pharetra efficitur", "Note_Content": "Pellentesque egestas mauris ex, id vehicula nibh consequat eget", "Parent_Id": {"name": null, "id": "4970762000000335412"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2022-03-22T01:17:56-07:00", "Modified_Time": "2022-03-22T01:17:56-07:00"}, "emitted_at": 1647946348922}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Mauris fringilla", "Note_Content": "Interdum et malesuada fames ac ante ipsum primis in faucibusu", "Parent_Id": {"name": null, "id": "4970762000000335412"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2022-03-22T01:17:56-07:00", "Modified_Time": "2022-03-22T01:17:56-07:00"}, "emitted_at": 1647946348923}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Quisque scelerisque pulvinar ante", "Note_Content": "Donec non est ut nunc congue blandit", "Parent_Id": {"name": null, "id": "4970762000000335412"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2022-03-22T01:17:56-07:00", "Modified_Time": "2022-03-22T01:17:56-07:00"}, "emitted_at": 1647946348923}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Nulla id finibus tellus", "Note_Content": "Nam molestie in mi vel egestas", "Parent_Id": {"name": null, "id": "4970762000000335412"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2022-03-22T01:17:56-07:00", "Modified_Time": "2022-03-22T01:17:56-07:00"}, "emitted_at": 1647946348924}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Class aptent taciti sociosqu ad litora torquent", "Note_Content": "Maecenas et quam at massa dictum tincidun", "Parent_Id": {"name": null, "id": "4970762000000335412"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2022-03-22T01:17:56-07:00", "Modified_Time": "2022-03-22T01:17:56-07:00"}, "emitted_at": 1647946348924}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Fusce ornare pulvinar mi", "Note_Content": "Ut vestibulum gravida semper", "Parent_Id": {"name": null, "id": "4970762000000335412"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2022-03-22T01:17:56-07:00", "Modified_Time": "2022-03-22T01:17:56-07:00"}, "emitted_at": 1647946348925}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Lorem ipsum dolor sit amet", "Note_Content": "Sed et dui suscipit, rhoncus metus vel, aliquam arcu", "Parent_Id": {"name": null, "id": "4970762000000335412"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2022-03-22T01:17:56-07:00", "Modified_Time": "2022-03-22T01:17:56-07:00"}, "emitted_at": 1647946348925}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Deal details", "Note_Content": "100 subscriptions deal. Details has been sent", "Parent_Id": {"name": null, "id": "4970762000000335187"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:40-07:00", "Modified_Time": "2021-08-03T04:48:40-07:00"}, "emitted_at": 1647946348926}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Info", "Note_Content": "Interested in learning more", "Parent_Id": {"name": null, "id": "4970762000000335186"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:40-07:00", "Modified_Time": "2021-08-03T04:48:40-07:00"}, "emitted_at": 1647946348926}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Call notes", "Note_Content": "Called the prospect. Need to schedule another call with his manager.", "Parent_Id": {"name": null, "id": "4970762000000335185"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:40-07:00", "Modified_Time": "2021-08-03T04:48:40-07:00"}, "emitted_at": 1647946348926}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Call scheduled", "Note_Content": "Prospect wanted to schedule call on Friday at 11:30 AM", "Parent_Id": {"name": null, "id": "4970762000000335184"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:40-07:00", "Modified_Time": "2021-08-03T04:48:40-07:00"}, "emitted_at": 1647946348927}
+{"stream": "incremental_notes_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Note_Title": "Follow up call", "Note_Content": "Spoke to him today. wants a call after 3 months", "Parent_Id": {"name": null, "id": "4970762000000335183"}, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:40-07:00", "Modified_Time": "2021-08-03T04:48:40-07:00"}, "emitted_at": 1647946348927}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "Trade Show", "First_Name": "Kris", "Last_Name": "Marrier (Sample)", "Full_Name": "Kris Marrier (Sample)", "Account_Name": {"name": "King (Sample)", "id": "4970762000000335098"}, "Vendor_Name": null, "Email": "krismarrier@gmail.com", "Title": "Quality Engineer", "Department": "Engineering", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "kris_marrier", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2021-08-03T04:49:10-07:00", "Twitter": "krismarrier_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "228 Runamuck Pl #2808", "Other_Street": null, "Mailing_City": "Baltimore", "Other_City": null, "Mailing_State": "MD", "Other_State": null, "Mailing_Zip": "21224", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a65131aa9ba7d2035ec2dfa13e1912bde974abb2b6a38a4184620ea1481de850416374d4cdadf484f01b46baee98a702bb"}, "emitted_at": 1647952071135}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "Advertisement", "First_Name": "Sage", "Last_Name": "Wieser (Sample)", "Full_Name": "Sage Wieser (Sample)", "Account_Name": {"name": "Truhlar And Truhlar (Sample)", "id": "4970762000000335097"}, "Vendor_Name": null, "Email": "sage-wieser@truhlar.uk", "Title": "Product Engineer", "Department": "Engineering", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "sage_wieser", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2022-03-22T01:03:25-07:00", "Twitter": "sagewieser_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "5 Boston Ave #88", "Other_Street": null, "Mailing_City": "Sioux Falls", "Other_City": null, "Mailing_State": "SD", "Other_State": null, "Mailing_Zip": "57105", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a601426979380be3a1991427cf85d2359d69100e365e8f162ef063bca8a3be5c6f77f71bff8ae8c11f9a7c1a5ad212edb1"}, "emitted_at": 1647952071138}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "Web Download", "First_Name": "Leota", "Last_Name": "Dilliard (Sample)", "Full_Name": "Leota Dilliard (Sample)", "Account_Name": {"name": "Commercial Press (Sample)", "id": "4970762000000335096"}, "Vendor_Name": null, "Email": "leota-dilliard@hotmail.com", "Title": "Information Systems Manager", "Department": "Management", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "leota_chill", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2022-03-22T01:02:57-07:00", "Twitter": "leotachill_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "7 W Jackson Blvd", "Other_Street": null, "Mailing_City": "San Jose", "Other_City": null, "Mailing_State": "CA", "Other_State": null, "Mailing_Zip": "95111", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a61bc3e9515e66e6c1d972e7d02705929fa7923814e93cabd71b7b90c8c23225fffb4e4b9e33dd299a8cfccbf12643b0fd"}, "emitted_at": 1647952071138}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "Seminar Partner", "First_Name": "Mitsue", "Last_Name": "Tollner (Sample)", "Full_Name": "Mitsue Tollner (Sample)", "Account_Name": {"name": "Morlong Associates (Sample)", "id": "4970762000000335095"}, "Vendor_Name": null, "Email": "tollner-morlong@gmail.com", "Title": "Internal Auditor", "Department": "Audit", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "mitsue_tollner", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:10-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2022-03-22T01:03:38-07:00", "Twitter": "mitsuetollner_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "7 Eads St", "Other_Street": null, "Mailing_City": "Chicago", "Other_City": null, "Mailing_State": "IL", "Other_State": null, "Mailing_Zip": "60632", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a609a3114e5aa7be1f2f16d0ebc849eb92c5faba8fe1b5aa39ed1fa125b85ee7a5071b20d9855e43c390ea0529f5036345"}, "emitted_at": 1647952071139}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "Online Store", "First_Name": "Simon", "Last_Name": "Morasca (Sample)", "Full_Name": "Simon Morasca (Sample)", "Account_Name": {"name": "Chapman (Sample)", "id": "4970762000000335094"}, "Vendor_Name": null, "Email": "simonm@chapman.com", "Title": "Information Systems Manager", "Department": "Management", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "simon_m", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Twitter": "simonmorasc_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "3 Mcauley Dr", "Other_Street": null, "Mailing_City": "Ashland", "Other_City": null, "Mailing_State": "OH", "Other_State": null, "Mailing_Zip": "44805", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6806f7362e70c88bf138a379c9096aaa0e00a835e9137205512a19cd6eabdf659974aaeec9d24ae576e1ae0ed1799ea9c"}, "emitted_at": 1647952071140}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "Partner", "First_Name": "Donette", "Last_Name": "Foller (Sample)", "Full_Name": "Donette Foller (Sample)", "Account_Name": {"name": "Printing Dimensions (Sample)", "id": "4970762000000335093"}, "Vendor_Name": null, "Email": "foller-donette@in.com", "Title": "Nurse", "Department": "Critical Care Unit", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "donette.foller", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Twitter": "donettefoller_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "34 Center St", "Other_Street": null, "Mailing_City": "Hamilton", "Other_City": null, "Mailing_State": "OH", "Other_State": null, "Mailing_Zip": "45011", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a60d7d9b4e1f6e31af5cc3d0d96b915a0561a7dc1279562669fd3ee390d5b7bbaa30803e5b01e96cbfc7c2c1db7416d8b4"}, "emitted_at": 1647952071141}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "External Referral", "First_Name": "Capla", "Last_Name": "Paprocki (Sample)", "Full_Name": "Capla Paprocki (Sample)", "Account_Name": {"name": "Feltz Printing Service (Sample)", "id": "4970762000000335092"}, "Vendor_Name": null, "Email": "capla-paprocki@yahoo.com", "Title": "Systems Administrator II", "Department": "Admin", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "lpaprocki", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2022-03-22T01:00:53-07:00", "Twitter": "lpaprocki_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "639 Main St", "Other_Street": null, "Mailing_City": "Anchorage", "Other_City": null, "Mailing_State": "AK", "Other_State": null, "Mailing_Zip": "99501", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a68f72705594cc1ebda80478dd135dd10af8da004048b15bf793e698932904dbf13720c369133ba08e93de8a9942f9ef59"}, "emitted_at": 1647952071142}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "Employee Referral", "First_Name": "James", "Last_Name": "Venere (Sample)", "Full_Name": "James Venere (Sample)", "Account_Name": {"name": "Chemel (Sample)", "id": "4970762000000335091"}, "Vendor_Name": null, "Email": "ljames-venere@chemel.org", "Title": "Geologist IV", "Department": "Geology", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "james_venere", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Twitter": "jamesvenere_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "8 W Cerritos Ave #54", "Other_Street": null, "Mailing_City": "Bridgeport", "Other_City": null, "Mailing_State": "NJ", "Other_State": null, "Mailing_Zip": "8014", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6a0d3aab36afc9f203ecb1a88b1f7dc6a40e4f6c391fd61eff7f9e41323c994ffabc4eb09b6218abdfd3cf01fb393e581"}, "emitted_at": 1647952071143}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "Cold Call", "First_Name": "Josephine", "Last_Name": "Darakjy (Sample)", "Full_Name": "Josephine Darakjy (Sample)", "Account_Name": {"name": "Chanay (Sample)", "id": "4970762000000335090"}, "Vendor_Name": null, "Email": "joesphine-darakjy@chanay.com", "Title": "Assistant Media Planner", "Department": "Media Planning", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "josephine_darakjy", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Twitter": "josephinedarakjy_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "4 B Blue Ridge Blvd", "Other_Street": null, "Mailing_City": "Brighton", "Other_City": null, "Mailing_State": "MI", "Other_State": null, "Mailing_Zip": "48116", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a611d6454c87da50595759e1db2caded196459aebc3e71499843126532f4defab769fc60a67799e313c649616e69c6d2a8"}, "emitted_at": 1647952071144}
+{"stream": "incremental_contacts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Lead_Source": "Advertisement", "First_Name": "John", "Last_Name": "Butt (Sample)", "Full_Name": "John Butt (Sample)", "Account_Name": {"name": "Benton (Sample)", "id": "4970762000000335089"}, "Vendor_Name": null, "Email": "john-buttbenton@gmail.com", "Title": "Chief Design Engineer", "Department": "Design", "Phone": "555-555-5555", "Home_Phone": null, "Other_Phone": null, "Fax": null, "Mobile": "555-555-5555", "Date_of_Birth": null, "Tag": [], "Assistant": null, "Asst_Phone": null, "Email_Opt_Out": false, "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Skype_ID": "j_butt", "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:37-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Salutation": null, "Secondary_Email": null, "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Twitter": "johnbuttbent_sample", "Reporting_To": null, "Unsubscribed_Mode": null, "Unsubscribed_Time": null, "Mailing_Street": "6649 N Blue Gum St", "Other_Street": null, "Mailing_City": "New Orleans", "Other_City": null, "Mailing_State": "LA", "Other_State": null, "Mailing_Zip": "70116", "Other_Zip": null, "Mailing_Country": "United States", "Other_Country": null, "Description": null, "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a67ed114673c58efb8d2367d6e6a685fa37bc06505bd76d9a02b87e364b8ac304574ba9d20c559705e96a2ff6e9767b2a7"}, "emitted_at": 1647952071144}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "King (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://kingmanufacturing.com", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Vendor", "Ownership": "Partnership", "Industry": "Manufacturing", "Employees": 445, "Annual_Revenue": 850000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Last_Activity_Time": "2021-08-03T04:49:10-07:00", "Billing_Street": "228 Runamuck Pl #2808", "Shipping_Street": null, "Billing_City": "Baltimore", "Shipping_City": null, "Billing_State": "MD", "Shipping_State": null, "Billing_Code": "21224", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "King is a multinational electronics contract manufacturing company with its headquarters in Baltimore, United States.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6a9851b637cb1e022af4adefde9413d61548e1d47f44a8becdce906639c5a44172fc281f84e19596f71c50bb807f20246"}, "emitted_at": 1647952220730}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "Truhlar And Truhlar (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://truhlarandtruhlartech.com/", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Supplier", "Ownership": "Privately Held", "Industry": "Technology", "Employees": 23, "Annual_Revenue": 200000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Last_Activity_Time": "2022-03-22T01:03:25-07:00", "Billing_Street": "5 Boston Ave #88", "Shipping_Street": null, "Billing_City": "Sioux Falls", "Shipping_City": null, "Billing_State": "SD", "Shipping_State": null, "Billing_Code": "57105", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "Truhlar and Truhlar is a technology supplier of noise reduction systems and audio encoding / compression.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6bebc3e1a6c7098139726f201d2abedb0cc3ab797affa2c5441fb08b4323a2228fea0895246b160a68a0fffa979833441"}, "emitted_at": 1647952220730}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "Commercial Press (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://commercialpress.com/", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Reseller", "Ownership": "Privately Held", "Industry": "Education", "Employees": 456, "Annual_Revenue": 650000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Last_Activity_Time": "2022-03-22T01:02:57-07:00", "Billing_Street": "7 W Jackson Blvd", "Shipping_Street": null, "Billing_City": "San Jose", "Shipping_City": null, "Billing_State": "CA", "Shipping_State": null, "Billing_Code": "95111", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "Commercial Press is an American owned reseller of renowned authors across the education sector.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a616db811658ce20bcb619f8cd72586b6968db4cda5076bfec41a2a460a634ef493ac33b9aa35b17ec5140f9f192c9c67d"}, "emitted_at": 1647952220731}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "Morlong Associates (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://morlongassociates.com/", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Partner", "Ownership": "Privately Held", "Industry": "Financial Services", "Employees": 300, "Annual_Revenue": 190000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:09-07:00", "Last_Activity_Time": "2022-03-22T01:03:38-07:00", "Billing_Street": "7 Eads St", "Shipping_Street": null, "Billing_City": "Chicago", "Shipping_City": null, "Billing_State": "IL", "Shipping_State": null, "Billing_Code": "60632", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "Morlong Associates is an American investment management firm founded in 1995.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a612dd39c84c4d07f4207ca971f2e5fe0ad9692d83587b8a2edc14bbf8ff72e45ef2a7e22219aef9a9b18910ea621182a7"}, "emitted_at": 1647952220731}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "Chapman (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://chapmanus.com/", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Other", "Ownership": "Privately Held", "Industry": "Consulting", "Employees": 2000, "Annual_Revenue": 230000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:08-07:00", "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Billing_Street": "3 Mcauley Dr", "Shipping_Street": null, "Billing_City": "Ashland", "Shipping_City": null, "Billing_State": "OH", "Shipping_State": null, "Billing_Code": "44805", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "Founded in 1996 and headquartered in Ashland, Ohio. They operate from 10 locations across the country.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a63f6c96af94bb988771c65dcaede2b5c089ff3c28b71fb5957dfb090e69d3c77742f29c4adf9d96c324cac81c707b49ea"}, "emitted_at": 1647952220731}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "Printing Dimensions (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://printingdimensions.com", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Investor", "Ownership": "Privately Held", "Industry": "Real Estate", "Employees": 80, "Annual_Revenue": 270000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:08-07:00", "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Billing_Street": "34 Center St", "Shipping_Street": null, "Billing_City": "Hamilton", "Shipping_City": null, "Billing_State": "OH", "Shipping_State": null, "Billing_Code": "45011", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "Printing Dimensions is a privately-held real estate developer based in Hamilton, Ohio.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6156409a0551f2ea98dfd23f9e0ec45cf8d09cd93acdffc882a2285f16227daa6c81d66db0e39bd471ccf2f7c003f97f4"}, "emitted_at": 1647952220731}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "Feltz Printing Service (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://feltzprinting.com/", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Integrator", "Ownership": "Privately Held", "Industry": "Manufacturing", "Employees": 3500, "Annual_Revenue": 170000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:08-07:00", "Last_Activity_Time": "2022-03-22T01:00:53-07:00", "Billing_Street": "639 Main St", "Shipping_Street": null, "Billing_City": "Anchorage", "Shipping_City": null, "Billing_State": "AK", "Shipping_State": null, "Billing_Code": "99501", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "Feltz Printing Service is a privately-held manufacturing company based out of Anchorage, Alaska.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6570a4f9d0d765928249fb9c3012a47cf86ef407ade74d554bbc39adba736bb6a723ed3b3c89b8d90da36a9285d8f612d"}, "emitted_at": 1647952220732}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "Chemel (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://chemelus.com/", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Distributor", "Ownership": "Public Company", "Industry": "Communications", "Employees": 1200, "Annual_Revenue": 830000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:08-07:00", "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Billing_Street": "8 W Cerritos Ave #54", "Shipping_Street": null, "Billing_City": "Bridgeport", "Shipping_City": null, "Billing_State": "NJ", "Shipping_State": null, "Billing_Code": "8014", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "Chemel is a public owned communications company with an annual revenue of 830000 USD and about 1200 employees.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6d078c5e285bc94cb3aef8bc2c931bd265f92669fec8fa0e6ebf2a1e38c0e93ec192bcd369074b326dd5416bd8fa31acb"}, "emitted_at": 1647952220732}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "Chanay (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://chanayus.com/", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Competitor", "Ownership": "Government", "Industry": "Government/Military", "Employees": 2500, "Annual_Revenue": 200000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:08-07:00", "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Billing_Street": "4 B Blue Ridge Blvd", "Shipping_Street": null, "Billing_City": "Brighton", "Shipping_City": null, "Billing_State": "MI", "Shipping_State": null, "Billing_Code": "48116", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "Chanay is a government owned organisation with 2500 employees. It consists of the Navy and Coast Guard units.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a663860a0d672a807aa8384f4a98bc238dabce422f6f49b24966e21eb622d06c8b27ac394ef9f5190597d76694f110f0a1"}, "emitted_at": 1647952220732}
+{"stream": "incremental_accounts_zoho_crm_stream", "data": {"Owner": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Rating": null, "Account_Name": "Benton (Sample)", "Phone": "555-555-5555", "Account_Site": null, "Fax": null, "Parent_Account": null, "Website": "http://bentonus.com/", "Account_Number": "0", "Ticker_Symbol": null, "Account_Type": "Analyst", "Ownership": "Privately Held", "Industry": "Technology", "Employees": 50, "Annual_Revenue": 100000, "SIC_Code": null, "Tag": [], "Created_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Modified_By": {"name": "Jean Lafleur", "id": "4970762000000326001", "email": "integration-test@airbyte.io"}, "Created_Time": "2021-08-03T04:48:36-07:00", "Modified_Time": "2021-08-03T04:49:08-07:00", "Last_Activity_Time": "2021-08-03T04:49:09-07:00", "Billing_Street": "6649 N Blue Gum St", "Shipping_Street": null, "Billing_City": "New Orleans", "Shipping_City": null, "Billing_State": "LA", "Shipping_State": null, "Billing_Code": "70116", "Shipping_Code": null, "Billing_Country": "United States", "Shipping_Country": null, "Description": "Benton is a privately held technology company with 50 employees with an annual revenue of 100000 USD.", "Record_Image": "d025137519fb542c97c16281ccedd708aece0f671ed7d8989547e236793483a6bd5b2c9a3eb5db86d6d398351e9f948599372eb10d6b05ab7517009523815ebb71af28d372a987a20a16db9e7c4b997b"}, "emitted_at": 1647952220732}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Missed call from Mitsue Tollner (Sample)", "Call_Type": "Missed", "Call_Start_Time": "2022-03-22T01:03:00-07:00", "Call_Duration": "00:00", "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930386}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Incoming call from Sage Wieser (Sample)", "Call_Type": "Inbound", "Call_Start_Time": "2022-03-22T00:30:00-07:00", "Call_Duration": "01:00", "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930386}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Call scheduled with Leota Dilliard (Sample)", "Call_Type": "Outbound", "Call_Start_Time": "2022-03-22T12:00:00-07:00", "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930387}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Call scheduled with Brian Dolan", "Call_Type": "Outbound", "Call_Start_Time": "2022-03-31T11:30:00-07:00", "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930387}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Call scheduled with Sage Wieser (Sample)", "Call_Type": "Outbound", "Call_Start_Time": "2022-04-09T19:00:00-07:00", "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930387}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Missed call from Yvonne Tjepkema (Sample)", "Call_Type": "Missed", "Call_Start_Time": "2022-03-22T00:30:00-07:00", "Call_Duration": "00:00", "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930387}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Incoming call from Capla Paprocki (Sample)", "Call_Type": "Inbound", "Call_Start_Time": "2022-03-22T00:30:00-07:00", "Call_Duration": "23:23", "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930388}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Call scheduled with Theola Frey (Sample)", "Call_Type": "Outbound", "Call_Start_Time": "2022-03-22T10:00:00-07:00", "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930388}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Call scheduled with Leota Dilliard (Sample)", "Call_Type": "Outbound", "Call_Start_Time": "2022-03-22T00:00:00-07:00", "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930388}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Follow up with Lead", "Call_Type": "Outbound", "Call_Start_Time": "2021-08-03T06:48:39-07:00", "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930388}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Demo", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": "2021-08-03T08:48:39-07:00", "End_DateTime": "2021-08-03T09:48:39-07:00"}, "emitted_at": 1647952930389}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Webinar", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": "2021-08-03T10:48:39-07:00", "End_DateTime": "2021-08-03T11:48:39-07:00"}, "emitted_at": 1647952930389}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "TradeShow", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": "2021-08-03T00:00:00+00:00", "End_DateTime": "2021-08-03T23:59:59+00:00"}, "emitted_at": 1647952930389}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Webinar", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": "2021-08-03T09:48:39-07:00", "End_DateTime": "2021-08-03T12:48:39-07:00"}, "emitted_at": 1647952930389}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Seminar", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": "2021-08-03T08:48:39-07:00", "End_DateTime": "2021-08-03T10:48:39-07:00"}, "emitted_at": 1647952930389}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Attend Customer conference", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": "2021-08-03T00:00:00+00:00", "End_DateTime": "2021-08-03T23:59:59+00:00"}, "emitted_at": 1647952930390}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "CRM Webinar", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": "2021-08-03T07:48:39-07:00", "End_DateTime": "2021-08-03T09:48:39-07:00"}, "emitted_at": 1647952930390}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "CRM Webinar", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": "2021-08-03T06:48:39-07:00", "End_DateTime": "2021-08-03T07:48:39-07:00"}, "emitted_at": 1647952930390}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "CRM Webinar", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": "2021-08-03T06:48:39-07:00", "End_DateTime": "2021-08-03T07:48:39-07:00"}, "emitted_at": 1647952930390}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Complete CRM Getting Started steps", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930391}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Register for upcoming CRM Webinars", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930391}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Complete CRM Getting Started steps", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930391}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Refer CRM Videos", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930391}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Competitor Comparison Document", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930391}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Get Apporval from Manager", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930392}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Get Approval from Manager", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930392}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Get Apporval from Manager", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930392}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Register for upcoming CRM Webinars", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930392}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Refer CRM Videos", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930393}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Customize CRM to your needs", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930393}
+{"stream": "incremental_activities_zoho_crm_stream", "data": {"Subject": "Complete CRM Getting Started steps", "Call_Type": null, "Call_Start_Time": null, "Call_Duration": null, "Start_DateTime": null, "End_DateTime": null}, "emitted_at": 1647952930393}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Complete CRM Getting Started steps"}, "emitted_at": 1647953238190}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Register for upcoming CRM Webinars"}, "emitted_at": 1647953238192}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Complete CRM Getting Started steps"}, "emitted_at": 1647953238192}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Refer CRM Videos"}, "emitted_at": 1647953238193}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Competitor Comparison Document"}, "emitted_at": 1647953238194}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Get Apporval from Manager"}, "emitted_at": 1647953238195}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Get Approval from Manager"}, "emitted_at": 1647953238195}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Get Apporval from Manager"}, "emitted_at": 1647953238195}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Register for upcoming CRM Webinars"}, "emitted_at": 1647953238196}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Refer CRM Videos"}, "emitted_at": 1647953238196}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Customize CRM to your needs"}, "emitted_at": 1647953238196}
+{"stream": "incremental_tasks_zoho_crm_stream", "data": {"Subject": "Complete CRM Getting Started steps"}, "emitted_at": 1647953238196}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Missed", "Call_Start_Time": "2022-03-22T01:03:00-07:00", "Call_Duration": "00:00"}, "emitted_at": 1647953598638}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Inbound", "Call_Start_Time": "2022-03-22T00:30:00-07:00", "Call_Duration": "01:00"}, "emitted_at": 1647953598639}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Outbound", "Call_Start_Time": "2022-03-22T12:00:00-07:00", "Call_Duration": null}, "emitted_at": 1647953598640}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Outbound", "Call_Start_Time": "2022-03-31T11:30:00-07:00", "Call_Duration": null}, "emitted_at": 1647953598640}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Outbound", "Call_Start_Time": "2022-04-09T19:00:00-07:00", "Call_Duration": null}, "emitted_at": 1647953598640}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Missed", "Call_Start_Time": "2022-03-22T00:30:00-07:00", "Call_Duration": "00:00"}, "emitted_at": 1647953598641}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Inbound", "Call_Start_Time": "2022-03-22T00:30:00-07:00", "Call_Duration": "23:23"}, "emitted_at": 1647953598641}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Outbound", "Call_Start_Time": "2022-03-22T10:00:00-07:00", "Call_Duration": null}, "emitted_at": 1647953598641}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Outbound", "Call_Start_Time": "2022-03-22T00:00:00-07:00", "Call_Duration": null}, "emitted_at": 1647953598641}
+{"stream": "incremental_calls_zoho_crm_stream", "data": {"Call_Type": "Outbound", "Call_Start_Time": "2021-08-03T06:48:39-07:00", "Call_Duration": null}, "emitted_at": 1647953598641}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "King", "Stage": "Identify Decision Makers"}, "emitted_at": 1647953915500}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "Truhlar And Truhlar Attys", "Stage": "Needs Analysis"}, "emitted_at": 1647953915501}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "Commercial Press", "Stage": "Closed Lost"}, "emitted_at": 1647953915502}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "Morlong Associates", "Stage": "Closed Won"}, "emitted_at": 1647953915503}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "Chapman", "Stage": "Negotiation/Review"}, "emitted_at": 1647953915505}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "Printing Dimensions", "Stage": "Proposal/Price Quote"}, "emitted_at": 1647953915506}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "Feltz Printing Service", "Stage": "Identify Decision Makers"}, "emitted_at": 1647953915507}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "Chemel", "Stage": "Value Proposition"}, "emitted_at": 1647953915508}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "Chanay", "Stage": "Needs Analysis"}, "emitted_at": 1647953915509}
+{"stream": "incremental_deals_zoho_crm_stream", "data": {"Deal_Name": "Benton", "Stage": "Qualification"}, "emitted_at": 1647953915510}
+{"stream": "incremental_events_zoho_crm_stream", "data": {"Event_Title": "Demo", "Start_DateTime": "2021-08-03T08:48:39-07:00", "End_DateTime": "2021-08-03T09:48:39-07:00"}, "emitted_at": 1647954113287}
+{"stream": "incremental_events_zoho_crm_stream", "data": {"Event_Title": "Webinar", "Start_DateTime": "2021-08-03T10:48:39-07:00", "End_DateTime": "2021-08-03T11:48:39-07:00"}, "emitted_at": 1647954113288}
+{"stream": "incremental_events_zoho_crm_stream", "data": {"Event_Title": "TradeShow", "Start_DateTime": "2021-08-03T00:00:00+00:00", "End_DateTime": "2021-08-03T23:59:59+00:00"}, "emitted_at": 1647954113289}
+{"stream": "incremental_events_zoho_crm_stream", "data": {"Event_Title": "Webinar", "Start_DateTime": "2021-08-03T09:48:39-07:00", "End_DateTime": "2021-08-03T12:48:39-07:00"}, "emitted_at": 1647954113290}
+{"stream": "incremental_events_zoho_crm_stream", "data": {"Event_Title": "Seminar", "Start_DateTime": "2021-08-03T08:48:39-07:00", "End_DateTime": "2021-08-03T10:48:39-07:00"}, "emitted_at": 1647954113291}
+{"stream": "incremental_events_zoho_crm_stream", "data": {"Event_Title": "Attend Customer conference", "Start_DateTime": "2021-08-03T00:00:00+00:00", "End_DateTime": "2021-08-03T23:59:59+00:00"}, "emitted_at": 1647954113292}
+{"stream": "incremental_events_zoho_crm_stream", "data": {"Event_Title": "CRM Webinar", "Start_DateTime": "2021-08-03T07:48:39-07:00", "End_DateTime": "2021-08-03T09:48:39-07:00"}, "emitted_at": 1647954113292}
+{"stream": "incremental_events_zoho_crm_stream", "data": {"Event_Title": "CRM Webinar", "Start_DateTime": "2021-08-03T06:48:39-07:00", "End_DateTime": "2021-08-03T07:48:39-07:00"}, "emitted_at": 1647954113293}
+{"stream": "incremental_events_zoho_crm_stream", "data": {"Event_Title": "CRM Webinar", "Start_DateTime": "2021-08-03T06:48:39-07:00", "End_DateTime": "2021-08-03T07:48:39-07:00"}, "emitted_at": 1647954113294}
+{"stream": "incremental_vendors_zoho_crm_stream", "data": {"Vendor_Name": "Oracle"}, "emitted_at": 1647954300711}
+{"stream": "incremental_vendors_zoho_crm_stream", "data": {"Vendor_Name": "cisco"}, "emitted_at": 1647954300712}
+{"stream": "incremental_vendors_zoho_crm_stream", "data": {"Vendor_Name": "Intel"}, "emitted_at": 1647954300713}
+{"stream": "incremental_vendors_zoho_crm_stream", "data": {"Vendor_Name": "Airbyte"}, "emitted_at": 1647954300713}
+{"stream": "incremental_vendors_zoho_crm_stream", "data": {"Vendor_Name": "Dummy vendor"}, "emitted_at": 1647954300713}
+{"stream": "incremental_products_zoho_crm_stream", "data": {"Product_Name": "RDBMS"}, "emitted_at": 1647954462243}
+{"stream": "incremental_products_zoho_crm_stream", "data": {"Product_Name": "Airbyte-Integration"}, "emitted_at": 1647954462244}
+{"stream": "incremental_quotes_zoho_crm_stream", "data": {"Subject": "Price change", "Product_Details": [{"product": {"Product_Code": "1010101", "name": "RDBMS", "id": "4970762000000802169"}, "quantity": 1, "Discount": 0, "total_after_discount": 333, "net_total": 333, "book": null, "Tax": 0, "list_price": 333, "unit_price": 333, "quantity_in_stock": -1, "total": 333, "id": "4970762000000802207", "product_description": null, "line_tax": []}]}, "emitted_at": 1647954684205}
+{"stream": "incremental_sales__orders_zoho_crm_stream", "data": {"Subject": "Price change", "Product_Details": [{"product": {"Product_Code": "1010101", "name": "RDBMS", "id": "4970762000000802169"}, "quantity": 1, "Discount": 0, "total_after_discount": 333, "net_total": 333, "book": null, "Tax": 0, "list_price": 333, "unit_price": 333, "quantity_in_stock": -1, "total": 333, "id": "4970762000000809183", "product_description": null, "line_tax": []}]}, "emitted_at": 1647954842048}
+{"stream": "incremental_purchase__orders_zoho_crm_stream", "data": {"Subject": "contract prolongation for 2022", "Vendor_Name": {"name": "Airbyte", "id": "4970762000000343001"}, "Product_Details": [{"product": {"Product_Code": "123456", "name": "Airbyte-Integration", "id": "4970762000000343012"}, "quantity": 1, "Discount": 0, "total_after_discount": 200, "net_total": 200, "book": null, "Tax": 0, "list_price": 200, "unit_price": 200, "quantity_in_stock": 20, "total": 200, "id": "4970762000000809137", "product_description": "This is the test product 1", "line_tax": []}]}, "emitted_at": 1647954938749}
+{"stream": "incremental_price__books_zoho_crm_stream", "data": {"Price_Book_Name": "Exhibit"}, "emitted_at": 1647955176883}
+{"stream": "incremental_solutions_zoho_crm_stream", "data": {"Solution_Title": "Optimistic locking"}, "emitted_at": 1647955273371}
+{"stream": "incremental_cases_zoho_crm_stream", "data": {"Status": "Escalated", "Case_Origin": "Email", "Subject": "Implement optimistic locking"}, "emitted_at": 1647955370238}
+{"stream": "incremental_invoices_zoho_crm_stream", "data": {"Subject": "Price change", "Product_Details": [{"product": {"Product_Code": "1010101", "name": "RDBMS", "id": "4970762000000802169"}, "quantity": 1, "Discount": 0, "total_after_discount": 333, "net_total": 333, "book": null, "Tax": 0, "list_price": 333, "unit_price": 333, "quantity_in_stock": -1, "total": 333, "id": "4970762000000809239", "product_description": null, "line_tax": []}]}, "emitted_at": 1647955476518}
+{"stream": "incremental_attachments_zoho_crm_stream", "data": {"File_Name": "lorem", "Size": "591", "Parent_Id": {"name": null, "id": "4970762000000335412"}}, "emitted_at": 1647955752413}
+{"stream": "incremental_campaigns_zoho_crm_stream", "data": {"Campaign_Name": "Easter 2022"}, "emitted_at": 1647955860393}
diff --git a/airbyte-integrations/connectors/source-zoho-crm/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/invalid_config.json
new file mode 100644
index 0000000000000..22ce532eaac92
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/invalid_config.json
@@ -0,0 +1,9 @@
+{
+ "refresh_token": "refresh_token",
+ "client_id": "client_id",
+ "client_secret": "client_secret",
+ "environment": "Developer",
+ "dc_region": "US",
+ "start_datetime": "yesterday",
+ "edition": "Free"
+}
diff --git a/airbyte-integrations/connectors/source-zoho-crm/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/sample_config.json
new file mode 100644
index 0000000000000..419391f463e85
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/sample_config.json
@@ -0,0 +1,9 @@
+{
+ "refresh_token": "1000.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "client_id": "1000.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
+ "client_secret": "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii",
+ "environment": "Production",
+ "dc_region": "US",
+ "start_datetime": "2000-01-01T00:00:00+00:00",
+ "edition": "Free"
+}
\ No newline at end of file
diff --git a/airbyte-integrations/connectors/source-zoho-crm/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/sample_state.json
new file mode 100644
index 0000000000000..16e9d2072beb8
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/sample_state.json
@@ -0,0 +1,5 @@
+{
+ "incremental_leads_zoho_crm_stream": {
+ "Modified_Time": "2021-08-04T04:06:27-07:00"
+ }
+}
\ No newline at end of file
diff --git a/airbyte-integrations/connectors/source-zoho-crm/integration_tests/test_stream_factory.py b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/test_stream_factory.py
new file mode 100644
index 0000000000000..ead034e1d42fe
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/integration_tests/test_stream_factory.py
@@ -0,0 +1,110 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+import functools
+import json
+import time
+from pathlib import Path
+
+import pytest
+import requests
+from source_zoho_crm.streams import IncrementalZohoCrmStream, ZohoStreamFactory
+
+HERE = Path(__file__).parent
+
+
+@pytest.fixture()
+def config():
+ with open(HERE.parent / "secrets/config.json", "r") as file:
+ return json.loads(file.read())
+
+
+@pytest.fixture
+def request_sniffer(mocker):
+ def request_decorator(stats):
+ def decorator(func):
+ def inner(*args, **kwargs):
+ resp = func(*args, **kwargs)
+ stats[resp.url.split("/crm", 1)[-1]] = resp.status_code
+ return resp
+
+ return inner
+
+ return decorator
+
+ stats = {}
+ decorated = request_decorator(stats)
+ mocker.patch("source_zoho_crm.api.requests.request", decorated(requests.request))
+ mocker.patch("source_zoho_crm.api.requests.get", decorated(requests.get))
+ return stats
+
+
+def timeit(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ start = time.time()
+ result = func(*args, **kwargs)
+ end = time.time()
+ total_time = int(end - start)
+ print(f"{func}.__name__ execution took {total_time} seconds")
+ assert total_time <= 15
+ return result
+
+ return wrapper
+
+
+def test_stream_factory(request_sniffer, config):
+ factory = ZohoStreamFactory(config)
+ streams = timeit(factory.produce)()
+ expected_stream_names = {
+ "Accounts",
+ "Activities",
+ "Attachments",
+ "Calls",
+ "Campaigns",
+ "Cases",
+ "Contacts",
+ "Deals",
+ "Events",
+ "Invoices",
+ "Leads",
+ "Notes",
+ "Price_Books",
+ "Products",
+ "Purchase_Orders",
+ "Quotes",
+ "Sales_Orders",
+ "Solutions",
+ "Tasks",
+ "Vendors",
+ }
+ stream_names = set()
+ for stream in streams:
+ assert stream.supports_incremental
+ assert isinstance(stream, IncrementalZohoCrmStream)
+ assert stream.primary_key
+ assert stream.url_base
+ assert stream.path()
+ assert stream.get_json_schema()
+ stream_names.add(stream.module.api_name)
+ assert expected_stream_names.issubset(stream_names)
+
+ expected_stream_names, unexpected_stream_names = set(), set()
+ # to build a schema for a stream, a sequence of requests is made:
+ # one `/settings/modules` which introduces a list of modules,
+ # one `/settings/modules/{module_name}` per module and
+ # one `/settings/fields?module={module_name}` per module.
+ # Any of former two can result in 204 and empty body what blocks us
+ # from generating stream schema and, therefore, a stream.
+ for url, status in request_sniffer.items():
+ assert status in (200, 204)
+ module = url.split("?module=")[-1]
+ if module == url:
+ module = url.split("modules/")[-1]
+ if module == url:
+ continue
+ expected_stream_names.add(module)
+ if status == 204:
+ unexpected_stream_names.add(module)
+ assert expected_stream_names - unexpected_stream_names == stream_names
diff --git a/airbyte-integrations/connectors/source-zoho-crm/main.py b/airbyte-integrations/connectors/source-zoho-crm/main.py
new file mode 100644
index 0000000000000..e71b10da98f2b
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/main.py
@@ -0,0 +1,13 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+
+import sys
+
+from airbyte_cdk.entrypoint import launch
+from source_zoho_crm import SourceZohoCrm
+
+if __name__ == "__main__":
+ source = SourceZohoCrm()
+ launch(source, sys.argv[1:])
diff --git a/airbyte-integrations/connectors/source-zoho-crm/requirements.txt b/airbyte-integrations/connectors/source-zoho-crm/requirements.txt
new file mode 100644
index 0000000000000..0411042aa0911
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/requirements.txt
@@ -0,0 +1,2 @@
+-e ../../bases/source-acceptance-test
+-e .
diff --git a/airbyte-integrations/connectors/source-zoho-crm/setup.py b/airbyte-integrations/connectors/source-zoho-crm/setup.py
new file mode 100644
index 0000000000000..8aaa882c27598
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/setup.py
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+
+from setuptools import find_packages, setup
+
+MAIN_REQUIREMENTS = [
+ "airbyte-cdk~=0.1",
+]
+
+TEST_REQUIREMENTS = [
+ "pytest~=6.1",
+ "pytest-mock~=3.6.1",
+ "source-acceptance-test",
+]
+
+setup(
+ name="source_zoho_crm",
+ description="Source implementation for Zoho Crm.",
+ author="Airbyte",
+ author_email="contact@airbyte.io",
+ packages=find_packages(),
+ install_requires=MAIN_REQUIREMENTS,
+ package_data={"": ["*.json", "schemas/*.json", "schemas/shared/*.json"]},
+ extras_require={
+ "tests": TEST_REQUIREMENTS,
+ },
+)
diff --git a/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/__init__.py b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/__init__.py
new file mode 100644
index 0000000000000..117179e10855b
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/__init__.py
@@ -0,0 +1,8 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+
+from .source import SourceZohoCrm
+
+__all__ = ["SourceZohoCrm"]
diff --git a/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/api.py b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/api.py
new file mode 100644
index 0000000000000..74fb140385343
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/api.py
@@ -0,0 +1,94 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+import logging
+from types import MappingProxyType
+from typing import Any, List, Mapping, MutableMapping, Tuple
+from urllib.parse import urlsplit, urlunsplit
+
+import requests
+
+from .auth import ZohoOauth2Authenticator
+
+logger = logging.getLogger(__name__)
+
+
+class ZohoAPI:
+ _DC_REGION_TO_ACCESS_URL = MappingProxyType(
+ {
+ "US": "https://accounts.zoho.com",
+ "AU": "https://accounts.zoho.com.au",
+ "EU": "https://accounts.zoho.eu",
+ "IN": "https://accounts.zoho.in",
+ "CN": "https://accounts.zoho.com.cn",
+ "JP": "https://accounts.zoho.jp",
+ }
+ )
+ _DC_REGION_TO_API_URL = MappingProxyType(
+ {
+ "US": "https://zohoapis.com",
+ "AU": "https://zohoapis.com.au",
+ "EU": "https://zohoapis.eu",
+ "IN": "https://zohoapis.in",
+ "CN": "https://zohoapis.com.cn",
+ "JP": "https://zohoapis.jp",
+ }
+ )
+ _API_ENV_TO_URL_PREFIX = MappingProxyType({"production": "", "developer": "developer", "sandbox": "sandbox"})
+ _CONCURRENCY_API_LIMITS = MappingProxyType({"Free": 5, "Standard": 10, "Professional": 15, "Enterprise": 20, "Ultimate": 25})
+
+ def __init__(self, config: Mapping[str, Any]):
+ self.config = config
+ self._authenticator = None
+
+ @property
+ def authenticator(self) -> ZohoOauth2Authenticator:
+ if not self._authenticator:
+ authenticator = ZohoOauth2Authenticator(
+ f"{self._access_url}/oauth/v2/token", self.config["client_id"], self.config["client_secret"], self.config["refresh_token"]
+ )
+ self._authenticator = authenticator
+ return self._authenticator
+
+ @property
+ def _access_url(self) -> str:
+ return self._DC_REGION_TO_ACCESS_URL[self.config["dc_region"].upper()]
+
+ @property
+ def max_concurrent_requests(self) -> int:
+ return self._CONCURRENCY_API_LIMITS[self.config["edition"]]
+
+ @property
+ def api_url(self) -> str:
+ schema, domain, *_ = urlsplit(self._DC_REGION_TO_API_URL[self.config["dc_region"].upper()])
+ prefix = self._API_ENV_TO_URL_PREFIX[self.config["environment"].lower()]
+ if prefix:
+ domain = f"{prefix}.{domain}"
+ return urlunsplit((schema, domain, *_))
+
+ def _json_from_path(self, path: str, key: str, params: MutableMapping[str, str] = None) -> List[MutableMapping[Any, Any]]:
+ response = requests.get(url=f"{self.api_url}{path}", headers=self.authenticator.get_auth_header(), params=params or {})
+ if response.status_code == 204:
+ # Zoho CRM returns `No content` for Metadata of some modules
+ logger.warning(f"{key.capitalize()} Metadata inaccessible: {response.content} [HTTP status {response.status_code}]")
+ return []
+ return response.json()[key]
+
+ def module_settings(self, module_name: str) -> List[MutableMapping[Any, Any]]:
+ return self._json_from_path(f"/crm/v2/settings/modules/{module_name}", key="modules")
+
+ def modules_settings(self) -> List[MutableMapping[Any, Any]]:
+ return self._json_from_path("/crm/v2/settings/modules", key="modules")
+
+ def fields_settings(self, module_name: str) -> List[MutableMapping[Any, Any]]:
+ return self._json_from_path("/crm/v2/settings/fields", key="fields", params={"module": module_name})
+
+ def check_connection(self) -> Tuple[bool, Any]:
+ path = "/crm/v2/settings/modules"
+ response = requests.get(url=f"{self.api_url}{path}", headers=self.authenticator.get_auth_header())
+ try:
+ response.raise_for_status()
+ except requests.exceptions.HTTPError as exc:
+ return False, exc.response.content
+ return True, None
diff --git a/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/auth.py b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/auth.py
new file mode 100644
index 0000000000000..1bac84ff04160
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/auth.py
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+from typing import Any, Dict, Mapping, Tuple
+
+import requests
+from airbyte_cdk.sources.streams.http.requests_native_auth import Oauth2Authenticator
+
+
+class ZohoOauth2Authenticator(Oauth2Authenticator):
+ def _prepare_refresh_token_params(self) -> Dict[str, str]:
+ return {
+ "refresh_token": self.refresh_token,
+ "client_id": self.client_id,
+ "client_secret": self.client_secret,
+ "grant_type": "refresh_token",
+ }
+
+ def get_auth_header(self) -> Mapping[str, Any]:
+ token = self.get_access_token()
+ return {"Authorization": f"Zoho-oauthtoken {token}"}
+
+ def refresh_access_token(self) -> Tuple[str, int]:
+ """
+ This method is overridden because token parameters should be passed via URL params, not via the request payload.
+ Returns a tuple of (access_token, token_lifespan_in_seconds)
+ """
+ try:
+ response = requests.request(method="POST", url=self.token_refresh_endpoint, params=self._prepare_refresh_token_params())
+ response.raise_for_status()
+ response_json = response.json()
+ return response_json[self.access_token_name], response_json[self.expires_in_name]
+ except Exception as e:
+ raise Exception(f"Error while refreshing access token: {e}") from e
diff --git a/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/exceptions.py b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/exceptions.py
new file mode 100644
index 0000000000000..38113d78badf7
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/exceptions.py
@@ -0,0 +1,11 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+
+class IncompleteMetaDataException(Exception):
+ pass
+
+
+class UnknownDataTypeException(Exception):
+ pass
diff --git a/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/source.py b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/source.py
new file mode 100644
index 0000000000000..47cdb79d57d8f
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/source.py
@@ -0,0 +1,35 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+import logging
+from typing import TYPE_CHECKING, Any, List, Mapping, Tuple
+
+from airbyte_cdk.sources import AbstractSource
+
+from .api import ZohoAPI
+from .streams import ZohoStreamFactory
+
+if TYPE_CHECKING:
+ # This is a workaround to avoid circular import in the future.
+ # TYPE_CHECKING is False at runtime, but True when system performs type checking
+ # See details here https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING
+ from airbyte_cdk.sources.streams import Stream
+
+
+class SourceZohoCrm(AbstractSource):
+ def check_connection(self, logger: logging.Logger, config: Mapping[str, Any]) -> Tuple[bool, any]:
+ """
+ :param config: the user-input config object conforming to the connector's spec.json
+ :param logger: logger object
+ :return Tuple[bool, any]: (True, None) if the input config can be used to connect to the API successfully, (False, error) otherwise.
+ """
+ api = ZohoAPI(config)
+ return api.check_connection()
+
+ def streams(self, config: Mapping[str, Any]) -> List["Stream"]:
+ """
+ :param config: A Mapping of the user input configuration as defined in the connector spec.
+ """
+ stream_factory = ZohoStreamFactory(config)
+ return stream_factory.produce()
diff --git a/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/spec.json b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/spec.json
new file mode 100644
index 0000000000000..71a996e30e4ed
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/spec.json
@@ -0,0 +1,58 @@
+{
+ "documentationUrl": "https://docs.airbyte.com/integrations/sources/zoho-crm",
+ "connectionSpecification": {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Zoho Crm Configuration",
+ "type": "object",
+ "required": [
+ "client_id", "client_secret", "refresh_token", "environment", "dc_region", "edition"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "client_id": {
+ "type": "string",
+ "title": "Client ID",
+ "description": "OAuth2.0 Client ID",
+ "airbyte_secret": true
+ },
+ "client_secret": {
+ "type": "string",
+ "title": "Client Secret",
+ "description": "OAuth2.0 Client Secret",
+ "airbyte_secret": true
+ },
+ "refresh_token": {
+ "type": "string",
+ "title": "Refresh Token",
+ "description": "OAuth2.0 Refresh Token",
+ "airbyte_secret": true
+ },
+ "dc_region": {
+ "title": "Data Center Location",
+ "type": "string",
+ "description": "Please choose the region of your Data Center location. More info by this Link",
+ "enum": ["US", "AU", "EU", "IN", "CN", "JP"]
+ },
+ "environment": {
+ "title": "Environment",
+ "type": "string",
+ "description": "Please choose the environment",
+ "enum": ["Production", "Developer", "Sandbox"]
+ },
+ "start_datetime": {
+ "title": "Start Date",
+ "type": ["null", "string"],
+ "examples": ["2000-01-01", "2000-01-01 13:00", "2000-01-01 13:00:00", "2000-01-01T13:00+00:00", "2000-01-01T13:00:00-07:00"],
+ "description": "ISO 8601, for instance: `YYYY-MM-DD`, `YYYY-MM-DD HH:MM:SS+HH:MM`",
+ "format": "date-time"
+ },
+ "edition": {
+ "title": "Zoho CRM Edition",
+ "type": "string",
+ "description": "Choose your Edition of Zoho CRM to determine API Concurrency Limits",
+ "enum": ["Free", "Standard", "Professional", "Enterprise", "Ultimate"],
+ "default": "Free"
+ }
+ }
+ }
+}
diff --git a/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/streams.py b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/streams.py
new file mode 100644
index 0000000000000..739ff842356b3
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/streams.py
@@ -0,0 +1,155 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+import concurrent.futures
+import datetime
+import math
+from abc import ABC
+from dataclasses import asdict
+from http import HTTPStatus
+from typing import Any, Dict, Iterable, List, Mapping, MutableMapping, Optional
+
+import requests
+from airbyte_cdk.sources.streams.http import HttpStream
+
+from .api import ZohoAPI
+from .exceptions import IncompleteMetaDataException, UnknownDataTypeException
+from .types import FieldMeta, ModuleMeta, ZohoPickListItem
+
+# 204 and 304 status codes are valid successful responses,
+# but `.json()` will fail because the response body is empty
+EMPTY_BODY_STATUSES = (HTTPStatus.NO_CONTENT, HTTPStatus.NOT_MODIFIED)
+
+
+class ZohoCrmStream(HttpStream, ABC):
+ primary_key: str = "id"
+ module: ModuleMeta = None
+
+ def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]:
+ if response.status_code in EMPTY_BODY_STATUSES:
+ return None
+ pagination = response.json()["info"]
+ if not pagination["more_records"]:
+ return None
+ return {"page": pagination["page"] + 1}
+
+ def request_params(
+ self, stream_state: Mapping[str, Any], stream_slice: Mapping[str, any] = None, next_page_token: Mapping[str, Any] = None
+ ) -> MutableMapping[str, Any]:
+ return next_page_token or {}
+
+ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]:
+ data = [] if response.status_code in EMPTY_BODY_STATUSES else response.json()["data"]
+ yield from data
+
+ def path(self, *args, **kwargs) -> str:
+ return f"/crm/v2/{self.module.api_name}"
+
+ def get_json_schema(self) -> Optional[Dict[Any, Any]]:
+ try:
+ return asdict(self.module.schema)
+ except IncompleteMetaDataException:
+ # to build a schema for a stream, a sequence of requests is made:
+ # one `/settings/modules` which introduces a list of modules,
+ # one `/settings/modules/{module_name}` per module and
+ # one `/settings/fields?module={module_name}` per module.
+ # Any of former two can result in 204 and empty body what blocks us
+ # from generating stream schema and, therefore, a stream.
+ self.logger.warning(
+ f"Could not retrieve fields Metadata for module {self.module.api_name}. " f"This stream will not be available for syncs."
+ )
+ return None
+ except UnknownDataTypeException as exc:
+ self.logger.warning(f"Unknown data type in module {self.module.api_name}, skipping. Details: {exc}")
+ raise
+
+
+class IncrementalZohoCrmStream(ZohoCrmStream):
+ cursor_field = "Modified_Time"
+
+ def __init__(self, authenticator: "requests.auth.AuthBase" = None, config: Mapping[str, Any] = None):
+ super().__init__(authenticator)
+ self._config = config
+ self._state = {}
+ self._start_datetime = self._config.get("start_datetime") or "1970-01-01T00:00:00+00:00"
+
+ @property
+ def state(self) -> Mapping[str, Any]:
+ if not self._state:
+ self._state = {self.cursor_field: self._start_datetime}
+ return self._state
+
+ @state.setter
+ def state(self, value: Mapping[str, Any]):
+ self._state = value
+
+ def read_records(self, *args, **kwargs) -> Iterable[Mapping[str, Any]]:
+ for record in super().read_records(*args, **kwargs):
+ current_cursor_value = datetime.datetime.fromisoformat(self.state[self.cursor_field])
+ latest_cursor_value = datetime.datetime.fromisoformat(record[self.cursor_field])
+ new_cursor_value = max(latest_cursor_value, current_cursor_value)
+ self.state = {self.cursor_field: new_cursor_value.isoformat("T", "seconds")}
+ yield record
+
+ def request_headers(
+ self, stream_state: Mapping[str, Any], stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None
+ ) -> Mapping[str, Any]:
+ last_modified = stream_state.get(self.cursor_field, self._start_datetime)
+ # since API filters inclusively, we add 1 sec to prevent duplicate reads
+ last_modified_dt = datetime.datetime.fromisoformat(last_modified)
+ last_modified_dt += datetime.timedelta(seconds=1)
+ last_modified = last_modified_dt.isoformat("T", "seconds")
+ return {"If-Modified-Since": last_modified}
+
+
+class ZohoStreamFactory:
+ def __init__(self, config: Mapping[str, Any]):
+ self.api = ZohoAPI(config)
+ self._config = config
+
+ def _init_modules_meta(self) -> List[ModuleMeta]:
+ modules_meta_json = self.api.modules_settings()
+ modules = [ModuleMeta.from_dict(module) for module in modules_meta_json]
+ return list(filter(lambda module: module.api_supported, modules))
+
+ def _populate_fields_meta(self, module: ModuleMeta):
+ fields_meta_json = self.api.fields_settings(module.api_name)
+ fields_meta = []
+ for field in fields_meta_json:
+ pick_list_values = field.get("pick_list_values", [])
+ if pick_list_values:
+ field["pick_list_values"] = [ZohoPickListItem.from_dict(pick_list_item) for pick_list_item in field["pick_list_values"]]
+ fields_meta.append(FieldMeta.from_dict(field))
+ module.fields = fields_meta
+
+ def _populate_module_meta(self, module: ModuleMeta):
+ module_meta_json = self.api.module_settings(module.api_name)
+ module.update_from_dict(next(iter(module_meta_json), None))
+
+ def produce(self) -> List[HttpStream]:
+ modules = self._init_modules_meta()
+ streams = []
+
+ def populate_module(module):
+ self._populate_module_meta(module)
+ self._populate_fields_meta(module)
+
+ def chunk(max_len, lst):
+ for i in range(math.ceil(len(lst) / max_len)):
+ yield lst[i * max_len : (i + 1) * max_len]
+
+ max_concurrent_request = self.api.max_concurrent_requests
+ with concurrent.futures.ThreadPoolExecutor(max_workers=max_concurrent_request) as executor:
+ for batch in chunk(max_concurrent_request, modules):
+ executor.map(lambda module: populate_module(module), batch)
+
+ bases = (IncrementalZohoCrmStream,)
+ for module in modules:
+ stream_cls_attrs = {"url_base": self.api.api_url, "module": module}
+ stream_cls_name = f"Incremental{module.api_name}ZohoCRMStream"
+ incremental_stream_cls = type(stream_cls_name, bases, stream_cls_attrs)
+ stream = incremental_stream_cls(self.api.authenticator, config=self._config)
+ if stream.get_json_schema():
+ streams.append(stream)
+ return streams
diff --git a/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/types.py b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/types.py
new file mode 100644
index 0000000000000..5a3d8b2e1bc5d
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/source_zoho_crm/types.py
@@ -0,0 +1,221 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+import copy
+import dataclasses
+from decimal import Decimal
+from enum import Enum
+from typing import Any, Dict, Iterable, List, MutableMapping, Optional, Union
+
+from .exceptions import IncompleteMetaDataException, UnknownDataTypeException
+
+
+@dataclasses.dataclass
+class Schema:
+ description: str
+ properties: Dict[str, Any]
+ schema: str = "http://json-schema.org/draft-07/schema#"
+ type: str = "object"
+ additionalProperties: Any = True
+ required: Optional[List[str]] = dataclasses.field(default_factory=list)
+
+
+class ZohoBaseType(Enum):
+ @classmethod
+ def all(cls) -> List[str]:
+ return list(map(lambda f: f.value, cls))
+
+ def __eq__(self, other: object) -> bool:
+ if type(other) is type(self):
+ return super().__eq__(other)
+ if type(other) == str:
+ return self.value == other
+ raise NotImplementedError(f"Type Mismatch: Enum and {type(other).__name__}")
+
+
+class ZohoJsonType(ZohoBaseType):
+ string = "string"
+ integer = "integer"
+ double = "double"
+ boolean = "boolean"
+ array = "jsonarray"
+ object = "jsonobject"
+
+
+class ZohoDataType(ZohoBaseType):
+ textarea = "textarea"
+ event_reminder = "event_reminder"
+ phone = "phone"
+ text = "text"
+ profileimage = "profileimage"
+ picklist = "picklist"
+ bigint = "bigint"
+ website = "website"
+ email = "email"
+ date = "date"
+ datetime = "datetime"
+ integer = "integer"
+ currency = "currency"
+ double = "double"
+ boolean = "boolean"
+ lookup = "lookup"
+ ownerlookup = "ownerlookup"
+ autonumber = "autonumber"
+ multiselectpicklist = "multiselectpicklist"
+ RRULE = "RRULE"
+ ALARM = "ALARM"
+
+ @classmethod
+ def numeric_string_types(cls) -> Iterable["ZohoDataType"]:
+ return cls.autonumber, cls.bigint
+
+
+class FromDictMixin:
+ @classmethod
+ def _field_names(cls) -> Iterable[str]:
+ return [field.name for field in dataclasses.fields(cls)]
+
+ @classmethod
+ def _filter_by_names(cls, dct: Dict[Any, Any]) -> Dict[Any, Any]:
+ return {key: val for key, val in dct.items() if key in cls._field_names()}
+
+ @classmethod
+ def from_dict(cls, dct: MutableMapping[Any, Any]) -> object:
+ return cls(**cls._filter_by_names(dct))
+
+ def update_from_dict(self, dct: MutableMapping[Any, Any]):
+ for key, val in self._filter_by_names(dct).items():
+ setattr(self, key, val)
+
+
+@dataclasses.dataclass
+class ZohoPickListItem(FromDictMixin):
+ display_value: str
+ actual_value: str
+
+
+FieldType = Dict[Any, Any]
+
+
+@dataclasses.dataclass
+class FieldMeta(FromDictMixin):
+ json_type: str
+ length: Optional[int]
+ api_name: str
+ data_type: str
+ decimal_place: Optional[int]
+ system_mandatory: bool
+ display_label: str
+ pick_list_values: Optional[List[ZohoPickListItem]]
+
+ def _default_type_kwargs(self) -> Dict[str, str]:
+ return {"title": self.display_label}
+
+ def _picklist_items(self) -> Iterable[Union[str, None]]:
+ default_list = [None]
+ if not self.pick_list_values:
+ return default_list
+ return default_list + [pick_item.display_value for pick_item in self.pick_list_values]
+
+ def _boolean_field(self) -> FieldType:
+ return {"type": ["null", "boolean"], **self._default_type_kwargs()}
+
+ def _integer_field(self) -> FieldType:
+ return {"type": ["null", "integer"], **self._default_type_kwargs()}
+
+ def _double_field(self) -> FieldType:
+ typedef = {"type": ["null", "number"], **self._default_type_kwargs()}
+ if self.decimal_place:
+ typedef["multipleOf"] = float(Decimal("0.1") ** self.decimal_place)
+ return typedef
+
+ def _string_field(self) -> FieldType:
+ if self.api_name == "Reminder":
+ # this is a special case. although datatype = `picklist`,
+ # actual values do not correspond to the values in the list
+ return {"type": ["null", "string"], "format": "date-time", **self._default_type_kwargs()}
+
+ typedef = {"type": ["null", "string"], "maxLength": self.length, **self._default_type_kwargs()}
+ if self.data_type == ZohoDataType.website:
+ typedef["format"] = "uri"
+ elif self.data_type == ZohoDataType.email:
+ typedef["format"] = "email"
+ elif self.data_type == ZohoDataType.date:
+ typedef["format"] = "date"
+ elif self.data_type == ZohoDataType.datetime:
+ typedef["format"] = "date-time"
+ elif self.data_type in ZohoDataType.numeric_string_types():
+ typedef["airbyte_type"] = "big_integer"
+ elif self.data_type == ZohoDataType.picklist and self.pick_list_values:
+ typedef["enum"] = self._picklist_items()
+ return typedef
+
+ def _jsonarray_field(self) -> FieldType:
+ typedef = {"type": "array", **self._default_type_kwargs()}
+ if self.api_name in ("Product_Details", "Pricing_Details"):
+ # these two fields are said to be text, but are actually complex objects
+ typedef["items"] = {"type": "object"}
+ return typedef
+ if self.api_name == "Tag":
+ # `Tag` is defined as string, but is actually an object
+ typedef["items"] = {
+ "type": "object",
+ "additionalProperties": False,
+ "required": ["name", "id"],
+ "properties": {"name": {"type": "string"}, "id": {"type": "string"}},
+ }
+ return typedef
+ if self.data_type in (ZohoDataType.text, *ZohoDataType.numeric_string_types()):
+ typedef["items"] = {"type": "string"}
+ if self.data_type in ZohoDataType.numeric_string_types():
+ typedef["items"]["airbyte_type"] = "big_integer"
+ if self.data_type == ZohoDataType.multiselectpicklist:
+ typedef["minItems"] = 1
+ typedef["uniqueItems"] = True
+ items = {"type": ["null", "string"]}
+ if self.pick_list_values:
+ items["enum"] = self._picklist_items()
+ typedef["items"] = items
+ return typedef
+
+ def _jsonobject_field(self) -> FieldType:
+ lookup_typedef = {
+ "type": ["null", "object"],
+ "additionalProperties": False,
+ "required": ["name", "id"],
+ "properties": {"name": {"type": ["null", "string"]}, "id": {"type": "string"}},
+ **self._default_type_kwargs(),
+ }
+ if self.data_type == ZohoDataType.lookup:
+ return lookup_typedef
+ if self.data_type == ZohoDataType.ownerlookup:
+ owner_lookup_typedef = copy.deepcopy(lookup_typedef)
+ owner_lookup_typedef["required"] += ["email"]
+ owner_lookup_typedef["properties"]["email"] = {"type": "string", "format": "email"}
+ return owner_lookup_typedef
+ # exact specification unknown
+ return {"type": ["null", "object"]}
+
+ @property
+ def schema(self) -> FieldType:
+ if self.json_type in ZohoJsonType.all():
+ return getattr(self, f"_{self.json_type}_field")()
+ raise UnknownDataTypeException(f"JSON type: {self.json_type}, data type:{self.data_type}")
+
+
+@dataclasses.dataclass
+class ModuleMeta(FromDictMixin):
+ api_name: str
+ module_name: str
+ api_supported: bool
+ fields: Optional[Iterable[FieldMeta]] = dataclasses.field(default_factory=list)
+
+ @property
+ def schema(self) -> Schema:
+ if not self.fields:
+ raise IncompleteMetaDataException("Not enough data")
+ required = ["id", "Modified_Time"] + [field_.api_name for field_ in self.fields if field_.system_mandatory]
+ field_to_properties = {field_.api_name: field_.schema for field_ in self.fields}
+ properties = {"id": {"type": "string"}, "Modified_Time": {"type": "string", "format": "date-time"}, **field_to_properties}
+ return Schema(description=self.module_name, properties=properties, required=required)
diff --git a/airbyte-integrations/connectors/source-zoho-crm/unit_tests/__init__.py b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/__init__.py
new file mode 100644
index 0000000000000..46b7376756ec6
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/__init__.py
@@ -0,0 +1,3 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
diff --git a/airbyte-integrations/connectors/source-zoho-crm/unit_tests/conftest.py b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/conftest.py
new file mode 100644
index 0000000000000..0ff4cd2a7c892
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/conftest.py
@@ -0,0 +1,29 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+from unittest.mock import Mock
+
+import pytest
+import requests
+
+
+@pytest.fixture
+def response_mocker():
+ def factory(status=200, content=""):
+ response = requests.Response()
+ response.status_code = status
+ response._content = content
+ return response
+
+ return factory
+
+
+@pytest.fixture
+def request_mocker(response_mocker):
+ def factory(status=200, content=""):
+ response = response_mocker(status, content)
+ request_mock = Mock(return_value=response)
+ return request_mock
+
+ return factory
diff --git a/airbyte-integrations/connectors/source-zoho-crm/unit_tests/parametrize.py b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/parametrize.py
new file mode 100644
index 0000000000000..aba60bcdc979c
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/parametrize.py
@@ -0,0 +1,223 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+from collections import namedtuple
+
+import pytest
+
+TestCase = namedtuple("TestCase", ("json_type", "data_type", "length", "decimal_place", "api_name", "pick_list_values", "expected_values"))
+
+
+datatype_inputs = pytest.mark.parametrize(
+ TestCase._fields,
+ (
+ TestCase("boolean", "boolean", None, None, "Field", [], {"title": "Field", "type": ["null", "boolean"]}),
+ TestCase("double", "double", None, 3, "Field", [], {"multipleOf": 0.001, "title": "Field", "type": ["null", "number"]}),
+ TestCase("double", "currency", None, 2, "Field", [], {"multipleOf": 0.01, "title": "Field", "type": ["null", "number"]}),
+ TestCase("integer", "integer", None, None, "Field", [], {"title": "Field", "type": ["null", "integer"]}),
+ TestCase("string", "profileimage", 256, None, "Field", [], {"maxLength": 256, "title": "Field", "type": ["null", "string"]}),
+ TestCase(
+ "string",
+ "picklist",
+ 128,
+ None,
+ "Field",
+ ["Chelsea", "Arsenal", "ManUtd"],
+ {"enum": [None, "Chelsea", "Arsenal", "ManUtd"], "maxLength": 128, "title": "Field", "type": ["null", "string"]},
+ ),
+ TestCase("string", "textarea", 1024, None, "Field", [], {"maxLength": 1024, "title": "Field", "type": ["null", "string"]}),
+ TestCase(
+ "string",
+ "website",
+ 256,
+ None,
+ "Field",
+ [],
+ {
+ "format": "uri",
+ "maxLength": 256,
+ "title": "Field",
+ "type": ["null", "string"],
+ },
+ ),
+ TestCase(
+ "string",
+ "date",
+ 16,
+ None,
+ "Field",
+ [],
+ {
+ "format": "date",
+ "maxLength": 16,
+ "title": "Field",
+ "type": ["null", "string"],
+ },
+ ),
+ TestCase(
+ "string",
+ "datetime",
+ 32,
+ None,
+ "Field",
+ [],
+ {
+ "format": "date-time",
+ "maxLength": 32,
+ "title": "Field",
+ "type": ["null", "string"],
+ },
+ ),
+ TestCase("string", "text", 1024, None, "Field", [], {"maxLength": 1024, "title": "Field", "type": ["null", "string"]}),
+ TestCase("string", "phone", 16, None, "Field", [], {"maxLength": 16, "title": "Field", "type": ["null", "string"]}),
+ TestCase(
+ "string",
+ "bigint",
+ None,
+ None,
+ "Field",
+ [],
+ {
+ "airbyte_type": "big_integer",
+ "maxLength": None,
+ "title": "Field",
+ "type": ["null", "string"],
+ },
+ ),
+ TestCase(
+ "string",
+ "event_reminder",
+ None,
+ None,
+ "Reminder",
+ ["15 min", "30 min", "1 hour"],
+ {"format": "date-time", "title": "Reminder", "type": ["null", "string"]},
+ ),
+ TestCase(
+ "string",
+ "email",
+ 256,
+ None,
+ "Field",
+ [],
+ {
+ "format": "email",
+ "maxLength": 256,
+ "title": "Field",
+ "type": ["null", "string"],
+ },
+ ),
+ TestCase(
+ "string",
+ "autonumber",
+ 512,
+ None,
+ "Field",
+ [],
+ {
+ "airbyte_type": "big_integer",
+ "maxLength": 512,
+ "title": "Field",
+ "type": ["null", "string"],
+ },
+ ),
+ TestCase(
+ "jsonobject",
+ "ownerlookup",
+ None,
+ None,
+ "Field",
+ [],
+ {
+ "additionalProperties": False,
+ "properties": {
+ "email": {"format": "email", "type": "string"},
+ "id": {"type": "string"},
+ "name": {"type": ["null", "string"]},
+ },
+ "required": ["name", "id", "email"],
+ "title": "Field",
+ "type": ["null", "object"],
+ },
+ ),
+ TestCase("jsonobject", "RRULE", None, None, "Field", [], {"type": ["null", "object"]}),
+ TestCase("jsonobject", "ALARM", None, None, "Field", [], {"type": ["null", "object"]}),
+ TestCase(
+ "jsonobject",
+ "lookup",
+ None,
+ None,
+ "Field",
+ [],
+ {
+ "additionalProperties": False,
+ "properties": {"id": {"type": "string"}, "name": {"type": ["null", "string"]}},
+ "required": ["name", "id"],
+ "title": "Field",
+ "type": ["null", "object"],
+ },
+ ),
+ TestCase(
+ "jsonarray",
+ "bigint",
+ 1024,
+ None,
+ "Field",
+ [],
+ {"items": {"airbyte_type": "big_integer", "type": "string"}, "title": "Field", "type": "array"},
+ ),
+ TestCase("jsonarray", "text", 2056, None, "Field", [], {"items": {"type": "string"}, "title": "Field", "type": "array"}),
+ TestCase(
+ "jsonarray",
+ "text",
+ 2056,
+ None,
+ "Pricing_Details",
+ [],
+ {"items": {"type": "object"}, "title": "Pricing_Details", "type": "array"},
+ ),
+ TestCase(
+ "jsonarray",
+ "text",
+ 2056,
+ None,
+ "Product_Details",
+ [],
+ {"items": {"type": "object"}, "title": "Product_Details", "type": "array"},
+ ),
+ TestCase(
+ "jsonarray",
+ "string",
+ None,
+ None,
+ "Tag",
+ [],
+ {
+ "items": {
+ "additionalProperties": False,
+ "properties": {"id": {"type": "string"}, "name": {"type": "string"}},
+ "required": ["name", "id"],
+ "type": "object",
+ },
+ "title": "Tag",
+ "type": "array",
+ },
+ ),
+ TestCase(
+ "jsonarray",
+ "multiselectpicklist",
+ 128,
+ None,
+ "Field",
+ ["A", "B", "C", "D"],
+ {
+ "items": {"enum": [None, "A", "B", "C", "D"], "type": ["null", "string"]},
+ "minItems": 1,
+ "title": "Field",
+ "type": "array",
+ "uniqueItems": True,
+ },
+ ),
+ ),
+)
diff --git a/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_api.py b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_api.py
new file mode 100644
index 0000000000000..1362cf1d1e21e
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_api.py
@@ -0,0 +1,73 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+from unittest.mock import Mock
+
+import pytest
+from source_zoho_crm.api import ZohoAPI
+
+
+@pytest.fixture
+def config():
+ return {
+ "client_id": "client_id",
+ "client_secret": "client_secret",
+ "refresh_token": "refresh_token",
+ "dc_region": "US",
+ "environment": "Developer",
+ "edition": "Free",
+ }
+
+
+def test_cached_authenticator(config):
+ api = ZohoAPI(config)
+ # guarantee that each call to API won't lead to refreshing a token every time
+ assert api.authenticator is api.authenticator
+
+
+@pytest.mark.parametrize(
+ ("region", "environment", "expected_result"),
+ (
+ ("US", "Developer", "https://developer.zohoapis.com"),
+ ("US", "Production", "https://zohoapis.com"),
+ ("US", "Sandbox", "https://sandbox.zohoapis.com"),
+ ("AU", "Developer", "https://developer.zohoapis.com.au"),
+ ("IN", "Production", "https://zohoapis.in"),
+ ("CN", "Sandbox", "https://sandbox.zohoapis.com.cn"),
+ ),
+)
+def test_api_url(config, region, environment, expected_result):
+ config["dc_region"] = region
+ config["environment"] = environment
+ api = ZohoAPI(config)
+ assert api.api_url == expected_result
+
+
+def mock_request(mocker, request):
+ mocker.patch("source_zoho_crm.api.requests.get", request)
+ mocker.patch("source_zoho_crm.api.ZohoOauth2Authenticator.get_auth_header", Mock(return_value={}))
+
+
+def test_check_connection_success(mocker, request_mocker, config):
+ mock_request(mocker, request_mocker(content=b'{"access_token": "token", "expires_in": 3600}'))
+ api = ZohoAPI(config)
+ assert api.check_connection() == (True, None)
+
+
+def test_check_connection_fail(mocker, request_mocker, config):
+ mock_request(mocker, request_mocker(status=401, content=b"Authentication failure"))
+ api = ZohoAPI(config)
+ assert api.check_connection() == (False, b"Authentication failure")
+
+
+def test_json_from_path_success(mocker, request_mocker, config):
+ mock_request(mocker, request_mocker(content=b'{"fields": ["a", "b"], "modules": []}'))
+ api = ZohoAPI(config)
+ assert api._json_from_path("/fields", "fields") == ["a", "b"]
+
+
+def test_json_from_path_fail(mocker, request_mocker, config):
+ mock_request(mocker, request_mocker(status=204, content=b"No content"))
+ api = ZohoAPI(config)
+ assert api._json_from_path("/fields", "fields") == []
diff --git a/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_auth.py b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_auth.py
new file mode 100644
index 0000000000000..1a8f347f32de4
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_auth.py
@@ -0,0 +1,32 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+from unittest.mock import Mock
+
+from source_zoho_crm.auth import ZohoOauth2Authenticator
+
+authenticator = ZohoOauth2Authenticator("http://dummy.url/oauth/v2/token", "client_id", "client_secret", "refresh_token")
+
+
+def test_refresh_access_token(mocker, request_mocker):
+ request = request_mocker(content=b'{"access_token": "access_token", "expires_in": 86400}')
+ mocker.patch("source_zoho_crm.auth.requests.request", request)
+
+ token, expires = authenticator.refresh_access_token()
+ assert (token, expires) == ("access_token", 24 * 60 * 60)
+ request.assert_called_once_with(
+ method="POST",
+ url="http://dummy.url/oauth/v2/token",
+ params={
+ "refresh_token": "refresh_token",
+ "client_id": "client_id",
+ "client_secret": "client_secret",
+ "grant_type": "refresh_token",
+ },
+ )
+
+
+def test_get_auth_header(mocker):
+ mocker.patch.object(ZohoOauth2Authenticator, "get_access_token", Mock(return_value="token"))
+ assert authenticator.get_auth_header() == {"Authorization": "Zoho-oauthtoken token"}
diff --git a/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_incremental_streams.py b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_incremental_streams.py
new file mode 100644
index 0000000000000..d138d74b4d636
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_incremental_streams.py
@@ -0,0 +1,75 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+from unittest.mock import Mock
+
+import pytest
+from airbyte_cdk.models import SyncMode
+from source_zoho_crm.streams import IncrementalZohoCrmStream as BaseIncrementalZohoCrmStream
+
+
+@pytest.fixture
+def stream_factory(mocker):
+ def wrapper(stream_name, schema=None):
+ class IncrementalZohoStream(BaseIncrementalZohoCrmStream):
+ url_base = "https://dummy.com"
+ _path = f"/crm/v2/{stream_name}"
+ json_schema = schema or {}
+ primary_key = "id"
+
+ return IncrementalZohoStream(config={})
+
+ return wrapper
+
+
+def test_cursor_field(stream_factory):
+ stream = stream_factory("Leads")
+ assert stream.cursor_field == "Modified_Time"
+
+
+def test_updated_state(mocker, stream_factory):
+ stream = stream_factory("Leads")
+ assert stream.state == {"Modified_Time": "1970-01-01T00:00:00+00:00"}
+ mocker.patch(
+ "source_zoho_crm.streams.HttpStream.read_records",
+ Mock(
+ return_value=[
+ {"Name": "Joan", "Surname": "Arc", "Modified_Time": "2021-12-12T13:15:09+02:00"},
+ {"Name": "Jack", "Surname": "Sparrow", "Modified_Time": "2022-03-03T12:31:05+02:00"},
+ {"Name": "Ron", "Surname": "Weasley", "Modified_Time": "2022-02-03T00:00:00+02:00"},
+ ]
+ ),
+ )
+ for _ in stream.read_records(SyncMode.incremental):
+ # this is generator, so we have to exhaust it before asserting anything
+ pass
+ assert stream.state == {"Modified_Time": "2022-03-03T12:31:05+02:00"}
+
+
+@pytest.mark.parametrize(
+ ("state", "expected_header"),
+ (
+ ({"Modified_Time": "2021-09-17T13:30:28-07:00"}, {"If-Modified-Since": "2021-09-17T13:30:29-07:00"}),
+ ({}, {"If-Modified-Since": "1970-01-01T00:00:01+00:00"}),
+ ),
+)
+def test_request_headers(stream_factory, state, expected_header):
+ stream = stream_factory("Leads")
+ inputs = {"stream_slice": None, "stream_state": state, "next_page_token": None}
+ assert stream.request_headers(**inputs) == expected_header
+
+
+def test_supports_incremental(stream_factory):
+ stream = stream_factory("Leads")
+ assert stream.supports_incremental
+
+
+def test_source_defined_cursor(stream_factory):
+ stream = stream_factory("Leads")
+ assert stream.source_defined_cursor
+
+
+def test_stream_checkpoint_interval(stream_factory):
+ stream = stream_factory("Leads")
+ assert stream.state_checkpoint_interval is None
diff --git a/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_streams.py
new file mode 100644
index 0000000000000..461c633fc69d1
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_streams.py
@@ -0,0 +1,113 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+from http import HTTPStatus
+from unittest.mock import Mock
+
+import pytest
+from source_zoho_crm.streams import ZohoCrmStream
+from source_zoho_crm.types import FieldMeta, ModuleMeta
+
+
+@pytest.fixture
+def stream_factory(mocker):
+ def wrapper(stream_module=None):
+ class FullRefreshZohoStream(ZohoCrmStream):
+ url_base = "https://dummy.com"
+ module = stream_module
+
+ return FullRefreshZohoStream()
+
+ return wrapper
+
+
+@pytest.mark.parametrize(("next_page_token", "expected_result"), (({"page": 2}, {"page": 2}), (None, {})))
+def test_request_params(stream_factory, next_page_token, expected_result):
+ stream = stream_factory()
+ inputs = {"stream_slice": None, "stream_state": None, "next_page_token": next_page_token}
+ assert stream.request_params(**inputs) == expected_result
+
+
+@pytest.mark.parametrize(
+ ("status", "content", "expected_token"),
+ ((200, b'{"data": [{"name": "Chris"}], "info": {"page": 1, "more_records": true}}', {"page": 2}), (204, b"", None)),
+)
+def test_next_page_token(response_mocker, stream_factory, status, content, expected_token):
+ stream = stream_factory()
+ response = response_mocker(status, content)
+ inputs = {"response": response}
+ assert stream.next_page_token(**inputs) == expected_token
+
+
+@pytest.mark.parametrize(
+ ("status", "content", "expected_obj"),
+ ((204, b"", None), (200, b'{"data": [{"Name": "Abraham Lincoln"}]}', {"Name": "Abraham Lincoln"})),
+)
+def test_parse_response(stream_factory, response_mocker, status, content, expected_obj):
+ stream = stream_factory()
+ inputs = {"response": response_mocker(status, content)}
+ assert next(stream.parse_response(**inputs), None) == expected_obj
+
+
+def test_request_headers(stream_factory):
+ stream = stream_factory()
+ inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None}
+ expected_headers = {}
+ assert stream.request_headers(**inputs) == expected_headers
+
+
+def test_http_method(stream_factory):
+ stream = stream_factory()
+ expected_method = "GET"
+ assert stream.http_method == expected_method
+
+
+@pytest.mark.parametrize(
+ ("http_status", "should_retry"),
+ [
+ (HTTPStatus.OK, False),
+ (HTTPStatus.BAD_REQUEST, False),
+ (HTTPStatus.TOO_MANY_REQUESTS, True),
+ (HTTPStatus.INTERNAL_SERVER_ERROR, True),
+ ],
+)
+def test_should_retry(stream_factory, http_status, should_retry):
+ response_mock = Mock()
+ response_mock.status_code = http_status
+ stream = stream_factory()
+ assert stream.should_retry(response_mock) == should_retry
+
+
+def test_backoff_time(stream_factory):
+ response_mock = Mock()
+ stream = stream_factory()
+ expected_backoff_time = None
+ assert stream.backoff_time(response_mock) == expected_backoff_time
+
+
+def test_dynamic_attrs(stream_factory):
+ field = FieldMeta(
+ json_type="string",
+ api_name="Name",
+ data_type="text",
+ system_mandatory=True,
+ display_label="Name",
+ length=None,
+ decimal_place=None,
+ pick_list_values=[],
+ )
+ stream = stream_factory(ModuleMeta(api_name="Leads", module_name="Leads", api_supported=True, fields=[field]))
+ assert stream.path() == "/crm/v2/Leads"
+ assert stream.get_json_schema() == {
+ "additionalProperties": True,
+ "description": "Leads",
+ "properties": {
+ "Modified_Time": {"format": "date-time", "type": "string"},
+ "Name": {"maxLength": None, "title": "Name", "type": ["null", "string"]},
+ "id": {"type": "string"},
+ },
+ "required": ["id", "Modified_Time", "Name"],
+ "schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ }
diff --git a/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_types.py b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_types.py
new file mode 100644
index 0000000000000..157ba94ee4743
--- /dev/null
+++ b/airbyte-integrations/connectors/source-zoho-crm/unit_tests/test_types.py
@@ -0,0 +1,92 @@
+#
+# Copyright (c) 2021 Airbyte, Inc., all rights reserved.
+#
+
+from dataclasses import asdict
+
+import pytest
+from source_zoho_crm.exceptions import IncompleteMetaDataException, UnknownDataTypeException
+from source_zoho_crm.types import FieldMeta, ModuleMeta, ZohoBaseType, ZohoPickListItem
+
+from .parametrize import datatype_inputs
+
+
+class TestData(ZohoBaseType):
+ array = "array"
+ object = "object"
+ string = "string"
+ integer = "integer"
+
+
+def test_base_type():
+ assert TestData.array == TestData.array
+ assert TestData.string != TestData.integer
+ assert TestData.object == "object"
+ assert TestData.all() == ["array", "object", "string", "integer"]
+ assert TestData.integer in TestData.all()
+
+
+def test_module_schema_missing_fields():
+ with pytest.raises(IncompleteMetaDataException):
+ module = ModuleMeta(api_name="Leads", module_name="Leads", api_supported=True, fields=[])
+ _ = module.schema
+
+
+def test_module_schema():
+ fields = [
+ FieldMeta(
+ json_type="string",
+ length=256,
+ api_name="Content",
+ data_type="text",
+ decimal_place=None,
+ system_mandatory=True,
+ display_label="Note content",
+ pick_list_values=[],
+ )
+ ]
+ module = ModuleMeta(api_name="Notes", module_name="Notes", api_supported=True, fields=fields)
+ assert asdict(module.schema) == {
+ "additionalProperties": True,
+ "description": "Notes",
+ "properties": {
+ "Content": {"maxLength": 256, "title": "Note content", "type": ["null", "string"]},
+ "Modified_Time": {"format": "date-time", "type": "string"},
+ "id": {"type": "string"},
+ },
+ "required": ["id", "Modified_Time", "Content"],
+ "schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ }
+
+
+def test_field_schema_unknown_type():
+ field = FieldMeta(
+ json_type="datetime",
+ length=None,
+ api_name="dummy",
+ data_type="timestampwtz",
+ decimal_place=None,
+ system_mandatory=False,
+ display_label="",
+ pick_list_values=[],
+ )
+ with pytest.raises(UnknownDataTypeException):
+ _ = field.schema
+
+
+@datatype_inputs
+def test_field_schema(json_type, data_type, length, decimal_place, api_name, pick_list_values, expected_values):
+ if pick_list_values:
+ pick_list_values = [ZohoPickListItem(actual_value=value, display_value=value) for value in pick_list_values]
+ field = FieldMeta(
+ json_type=json_type,
+ length=length,
+ api_name=api_name,
+ data_type=data_type,
+ decimal_place=decimal_place,
+ system_mandatory=True,
+ display_label=api_name,
+ pick_list_values=pick_list_values,
+ )
+ assert field.schema == expected_values
diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md
index 5b812681d1ae6..61ed01c463bc2 100644
--- a/docs/SUMMARY.md
+++ b/docs/SUMMARY.md
@@ -177,6 +177,7 @@
* [Zendesk Support](integrations/sources/zendesk-support.md)
* [Zendesk Talk](integrations/sources/zendesk-talk.md)
* [Zenloop](integrations/sources/zenloop.md)
+ * [Zoho CRM](integrations/sources/zoho-crm.md)
* [Zoom](integrations/sources/zoom.md)
* [Zuora](integrations/sources/zuora.md)
* [Destinations](integrations/destinations/README.md)
diff --git a/docs/integrations/sources/zoho-crm.md b/docs/integrations/sources/zoho-crm.md
new file mode 100644
index 0000000000000..cda2cc5b29b60
--- /dev/null
+++ b/docs/integrations/sources/zoho-crm.md
@@ -0,0 +1,135 @@
+# Zoho CRM
+
+## Sync overview
+
+The Zoho CRM source supports both Full Refresh and Incremental syncs. You can choose if this connector will copy only the new or updated data, or all rows in the tables and columns you set up for replication, every time a sync is run.
+
+Airbyte uses [REST API](https://www.zoho.com/crm/developer/docs/api/v2/modules-api.html) to fetch data from Zoho CRM.
+
+This Source Connector is based on an [Airbyte CDK](https://docs.airbyte.io/connector-development/cdk-python).
+
+### Output schema
+
+This Source is capable of syncing:
+
+* standard modules available in Zoho CRM account
+* custom modules manually added by user, available in Zoho CRM account
+* custom fields in both standard and custom modules, available in Zoho CRM account
+
+The discovering of Zoho CRM module schema is made dynamically based on Metadata API and should generally take no longer than 10 to 30 seconds.
+
+### Notes:
+
+Some of Zoho CRM Modules may not be available for sync due to limitations of Zoho CRM Edition or permissions scope. For details refer to the [Scopes](https://www.zoho.com/crm/developer/docs/api/v2/scopes.html) section in the Zoho CRM documentation.
+
+Connector streams and schemas are built dynamically on top of Metadata that is available from the REST API - please see [Modules API](https://www.zoho.com/crm/developer/docs/api/v2/modules-api.html), [Modules Metadata API](https://www.zoho.com/crm/developer/docs/api/v2/module-meta.html), [Fields Metadata API](https://www.zoho.com/crm/developer/docs/api/v2/field-meta.html).
+The list of available streams is the list of Modules as long as Module Metadata is available for each of them from the Zoho CRM API, and Fields Metadata is available for each of the fields. If a module you want to sync is not available from this connector, it's because the Zoho CRM API does not make it available.
+
+### Data type mapping
+
+| Integration Type | Airbyte Type | Notes |
+|:----------------------|:-------------|:--------------------------|
+| `boolean` | `boolean` | |
+| `double` | `number` | |
+| `currency` | `number` | |
+| `integer` | `integer` | |
+| `profileimage` | `string` | |
+| `picklist` | `string` | enum |
+| `textarea` | `string` | |
+| `website` | `string` | format: uri |
+| `date` | `string` | format: date |
+| `datetime` | `string` | format: date-time |
+| `text` | `string` | |
+| `phone` | `string` | |
+| `bigint` | `string` | airbyte_type: big_integer |
+| `event_reminder` | `string` | |
+| `email` | `string` | format: email |
+| `autonumber` | `string` | airbyte_type: big_integer |
+| `jsonarray` | `array` | |
+| `jsonobject` | `object` | |
+| `multiselectpicklist` | `array` | |
+| `lookup` | `object` | |
+| `ownerlookup` | `object` | |
+| `RRULE` | `object` | |
+| `ALARM` | `object` | |
+
+Any other data type not listed in the table above will be treated as `string`.
+
+### Features
+
+| Feature | Supported? \(Yes/No\) |
+|:------------------------------------------|:----------------------|
+| Full Refresh Overwrite Sync | Yes |
+| Full Refresh Append Sync | Yes |
+| Incremental - Append Sync | Yes |
+| Incremental - Append + Deduplication Sync | Yes |
+| Namespaces | No |
+
+## List of Supported Environments for Zoho CRM
+
+### Production
+
+| Environment | Base URL |
+|:------------|:------------------------|
+| US | https://zohoapis.com |
+| AU | https://zohoapis.com.au |
+| EU | https://zohoapis.eu |
+| IN | https://zohoapis.in |
+| CN | https://zohoapis.com.cn |
+| JP | https://zohoapis.jp |
+
+### Sandbox
+
+| Environment | Endpoint |
+|:------------|:--------------------------------|
+| US | https://sandbox.zohoapis.com |
+| AU | https://sandbox.zohoapis.com.au |
+| EU | https://sandbox.zohoapis.eu |
+| IN | https://sandbox.zohoapis.in |
+| CN | https://sandbox.zohoapis.com.cn |
+| JP | https://sandbox.zohoapis.jp |
+
+### Developer
+
+| Environment | Endpoint |
+|:------------|:-----------------------------------|
+| US | https://developer.zohoapis.com |
+| AU | https://developer.zohoapis.com.au |
+| EU | https://developer.zohoapis.eu |
+| IN | https://developer.zohoapis.in |
+| CN | https://developer.zohoapis.com.cn |
+| JP | https://developer.zohoapis.jp |
+
+For more information about available environments, please visit [this page](https://www.zoho.com/crm/developer/sandbox.html?src=dev-hub)
+
+### Performance considerations
+
+Also, Zoho CRM API calls are associated with credits, each Zoho CRM edition has a limit in a 24-hour rolling window, so please, consider it when configuring your connections.
+More details about Zoho CRM API credit system can be found [here](https://www.zoho.com/crm/developer/docs/api/v2/api-limits.html).
+
+### Note about using the Zoho Developer Environment
+
+The Zoho Developer environment API is inconsistent with production environment API. It contains about half of the modules supported in the production environment. Keep this in mind when pulling data from the Developer environment.
+
+## Setup Guide (Airbyte Open Source)
+
+To set up a connection with a Zoho CRM source, you will need to choose start sync date, Zoho CRM edition, region and environment. The latest are described above. Except for those, you will need OAuth2.0 credentials - Client ID, Client Secret and Refresh Token.
+
+### Get Client ID, Client Secret, and Grant Token
+
+1. Log into https://api-console.zoho.com/
+2. Choose client
+3. Enter a scope the future refresh and access tokens will cover. For instance, it can be `ZohoCRM.modules.ALL, ZohoCRM.settings.ALL, ZohoCRM.settings.modules.ALL`. **Make sure the scope covers all needed modules**.
+4. Enter grant token's lifetime and description, click "Create".
+5. Copy Grant token, close the popup and copy Client ID and Client Secret on the "Client Secret" tab.
+
+### Create Refresh Token
+
+For generating the refresh token, please refer to [this page](https://www.zoho.com/crm/developer/docs/api/v2/access-refresh.html).
+Make sure to complete the auth flow quickly, as the initial token granted by Zoho CRM is only live for a few minutes before it can no longer be used to generate a refresh token.
+
+## Changelog
+
+| Version | Date | Pull Request | Subject |
+|:--------|:-----------|:---------------------------------------------------------|:----------------|
+| 0.1.0 | 2022-03-30 | [11193](https://github.com/airbytehq/airbyte/pull/11193) | Initial release |