# For AI agents

This page is written for coding agents building a Made Card partner integration. The machine-readable sources are:

- `/partners/openapi.json`: the full partner API as OpenAPI 3.1
- `/partners/llms.txt`: an index of these docs in llms.txt format
- `/partners/llms-full.txt`: every guide in one markdown file
- `/partners/docs/<page>.md`: any single guide as raw markdown

## The integration in ten lines

1. Server: `POST /v1/partners/auth` with `client_id` and `client_secret`, cache `data.access_token` as the partner JWT.
2. Server: send `Authorization: Bearer <partner JWT>` on every other call.
3. Server: `POST /v1/partners/link/sessions` with `kind: "apply"` and the customer's `prefill`, return `data.link_url` to the page.
4. Browser: load `/sdk/v1/made-link.js`, build `MadeLink.create({ linkUrl, onSuccess, onExit })` as soon as the page has `link_url`, and call `handler.open()` inside the click handler.
5. Browser: in `onSuccess(publicToken)`, send `publicToken` to your server. Nothing else.
6. Server: `POST /v1/partners/link/token` with `public_token`, store `data.access_token` (`link_...`) and `data.user_id`.
7. Server, later visits: `POST /v1/partners/sessions/launch` with that `access_token` and a `target_path`, then open `data.launch_url`.
8. Server: read data with `GET /v1/partners/customers/{user_id}` and its `/transactions`, `/payments`, `/rewards`.
9. On `PRTN_0010`, start a `kind: "login"` session to reconnect that customer.
10. On any `AUTH_001x`, get a new partner JWT and retry once.

## Rules

- Never send `client_secret`, the partner JWT, or a `link_...` token to a browser or mobile app.
- The customer `access_token` goes in the JSON body. It is never an `Authorization` header.
- Use `kind: "apply"` for new customers. Use `kind: "login"` only to reconnect. Returning customers get a launch URL, not a new session.
- Create the link session before the click and call `open()` synchronously in the click handler. Otherwise browsers block the window and `onExit` receives `POPUP_BLOCKED`.
- The page that opens Made Link must be on one of the partner's allowed origins (see Apply and link). Otherwise `onExit` receives `ORIGIN_NOT_ALLOWED` and `onSuccess` never fires.
- Handle every `onExit` code: `POPUP_BLOCKED`, `USER_CLOSED`, `ORIGIN_NOT_ALLOWED`, `IDENTITY_MISMATCH`, `SESSION_EXPIRED`. `onExit(null)` means the customer left from inside the Made window.
- For `kind: "apply"`, `onSuccess` fires once the customer has a Made login. The Made window stays open while they finish; check the application with `GET /v1/partners/customers/{user_id}`.
- A launch URL opens `target_path` only once `account_id` is set. Before that it opens the customer's application.
- A working reference integration runs at https://madepartner.com (Northstar Home Loans, demo, on staging).
- `public_token` and `launch_url` are single use. Do not store or reuse them.
- Branch on the `error` code, not the message.
- Nothing in this API moves money or changes an account. Do not look for a payment endpoint.

## Prompt you can paste

```text
Integrate my app with the Made Card Partners API.
Spec: https://staging-api.getmcard.com/partners/openapi.json
Guide: https://staging-api.getmcard.com/partners/llms-full.txt
Follow the "For AI agents" rules exactly. Keep client_secret, the partner JWT,
and every link_ token on the server. Build: a server route that creates a link
session, a page that loads Made Link and opens it from a button, a server route
that exchanges the public_token and stores the access_token, and a route that
returns a launch URL.
Use environment variables MADE_API_BASE, MADE_CLIENT_ID, MADE_CLIENT_SECRET.
```

## Minimal Node server

```js
import express from "express";

const API = process.env.MADE_API_BASE; // https://staging-api.getmcard.com/v1
const app = express();
app.use(express.json());

let jwt = null;
let jwtExpiresAt = 0;

async function partnerJwt() {
  if (jwt && Date.now() < jwtExpiresAt - 60_000) return jwt;
  const res = await fetch(`${API}/partners/auth`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ client_id: process.env.MADE_CLIENT_ID, client_secret: process.env.MADE_CLIENT_SECRET }),
  });
  const { data } = await res.json();
  jwt = data.access_token;
  jwtExpiresAt = Date.now() + data.expires_in * 1000;
  return jwt;
}

async function made(path, body) {
  const res = await fetch(`${API}${path}`, {
    method: "POST",
    headers: { Authorization: `Bearer ${await partnerJwt()}`, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  const payload = await res.json();
  if (!res.ok) throw Object.assign(new Error(payload.message), { code: payload.error, status: res.status });
  return payload.data;
}

app.post("/made/link-session", async (req, res) => {
  const session = await made("/partners/link/sessions", { kind: "apply", prefill: req.body.prefill ?? {} });
  res.json({ link_url: session.link_url });
});

app.post("/made/exchange", async (req, res) => {
  const link = await made("/partners/link/token", { public_token: req.body.public_token });
  // Save link.access_token and link.user_id with your customer record here.
  res.json({ connected: true });
});

app.post("/made/launch", async (req, res) => {
  const accessToken = "link_..."; // Load the stored token for the signed-in customer.
  const launch = await made("/partners/sessions/launch", { access_token: accessToken, target_path: "/dashboard/home" });
  res.json({ launch_url: launch.launch_url });
});

app.listen(3000);
```
