Skip to main content

OpenMerge Python

Typed, server-side Python client for OpenMerge's unified integration API.

Setup

python -m pip install openmerge
import os
from openmerge import OpenMerge

with OpenMerge(api_key=os.environ["OPENMERGE_API_KEY"]) as client:
    integrations = client.list_integrations("ws_123")
    models = client.list_models("ws_123")
    accounts = client.list_linked_accounts("ws_123")

For FastAPI and other async applications, reuse one AsyncOpenMerge instance per application lifespan. It uses a pooled httpx.AsyncClient:

from openmerge import AsyncOpenMerge

async def list_accounts():
    async with AsyncOpenMerge(api_key=os.environ["OPENMERGE_API_KEY"]) as client:
        return await client.list_linked_accounts("ws_123")

All network methods have async equivalents, including mapping, reconnect, sync and bulk writeback. Use async for with iterate_records. An injected HTTP client remains caller-owned; otherwise aclose() releases the pool.

Releases are published by .github/workflows/ci.yml on version tags such as v0.3.0, after CI passes, using PyPI Trusted Publishing and the pypi environment.

Per-connection application-to-CRM defaults are passed server-side when the link token is minted. Developer IR policy determines whether the customer can change the selection in the widget:

link = client.create_link_token(
    "ws_123",
    "customer_456",
    mapping_overrides={"salesforce": {"Contact": {"research_bio": "Research_Bio__c"}}},
)

Hosted widgets

Every token response includes a short-lived hostedUrl. It is the universal fallback for server-rendered applications, native apps, desktop apps, and teams that do not install a frontend SDK. Appearance uses validated design tokens; arbitrary CSS and scripts never cross the widget boundary.

from openmerge import MappingLinkToken, WidgetAppearance, hosted_widget_url

appearance: WidgetAppearance = {
    "mode": "dark",
    "colors": {
        "primary": "#f97316",
        "surfaceHover": "#18181b",
        "borderStrong": "#3f3f46",
    },
    "branding": {
        "productName": "Acme Connect",
        "logoUrl": "https://assets.example/acme.svg",
    },
}

link = client.create_link_token(
    "ws_123",
    "customer_456",
    host_origin="https://app.example.com",
    appearance=appearance,
)
connect_url = link["hostedUrl"]
manager_url = hosted_widget_url(link, "manager")
sync_status_url = hosted_widget_url(link, "sync-status")

reconnect = client.create_reconnect_token(
    "ws_123",
    "linked_account_789",
    host_origin="https://app.example.com",
    appearance=appearance,
)

For connection-specific field mapping, mint a mapping token after schema discovery. The specialized mapping-token response always carries both the hosted mapping URL and the discovery job ID:

mapping: MappingLinkToken = client.create_connection_mapping_token(
    "linked_account_789",
    host_origin="https://app.example.com",
    appearance=appearance,
)
mapping_url = hosted_widget_url(mapping, model="Contact")
discovery_job_id = mapping["discoveryJobId"]

When mode is omitted, hosted_widget_url preserves a valid mode already in the API-provided URL and otherwise defaults to connect. By default, it trusts only https://widgets.openmerge.dev plus HTTP(S) loopback development origins. Self-hosted deployments must pass their exact origin explicitly:

self_hosted = hosted_widget_url(
    "https://connect.customer.example/connect/token-from-openmerge",
    allowed_origins=["https://connect.customer.example"],
)

host_origin must be the exact embedding origin. Omit it for a top-level hosted flow. Never send an OpenMerge workspace API key to a browser or WebView.

Account lifecycle and writebacks

account = client.get_linked_account("linked_account_789")
client.pause_linked_account(account["id"])
client.resume_linked_account(account["id"])
client.update_linked_account_schedule(
    account["id"],
    3600,
    reconciliation_seconds_by_model={"Contact": 86400},
)

write = client.submit_writeback(
    "Contact",
    "contact_123",
    workspace_id="ws_123",
    linked_account_id=account["id"],
    changes={"email": "new@example.com"},
    idempotency_key="customer-event-123",
)
completed = client.wait_for_writeback(write["id"], "ws_123")

Provider create, upsert, delete, bulk, and custom-field operations use the same durable write journal and require an application-owned idempotency key. Bulk requests are capped at 100 actions by the public API:

