IdenIden Docs
Bridge Developer Guide

Cloning Apps with clone_from

Share resource type definitions across multiple Bridge apps using the clone_from parameter.

When you manage multiple instances of the same SaaS application -- separate Jira projects for different business units, regional HR systems, or per-department Salesforce orgs -- each instance needs its own Bridge app so it can sync independently. But all instances share the same resource structure (accounts, groups, licenses).

The clone_from parameter lets you create a new Bridge app that shares an existing app's resource type definitions, without duplicating setup.

When to Use clone_from

Use clone_from when you have multiple instances of the same external system and they all share the same resource schema. Common scenarios:

  • Regional instances -- An HR system deployed per region (EMEA, APAC, Americas), each with its own user base but the same account/group structure.
  • Business unit isolation -- Separate Jira instances for Engineering, Legal, and Finance, all with the same resource types (users, projects, roles).
  • Environment separation -- Production and staging instances of the same application where you want to track both in Iden.

How It Works

When you create an app with clone_from, the new app shares the parent's resource type definitions. You don't need to register resource types again — the clone inherits them immediately.

Each app syncs independently — starting a sync session, pushing pages, and completing the sync all happen per-app. The resource type definitions are shared, but the actual records (users, groups, licenses) are tracked separately per app.

Creating a Cloned App

First, create a parent app and register its resource types using the standard flow (see Getting Started). Then create clones by passing the parent's id as clone_from.

curl:

# Assume PARENT_APP_ID is the id of an existing Bridge app
curl -X POST "https://developer.idenhq.com/org/acme/api/v1/bridge/apps/" \
  -H "Authorization: Api-Key <your-api-key>" \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"Jira - Legal\", \"clone_from\": \"${PARENT_APP_ID}\"}"

Python:

import requests

BASE_URL = "https://developer.idenhq.com/org/acme"
HEADERS = {
    "Authorization": "Api-Key <your-api-key>",
    "Content-Type": "application/json",
}

# Create the parent app first (or use an existing one)
parent = requests.post(
    f"{BASE_URL}/api/v1/bridge/apps/",
    headers=HEADERS,
    json={"name": "Jira - Engineering"},
).json()
parent_app_id = parent["id"]

# Register resource types on the parent
for rt in [
    {"slug": "project", "name": "Project", "type": "group"},
    {"slug": "role", "name": "Role", "type": "group"},
    {"slug": "account", "name": "Account", "type": "account"},
]:
    requests.post(
        f"{BASE_URL}/api/v1/bridge/apps/{parent_app_id}/resource-types/",
        headers=HEADERS,
        json=rt,
    ).raise_for_status()

# Create cloned apps
for name in ["Jira - Legal", "Jira - Finance"]:
    clone = requests.post(
        f"{BASE_URL}/api/v1/bridge/apps/",
        headers=HEADERS,
        json={"name": name, "clone_from": parent_app_id},
    )
    clone.raise_for_status()
    print(f"Created clone: {clone.json()['name']} ({clone.json()['id']})")

Response (201 Created):

{
  "id": "b2c3d4e5-...",
  "name": "Jira - Legal",
  "configuration_status": "CHECKING_CONFIGURATION",
  "created_at": "2025-01-15T11:00:00Z",
  "last_synced_at": null
}

The cloned app is ready to sync immediately -- you do not need to register resource types again.

What Gets Shared vs. Independent

AspectShared across clonesIndependent per app
Resource type definitionsYes -- slugs, names, and types are shared
Synced records (users, groups, licenses)Yes -- each app has its own data
Sync sessionsYes -- each app starts/completes its own syncs
Provisioning tasksYes -- tasks are scoped to a single app
Sync progress and historyYes -- tracked per app

Resource type definitions are shared, but all runtime data is per-app.

Syncing Cloned Apps

Each clone syncs independently using the shared resource type slugs. The sync flow is identical to a regular app.

Python:

import time
import requests

