Skip to content

Integration  ·  Platform engineering

How to build an API facade

Four teams have each written their own Salesforce client, and a field rename breaks all four in different ways. The shape of a facade that absorbs it, the prompts that build it, and the production detail that decides whether it holds.

Built with Tray Headless

  1. System Consumers
  2. Step Verify caller
  3. Step Map to the contract
  4. Step Call upstream
  5. System Upstream systems
Also Cache

One contract in front of many systems. The facade owns the shape, so an upstream change stops at the boundary.

The short answer

What is an API facade?

Done well, an API facade is four things: one stable contract that consumers code against, mapping that absorbs upstream change rather than passing it through, the caller identity carried all the way to the source system, and explicit behaviour when an upstream is slow or down. Teams usually come unstuck on building a pass-through. A facade that forwards the upstream shape has moved the coupling one hop and solved nothing.

What matters here

  • The facade owns the shape. A pass-through moves the coupling one hop and changes nothing.
  • A renamed upstream field should stop at the facade. That is the entire value proposition.
  • Carry the caller identity through. A shared service account destroys the audit trail in every system behind it.
  • Version the contract and run versions in parallel. You cannot make every consumer change on the same day.
  • Decide what happens when an upstream is down, because the default is that your facade is down too.

Who this is for

You run platform or integration engineering. Several teams integrate with the same systems, each has its own client, and every upstream change turns into several separate incidents.

How it works in practice

The sequence, from a consumer calling the facade to getting an answer.

  1. 1

    The caller is authenticated and identified

    As themselves, not as the facade, because the identity has to reach the source.

  2. 2

    The request is mapped to the facade contract

    Which is designed around what consumers need, not around what an upstream returns.

  3. 3

    One or more upstreams are called

    Often several, which is the case that justifies a facade at all.

  4. 4

    Responses are mapped into the contract shape

    Absorbing field names, enums and quirks, so none of them reach the consumer.

  5. 5

    Failure is handled deliberately

    Partial data with a stated gap, a cached answer with its age, or a clear error.

  6. 6

    The call is logged with the caller and the upstreams

    So a question about who read what has an answer.

What the facade is made of

Four parts. The second is the one that decides whether this was worth building.

A contract consumers designed against

Shaped by what callers need. If it mirrors an upstream response, you have written a proxy and inherited every future change.

Absorbing mappings

A renamed field, a new enum value, a split object: all handled inside the facade, none visible outside it.

Carried identity

The caller reaching the source system as themselves. A shared service account makes every upstream audit log useless.

Defined degradation

What a consumer gets when an upstream is slow or down, decided in advance rather than discovered during an incident.

The Tray Headless prompts

Paste these into Claude Code or Codex with the Tray Headless plugin installed. Each stage runs on its own. The systems named in them are the worked example rather than a requirement, and every prompt says so.

