API Contracts

By the Syncfusion DevTutorials team · Last updated

Stop the agent from inventing public names. Declare the interface other code already expects, in a code block in spec.md, and watch a consumer test compile against it.

The Agent Picks a Name

Welcome to Part 2.

Until now, the agent built new features on its own.

Now it works inside a real project, where other code already exists.

That other code already calls the new feature by a fixed name:

  • In the React app, the toolbar already calls exportOrders.
  • In the ASP.NET Core app, the nightly tool already calls ExportAsync.

The agent does not know these names.

If the spec does not tell it, it makes up its own.

Simple lesson: if other code already uses a name, write that name in the spec.

A Real-World Example

A wall socket is already in the wall. Its shape cannot change.

A lamp maker builds a great lamp, but never looks at the socket. The plug does not fit.

The socket is the code that already exists. The plug is the new feature. The agent is the lamp maker.

The agent names the API

Click Run it and open the Agent tab.

  • Agent tab: the agent makes up a name, like exportToXlsx or an ExportController.
  • Preview & Checks: the old code still calls exportOrders or ExportAsync, so the caller test fails.

Simple lesson: if the spec does not name the public parts, the agent picks its own names. The code that already exists may need different ones.

TL;DR

  1. API = the part of your code that other code calls.
  2. An API has a name, its inputs, and what it gives back.
  3. Caller (or consumer) = code that already uses the API.
  4. Contract = the API written down in the spec, before any code is built.
  5. Put the contract in an Interfaces section of spec.md, as a code block.
  6. When a caller already exists, copy the names and types from it.
  7. Caller test = a check that the caller still works with what the agent built.
  8. A wrong name, a wrong input, or a wrong return type breaks the caller.

What You Will Learn

In this lesson, you will learn:

  1. What an API and a contract are.
  2. Why public names must be chosen with care.
  3. How to write a contract for a function and its types.
  4. How a caller test checks a contract.
  5. Why the agent builds a wrong contract exactly as written.

About the caller test in this course

  • It is a small, fixed checker. It is not a real compiler and not an AI.
  • It compares your contract with the caller code.
  • It tells you what does not match, like a missing field or a wrong return type.

What a Contract Is

A contract describes the shape both sides agree on before anyone builds it.

APIs and Callers

API = how one piece of code talks to another.

For a function, the API is:

  • the function name
  • the names and types of its inputs
  • the type it gives back

Caller = the code that uses the API.

In this lesson, the caller already exists. It was written before the export feature.

React (src/components/Toolbar.tsx):

const blob: Blob = await exportOrders(visibleRows, {
  columns: visibleColumns,
  dateFormat: "YYYY-MM-DD",
});

ASP.NET Core (tools/NightlyClient/ExportJob.cs):

Stream file = await exporter.ExportAsync(
    new ExportRequest(Status: "open", From: null, To: null),
    ct);

The caller already says what it needs.

The contract must match it.

Why a Made-Up Name Costs So Much

You can change private code whenever you like. Nobody else uses it.

A public name is different. Other code depends on it.

Imagine the agent ships exportToXlsx, but every caller uses exportOrders.

Now you have only bad choices:

  • rename every caller
  • keep both names forever
  • or break the callers

In simple words: a public name is a promise. Write that promise in the spec on purpose.

Watch the agent with no contract, and then with one.

With no contract, the agent picks every name itself. With a contract, every part matches and the caller compiles.

Simple lesson: one naming choice can break every caller in the project.

Write the Contract

Put It in a Code Block

The contract goes in spec.md, under a section called:

## Interfaces

Write it as a code block, in the language of your project.

React example:

export function exportOrders(
  rows: Order[],
  options: ExportOptions
): Promise<Blob>;

export interface ExportOptions {
  columns: string[];
  dateFormat: "YYYY-MM-DD";
}

ASP.NET Core example:

public record ExportRequest(
    string? Status,
    DateOnly? From,
    DateOnly? To
);

public interface IOrderExporter
{
    Task<Stream> ExportAsync(
        ExportRequest request,
        CancellationToken ct
    );
}

A contract only shows the shape of the API.

It does not say how the code works inside. The agent still writes that part.

Copy From the Caller

When a caller already exists, do not design a new API.

