> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dualentry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Build a Custom DualEntry Integration

> Design, build, and certify a custom DualEntry Public API integration: partner access, auth, sync, field mapping, cut-off dates, and error handling.

This guide walks through building a custom DualEntry Public API integration for the partner program, from getting access through implementation. For the architecture and concepts behind an integration, see [How Custom Integrations Work](./how-custom-integrations-work). For the certification checklist your integration must pass before launch, see [Custom Integration Reference](./custom-integration-reference).

## Before you start

### Get partner access

Before writing any code, you need:

1. A signed partner agreement with DualEntry.
2. API credentials for your partner account.
3. Access to a DualEntry sandbox organization for development and certification testing.
4. Access to the partner API documentation (request through your partnership contact).

Contact the partnerships team to get these set up. Don't start coding against a customer's production org. Sandbox first.

### Define your integration scope

Work with the integrations team to nail down the following before you write code:

1. **Data flow direction**: Pull-only, push-only, or bidirectional.
2. **Record types in scope**: Customers, vendors, invoices, bills, journal entries, payments, etc.
3. **Field mapping**: Which fields on your side map to which DualEntry fields, and what transformations are needed.
4. **DualEntry modules affected**: Accounts Payable, Accounts Receivable, General Ledger, Revenue Recognition, Payroll.
5. **Multi-entity support**: Whether customers need multiple instances of your integration in a single DualEntry org (for example, one per legal entity).
6. **Attachment handling**: Whether you sync files (PDFs, receipts) along with records.

### Key questions to resolve early

* Does your platform support sandbox or test environments? You need one for end-to-end testing.
* Does your API return paginated results? What's the maximum page size? See [Pagination](/developers/guides/api/core-concepts/pagination) for DualEntry's conventions.
* Do you support webhooks, or does DualEntry need to poll? What polling interval is acceptable?
* What are your rate limits? What's your recommended backoff strategy? See [Rate limiting](/developers/guides/api/core-concepts/rate-limiting) for DualEntry's limits.
* Do you return stable, immutable external IDs that DualEntry can use for deduplication?
* If syncing attachments: what's the maximum file size, and what file types do you support?

## Implementation

### Authentication

Implement the auth flow that fits your security model and your customers' expectations. See [Authentication](/developers/guides/api/authentication) for DualEntry's supported flows in detail.

The DualEntry side of this is simpler than most APIs you will have integrated with. There is one mechanism: an organization API key in the `X-API-KEY` header. There is no token exchange, no refresh step, and no session to maintain, so the same header works on the first request and the millionth.

What that means for your implementation:

* Store the key encrypted, scoped to the customer's organization.
* Validate the key on save by making a lightweight API call before persisting it, so a typo surfaces during setup rather than on the first sync.
* The key identifies the organization, so your requests carry no separate tenant parameter and a key can never reach another organization's data.
* If your setup UI exposes fields like Client ID or Client Secret for your own side of the connection, label them clearly. Ambiguous auth fields are a top source of failed setups, and they are more confusing when the other side of the integration needs only a single key.

DualEntry's own OAuth endpoints exist, and the CLI uses them, but they are a way to obtain an API key through a browser sign-in rather than a request authentication method. The key is what authenticates every subsequent call, so do not design a token-refresh path you will never need.

<Warning>
  **DualEntry's own API keys have no rotation grace period.** The DualEntry Public API authenticates with a long-lived organization API key rather than expiring OAuth access or refresh tokens. When a new API key is issued, the previous key is revoked immediately in the same operation. There is no overlap window during which both keys work. Design your key rotation as an atomic swap: obtain the new key, update your credential store, and retry any in-flight requests that fail with 401 using the new key.
</Warning>

### Sync engine

Build the sync logic that moves data between systems.

**Pull integrations** (DualEntry consumes data from your platform):

