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
| Aspect | Shared across clones | Independent per app |
|---|---|---|
| Resource type definitions | Yes -- slugs, names, and types are shared | |
| Synced records (users, groups, licenses) | Yes -- each app has its own data | |
| Sync sessions | Yes -- each app starts/completes its own syncs | |
| Provisioning tasks | Yes -- tasks are scoped to a single app | |
| Sync progress and history | Yes -- 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 licensesclone_from vs. Separate Apps
| Scenario | Approach |
|---|---|
| Same SaaS product, multiple instances with identical schema | clone_from -- share resource type definitions |
| Different external systems with different resource structures | Separate apps -- each gets its own resource types |
| One instance now, might add more later | Start 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
| Error | Cause | Resolution |
|---|---|---|
404 Not Found on create | The clone_from UUID does not match any existing Bridge app | Verify the parent app's id value |
409 Conflict on resource type creation | A resource type with the same slug already exists in this clone group | The type is already registered -- no action needed. List resource types to confirm |
400 Bad Request on create | clone_from is not a valid UUID format | Check the UUID value for typos or formatting issues |