> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usefini.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API contract

> What your endpoints need to look like so Fini's agent can call them. A spec for the customer engineers building the APIs Fini wires into Attributes and Actions.

This page is for **customer engineers** building the APIs that Fini will call to read your data or take actions in your systems. Read this once before you start wiring endpoints, it covers the contract Fini expects, the auth pattern, and the request/response shapes that make your APIs easy to integrate.

You build the endpoints. Fini's team handles everything on the Fini side, naming them, mapping the response fields into agent-readable variables, and stitching them into Attributes (for reads) and Actions (for writes). You don't need to know about Fini's internal configuration. You just need to expose endpoints that match the contract below.

## What you need to build

For most integrations you'll expose two kinds of endpoints:

| Endpoint type | Method | When Fini calls it | Example |
| - | - | - | - |
| **Read** | `GET` | When the agent needs context about a user mid-conversation | Look up a customer's plan, account state, or recent orders |
| **Write / action** | `POST` or `PUT` | When the agent needs to perform an operation in your system | Cancel a subscription, update an address, escalate a ticket |

A single integration usually involves several of each. The cancellation flow, for example, uses one `GET` to look up the customer's account (via Attributes), then a `POST` to execute the cancellation (via Actions).

## The contract

Every endpoint Fini calls needs to satisfy four things:

1. **HTTPS only.** No HTTP fallback. Use a real TLS certificate; self-signed certs will fail.
2. **JSON in, JSON out.** Request bodies (for `POST`/`PUT`) and all responses must be `application/json`. No XML, no form-encoded.
3. **API key auth via header.** Every request from Fini carries an `x-api-key` header. Validate it on every call.
4. **Predictable response shapes.** Return a flat JSON object with stable field names. The fields you return become the variables the agent can reference; they need to be the same every time.

Latency target: respond in under two seconds for reads, under five seconds for writes. Slower endpoints will work but degrade the customer experience, the agent is mid-conversation and the customer is waiting.

## Authentication

Fini sends a single header on every request:

<CodeGroup>
  ```http HTTP theme={null}
  x-api-key: fini_example_a1b2c3d4e5f67890abcdef1234567890
  ```

  ```bash cURL theme={null}
  curl -H "x-api-key: fini_example_a1b2c3d4e5f67890abcdef1234567890" \
    https://api.yourcompany.com/...
  ```

  ```javascript Node.js theme={null}
  headers: {
    "x-api-key": "fini_example_a1b2c3d4e5f67890abcdef1234567890",
  }
  ```

  ```python Python theme={null}
  headers = {
      "x-api-key": "fini_example_a1b2c3d4e5f67890abcdef1234567890",
  }
  ```
</CodeGroup>

The key is issued by you to Fini, you decide the format. Rotate it the same way you'd rotate any API key; Fini can update its stored value on your timeline.

Validate the key on every request. Reject anything else with `401 Unauthorized`.

<Tip>
  If you have separate environments (sandbox, staging, production), issue separate keys for each. Fini's team will store them per-environment so the same agent can point at different backends as it moves through your release process.
</Tip>

<Warning>
  Never send the API key over email or chat. Use a secrets manager (1Password, Bitwarden, HashiCorp Vault) or your team's preferred secure channel. If a key leaks, rotate it immediately and notify your Fini contact so we can update our stored value.
</Warning>

## Sample GET endpoint

Use `GET` when the agent needs to **read** something from your system. The customer's identifier (usually email, sometimes a customer ID) comes in as a query parameter. The response is a flat JSON object with all the fields the agent might need.

### Query parameters

<ParamField query="email" type="string" required>
  The customer's identifier. Fini interpolates this from the conversation context when it makes the call. Use a customer ID instead if your system isn't email-keyed.
</ParamField>

<ParamField query="flow" type="string">
  Optional. If a single endpoint serves multiple lookup variants (phone-only details vs. full account details), use `flow` to route within one endpoint instead of building several.
