Skip to content

Look Ma, No Code! Why Truto’s Zero-Code Architecture Wins

See how Truto adds new API integrations without deploying code - JSON blueprints, JSONata mappings, and live patches for breaking API changes.

Roopendra Talekar Roopendra Talekar · · 11 min read
Look Ma, No Code! Why Truto’s Zero-Code Architecture Wins

You are likely reading this because you are skeptical of unified APIs. You should be.

Most unified API platforms are "black boxes." They promise to abstract away the complexity of 50 different integrations, but under the hood, they solve the problem with brute force. They maintain 50 separate code paths—one for Salesforce, one for HubSpot, one for NetSuite. When an API changes, their engineers have to rewrite code, redeploy, and hope they didn't break the other 49 integrations.

This architectural rigidity is why you wait months for a new endpoint. It’s why you can’t map that one custom field your enterprise customer needs right now. It is the hidden cost of the Adapter Pattern at scale.

Truto is different. We don't write integration code. Our entire platform—runtime, database, proxy, and sync engine—contains zero integration-specific logic.

We built a programmable unified API based on the Interpreter Pattern. This isn't just an implementation detail; it is the reason we can ship new integrations in hours and why you can customize them without waiting on our roadmap. If you're evaluating no-code API integration platforms, or trying to figure out how to add API integrations without touching a build pipeline, the sections below walk through the full end-to-end flow with a real config.

The "If-This-Then-That" Trap of Traditional Unified APIs

To understand why Truto’s architecture is radical, you have to look at how the rest of the industry builds integrations.

The standard approach is the Strategy Pattern (or Adapter Pattern). The platform defines a common interface (e.g., UnifiedContact), and then writes a specific adapter class for each provider.

It looks something like this in their codebase:

// The "Brute Force" Approach
if (provider === 'hubspot') {
  return hubspotAdapter.getContacts(params);
} else if (provider === 'salesforce') {
  return salesforceAdapter.getContacts(params);
}

This scales linearly with pain.

  • Maintenance Hell: Every new integration adds more code to maintain. A bug in the Salesforce pagination logic doesn't get fixed for HubSpot.
  • Rigid Schemas: Because the logic is hardcoded, your unified APIs are lying to you about flexibility. If the vendor didn't code a mapping for a specific field, you can't access it.
  • Slow Velocity: Adding a new integration requires a full engineering cycle: code, review, test, deploy.

We realized early on that schema normalization is the hardest problem in SaaS integrations, and solving it with if/else statements is a losing battle.

The Architecture of Zero: 38 Tables, No Integration Columns

Truto’s architecture is built on a single, provocative constraint: No integration-specific code, column, or UI.

If you inspect our database schema, you will find 38 tables. You will find tables for users, teams, and integrated_accounts. But you will not find a hubspot_token column. You won't find a salesforce_instance_url field. You won't find a zendesk_subdomain.

Instead, everything is generic.

  • The Integration Table: Contains a config JSON blob. This blob describes the API's base URL, authentication scheme (OAuth2, API Key), and pagination rules.
  • The Integrated Account Table: Contains a context JSON blob. This holds the actual credentials and tokens for a specific customer's connection.
  • The Mapping Table: Contains config JSON blobs with JSONata expressions that define how to transform data.

This means adding a new integration is purely a data operation. We insert a row into the integration table describing the API, and rows into the unified_model_resource_method table describing the mappings.

We do not deploy code to add an integration. The runtime engine that processes a Salesforce request is the exact same binary that processes a Notion request. It just reads a different configuration. This means bug fixes and improvements are instant. As soon as you click Save, the behavior changes immediately for every connected account—no build pipeline, no CI/CD wait time, and no deployment window required.

JSONata: The Secret Weapon for Declarative Mapping

If we don't write code to map data, how do we handle the massive differences between APIs?

HubSpot uses filterGroups arrays with specific operators. Salesforce uses SOQL (Salesforce Object Query Language). NetSuite uses XML.

We use JSONata, a declarative query and transformation language for JSON. Instead of writing imperative code to loop through arrays and rename fields, we store transformation logic as data strings.

Example: The "List Contacts" Problem

Here is how the exact same generic code path handles two completely different API paradigms using stored JSONata expressions.

1. HubSpot (The REST + Filter Array approach)

HubSpot requires constructing a complex JSON payload for filtering. Our stored mapping looks like this:

# Stored in the database, not code
request_body_mapping: >-
  rawQuery.{
    "filterGroups": [{
      "filters": [
        first_name ? { "propertyName": "firstname", "operator": "CONTAINS_TOKEN", "value": first_name },
        email ? { "propertyName": "email", "operator": "EQ", "value": email }
      ]
    }]
  }

2. Salesforce (The SOQL approach)

