---
title: The Unified API That Doesn't Force Standardized Data Models on Custom Objects
slug: the-unified-api-that-doesnt-use-standardized-data-models-for-custom-objects
date: 2026-04-08
author: Nachi Raman
categories: [Engineering, General]
excerpt: Traditional unified APIs strip custom fields to force data into rigid schemas. Learn how declarative mapping architectures handle enterprise custom objects without code.
tldr: "Modern unified APIs use declarative JSONata mappings and a three-level override hierarchy to support enterprise custom objects dynamically, eliminating the need for rigid schemas or custom integration code."
canonical: https://truto.one/blog/the-unified-api-that-doesnt-use-standardized-data-models-for-custom-objects/
---

# The Unified API That Doesn't Force Standardized Data Models on Custom Objects


Your integration infrastructure just cost you a six-figure enterprise deal.

The technical evaluation was flawless. The buyer's head of RevOps was excited. Then their Salesforce admin sent over the schema for their organization: 147 custom fields on the Contact object, a highly modified `Deal_Registration__c` object with nested relationships that drives their entire partner pipeline, and a `Revenue_Forecast__c` rollup field that powers their quarterly board decks.

Your unified API provider's common data model flattened all of it into `first_name`, `last_name`, and `email`. It completely dropped the custom objects. To get the data, you were forced to bypass the unified schema entirely and write raw Salesforce SOQL against a passthrough endpoint. You were back to reading vendor documentation, managing provider-specific authentication quirks, and maintaining custom code for a single tenant. The abstraction failed, and the deal died in technical review.

If you are evaluating integration infrastructure for the enterprise market, you are likely looking for a **unified API that doesn't use standardized data models for custom objects**. Traditional unified APIs force data into a rigid, lowest-common-denominator schema. Modern unified API architectures solve this by replacing rigid schemas with a **declarative mapping architecture** that translates per-tenant schema variations dynamically, requiring zero integration-specific code.

This guide breaks down why rigid schemas fail in the enterprise, the architectural flaws of both the "passthrough" and "code-first" alternatives, and how to architect an integration layer that adapts to infinite schema variations using functional transformation languages and multi-level override hierarchies.

## The Enterprise Reality: Why Standardized Data Models Fail

Custom fields are not edge cases. They are the default state of every enterprise SaaS deployment.

