# Horizon Verify: first-customer integration

## Who this is for

An engineering team operating a support agent that can approve a refund,
issue it, and close a support ticket. A workflow owner must define approval
rules, allowed amount, exact payment and ticket records, and independent
read permissions. Start with the customer's sandbox/test system.

## Set up

1. Complete the free engine checks at https://horizon-orchestra.com/setup.
2. Arrange a pilot and receive separate administration and runtime API keys.
   Managed activation is unavailable until Horizon's customer database is
   configured. The page reports availability; the public demo needs no key.
3. On your application server, set `HORIZON_API_KEY` to the runtime key through
   your secret manager. Do not paste it into source code or a browser bundle.
4. Download these files:

```sh
curl -fsSLO https://horizon-orchestra.com/horizon-verify.mjs
curl -fsSLO https://horizon-orchestra.com/customer-adapter.mjs
curl -fsSLO https://horizon-orchestra.com/refund-contract.json
```

5. Adapt the contract to your test record. Its default customer ID is a demo
   placeholder. Pin the specific customer/order/payment identity and amount
   as invariants. Assertions must identify the intended record, not only a
   generic `refunded: true`. Review these rules separately from the agent.
6. Call `verifyRefund()` from `customer-adapter.mjs` with your own callbacks:
   `readRefundState`, `approve`, `refund`, `closeTicket`, and `saveEvidence`.
   Each action receives `{idempotencyKey}`. Your downstream system must enforce
   the key; the SDK cannot add idempotency to a provider that lacks it.
7. `readRefundState()` must independently query the actual business record
   after a write, including a write that timed out. Return only the fields the
   contract needs. Never construct this state from an agent's claimed result.
8. `saveEvidence()` should store the full exported evidence in your own durable,
   access-controlled store for reconciliation. Hosted accounting stores hashes
   and usage only; it cannot restore a lost SDK process.

SDK calls go to `POST /api/v1/verify` when an API key is provided. Without a key,
the sample uses the stateless public `/api/verify` playground instead.

## Acceptance before real actions

Run these against the customer's own TEST systems and retain the evidence:

| Case | Required outcome |
| --- | --- |
| Normal approved refund | Correct payment refunded once; ticket closes afterward |
| Response lost after refund commits | Read-back confirms one refund; no repeat write |
| API reports success but no refund exists | Verification fails; ticket stays open |
| Refund before approval | SDK refuses before the refund callback runs |
| Refund requested twice | Verified step refuses a second execution; provider deduplicates retry key |
| Wrong customer, amount, or payment record | Contract fails and blocks later steps |
| Read-back unavailable | Workflow stops for reconciliation; no blind retry |
| Horizon unavailable after a write | Preserve evidence; verify the record before retrying the action |
| Allowance exhausted | New run refused during preflight, before a business write |

Have another team member complete the documented setup from a clean checkout.
Measure setup time and support interventions. Only activate real writes after
both Horizon and the customer owner review the evidence and agree to proceed.
The public fixtures cannot substitute for this customer acceptance.

## Usage and limits

An admitted run consumes one allowance unit, including failed or abandoned runs.
The SDK reserves it with a preflight before any writes. Invalid schema and
engine errors do not create a run. Each run includes 128 distinct checkpoints;
identical replay consumes neither another run nor another checkpoint. All
requests still obey the customer's request-per-minute limit. Do not change run
IDs merely to get past a failed workflow: reconcile the actual record first.

A run ID is bound to its contract and initial-state hashes within one customer.
Reusing it for different inputs returns HTTP 409. Different customers may use
identical run IDs. Use a fresh opaque ID for a genuinely new business operation.
An admitted run may continue for seven days after admission, including past
period end or quota exhaustion, subject to the key remaining active, rate
limits, and checkpoint budget. There is no automatic overage billing.

Starter: $99/month, 10,000 runs, 120 requests/minute.
Growth: $499/month, 100,000 runs, 600 requests/minute.
Pilot: $500 once, 30 days, 10,000 runs, 120 requests/minute, one scoped workflow
and two setup sessions. Pilot does not renew automatically. Monthly plans use
calendar-month periods; allowances do not roll over. Activation and renewal are
manual by agreement during launch. Approved pilots can pay through a private Stripe invitation after scope and setup
are agreed. Monthly subscriptions remain arranged separately.

128 checkpoints support the usual sequential SDK flow up to 32 steps. Long
retry sequences can exhaust that budget; stop and reconcile rather than
silently switching to an unmetered endpoint or bypassing checks.

## Account and key rotation

`GET /api/v1/account` with `Authorization: Bearer <runtime key>` returns only
that customer's plan, allowance, and period. There is no caller-selected tenant.

`POST /api/v1/keys` with the separate administration key revokes all current
runtime keys for that customer and returns a new runtime key once. Update your
secret manager immediately; old keys stop working. Other customers' keys remain
valid. Keep the administration key offline. Lost administration keys require
operator recovery after ownership verification; self-serve account recovery
is not implemented. Neither account nor key APIs accept browser cookies.

HTTP 401: invalid/revoked key; 402: no active service period; 409: conflicting
or expired run; 429: quota/rate/checkpoint limit; 503: service unavailable.
HTTP 200 still requires `report.passed` for final completion. An intermediate
report can include `missing_finish` while the SDK verifies a step.

## Boundaries

The Rust engine verifies supplied snapshots and ordering. It does not attest
the reader, run payment actions, provide durable orchestration, guarantee exactly
once across process restarts, or prove that no changes occurred between reads.
Business execution and independent read credentials remain in your environment.
Use a durable runner and provider transactions/idempotency for restart recovery.

Hosted accounting retains customer records, key hashes, period usage, and
run/checkpoint hashes until operator deletion. Raw submitted business evidence
is evaluated in a temporary file, deleted after evaluation, and is neither
persisted in the customer database nor logged by the application. Hosting
infrastructure still processes requests. Send only minimal, non-secret
projections. The beta does not promise an SLA or compliance certification.