</ParamField>

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.yourcompany.com/getUserDetails?flow=phone&email=customer@example.com" \
    -H "x-api-key: fini_example_a1b2c3d4e5f67890abcdef1234567890"
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.yourcompany.com/getUserDetails?flow=phone&email=customer@example.com",
    {
      method: "GET",
      headers: {
        "x-api-key": "fini_example_a1b2c3d4e5f67890abcdef1234567890",
      },
    }
  );
  const data = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      "https://api.yourcompany.com/getUserDetails",
      params={"flow": "phone", "email": "customer@example.com"},
      headers={"x-api-key": "fini_example_a1b2c3d4e5f67890abcdef1234567890"},
  )
  data = response.json()
  ```

  ```go Go theme={null}
  req, _ := http.NewRequest("GET",
      "https://api.yourcompany.com/getUserDetails?flow=phone&email=customer@example.com", nil)
  req.Header.Set("x-api-key", "fini_example_a1b2c3d4e5f67890abcdef1234567890")
  resp, err := http.DefaultClient.Do(req)
  ```
</CodeGroup>

<Tip>
  Path structure is your call, use whatever URL convention your service follows. Fini's team will template the dynamic parts when wiring this in.
</Tip>

### Response

Return `200 OK` with a flat JSON object containing every field the agent might want to reference:

```json theme={null}
{
  "customer_id": "cus_abc123",
  "plan": "Pro",
  "is_vip": false,
  "account_closed": false,
  "phone_number": "+1-555-0100",
  "zip_code": "94103",
  "month_of_signup": "2024-03",
  "subscription_renews_at": "2026-06-15"
}
```

<ResponseField name="customer_id" type="string">
  Your system's primary identifier for this customer. Useful for downstream Actions that need to write back.
</ResponseField>

<ResponseField name="plan" type="string">
  The customer's current plan or tier. Low-cardinality enum recommended (e.g., `Free`, `Pro`, `Enterprise`).
</ResponseField>

<ResponseField name="is_vip" type="boolean">
  Whether this customer qualifies for VIP treatment. The agent can gate behavior on this in Reply Rules and Rulebook.
</ResponseField>

<ResponseField name="account_closed" type="boolean">
  Whether the account is currently closed or suspended. Critical for the agent to know before attempting any writes.
</ResponseField>

<ResponseField name="phone_number" type="string">
  E.164-formatted phone number, or any consistent format your system uses.
</ResponseField>

<ResponseField name="zip_code" type="string">
  Postal code or ZIP. String, not number, leading zeros matter.
</ResponseField>

<ResponseField name="month_of_signup" type="string">
  ISO 8601 year-month, e.g., `2024-03`. Useful for tenure-based logic.
</ResponseField>

<ResponseField name="subscription_renews_at" type="string">
  ISO 8601 date the subscription next renews. Surface this in replies to set customer expectations.
</ResponseField>

Each field becomes a variable the agent can reference at runtime, in conditions (*"if `is_vip` is true…"*), in Reply templates (interpolating `plan` and `subscription_renews_at` into the agent's message), or as inputs to subsequent Actions.

Error responses should use the appropriate HTTP status code with a JSON body explaining what went wrong:

```json theme={null}
{
  "error": "user_not_found",
  "message": "No user matched the provided email."
}
```

| Status | When |
| - | - |
| `200 OK` | Lookup succeeded, body contains the requested fields. |
| `400 Bad Request` | Required parameter missing or malformed. |
| `401 Unauthorized` | Missing or invalid `x-api-key`. |
| `404 Not Found` | The customer exists in the request but not in your system. |
| `500 Internal Server Error` | Anything else. Fini's agent will catch this and fall back gracefully. |

## Sample POST / PUT endpoint

Use `POST` or `PUT` when the agent needs to **act** on your system, change state, trigger a workflow, write to a database. Follow REST conventions: `POST` to create something new, `PUT` to update or replace existing state, `DELETE` to remove. If you only pick one, `POST` is the safest default.

### Body parameters

<ParamField body="phone_number" type="string" required>
  The new phone number to set. Fini interpolates the value from the conversation context, typically extracted from the customer's own message or collected via a form.
</ParamField>

### Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT "https://api.yourcompany.com/updateUserDetails?flow=phone&email=customer@example.com" \
    -H "Content-Type: application/json" \
    -H "x-api-key: fini_example_a1b2c3d4e5f67890abcdef1234567890" \
    -d '{
      "phone_number": "+1-555-0100"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.yourcompany.com/updateUserDetails?flow=phone&email=customer@example.com",
    {
      method: "PUT",
      headers: {
        "Content-Type": "application/json",
        "x-api-key": "fini_example_a1b2c3d4e5f67890abcdef1234567890",
      },
      body: JSON.stringify({
        phone_number: "+1-555-0100",
      }),
    }
  );
  const data = await response.json();
  ```

  ```python Python theme={null}
  import requests

  response = requests.put(
      "https://api.yourcompany.com/updateUserDetails",
      params={"flow": "phone", "email": "customer@example.com"},
      headers={
          "Content-Type": "application/json",
          "x-api-key": "fini_example_a1b2c3d4e5f67890abcdef1234567890",
      },
      json={"phone_number": "+1-555-0100"},
  )
  data = response.json()
  ```

  ```go Go theme={null}
  body := strings.NewReader(`{"phone_number": "+1-555-0100"}`)
  req, _ := http.NewRequest("PUT",
      "https://api.yourcompany.com/updateUserDetails?flow=phone&email=customer@example.com", body)
  req.Header.Set("Content-Type", "application/json")
  req.Header.Set("x-api-key", "fini_example_a1b2c3d4e5f67890abcdef1234567890")
  resp, err := http.DefaultClient.Do(req)
  ```
</CodeGroup>

<Tip>
  The customer's identifier can travel either as a query parameter (matching the GET pattern) or in the JSON body. Pick one convention and stick with it across all your endpoints.
</Tip>