BASE_URL = "https://developer.idenhq.com/org/acme"
HEADERS = {
    "Authorization": "Api-Key <your-api-key>",
    "Content-Type": "application/json",
}


def sync_instance(app_id, groups, accounts):
    """Run a full sync for one instance."""
    # Start sync
    sync_id = requests.post(
        f"{BASE_URL}/api/v1/bridge/apps/{app_id}/sync/",
        headers=HEADERS,
    ).json()["sync_id"]

    # Push groups, then accounts
    requests.put(
        f"{BASE_URL}/api/v1/bridge/apps/{app_id}/sync/{sync_id}/project/",
        headers=HEADERS,
        json={"records": groups},
    ).raise_for_status()

    requests.put(
        f"{BASE_URL}/api/v1/bridge/apps/{app_id}/sync/{sync_id}/account/",
        headers=HEADERS,
        json={"records": accounts},
    ).raise_for_status()

    # Complete
    requests.post(
        f"{BASE_URL}/api/v1/bridge/apps/{app_id}/sync/{sync_id}/complete/",
        headers=HEADERS,
    ).raise_for_status()

    # Poll until done
    while True:
        status = requests.get(
            f"{BASE_URL}/api/v1/bridge/apps/{app_id}/sync/{sync_id}/",
            headers=HEADERS,
        ).json()
        if status["status"] in ("completed", "error", "abandoned"):
            return status
        time.sleep(5)


# Sync each instance with its own data
sync_instance(
    engineering_app_id,
    groups=[{"id": "p1", "name": "Backend"}, {"id": "p2", "name": "Frontend"}],
    accounts=[
        {"id": "u1", "email": "alice@acme.com", "memberships": {"project": [{"id": "p1"}]}},
    ],
)

sync_instance(
    legal_app_id,
    groups=[{"id": "p10", "name": "Contracts"}, {"id": "p11", "name": "Compliance"}],
    accounts=[
        {"id": "u50", "email": "carol@acme.com", "memberships": {"project": [{"id": "p10"}]}},
    ],
)

Note that the group IDs (p1, p10) and account IDs (u1, u50) are independent per app. Each app maintains its own namespace of records.

Adding Resource Types After Cloning

If you add a new resource type through any cloned app, all sibling clones inherit it immediately. You can register the new type through any app in the clone group.

# Adding a resource type via any app in the clone group
requests.post(
    f"{BASE_URL}/api/v1/bridge/apps/{legal_app_id}/resource-types/",
    headers=HEADERS,
    json={"slug": "license", "name": "License", "type": "license"},
).raise_for_status()

# Now all apps in the clone group (Engineering, Legal, Finance) can sync licenses

clone_from vs. Separate Apps

ScenarioApproach
Same SaaS product, multiple instances with identical schemaclone_from -- share resource type definitions
Different external systems with different resource structuresSeparate apps -- each gets its own resource types
One instance now, might add more laterStart with a regular app -- you can always clone from it later
Instances with slightly different schemas (e.g., one has extra group types)clone_from -- register the superset of resource types. Each clone only needs to push data for the types it uses

Limitations

  • Resource type changes affect all clones. If you rename or modify a resource type, the change applies to every app in the clone group.
  • Slug collisions. Resource type slugs must be unique across the clone group. If one clone tries to register a slug that already exists, the API returns 409 Conflict.
  • No un-cloning. Once an app is created with clone_from, it cannot be separated into a standalone app. Plan your app structure before creating clones.
  • Deleting the parent does not affect clones. Deleting the app you originally cloned from does not orphan the clones — they continue to work normally.

Error Handling

ErrorCauseResolution
404 Not Found on createThe clone_from UUID does not match any existing Bridge appVerify the parent app's id value
409 Conflict on resource type creationA resource type with the same slug already exists in this clone groupThe type is already registered -- no action needed. List resource types to confirm
400 Bad Request on createclone_from is not a valid UUID formatCheck the UUID value for typos or formatting issues

On this page