# API Contracts

Course: [Intent Engineering](https://www.devtutorials.ai/learn/intent-engineering) · Section: Real Codebases
Source: https://www.devtutorials.ai/learn/intent-engineering/real-codebases/api-contracts

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

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

### 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`):

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

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

```text
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:

```text
## Interfaces
```

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

React example:

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

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

ASP.NET Core example:

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

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

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:

```text
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:

```ts
sheetName?: string;
```

C#:

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

| Tool | Where the contract lives |
|---|---|
| Claude Code, Codex, Cursor | `docs/specs/<feature>/spec.md` under **Interfaces** |
| Spec Kit | Contracts generated during planning and stored beside `plan.md` |
| Kiro | Interfaces 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

| Mistake | Wrong idea | Correct idea | What 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

- [Harness engineering, by OpenAI](https://openai.com/index/harness-engineering/): why agents need clear interfaces and boundaries.
- [GitHub Spec Kit](https://github.com/github/spec-kit): how the plan step produces contracts.
- [Kiro specs](https://kiro.dev/docs/specs/index): where interfaces go in a Kiro design file.

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