Read the contract straight from the caller code.

Look for:

  • the function name
  • the order of the inputs
  • the type of each input
  • the option fields
  • the return type

For the React caller, that gives:

  • Function: exportOrders
  • Inputs: rows, options
  • Option fields: columns, dateFormat
  • Return type: Promise<Blob>

The caller uses await, so the function must return a Promise.

Watch the contract being copied from the caller, one piece at a time.

Each piece fixes one card. When every piece matches, the caller compiles.

Simple lesson: the caller already holds most of the contract. Copy it, do not invent it.

Lab Exercise: Declare the Contract

The note shows caller code that already exists.

Your task: add an Interfaces section and write the contract that matches it.

Declare the contract

The Caller Code Changes With the Variant

The caller code depends on the stakeholder variant you pick in Settings.

  • Variant A: the caller shown above.
  • Variant B: the caller passes one more option.
  • Variant C: the caller expects a different return type.

Your contract must match the variant on your screen.

If you copy a contract from another variant, the caller breaks.

On React, there is also a Type-check with TypeScript button. It runs the real TypeScript compiler, but only for reference. Your grade still comes from the contract checks.

Press Check my spec and open Preview & Checks.

A failed check names the exact problem. For example:

  • a wrong function name
  • a missing field
  • a wrong return type
  • the wrong number of inputs

Where to start

  • Check the function name and the return type first. Most caller errors come from these two.
  • Then check the inputs and the option fields.

Right Name, Wrong Type

The Agent Follows the Contract Exactly

A contract stops the agent from guessing.

But it does not check itself.

If the contract has a mistake, the agent builds that mistake exactly as written.

Right name, wrong type

Click Run it.

  • Agent tab: the agent shows FOLLOWS for the contract. Everything looks right.
  • Preview & Checks: the contract says void, but the caller expects Promise<Blob>. The caller fails.

The agent followed the contract perfectly. The contract itself was wrong.

This is a spec that is clear, but not correct.

Watch the same contract with the wrong return type, then with the right one.

The name and the inputs match. One wrong return type is enough to break the caller.

Fix the contract, not the caller.

  • When the caller test fails, change your contract.
  • Other teams already depend on the caller code.

Optional or Required

Adding a new field can also break callers.

Suppose you add this to the options:

sheetName: string

The callers that already exist do not pass it.

So every one of them breaks at once.

Instead, you can:

  • make the new field optional
  • or leave it out

TypeScript:

sheetName?: string;

C#:

string? SheetName

Simple lesson: a new field should be optional, unless every caller can pass it.

What Changes per Tool

The contract is a code block inside a Markdown file.

Each tool keeps it in a different place.

ToolWhere the contract lives
Claude Code, Codex, Cursordocs/specs/<feature>/spec.md under Interfaces
Spec KitContracts generated during planning and stored beside plan.md
KiroInterfaces section inside .kiro/specs/<feature>/design.md

If many features share one public API, you can also describe it in the repo docs. Then every feature uses the same contract.

Common Mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
No contract"The agent will pick a good name."Write the name in the spec.It ships exportToXlsx, and nobody calls it.
Words instead of code"Export takes rows and options."Use a code block with types.The agent guesses the types.
Wrong return type"It gives back the file somehow."Copy what the caller does with the result.The caller cannot use the result.
Fixing the caller"I will just update the toolbar."The caller stays the same. Fix the contract.Another team's code changes for your feature.
A new required field"Every export needs a sheet name."Make it optional, or leave it out.Every caller that already exists breaks.
Designing from scratch"Let me invent a clean API."Copy the contract from the caller.The clean API fits nothing.

Exercises

These exercises help you practise reading and writing contracts.

Press Check when you are ready. Use Show Hint if you get stuck.

What to Remember

  • An API is how code talks to other code.
  • A contract writes the API down before any code is built.
  • The contract goes in the Interfaces section of spec.md.
  • When a caller exists, copy the contract from it.
  • A public name is a promise that is hard to change later.
  • A caller test checks that the caller still works.
  • The agent builds the contract exactly as written, even when it is wrong.
  • A new field should be optional, unless every caller can pass it.

Further Reading

Next: Constraints and Trade-offs. You will learn how to keep the agent inside your team's rules and limits.