* Run sync as a scheduled background task on your side, or expose endpoints DualEntry can poll.
* Support configurable sync intervals. DualEntry stores the interval per organization and provider as a number of seconds, bounded at a minimum of 30 minutes and a maximum of 30 days, so build for a range rather than a fixed menu of choices.
* Implement a "sync-from" timestamp that defaults to the last successful sync.
* Support manual trigger ("Sync Now") from the DualEntry UI.
* Support full sync, sync-from-last-synced-date, and custom date range modes.

**Push integrations** (your platform consumes data from DualEntry):

* Trigger on relevant events (record creation, status change, void, etc.).
* Queue outbound work asynchronously to avoid blocking the originating request.
* Implement retry logic with exponential backoff for failures (see [Errors](/developers/guides/api/core-concepts/errors)).

**Webhook-based integrations:**

* Register a webhook endpoint during the customer setup flow.
* Validate webhook signatures or HMACs on every inbound payload. Never trust unsigned webhooks.
* Acknowledge receipt immediately and process the payload asynchronously.
* Make webhook processing idempotent: partners (and DualEntry) replay webhooks on failure.

**For all sync types:**

* Expose the next scheduled sync time to the customer in the UI.
* Report every sync run to DualEntry's Sync History so customers can see what happened and when.
* Add tracing or logging around your core sync steps so issues can be diagnosed quickly.

### Field mapping and data transformation

Maintain a clear mapping between your data model and DualEntry's. This becomes part of the integration documentation and your certification artifacts.

A typical mapping looks like:

| Your field         | DualEntry field             | Type              | Required | Transformation                                    |
| ------------------ | --------------------------- | ----------------- | -------- | ------------------------------------------------- |
| `invoice.amount`   | `transaction.amount`        | Decimal           | Yes      | Convert minor units to major (cents → dollars)    |
| `invoice.currency` | `transaction.currency_code` | String (ISO 4217) | Yes      | Validate against DualEntry's supported currencies |
| `invoice.date`     | `transaction.date`          | Date              | Yes      | Normalize to ISO 8601                             |

**Common transformations:**

* **Monetary amounts**: Many APIs use minor units (cents). DualEntry uses decimal major units (dollars). Convert explicitly; don't rely on automatic coercion.
* **Dates**: Normalize to ISO 8601. Pay attention to timezones: partner timestamps may be UTC, local, or ambiguous, and getting this wrong causes off-by-one-day bugs.
* **Statuses and enums**: Map your statuses to DualEntry statuses explicitly. Document any statuses that don't have a clean mapping.
* **External IDs**: Always store your stable external ID on the DualEntry record. This is what makes deduplication and incremental updates possible.

**Default values:**

* Define explicit behavior when a source field is null, missing, or invalid.
* Use the integration-level defaults the customer configured during setup (for example, default company, default GL account).
* Never create placeholder records like "Unknown Company". This corrupts the customer's data and creates cleanup work.

### Cut-off date enforcement

Customers set a **cut-off date** during setup that defines the earliest record date your integration should sync. Records dated before the cut-off are out of scope and must not flow into DualEntry.

Requirements:

* Accept the cut-off date during initial setup and store it in your integration config.
* Filter sync queries by cut-off date **at the query level**, not by fetching everything and filtering after. This is a recurring source of bugs.
* Apply the cut-off on initial sync **and** every subsequent sync.
* Handle edge cases:
  * Records dated exactly on the cut-off date → include them.
  * Records backdated by your platform after the cut-off → exclude them if their effective date is before the cut-off.

<Warning>
  Cut-off enforcement is one of the most common bug sources during certification. Test with records dated just before, on, and just after the cut-off, and verify that re-sync after a long pause still respects it.
</Warning>

### Duplicate prevention

Duplicate records are one of the most disruptive integration bugs: they corrupt customer ledgers and require manual cleanup. Get this right from day one.

Requirements:

* Use a stable external ID from your system as the deduplication key.
* Implement upsert logic: if a record with the same external ID already exists in DualEntry, update it instead of creating a new one.
* Make re-sync idempotent: running it twice in a row must never produce duplicates.
* Handle concurrent sync workers correctly. Use upsert semantics or proper locking so that two workers processing the same record don't race into a duplicate write or an integrity error.

If a customer reports duplicates, the investigation usually comes down to one of: missing external ID, an external ID that changed between syncs, or a race condition in concurrent workers. Make sure none of these are possible by design.

### Locked / read-only records

Records imported from your platform may be locked from manual editing in DualEntry to prevent the customer's accounting data from drifting from your source of truth.

* Define which fields are editable in DualEntry vs. locked to integration-only updates.
* Make the locking behavior visible to customers: they shouldn't be confused about why a field is grayed out.
* If admin override is supported, document the unlock flow.

### Error handling and retry

Robust error handling separates a good integration from a flaky one.

* Classify errors as transient (retry) or permanent (surface to user). Don't retry indefinitely on a permanent failure.
* Use exponential backoff for transient failures: timeouts, rate limits, 5xx responses. See [Errors](/developers/guides/api/core-concepts/errors) for DualEntry's error model and [Rate limiting](/developers/guides/api/core-concepts/rate-limiting) for backoff guidance.
* Respect rate limits. Read rate limit headers from API responses where available and adjust your throughput accordingly.
* Surface persistent failures in DualEntry's Sync History with **actionable** error messages. "Sync failed" is not actionable; "Account mapping missing for vendor `Acme Co.`: assign a GL account in Settings → Integrations" is.
* Handle integrity errors and duplicate-key violations gracefully in async workers: don't let one bad row kill the whole sync.

### Environments

* Build and test in a sandbox DualEntry organization.
* Maintain a sandbox account on your end too, so customers and the solutions team can demo the integration end-to-end.
* Don't ship until your integration works cleanly in both sandbox and production environments.

## Multi-currency and cross-company

If your integration moves money or creates journal entries (Money-In, Money-Out, Journal Entries, Intercompany Journal Entries, or Bank Transfers), multi-currency and cross-company handling are required.

### Multi-currency

* Sync the original transaction currency, not just an amount converted to the customer's base currency.
* Decide explicitly where the exchange rate comes from: your platform, DualEntry's daily rate, or a market rate. Document this so customers know what to expect.
* Calculate and post FX gain/loss entries where applicable.
* Handle partial payments in different currencies than the underlying invoice or bill.
* Handle currency mismatches (e.g., an invoice in EUR paid in USD) and document your resolution logic.

### Cross-company / intercompany

* Handle transactions that span multiple DualEntry entities.
* Create intercompany journal entries following DualEntry's intercompany rules.
* Generate elimination entries where required.
* For bank transfers across entities, post to the correct accounts in each entity.

## Multi-instance support

Some customers need multiple instances of your integration in a single DualEntry organization, for example, a customer with multiple entities on your platform mapped to multiple DualEntry companies.

If you support this:

* Allow multiple instances of the integration to be created within one DualEntry org.
* Differentiate instances by your platform's entity ID.
* Run sync schedules independently per instance.
* In any mapping or settings UI surfaced inside DualEntry, make it unambiguous which instance maps to which DualEntry company.

## Attachments

Not every integration needs to handle files. If yours does:

* Pull or push the relevant files (PDFs, receipts, supporting documents) along with their parent record. For the attachment schema and the available API operations, see [Working with Attachments](/developers/guides/api/building-blocks/working-with-attachments).
* Respect file size limits and supported file types on both sides.
* Handle missing or inaccessible attachments gracefully: never fail the entire sync because one file couldn't be retrieved.

## Result

Your integration authenticates with an organization API key, syncs data on the schedule and direction your customers need, respects cut-off dates and deduplication, and handles errors without silent data loss. Before shipping it to mutual customers, run it against the [Custom Integration Reference](./custom-integration-reference)'s certification checklist.