created = client.create_record(
    "Contact",
    workspace_id="ws_123",
    linked_account_id=account["id"],
    changes={"email": "new@example.com"},
    idempotency_key="contact-create-123",
)
upserted = client.upsert_record(
    "Contact",
    workspace_id="ws_123",
    linked_account_id=account["id"],
    unified_id="contact_123",
    changes={"research_bio": "Updated profile"},
    idempotency_key="contact-upsert-123",
)
client.bulk_records(
    "Contact",
    workspace_id="ws_123",
    linked_account_id=account["id"],
    items=[
        {"operation": "create", "changes": {"email": "second@example.com"}},
        {"operation": "create", "changes": {"email": "third@example.com"}},
    ],
    idempotency_key="contact-bulk-123",
)
client.create_custom_field(
    "Contact",
    workspace_id="ws_123",
    linked_account_id=account["id"],
    definition={
        "name": "research_bio",
        "label": "Research bio",
        "type": "long_text",
    },
    idempotency_key="contact-field-123",
)

Operations still obey the selected connector/model capability contract; an unsupported provider action is rejected by OpenMerge rather than silently degraded. One native bulk request must be operation-homogeneous. The SDK also rejects blank scope/record identifiers, empty write payloads, ambiguous bulk identities, and operation-invalid bulk fields before making an HTTP request.

Developer IR and mapping contracts

The effective Developer IR response includes the immutable base_document, the current materialized document, mapping requirements, hashes, and generation. Revisions are returned newest-first and can be used for audit or rollback tooling:

effective = client.get_developer_ir("ws_123", "oauth_app_123", "Contact")
revisions = client.list_developer_ir_revisions(
    "ws_123", "oauth_app_123", "Contact", limit=25
)
mapping_schema = client.get_connection_mapping(account["id"])

Mapping schema and job types include application/provider field metadata, binding provenance, schema observation state, generation pins, activation results, errors, and lifecycle timestamps.

delete_linked_account is destructive and starts governed credential/data erasure; its result includes the erasure job ID and state.

API keys stay on the server. Browser applications should receive only short-lived link tokens. Writes require an application-owned idempotency key. Webhooks must be verified against the exact raw body before JSON parsing:

import json
from typing import cast

from openmerge import WebhookEnvelope, verify_webhook_signature

verified = verify_webhook_signature(raw_body, signature_header, webhook_secret)
event = cast(WebhookEnvelope, json.loads(verified.payload))

Signature verification deliberately returns raw bytes. Parse the envelope only after verification so whitespace or encoding changes cannot invalidate the HMAC boundary. Typed envelopes expose event, complete tenant/connection ctx, record data, and replay/cursor breadcrumbs. RecordWebhookEnvelope models record.created, record.updated, and record.deleted CDC deliveries. DomainWebhookEnvelope models the control-plane telemetry outbox, whose breadcrumbs carry entity and request_id. WebhookEnvelope is the union accepted by a shared handler.

Development

python -m pip install -e ".[dev]"
ruff check .
mypy
pytest
python -m build

Codex and GPT-5.6

Codex and GPT-5.6 were used to audit the API contract, design the retry/idempotency and webhook-verification boundaries, generate the initial implementation, and build the automated test matrix. Maintainers remain responsible for review, release approval, and compatibility decisions.

Licensed under AGPL-3.0-only.

Metadata

Release files for openmerge 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openmerge 0.3.0
File Size Uploaded Provenance Actions
openmerge-0.3.0.tar.gz 34.4 kB With provenance Details for openmerge-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for openmerge 0.3.0
File Interpreter ABI Platform Actions
openmerge-0.3.0-py3-none-any.whl Python 3 none any Details for openmerge-0.3.0-py3-none-any.whl

Total release size: 64.7 kB

Release files / openmerge-0.3.0.tar.gz

Download URL openmerge-0.3.0.tar.gz
Size 34.4 kB
Tags Source
SHA-256 checksum
How to use checksums
c2b55edc91a882dcfd3ca4231e4e18702f1079f0b8443153fdd2f45c7401ba6d
BLAKE2b-256 checksum
How to use checksums
8d83582c02393fb1d320dc19abde2f35af30ed15ae77e47dfa852c9bf23454c9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Release files / openmerge-0.3.0-py3-none-any.whl

Download URL openmerge-0.3.0-py3-none-any.whl
Size 30.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1ab9191dbade9692517510bb4660738ff5e7f9033f65f6c71976a0ae8e78fb19
BLAKE2b-256 checksum
How to use checksums
2360b2f283461dc2448e01037522419ae731c3dbaa61c776a4063df8cec08dcf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

Security for openmerge 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for openmerge-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page