Once per project, run /tray-workflows:set-workspace to pick the workspace these build in. Point it at a sandbox first.

  1. 1

    Begin by designing the contract from the consumer side

    A contract shaped like an upstream response is a proxy.

    Headless skills build-workflow tray-patterns

    Use build-workflow. The systems in play are Salesforce, NetSuite and
    Zendesk, or whatever we run in those seats.
    
    Design the facade contract from what consumers actually need, not from
    what any upstream returns. For each endpoint, write down the fields a
    caller uses and where each one comes from, which is frequently more than
    one system.
    
    A customer endpoint might carry the account from the CRM, the billing
    status from the ERP and the open ticket count from support. No upstream
    returns that object, and that composition is the reason the facade
    exists.
    
    Name fields in your own vocabulary. If the contract says
    Account_Status__c you have published an upstream implementation detail
    and you will be maintaining it for years.
  2. 2

    Map so upstream changes stop at the boundary

    This is the entire value proposition.

    Headless skills build-workflow

    Use build-workflow. Put an explicit mapping between every upstream field
    and every contract field, and handle:
    
      A renamed upstream field, which changes only the mapping
      A new enum value, which maps to a contract value or to a stated
      unknown rather than passing through raw
      A field that splits into two upstream, which the mapping recombines
      A field an upstream removes, which becomes null with a documented
      reason rather than a missing key
    
    Never spread an unmapped upstream object into the response. It works on
    the day you write it and turns the facade into a proxy the first time
    somebody adds a field upstream.
    
    Validate every response against the contract before returning it. A
    facade that returns a shape it did not promise is worse than no facade,
    because consumers have stopped defending themselves.

    Validate outbound as well as inbound. The contract is a promise, and the only way it stays true is if something checks it on the way out.

  3. 3

    Carry the caller identity through to the source

    A shared service account destroys every audit log behind it.

    Headless skills tray-gotchas

    Use tray-gotchas, then handle identity properly.
    
    Authenticate the caller at the facade, and carry that identity to the
    upstream call instead of replacing it with one shared account.
    
    Where an upstream supports acting on behalf of a user, use it. Where it
    does not, record the caller against the call in the facade audit log so
    the chain can be reconstructed even though the upstream cannot see it.
    
    Enforce authorisation at the facade too. A consumer allowed to read
    customers is not automatically allowed to read their contracts, and the
    facade is where that distinction is cheap to make.
    
    Every upstream log line reading integration-service tells you nothing
    about who asked. Once several teams are behind the facade, that is the
    single most expensive shortcut available here.
  4. 4

    Decide what happens when an upstream is down

    The default is that your facade is down too.

    Headless skills tray-patterns

    Define degradation per endpoint before this goes anywhere near
    production:
    
      Return partial data with an explicit statement of which part is
      missing, instead of failing the whole response
      Serve a cached answer with its age attached, so the consumer can decide
      whether it is good enough
      Fail clearly, with which upstream failed and whether retrying will help
    
    Set a timeout per upstream that is shorter than the consumer timeout, or
    a slow upstream becomes a timeout everywhere at once.
    
    Cache deliberately: a stated TTL per endpoint, and never for anything a
    caller might write immediately afterwards. A facade that quietly serves
    stale data is harder to debug than one that is simply slow.
    
    The facade holds no state beyond that cache. Once it owns data, it is a
    system instead of a facade, and it needs a completely different
    conversation about consistency.
  5. 5

    Version the contract, and run versions in parallel

    You cannot make every consumer change on the same day.

    Version the contract from the first release, in the path, and run at
    least two versions at once.
    
    A breaking change means a new version, not an edit. Additive changes
    belong in the current version. Removing a field, renaming one or changing
    a type is breaking even if you think nobody uses it, and the facade logs
    will tell you whether that is true.
    
    Track usage per consumer per version so a retirement is a conversation
    with three named teams, not an announcement into the dark.
    
    Report: calls per consumer per endpoint, upstream latency and error rates
    separately from facade ones, cache hit rates, and contract validation
    failures.
    
    Validation failures are the leading indicator. Each one is an upstream
    change that reached a consumer.
  6. 6

    Validate, then hand the mappings to platform engineering

    Because upstreams change and consumers keep arriving.

    Run the per-step schema checks and the whole-workflow audit before this
    touches production. Rename a field in a sandbox upstream and confirm the
    contract response is unchanged.
    
    Then open the same workflow in Tray Build so platform engineering can add
    endpoints, adjust mappings and change the degradation rules in the visual
    canvas.

What it connects to

The facade composes several systems into one contract, which is why it exists.

Salesforce

Read account and contact data as the calling user, so the upstream audit trail stays meaningful.

Reads

NetSuite

Read billing status and balances, which no CRM response carries and every consumer asks for.

Reads

Zendesk

Read open ticket counts and recent contact, composed into the same customer object.

Reads

Snowflake

Serve derived attributes that no operational system holds, without consumers needing warehouse access.

Reads

Datadog

Emit per-upstream latency and error rates separately from facade metrics, so blame is attributable.

Writes

Same build, other stacks

The design does not change if you run something else in one of these seats. The same prompts build it against Microsoft Dynamics 365, SAP S/4HANA, Google BigQuery, Jira, HubSpot or Oracle.

Named systems are the ones most teams run, not the only ones that work. Each is an authentication in your Tray workspace, referenced by name, so the workflow never holds a credential. Where we have a connector page, the name links to it.

Running it in production

Several teams will depend on this. A contract that moves is worse than no contract.

The platform runs it, not a laptop under a desk

Consumers call it continuously, so it needs the uptime, timeouts and retry behaviour of infrastructure, not of a script.

Every call is logged with its caller

Who asked, which upstreams answered, and how long each took. This is the audit trail that a shared service account would have thrown away.

No credential ever lands in the workflow itself

Upstream credentials held in your workspace and scoped per system, with the caller identity carried through rather than replaced.

Platform engineering own the mappings

Endpoints, field mappings, cache TTLs and degradation rules all open in Tray Build, changed without a deployment.

Contract validation runs on the way out

A facade that returns an unpromised shape is worse than none, because consumers have already stopped defending themselves.

Questions people ask

Why not just proxy the upstream API?

Because a pass-through moves the coupling one hop and solves nothing. When the upstream renames a field, every consumer still breaks; the only difference is that they blame you.

Why carry the caller identity through?

Because a shared service account makes every upstream audit log useless. Once several teams sit behind one facade, no system behind it can answer who read a record.

Should the facade cache?

With a stated TTL per endpoint, and never for data a caller might write immediately afterwards. Beyond a cache the facade should hold no state, because owning data makes it a system instead of a facade.

How should the contract be versioned?

In the path, with at least two versions running at once and usage tracked per consumer. Removing or renaming a field is breaking even when you believe nobody uses it.

What happens when an upstream is down?

Whatever you decided in advance: partial data with the gap named, a cached answer with its age, or a clear error. Deciding during an incident means the answer is that the facade is down too.

Further reading

Background on the same subject, for the case rather than the build.

Last reviewed September 2026.