Businesses track different types of data based on unique operational processes. They utilize custom fields to map their operational reality into their systems of record. As we detailed in our guide on [handling custom Salesforce fields](https://truto.one/blog/how-to-handle-custom-salesforce-fields-across-enterprise-customers/), <cite index="6-5">Salesforce allows a ceiling of 800 custom fields on any single object, with up to 3,000 total custom objects per org.</cite> And those numbers reflect real usage. <cite index="2-8">As organizations grow, their data requirements become more complex, pushing the limits of Salesforce's built-in constraints.</cite> 

A large financial services firm doesn't use the out-of-the-box Contact object. They have bolted on compliance fields, regulatory identifiers, territory codes, and multi-currency attributes specific to their deal flow. When you build a B2B SaaS product that integrates with these systems, your application must be able to read, write, and react to that custom data.

The scale of the problem goes beyond a single CRM. As noted in our [2026 enterprise unified API evaluation](https://truto.one/blog/which-unified-api-is-best-for-enterprise-saas-in-2026/), <cite index="32-3">organizations average 897 applications but only 29% are integrated.</cite> <cite index="32-4,32-5">MuleSoft's 2025 Connectivity Benchmark reveals massive integration gaps creating data silos. Each disconnected system becomes an island of information preventing unified analytics and automation.</cite>

The fundamental flaw of traditional unified APIs is their rigid approach to [schema normalization](https://truto.one/blog/why-schema-normalization-is-the-hardest-problem-in-saas-integrations/). When a unified API provider builds a "CRM Contacts" common model, they pick the fields that exist across Salesforce, HubSpot, Pipedrive, and Zoho. That intersection is tiny: `id`, `first_name`, `last_name`, `email`, `phone`, `company`. Everything else gets stripped out of the unified model.

As we have written about in our [deep dive on rigid schema costs](https://truto.one/blog/your-unified-apis-are-lying-to-you-the-hidden-cost-of-rigid-schemas/), this lowest-common-denominator approach actively destroys the data your customer's business depends on.

<cite index="31-2">DATAVERSITY's 2024 Trends in Data Management survey highlights this growing challenge, with 68% of respondents citing data silos as their top concern - up 7% from the previous year.</cite> When a unified API drops the exact custom object your enterprise buyer relies on, they are creating *new* silos, not solving existing ones.

## The Passthrough Trap of Traditional Unified APIs

How do traditional unified APIs handle custom objects? They don't.

Most unified API vendors acknowledge the custom fields problem. When you inevitably encounter a custom object that falls outside their rigid schema, the vendor points you to their "passthrough" endpoint.

A [proxy API](https://truto.one/blog/what-is-a-proxy-api-2026-saas-architecture-guide/) or passthrough endpoint simply routes your raw HTTP request through their authentication layer and directly to the underlying provider.

This is a trap. It sounds reasonable until you actually try to build on it.

Here is what happens in practice. Your developer calls `GET /unified/crm/contacts` and gets a nice normalized response. Then the product team says "we also need the `Deal_Registration__c` object from Salesforce." So your developer switches to the passthrough endpoint. Immediately, you lose every benefit you purchased the unified API for in the first place:

*   **You lose response normalization:** You must parse the provider's specific JSON structure and PascalCase field names.
*   **You lose query normalization:** You must learn and write provider-specific query languages (like Salesforce SOQL or HubSpot's filterGroups).
*   **You lose pagination normalization:** You must implement logic to handle the provider's specific pagination style (cursor, offset, link headers).
*   **You lose error normalization:** You must catch and interpret the provider's unique error codes and formats.

You have just rebuilt the Salesforce integration from scratch. Except now you have *two* integration paths to maintain: the unified one for standard fields and the passthrough one for everything custom. 

The passthrough trap creates a bifurcated architecture:

```mermaid
flowchart LR
    A[Your App] -->|Standard fields| B[Unified API<br>Normalized Response]
    A -->|Custom fields| C[Passthrough API<br>Raw Provider Response]
    B --> D[Common Schema]
    C --> E[Provider-Specific Code<br>Per Customer]
    style C fill:#f9d0d0
    style E fill:#f9d0d0
```

The red path is the problem. Every customer with custom objects pushes you onto it. Once you are there, you are no longer writing against a unified API. You are building a point-to-point integration from scratch, using the unified API vendor purely as an overpriced OAuth token manager.

## The Code-First Trap: Why Writing Scripts Doesn't Scale

Recognizing the failure of rigid schemas and passthrough endpoints, a second category of integration tools emerged: embedded iPaaS and "code-first" platforms.

These platforms argue that unified APIs fundamentally cannot handle custom objects. Their proposed solution is to abandon the unified schema entirely and give you a scripting environment. They force your engineering team to write custom JavaScript or TypeScript scripts for every integration and every tenant.

Instead of a declarative schema, you write code:

```typescript
// The code-first maintenance nightmare
export async function syncContacts(tenant, providerClient) {
  if (tenant.id === 'acme_corp') {
    // Write custom logic for Acme's 147 custom fields
    const response = await providerClient.get('/query?q=SELECT Id, Deal_Registration__c FROM Contact');
    return transformAcmeData(response);
  } else if (tenant.id === 'globex') {
    // Write different custom logic for Globex
    const response = await providerClient.get('/query?q=SELECT Id, Revenue_Forecast__c FROM Contact');
    return transformGlobexData(response);
  }
}
```

The reality: you have just signed up to maintain N custom scripts for M custom objects across K customer tenants. Here is the math on a real mid-market SaaS product:

| Dimension | Count |
|-----------|-------|
| CRM integrations supported | 8 |
| Enterprise customers with custom objects | 25 |
| Average custom objects per customer | 4 |
| Custom scripts to maintain | **800** |

This approach does not scale. Each of those scripts handles authentication, pagination, error handling, and data mapping - all in imperative code that someone on your team wrote six months ago and nobody wants to touch.

Every time an enterprise customer alters their CRM schema, your integration breaks. Your engineering team must open a pull request, modify the integration script, write new tests, go through code review, and deploy the changes to production. When HubSpot changes their API versioning, you update 100+ scripts. You have effectively outsourced your integration hosting, but retained 100% of the engineering maintenance debt.

The code-first approach also has a staffing problem. <cite index="32-17,32-18">Skills gaps affect 87% of organizations across industries. McKinsey research reveals that 87% of organizations either face skill gaps already or expect them within the next five years.</cite> Asking your engineering team to maintain hundreds of integration scripts is not a good use of scarce developer talent.

Code-first platforms solve the *flexibility* problem while making the *maintenance* problem exponentially worse. What you actually need is flexibility *without* the per-integration code.

## Architecting a Unified API That Doesn't Use Standardized Data Models for Custom Objects

To solve the custom fields problem, we must abandon both the rigid schemas of traditional unified APIs and the unmaintainable scripts of code-first platforms. 

The solution is an architectural shift from the **strategy pattern** (writing imperative code per integration) to the **interpreter pattern** (executing declarative data). 

Here is the core idea: instead of writing a `SalesforceContactMapper.ts` and a `HubSpotContactMapper.ts`, you define the mapping as a declarative expression stored in a database. The runtime engine is a generic execution pipeline. It takes a declarative configuration describing how to talk to the third-party API, and a declarative mapping describing how to translate the data. It doesn't know or care whether it's talking to Salesforce, HubSpot, or a custom ERP. It just reads the config and executes.

This mapping relies on **JSONata**. <cite index="41-1">JSONata is a lightweight query and transformation language for JSON data</cite> - <cite index="46-1,46-2">it allows us to extract data from an input JSON document, combine the data using string and numeric operators, and format the structure of the output JSON document. What follows are the parts that turn this into a Turing complete, functional programming language.</cite> AWS Step Functions adopted JSONata as a first-class transformation language, validating it as production-grade infrastructure.

Here is what a mapping for a custom Salesforce object looks like in practice:

```jsonata
response.{
  "id": $string(Id),
  "registration_name": Deal_Registration__c.Name,
  "partner_account": { "id": Deal_Registration__c.Partner_Account__c },
  "revenue_forecast": $number(Deal_Registration__c.Revenue_Forecast__c),
  "stage": Deal_Registration__c.Stage__c,
  "custom_fields": $sift($, function($v, $k) {
    $k ~> /__c$/i and $boolean($v)
  }),
  "created_at": CreatedDate,
  "updated_at": LastModifiedDate
}
```

And here is the same unified operation for HubSpot's custom object, using a completely different native field structure - but producing an identical output shape:

```jsonata
response.{
  "id": $string(id),
  "registration_name": properties.deal_name,
  "partner_account": { "id": associations.companies.results[0].id },
  "revenue_forecast": $number(properties.revenue_forecast),
  "stage": properties.dealstage,
  "custom_fields": properties.$sift(function($v, $k) {
    $k in $diff
  }),
  "created_at": createdAt,
  "updated_at": updatedAt
}
```

Both expressions live in the database as plain strings. No code deployment. No pull requests. No CI/CD pipelines. The same generic engine evaluates both.

For a deeper technical walkthrough of how JSONata handles custom field mappings, see our [custom fields architecture guide](https://truto.one/blog/how-do-unified-apis-handle-custom-fields-2026-architecture-guide/).

## The Three-Level Override Hierarchy

Handling custom objects isn't just about mapping a provider's API. It's about mapping *each customer's specific configuration* of that provider's API. Customer A's Salesforce has `Revenue_Forecast__c`. Customer B's Salesforce has `Projected_ARR__c`. A static mapping won't work for both.

To handle per-tenant custom objects without writing integration-specific code, Truto utilizes a three-level override hierarchy. Mappings can be modified at any level, with each layer deep-merging on top of the previous one.

```mermaid
flowchart TD
    A[Level 1: Platform Base Mapping<br>Standard fields] --> B[Level 2: Environment Override<br>App-specific fields]
    B --> C[Level 3: Account Override<br>Tenant-specific custom objects]
    C --> D[Final Merged Mapping<br>Evaluated by Generic Engine]
    style A fill:#e8f4e8
    style B fill:#e8e8f4
    style C fill:#f4e8f4
    style D fill:#f4f4e8
```

**Level 1 - Platform Base:** The default JSONata mapping stored in the database. It handles standard fields that apply to 80% of use cases (e.g., mapping `FirstName` to `first_name`).

**Level 2 - Environment Override:** Your specific application environment can override the base mapping. If your SaaS product requires extracting a specific standard field that the base model ignores, you apply a JSONata override at the environment level.

**Level 3 - Account Override:** This is where enterprise custom objects are handled. Individual connected accounts (tenants) can have their own mapping overrides. If your enterprise customer "Acme Corp" has two Salesforce orgs with different custom field schemas, each org gets its own account-level override.

```json
// Account-level override for Acme Corp
{
  "unified_model_override": {
    "crm": {
      "contacts": {
        "list": {
          "response_mapping": "response.{ 'id': Id, 'first_name': FirstName, 'deal_registration': Deal_Registration__c }"
        }
      }
    }
  }
}
```

Each level deep-merges onto the previous one. The override only needs to specify what's *different* - everything else inherits from the base.

| What Can Be Overridden | Example Use Case |
|------------------------|------------------|
| `response_mapping` | Add custom fields to unified response |
| `query_mapping` | Support customer-specific filter parameters |
| `request_body_mapping` | Include custom fields in create/update operations |
| `resource` | Route to a custom object endpoint |
| `method` | Use POST instead of GET for search |
| `before` / `after` steps | Fetch extra data before the main call |

When a unified request comes in for Acme Corp, the execution engine merges the overrides, evaluates the JSONata expression, and returns the perfectly mapped custom object. Because the custom mapping happens inside the unified API engine, you still get all the benefits of the unified API. The engine still handles the OAuth token refresh, the cursor-based pagination, and the standardized error formatting.

> [!NOTE]
> **The `remote_data` safety net:** Even without overrides, Truto always preserves the original, unmodified third-party payload inside a `remote_data` object on every response. If an AI agent or downstream system needs a custom field that hasn't been explicitly mapped, the raw data is always available in the exact same API call.

## Handling Rate Limits on Heavy Custom Object Queries

Querying heavily customized enterprise objects - especially when executing complex SOQL queries or filtering on unindexed custom fields - consumes significant API quota. A Salesforce SOQL query that JOINs across a custom object with 200 fields will be slow. The same is true for HubSpot's custom object search, which uses POST-based filter queries that count against tighter rate limits than standard GET endpoints.

Many embedded iPaaS tools and unified API platforms attempt to be "helpful" by silently absorbing HTTP 429 (Too Many Requests) errors and automatically applying exponential backoff on your behalf. Your application thinks the call succeeded, but it is actually hanging or returning stale data.

This is a dangerous anti-pattern for enterprise systems.

When an integration platform silently retries requests, it holds open connections, masks the true state of the upstream API, and prevents your application (or your AI agents) from making intelligent routing decisions. If an agent is scraping data and hits a rate limit, the agent needs to know immediately so it can pause execution, switch tools, or inform the user.

Truto takes a radically transparent approach to [handling API rate limits](https://truto.one/blog/how-to-handle-third-party-api-rate-limits-when-an-ai-agent-is-scraping-data/).

> [!WARNING]
> **Truto does NOT retry, throttle, or apply backoff on rate limit errors.** When an upstream API returns a rate-limit error, Truto passes that HTTP 429 directly back to the caller. The caller is responsible for implementing their own retry logic. Silent retries hide information that agents and orchestration layers need to make good decisions.

What Truto *does* do is normalize the chaotic, provider-specific rate limit headers into a standardized format based on the IETF RateLimit header specification. <cite index="22-3">The IETF RateLimit header specification defines: RateLimit-Limit, containing the requests quota in the time window; RateLimit-Remaining, containing the remaining requests quota in the current window; RateLimit-Reset, containing the time remaining in the current window, specified in seconds.</cite>

Regardless of whether the upstream API is Zendesk, Salesforce (which sends `Sforce-Limit-Info`), or HubSpot (which uses `X-HubSpot-RateLimit-Daily-Remaining`), Truto parses their specific headers and returns:

```
ratelimit-limit: 100
ratelimit-remaining: 23
ratelimit-reset: 45
```

This matters immensely for automated workflows. Here is a practical example of how a caller handles this proactively:

```typescript
async function fetchCustomObjects(accountId: string) {
  const response = await fetch(
    `https://api.truto.one/unified/crm/contacts?integrated_account_id=${accountId}`,
    { headers: { Authorization: `Bearer ${API_TOKEN}` } }
  );

  if (response.status === 429) {
    const resetSeconds = parseInt(
      response.headers.get('ratelimit-reset') || '60'
    );
    console.log(`Rate limited. Retrying in ${resetSeconds}s`);
    await sleep(resetSeconds * 1000);
    return fetchCustomObjects(accountId); // retry
  }

  // Proactive backoff: slow down if we're running low
  const remaining = parseInt(
    response.headers.get('ratelimit-remaining') || '100'
  );
  if (remaining < 5) {
    await sleep(2000); // ease off before hitting the wall
  }

  return response.json();
}
```

## Extensibility Without the Maintenance Burden

Enterprise software is messy. It is highly customized, deeply nested, and constantly changing. 

The choice between unified APIs and custom integrations has always felt like a false dichotomy. Either you get a clean, standardized interface that ignores 80% of your enterprise customers' data, or you build everything from scratch and drown in maintenance.

A declarative, config-driven architecture breaks this tradeoff. Here is what changes:

**For product managers:** You stop telling enterprise prospects "custom objects are on the roadmap." Instead, you configure the mapping for their specific schema as part of onboarding. The override hierarchy means one customer's custom objects never affect another customer's integration.

**For engineering leaders:** Your integration maintenance burden grows with the number of *unique API patterns*, not the number of customers or custom fields. Most CRMs use REST with JSON responses, cursor-based pagination, and OAuth2. The generic engine handles these patterns once. Adding a customer's 147 custom fields is a data operation - no code review, no CI pipeline, no deployment.

**For the team building agentic AI products:** Your AI agents get structured, consistent data from custom objects across providers, with transparent rate limit signals they can actually act on. No silent failures. No stripped fields. No bifurcated passthrough paths.

The integration industry spent a decade pretending that forcing enterprise data into rigid schemas was an acceptable tradeoff for developer convenience. <cite index="32-13">Data silos cost organizations $7.8 million annually in lost productivity.</cite> A unified API that ignores custom objects isn't solving the data silo problem - it's creating a new one with a nicer REST interface.

The path forward is treating integration mappings as data, not code. By utilizing JSONata and a multi-level override hierarchy, custom objects become a configuration problem, not an engineering problem. You gain the speed of a unified API without sacrificing the deep extensibility required to close six-figure enterprise deals.

Stop rejecting enterprise deals because of rigid data models.

> Truto's declarative mapping architecture handles custom objects and fields across 100+ integrations without a single line of integration-specific code. See how the three-level override hierarchy works for your enterprise customers.
>
> [Talk to us](https://cal.com/truto/partner-with-truto)
