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

# LLM agents

> Give an agent the contract, context, and write rules it needs.

Use the OpenAPI document as the machine contract. Use the guides to explain the product decisions that schemas cannot express.

The published site supplies `/llms.txt` and `/llms-full.txt`. Give the agent only the pages that its task needs.

Read [Integration workflows](./integration-workflows) before the agent selects an operation.

## Give the agent the right context

Provide the environment base URL, the operation goal, and only the relevant OpenAPI operations.

Give the agent access to the client secret through a secret tool. Do not put the secret in the prompt.

Keep test agents on `https://sandbox.api.bahnexpress.fi`. Do not let a test agent select the production base URL.

## Use one write loop

For each write, the agent must follow this sequence:

1. Read the current resource when the action depends on current state.
2. Check the required scope, `allowed_actions`, and preconditions.
3. Prepare the request from the OpenAPI schema.
4. Create one idempotency key for the logical write.
5. Send the request and store the response with its request ID.
6. Stop or retry from the stable problem code.

Reuse an idempotency key only after a timeout or server error for the same request body.

An update or cancellation also needs the current `revision` in `If-Match`.

## Build valid time input

Send RFC 3339 timestamps with an offset. Send the IANA time zone for each requested window.

Pickup needs `available_from` or one requested window. Windows for one stop must not overlap.

## Prevent time and state mistakes

| Fact             | Agent rule                                                                                 |
| ---------------- | ------------------------------------------------------------------------------------------ |
| Requested window | Treat it as a customer request, not an ETA or commitment.                                  |
| Agreed window    | Treat it as a target range, not an ETA.                                                    |
| ETA              | Use the returned timestamp. Do not calculate one.                                          |
| Actual time      | Prefer it after the stop completes.                                                        |
| Order status     | Do not use it as pickup approach progress.                                                 |
| Pickup progress  | Do not infer `nearby` or `arrived` from a position.                                        |
| Position         | Use the point and show its `observed_at` value. Do not create a point from an area center. |

## Handle prices

Use an `available` price check result as the current price. It is not a reservation or guarantee.

Treat `custom_quote_required` as no automatic price. Do not promise that Bahn will provide a quote.

Read the accepted price from the created order.

## Handle webhooks

Verify the signature against the raw body before you trust the event.

Deduplicate by event `id`. Use `data.order_sequence` only to order events for one order.

Read `data.resource_url` when you need current state. Resolve the path against the origin in `source`.

Do not expect an additional order status event after an item transition or cancellation.

## Handle ordering channels

Send `ordered_for` for each ordering-channel price check and order. Use the official business ID and country code.

Omit `ordered_for` for a direct customer credential. Store the returned value with the Bahn order ID.

Use webhook `data.ordered_for` to select the correct customer.

## Stop on permanent errors

Stop after a validation, authentication, permission, or business-rule error. Continue only after the request or authorization changes.

After `revision_conflict`, read the order and make a new decision. After `idempotency_conflict`, use the original body or start a new logical write.