Salesforce requires a SQL-like string. Our stored mapping handles this by constructing the query dynamically:

# Stored in the database, not code
query_mapping: >-
  {
    "q": "SELECT Id, FirstName, LastName, Email FROM Contact WHERE " &
         (email ? "Email = '" & email & "'" : "Id != null")
  }

When a request comes in, our generic execution engine loads the configuration, evaluates the JSONata expression, and dispatches the request. It doesn't know it's talking to Salesforce. It just knows it needs to send the string result of that expression to the URL defined in the config.

This approach extends to multi-step API orchestration as well. We can define before and after steps in the configuration to chain multiple API calls together (e.g., "fetch the Company ID first, then create the Contact") without writing a single line of TypeScript.

The Interpreter Pattern: Why We Don't Deploy Code

Truto implements the Interpreter Pattern.

  • The Language: Our integration configuration schema (JSON) and mapping expressions (JSONata) form a Domain Specific Language (DSL) for integrations.
  • The Interpreter: Our runtime engine is a generic machine that executes this DSL.

This is why our architecture is superior to the "code-first" platforms like Nango or the "managed black box" platforms like Merge.

With a code-first platform, you inherit the maintenance burden. You write the scripts. You debug the edge cases.

With a black-box platform, they own the code, but you are held hostage by their release cycle. If their Salesforce adapter has a bug, you wait for them to deploy a fix.

With Truto's Interpreter pattern, the "code" is just configuration. This leads to our most powerful feature: Overrides.

The Ultimate Flex: Customer-Defined Overrides

Because integration logic is data, it can be patched at runtime. Truto implements a 3-level override hierarchy that allows you to customize the unified API without waiting for us.

  1. Platform Level: The default mapping we provide (e.g., standard Salesforce contacts).
  2. Environment Level: You can override mappings for your specific application. Need to map a custom tier field from Salesforce to priority in your app? You just patch the JSONata mapping in your environment config.
  3. Account Level: You can even override mappings for a single specific customer. If one of your enterprise clients has a wildly customized HubSpot instance, you can apply a mapping patch just for them.
Tip

Why this matters: You are no longer blocked by your unified API vendor. You can fix bugs, add fields, or change endpoint logic instantly by updating the configuration, not deploying code.

Adding a New API Integration Without Deploying Code: A Worked Example

Let's put the theory into practice. Here's how you add a brand-new integration for a fictional CRM called AcmeCRM - from zero to a working unified endpoint - purely through configuration. Every step below is a data operation. No repository, no PR, no deploy window.

The Full JSON API Blueprint

Every integration in Truto is a single JSON record. The config blob describes the base URL, authentication, resources, pagination, and rate limiting. Here is the complete blueprint you'd write for AcmeCRM:

{
  "name": "acmecrm",
  "category": "crm",
  "config": {
    "base_url": "https://api.acmecrm.com/v1",
    "label": "AcmeCRM",
    "credentials": {
      "format": "oauth2",
      "config": {
        "client": { "id": "{{env.ACME_CLIENT_ID}}", "secret": "{{env.ACME_CLIENT_SECRET}}" },
        "auth": {
          "tokenHost": "https://auth.acmecrm.com",
          "tokenPath": "/oauth/token",
          "authorizeHost": "https://auth.acmecrm.com",
          "authorizePath": "/oauth/authorize",
          "refreshPath": "/oauth/token"
        },
        "scope": ["contacts:read", "contacts:write"],
        "pkce": { "method": "S256" }
      }
    },
    "authorization": {
      "format": "bearer",
      "config": { "path": "oauth.token.access_token" }
    },
    "pagination": {
      "format": "cursor",
      "config": {
        "cursor_field": "meta.next_cursor",
        "cursor_query_param": "cursor"
      }
    },
    "rate_limit": {
      "is_rate_limited": "status = 429",
      "retry_after_header_expression": "headers.`retry-after`",
      "rate_limit_header_expression": "{ 'limit': headers.`x-ratelimit-limit`, 'remaining': headers.`x-ratelimit-remaining`, 'reset': headers.`x-ratelimit-reset` }"
    },
    "resources": {
      "contacts": {
        "list": {
          "method": "get",
          "path": "/contacts",
          "response_path": "data"
        },
        "get": {
          "method": "get",
          "path": "/contacts/{{id}}",
          "response_path": "data"
        },
        "create": {
          "method": "post",
          "path": "/contacts"
        }
      }
    }
  }
}

That is the entire integration definition. No adapter classes. No if (provider === 'acmecrm') branches sitting in a repo somewhere. If AcmeCRM adds a deals resource next month, you add another key under resources and it's live.

Creating the Integration via UI or API

Two ways to load this config into Truto:

Via the UI: Open the Integrations page, click New Integration, paste the JSON, hit Save. The record lands in the database, and the runtime picks it up on the next request. There is no build step between saving and serving traffic.

Via the API: POST it directly.

curl -X POST https://api.truto.one/integration \
  -H "Authorization: Bearer $TRUTO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d @acmecrm.json

Response:

{
  "id": "int_9x8y7z",
  "name": "acmecrm",
  "category": "crm",
  "version": 1,
  "config": { "...": "..." }
}

At this point, your customers can connect their AcmeCRM accounts through the standard OAuth flow. Truto handles the redirect, PKCE challenge, code exchange, and token storage automatically because the credentials.format told it exactly how the provider's OAuth works.

Adding the Unified Mapping

The integration config tells Truto how to talk to AcmeCRM. To expose it through the unified CRM API, you add a mapping row. Here's the JSONata response mapping for list contacts:

response_mapping: >-
  response.{
    "id": id,
    "first_name": first_name,
    "last_name": last_name,
    "name": first_name & " " & last_name,
    "email_addresses": [{ "email": email, "is_primary": true }],
    "phone_numbers": phone ? [{ "number": phone, "type": "phone" }],
    "created_at": created_at,
    "updated_at": updated_at
  }

Save this via the mapping API or the UI, and the standard GET /unified/crm/contacts endpoint works for AcmeCRM the same way it works for Salesforce or HubSpot. Every improvement you eventually make to the CRM unified model applies here for free.

Test Requests and Expected Responses

Now the fun part - hitting your fresh integration.

Request:

curl "https://api.truto.one/unified/crm/contacts?integrated_account_id=ia_abc123&limit=2" \
  -H "Authorization: Bearer $TRUTO_API_TOKEN"

Under the hood, Truto:

  1. Loads the integration config and the integrated account credentials
  2. Refreshes the OAuth token if it's within 30 seconds of expiring
  3. Constructs GET https://api.acmecrm.com/v1/contacts?cursor=... with an Authorization: Bearer <access_token> header
  4. Evaluates the JSONata response mapping against each returned item

AcmeCRM's raw response:

{
  "data": [
    { "id": "c_1", "first_name": "Ada", "last_name": "Lovelace", "email": "ada@example.com", "phone": "+1-555-0101", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-06-20T14:15:00Z" },
    { "id": "c_2", "first_name": "Alan", "last_name": "Turing", "email": "alan@example.com", "phone": null, "created_at": "2024-02-01T09:00:00Z", "updated_at": "2024-06-19T11:00:00Z" }
  ],
  "meta": { "next_cursor": "eyJvZmZzZXQiOjJ9" }
}

What your app receives (unified format):

{
  "result": [
    {
      "id": "c_1",
      "first_name": "Ada",
      "last_name": "Lovelace",
      "name": "Ada Lovelace",
      "email_addresses": [{ "email": "ada@example.com", "is_primary": true }],
      "phone_numbers": [{ "number": "+1-555-0101", "type": "phone" }],
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-06-20T14:15:00Z",
      "remote_data": { "...": "original AcmeCRM object preserved here" }
    },
    { "id": "c_2", "first_name": "Alan", "...": "..." }
  ],
  "next_cursor": "eyJvZmZzZXQiOjJ9",
  "result_count": 2
}

Same shape as every other CRM integration in Truto. The caller has no idea AcmeCRM even exists, and the raw provider object is always preserved as remote_data for anything the unified schema doesn't cover.

Editing Config to Patch a Breaking API Change

Here's the scenario every integration engineer dreads: AcmeCRM ships a breaking change. first_name and last_name are now nested inside a profile object. In a code-based platform, this means:

  1. Detect the change (usually via a 500 error alert at 2am)
  2. Write new adapter code
  3. Merge, test, deploy
  4. Wait for the release window

In Truto, you edit the JSONata expression. Here's the patch:

response_mapping: >-
  response.{
    "id": id,
    "first_name": profile.first_name,
    "last_name": profile.last_name,
    "name": profile.first_name & " " & profile.last_name,
    "email_addresses": [{ "email": email, "is_primary": true }],
    "phone_numbers": phone ? [{ "number": phone, "type": "phone" }],
    "created_at": created_at,
    "updated_at": updated_at
  }

Or via API:

curl -X PATCH https://api.truto.one/unified-model-resource-method/umrm_xyz \
  -H "Authorization: Bearer $TRUTO_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "config": { "response_mapping": "response.{ \"first_name\": profile.first_name, ... }" } }'

Save. Done. The next /unified/crm/contacts request uses the new mapping - no deploy, no downtime, no cache to bust. And if only a single enterprise customer's tenant is affected, you can apply the fix as an account-level override so the rest of your users continue on the base mapping unchanged.