### Response

Return a flat JSON object with at minimum `success` and `message`:

```json theme={null}
{
  "success": true,
  "message": "Phone number updated."
}
```

<ResponseField name="success" type="boolean" required>
  Whether the operation completed successfully. Fini branches deterministically on this, the agent composes one reply path when `true`, another when `false`.
</ResponseField>

<ResponseField name="message" type="string" required>
  Human-readable description of what happened. The agent can pass this through to the customer directly, or use it to compose a richer reply.
</ResponseField>

<ResponseField name="error_code" type="string">
  Required on failure. Machine-readable code (e.g., `INVALID_PHONE_FORMAT`, `USER_NOT_FOUND`) so the agent can route on specific failure modes in Rulebook.
</ResponseField>

On failure, return the same shape with `success: false` and an `error_code`:

```json theme={null}
{
  "success": false,
  "message": "Phone number format is invalid.",
  "error_code": "INVALID_PHONE_FORMAT"
}
```

For actions that produce results worth surfacing back to the customer, a confirmation reference, an effective date, a refund amount, include those as additional top-level fields:

```json theme={null}
{
  "success": true,
  "message": "Cancellation confirmed.",
  "confirmation_id": "conf_4F2A",
  "effective_date": "2026-06-15",
  "refund_amount_cents": 4900
}
```

These extra fields get interpolated into the agent's reply at runtime: *"Your cancellation is confirmed (ref: conf\_4F2A). Your final bill date is 2026-06-15."*

Use `200 OK` for "the operation completed, here's the result" (even if `success` is `false`, a known business-rule failure isn't an HTTP error). Reserve `4xx`/`5xx` for protocol-level failures: bad input, missing auth, server crashes.

## Response shape guidance

A few patterns that make your APIs much easier for Fini to consume:

**Flat is better than nested.** Fini's mapping layer expects top-level keys. Don't bury values three levels deep in a nested object, flatten them at the response edge.

```json theme={null}
// Recommended
{ "customer_id": "abc", "plan": "Pro", "is_vip": false }

// Avoid, nested objects need extra unwrapping in Fini config
{ "data": { "user": { "id": "abc", "subscription": { "plan": "Pro" } } } }
```

**Keep field names stable across responses.** If your GET sometimes returns `phone_number` and sometimes `phoneNumber`, the agent will look up `phone_number` from configuration and miss the other. Pick `snake_case` or `camelCase` and use it everywhere.

**Include every field every time, even when empty.** Return `"refund_amount": null` rather than omitting the field. The agent has conditions like *"if refund\_amount is greater than 0..."*, a missing field is harder to reason about than a `null`.

**Use consistent types.** If `is_vip` is a boolean today, don't return the string `"true"` tomorrow. Boolean values stay boolean; numbers stay numbers; dates stay ISO 8601 strings.

**Pick stable, low-cardinality enums for status fields.** `"plan": "Pro"` is easier to gate on than `"plan": "Pro tier - $99/mo"`. Keep human-readable labels in a separate field if you need them.

## What Fini does with your responses

Once your endpoints are live and Fini's team has wired them in:

* **Read endpoints** are exposed as **Attributes**, every field in the GET response becomes a variable the agent can reference anywhere (Reply Behavior conditions, Rulebook checks, message templates).
* **Write endpoints** are exposed as **Actions**, Rulebook rules can invoke them mid-conversation, binding their outputs (like `confirmation_id` or `effective_date`) into the agent's reply.

For an end-to-end example of how Reads and Actions compose into a real workflow, see the [cancellation flow walkthrough](/en/walkthroughs/cancellation-flow).

## Handoff checklist

When your endpoints are ready, send your Fini contact the following:

<Steps>
  <Step title="Endpoint list">
    For each endpoint: method, full URL (with any query parameter conventions), and a one-line description of what it does.
  </Step>

  <Step title="Sample request and response">
    A `curl` example for each endpoint with a real (sandbox) response body. Fini's team uses these to configure the mappings on our side.
  </Step>

  <Step title="API key">
    Share the API key via a secure channel, see the Authentication section above for delivery guidance.
  </Step>

  <Step title="Environments">
    If you have sandbox / staging / production, list each environment's base URL and its corresponding API key.
  </Step>

  <Step title="Rate limits and SLAs">
    Tell us your endpoint's rate limit (requests per minute) and expected p95 latency. Fini's agent will respect these.
  </Step>
</Steps>

Once we have the above, wiring on Fini's side typically takes a day or two. Then your agent can read from and act on your live systems in production.

<Tip>
  If you're not sure whether something fits the contract, a non-standard auth scheme, a streaming response, a webhook-style callback rather than a synchronous reply, reach out before building. We've seen most patterns and can confirm what works before you invest engineering time.
</Tip>


## Related topics

- [API overview](/en/api-reference/overview.md)
- [List sources](/en/api-reference/list-sources.md)
- [Get source](/en/api-reference/get-source.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.