Skip to content

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

Traditional unified APIs strip custom fields to force data into rigid schemas. Learn how declarative mapping architectures handle enterprise custom objects without code.

Nachi Raman Nachi Raman · · 12 min read
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, Salesforce allows a ceiling of 800 custom fields on any single object, with up to 3,000 total custom objects per org. And those numbers reflect real usage. As organizations grow, their data requirements become more complex, pushing the limits of Salesforce's built-in constraints.

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, organizations average 897 applications but only 29% are integrated. 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.

The fundamental flaw of traditional unified APIs is their rigid approach to schema normalization. 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, this lowest-common-denominator approach actively destroys the data your customer's business depends on.

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. 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 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:

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:

// 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. 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. 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. JSONata is a lightweight query and transformation language for JSON data - 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. 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:

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:

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.

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.

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.

// 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.

Info

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.

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. 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.

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:

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. Data silos cost organizations $7.8 million annually in lost productivity. 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.

Frequently Asked Questions

How do unified APIs handle custom objects?
Traditional unified APIs strip custom objects or force developers onto raw passthrough endpoints. Modern architectures use declarative mapping layers (like JSONata) to translate custom fields dynamically into the unified schema without custom code.
What is the passthrough trap in unified APIs?
When unified APIs don't support custom fields, developers use passthrough endpoints to bypass the unified layer. This forces them to write provider-specific API calls, losing response, query, pagination, and error normalization—defeating the purpose of the unified API.
Why is code-first integration unscalable for custom fields?
Writing custom scripts per tenant per integration creates massive maintenance debt. A mid-market SaaS with 25 enterprise customers and 8 integrations could easily end up maintaining 800 distinct scripts that break whenever a tenant alters their CRM schema.
How does a multi-level override hierarchy work?
It allows mappings to be customized without touching base code. A platform-level base mapping handles standard fields, an environment-level override handles application-specific needs, and an account-level override maps per-tenant schema variations using deep-merged JSONata configurations.
Why shouldn't a unified API automatically retry rate limits?
Silent retries hold open connections and obscure the actual state of the upstream API. Callers, especially AI agents, need raw HTTP 429 errors and standardized IETF rate limit headers to implement intelligent routing and backoff logic.

More from our Blog