How Truto Handles OAuth, Pagination, and Rate Limits in the Example

Look back at the AcmeCRM config and notice what you didn't write. All of the following are handled by the generic engine reading declarative config:

OAuth: The credentials.format: "oauth2" block tells Truto's runtime to handle the authorization code flow, PKCE, and token exchange. Refresh happens automatically before every request when the access token is within 30 seconds of expiring, and Truto also schedules a proactive refresh 60-180 seconds before expiry so long-running syncs never hit an expired token. A failed refresh flips the account to needs_reauth and fires an integrated_account:authentication_error webhook so you can prompt the user to reconnect.

Pagination: The pagination.format: "cursor" block declares how AcmeCRM paginates. Truto reads meta.next_cursor from responses, echoes it back as ?cursor=... on the next request, and surfaces a normalized next_cursor value in the unified response. Switching to page-based, offset, or Link-header pagination is a one-line config change - the runtime supports six formats out of the box.

Rate limits: The rate_limit block uses JSONata expressions to detect 429s and extract retry-after values from AcmeCRM's proprietary headers. Truto normalizes whatever the provider sends into standard ratelimit-limit, ratelimit-remaining, and ratelimit-reset response headers back to your app. Every integration surfaces rate limits the same way, so your client code doesn't branch on the provider.

Mapping: JSONata handles the translation in both directions - unified query params translate into AcmeCRM's format on the way in, and AcmeCRM's response translates into the unified schema on the way out. Both are strings in a database column. Both are editable at runtime.

Key Takeaways

  • Adding an integration is a data operation. You write a JSON config plus JSONata mappings, then POST them to Truto or paste them into the UI. That's the entire process.
  • No deployments, ever. New integrations, mapping tweaks, and API version patches are live the moment you hit Save. There is no build pipeline between you and production.
  • One runtime, every integration. Bug fixes and improvements to pagination, auth, and error handling apply to all 100+ integrations at once.
  • Three-level overrides. Patch breaking changes platform-wide, environment-scoped, or per-account without touching the base mapping.
  • OAuth, pagination, and rate limits are declarative. You describe the API's behavior in config; the engine executes it.

If you've been burned by "no-code" API integration platforms that turned out to be no-code until you hit an edge case, this is the difference: Truto's config surface is expressive enough to model any HTTP API, because JSONata is Turing-complete and the schema covers every dimension of authentication, transport, and transformation.

Look Ma, No Maintenance Burden

This architecture cascades benefits into every part of the platform:

  • Reliability: When we improve the pagination engine, every integration gets better instantly. There is no "legacy code" rotting in an old integration adapter.
  • MCP Support: Because our integrations are defined as structured data, we auto-generate MCP (Model Context Protocol) tool definitions. Every integration is immediately AI-ready.
  • Speed: We can add new integrations in hours, not weeks.

We didn't just build a unified API. We built a programmable integration engine. The difference isn't just academic—it's the difference between a tool that accelerates you and a tool that eventually blocks you.

FAQ

How does Truto's Interpreter Pattern differ from the Adapter Pattern used by platforms like Merge?
Platforms like Merge use the Adapter Pattern, maintaining separate codebases for each integration, which creates a "black box" you can't modify. Truto uses the Interpreter Pattern, where a generic engine executes integrations defined entirely as data. This allows for instant updates, infinite extensibility, and the ability to patch or override any integration logic without waiting for a vendor deployment.
What does "zero integration-specific code" mean for my engineering team?
It means you never have to wait for a code deployment to fix an integration bug or map a new field. Because Truto defines integrations as data (JSON/JSONata) rather than code, changes are instant. You can override mappings in your environment or for specific accounts immediately, eliminating the maintenance burden typical of unified APIs.
Can I customize integrations for specific customers?
Yes. Truto’s architecture supports a 3-level override hierarchy (Platform, Environment, Account). You can patch the JSONata mapping for a single customer’s Salesforce integration to handle custom fields or unique logic, without affecting any other customer or requiring a code change.
How does Truto handle custom fields?
Truto supports a 3-level override hierarchy. You can map custom fields globally for your app, or even for a specific connected account, by simply patching the JSONata mapping configuration.
Do I need to write code to add an integration?
No. Adding an integration in Truto is a configuration task, not a coding one. You define the API structure and data mappings in JSON, and our generic engine handles the execution.
What is JSONata used for in Truto?
JSONata is a declarative transformation language we use to map data between your unified schema and third-party APIs. It handles complex logic like filtering, conditional mapping, and data reshaping without imperative code.

More from our Blog

What is a Unified API?
Engineering

What is a Unified API?

Learn how a unified API normalizes data across SaaS platforms, abstracts away authentication, and accelerates your product's integration roadmap.

Uday Gajavalli Uday Gajavalli · · 20 min read