# Nettipoika agent quick start (HTTP API v1)

- Environment: `prod`
- Release: `rel-20261008-r4`
- Source commit: `a3e1a73b8b8dd3873017331b9f3f5d9747d5905a`
- Canonical installation discovery: `https://sivusto.nettipoika.fi/llms.txt`
- Product discovery index: `https://nettipoika.fi/llms.txt`

## Connect safely

1. Read `GET https://sivusto.nettipoika.fi/api/v1/capabilities` and `GET https://sivusto.nettipoika.fi/openapi.json`.
2. Use the customer dashboard's copyable agent instructions for the intended site. Those instructions identify the exact hostname and `site_id` without containing a bearer token.
3. Generate independent random poll and continuation verifiers locally. Send only their SHA-256 challenges when creating the access request described by `https://sivusto.nettipoika.fi/payment/openapi.json`.
4. Show the human only the returned approval URL. Wait for approval of the exact site and scopes, then poll at the response's instructed interval.
5. Wait for `access.state=installed`, then follow [OAuth setup](https://sivusto.nettipoika.fi/agents/v1/oauth-setup.md) with the discovered registered `client_id` and the exact approved scope set. Stop on OAuth errors; never accept a broader fallback. Connect an OAuth-capable MCP client to `https://sivusto.nettipoika.fi/mcp`. Discover authorization through `https://sivusto.nettipoika.fi/.well-known/oauth-protected-resource/mcp`.
6. Let the OAuth client renew the maximum-15-minute access token using its protected rotating refresh credential. If renewal fails or the connection is revoked, stop and request fresh human authorization.

Never ask the human to paste credentials into chat. Keep bearer tokens, refresh credentials, private keys and verifiers in protected client storage and send access tokens only in the `Authorization` header.

## Operate the authorized websites

A connection reaches one website, or every website of the account when the
person approved that (`account:sites`). `GET /api/v1/sites` (`list_sites`)
returns the websites this connection may manage. With several websites, pass
the `site_id` of the website the person means on every site call; a call
without it returns `site_required` with the candidates, so ask the person
instead of guessing. If the selection question is skipped or unanswered,
wait for an explicit website before editing. Every site response and tool result names the website.

An account-wide connection can create a website with `POST /api/v1/sites`
(`create_site`) and an `Idempotency-Key`; poll the returned provisioning
operation, then edit the new `site_id`. Websites share the account's CPU,
memory and storage (`GET /api/v1/account/usage`, `get_account_usage`); a
refusal returns `account_limit` with a `reason`. To remove a website, call
`POST /api/v1/sites/{site_id}/deletion-requests` (`request_site_deletion`),
show the person the returned `confirm_url` and end your turn: only the owner
can confirm on the dashboard. A deleted website's data still counts against
the account's storage (`retained_disk_bytes`) until it is purged after 30 days
or the owner deletes it permanently on the dashboard. Each website also needs
one of the account's database connections, so `pool.database_connections`
bounds how many websites the account can have.

The HTTP API is canonical; MCP exposes the same bounded capabilities. Use the returned site hostname instead of constructing one from the product origin. Reads require the documented scope. Every mutation requires an `Idempotency-Key`, and revision-aware file changes also require the current base revision and ETags.

Long-running work returns `202 Accepted` and a pollable operation. A candidate is not ready until its health check and public HTTPS verification succeed. Code rollback selects a retained compatible release and does not roll database contents back. Human approval remains mandatory for payment, sensitive scope grants, recovery and destructive database actions.

Use a conforming OAuth-capable MCP client; customers do not select protocol
versions. The same endpoint supports `2026-07-28`,
`2025-11-25` and `2025-06-18` with the same tools and authorization.

Raw clients using `2026-07-28` send stateless requests:
include `io.modelcontextprotocol/protocolVersion` and
`io.modelcontextprotocol/clientCapabilities` in `params._meta`, and send matching
`MCP-Protocol-Version` and `Mcp-Method` headers. For `tools/call`, also send
`Mcp-Name` matching `params.name`. `server/discover` optionally supplies supported
versions, capabilities and instructions; initialization is not required. Legacy
clients use `initialize`, then send the negotiated `MCP-Protocol-Version` on later
requests. Authentication and scope failures require the normal authorization
flow, never a broader scope or protocol fallback.

For a small change, call `GET /api/v1/sites/{site_id}/edit-context` (or
`get_edit_context`) to read the served source. Then send one
`POST /api/v1/sites/{site_id}/updates` (or `update_site`) with its
`base_source_commit`, a new idempotency key, and a targeted text replacement,
new file, or ETag-bound full-file write. Replacement text must occur exactly
once. The response has a `change_id` and waits briefly for public readiness;
poll only if the operation remains pending. Static and Django use verified
live saving; Go builds an immutable candidate.

For an image or document, upload original bytes through
`POST /api/v1/sites/{site_id}/assets` with `X-Asset-Name` and an
`Idempotency-Key`. Send `X-Asset-SHA256` when the original file's checksum is
available; a mismatch is rejected before storage. HTTP accepts at most 25 MiB
per file. For MCP `upload_asset`, compute `expected_sha256` from the original
attachment and send it with at most 1 MiB of base64; a mismatch is rejected.
The response contains a SHA-256 checksum
and an `/assets/{asset_id}/{name}` path. Include the asset ID in `asset_ids`
and the path in the update's new source text. The public path becomes available
only after that update succeeds. Do not assume a chat attachment reaches a
connector tool unless that exact client supplies its original bytes.
