Shared Forest API and MCP developer portal

Shared Forest is a free, shared illustrated forest that grows from its traffic. Every page load adds growth energy, and every visitor, human or AI agent, can plant one tree per day.

This page is the reference for the Shared Forest REST API, the Shared Forest MCP servers and the in-page WebMCP tools. Reading and planting work without an account and without API keys. Sponsoring trees over MCP needs a Shared Forest account connected with OAuth 2.1.

Quickstart

  1. Read the live forest: GET /api/forest.
  2. Plant a tree for your user: POST /api/visit with {"plant": true}.
  3. Watch the tree appear on the live canvas within seconds.
curl -s "https://sharedforest.com/api/forest?limit=5"
curl -s -X POST https://sharedforest.com/api/visit -H 'Content-Type: application/json' -H "Idempotency-Key: $(uuidgen)" -d '{"plant":true}'
curl -s -X POST https://sharedforest.com/ask -H 'Content-Type: application/json' -d '{"query":"how many trees grow in the forest?"}'

Authentication

The REST read endpoints, POST /api/visit, /ask and the docs MCP server need no credentials. CORS allows every origin.

The product MCP server at https://sharedforest.com/mcp is an OAuth 2.1 protected resource. Its read tools stay anonymous. start_sponsorship needs scope sponsor:write; get_order needs account:read or sponsor:write; my_orders and my_trees need account:read. Without a token these tools answer HTTP 401 with WWW-Authenticate: Bearer resource_metadata="https://sharedforest.com/.well-known/oauth-protected-resource/mcp". MCP clients such as Claude, Cursor and VS Code then run the flow by themselves.

  1. Discover: protected resource metadata (RFC 9728) and authorization server metadata (RFC 8414).
  2. Register: POST /oauth/register (Dynamic Client Registration, RFC 7591). Redirect URIs: https, loopback http or a native app scheme.
  3. Authorize: send the user to /oauth/authorize with response_type=code, PKCE code_challenge_method=S256 (required, plain is rejected) and the exact redirect URI. The user logs in, checks your client name, redirect host and scopes, and sets a spending limit (max EUR per order and per day).
  4. Exchange: POST /oauth/token with grant_type=authorization_code and the code_verifier. Access tokens last 15 minutes. Refresh tokens rotate on every use.
  5. Call: Authorization: Bearer <access_token> on POST /mcp. Orders from start_sponsorship belong to the user's account. The user still confirms and pays each order on confirm_url.
  6. Revoke: POST /oauth/token with token=<refresh_token> (RFC 7009).

Step-by-step walkthrough with curl: auth.md.

Endpoints

Method and pathPurposeSide effects
GET /api/forestLive snapshot of trees, groves and traffic statsNone
POST /api/visitRecord a visit or plant a treeQueues one visit
GET /ask, POST /askNLWeb question endpointNone
POST /mcpProduct MCP server (JSON-RPC)plant_tree queues a visit, design_tree and start_sponsorship create records
POST /mcp/docsDocs MCP server (JSON-RPC)None
GET /api/v1/pricing, GET /api/v1/pricing/quoteSponsorship pricesNone
POST /api/v1/designs, GET /api/v1/designs/{id}Design a sponsored treeCreates a design (3 per day)
GET /api/v1/spots, POST /api/v1/holds, DELETE /api/v1/holds/{id}Find, hold and release spotsHolds spots for 30 minutes
POST /api/v1/orders, GET /api/v1/orders/{id}Start and read a sponsorship orderCreates an unpaid order
POST /api/v1/orders/{id}/confirmHuman confirmation (confirm page only)Opens Stripe Checkout
POST /api/v1/orders/{id}/extend30 more minutes for an unconfirmed agent order, onceExtends the holds
POST /api/v1/orders/{id}/withdrawWithdrawal function (§ 356a BGB)Cancels the order

Machine-readable reference: OpenAPI 3.1 and the API catalog. /api/forest and /api/visit also answer under the version prefix /api/v1/.

GET /api/forest

Returns the trees and groves nearest the centre plus traffic stats. truncated is true when the world holds more: read them with GET /api/v1/chunks?bbox=. The response is cached for 30 seconds. Query parameters, all optional:

curl -s "https://sharedforest.com/api/forest?limit=2"

Example response (values are illustrative):

{
  "trees": [
    { "id": "3f9a1c2e", "type": "oak", "x": 4012.5, "y": 3988.1, "plantedAt": 1790150400000, "growthStage": 3, "origin": "human" },
    { "id": "b71d04aa", "type": "pine", "x": 4101.0, "y": 4050.7, "plantedAt": 1790150460000, "growthStage": 1, "origin": "agent" }
  ],
  "groves": [{ "id": "c20e9f11", "x": 3950, "y": 4020, "level": 1, "treesAbsorbed": 6 }],
  "stats": { "humans": 1520, "agents": 88, "bots": 301, "plantedToday": 12 },
  "visitors": 1909,
  "uniqueVisitors": 640,
  "generatedAt": "2026-09-23T10:00:00.000Z",
  "nextCursor": "bzoy",
  "limit": 2,
  "totalTrees": 57
}

POST /api/visit

Records one visit. {"plant": false} adds anonymous growth energy and stores no identifier. {"plant": true} plants one tree per visitor per day. Bots never plant: their response has "plant": false.

curl -s -X POST https://sharedforest.com/api/visit -H 'Content-Type: application/json' -H 'Idempotency-Key: my-retry-key-1' -d '{"plant":true}'
{ "queued": true, "visitId": "<uuid v4>", "plant": true }

The tree appears within seconds. The live WebSocket feed sends a visit_result event with the same visitId and the outcome planted, grew, absorbed, duplicate or counted.

GET or POST /ask

NLWeb endpoint. It answers from the live stats and these documents. Set streaming=true (GET) or prefer.streaming (POST) for Server-Sent Events.

curl -s "https://sharedforest.com/ask?query=how+many+agents+visited"
{ "query_id": "q-1", "_meta": { "response_type": "list", "version": "1.0" }, "results": [{ "url": "https://sharedforest.com/api/forest", "name": "Shared Forest live stats", "site": "https://sharedforest.com", "score": 1, "description": "The forest has 57 trees and 3 groves." }] }

Sponsor trees via API or MCP

A sponsorship puts 1-100 custom-designed trees in the forest for 12 months. Each tree shows a display name (1-32 characters) and an optional https link, labelled "Sponsored". The name goes live after automatic checks. The link goes live after a manual review. Prices include VAT. See pricing.

Trees per orderUnit priceRenewal per treeDiscountExample
1-49.00 EUR4.50 EUR0 %1 tree = 9.00 EUR
5-97.80 EUR3.90 EUR13 %5 trees = 39.00 EUR
10-246.90 EUR3.45 EUR23 %10 trees = 69.00 EUR
25-995.96 EUR2.98 EUR34 %25 trees = 149.00 EUR
1004.90 EUR2.45 EUR46 %100 trees = 490.00 EUR

Steps:

  1. Quote: MCP get_price_quote or GET /api/v1/pricing/quote?quantity=5.
  2. Design: MCP design_tree with a wish (1-200 characters) or POST /api/v1/designs {"wish": "..."}. Returns a design_id (dsn_...). Max 3 designs per caller per day. One design for all trees, or one per tree.
  3. Place (optional): MCP find_spots or GET /api/v1/spots?quantity=5&near=4096,4096. REST callers hold spots with POST /api/v1/holds and free unused ones with DELETE /api/v1/holds/{id}. Omit spots and the server picks free ones.
  4. Order: MCP start_sponsorship (needs an OAuth token with scope sponsor:write; the order belongs to the user's account and must fit the spending limit set at consent) or POST /api/v1/orders with channel: "mcp" or "api" and an Idempotency-Key. The order is unpaid. It expires 30 minutes after creation unless the human confirms. POST /api/v1/orders/{id}/extend adds 30 minutes once; the confirm page does this when it opens. The response has confirmUrl (https://sharedforest.com/sponsor/confirm/ord_...).
  5. Hand over: give confirmUrl to your user. The user accepts the terms, gives the withdrawal consent and pays on Stripe. That page calls POST /api/v1/orders/{id}/confirm.
  6. Follow up: MCP get_order or GET /api/v1/orders/{id} every 10 seconds or more until status is fulfilled. refund_pending means paid but not plantable: the operator refunds. treeIds then lists the new trees. Share https://sharedforest.com/?tree=<id>.
curl -s "https://sharedforest.com/api/v1/pricing/quote?quantity=5"
curl -s -X POST https://sharedforest.com/api/v1/designs -H 'Content-Type: application/json' -d '{"wish":"a silver birch with warm lanterns"}'
curl -s -X POST https://sharedforest.com/api/v1/orders -H 'Content-Type: application/json' -H "Idempotency-Key: $(uuidgen)" \
  -d '{"quantity":5,"designIds":["dsn_abc123"],"displayName":"Ada","email":"ada@example.com","channel":"api"}'

What an agent can do: quote, design, select spots, create an unpaid order, read its status. What an agent cannot do: accept the terms, give the withdrawal consent or pay for a consumer. German consumer law requires the consumer's own click on "Order and pay" (§ 312j (3) BGB) and the consumer's own express consent before the display starts (§ 356 (5) BGB). So a human always confirms on confirmUrl.

Business buyers (phase 2, off until announced): POST /api/v1/orders/{id}/pay with the buyer's EU VAT id answers 402 with a Machine Payments Protocol challenge (WWW-Authenticate: Payment). Retry with a Stripe Shared Payment Token. This path is B2B only because the consumer rules above do not apply to businesses. It appears in the OpenAPI document only when it is on.

Errors are problem+json: 422 blocked name, link or wish; 409 spot taken or sold out; 429 rate limit or cap (3 designs per caller per day, 100 held spots per IP, 3 open orders per email, 300 unconfirmed spots in total); 502 payment provider down; 503 sales not open yet.

Pagination

GET /api/forest without parameters returns the trees nearest the centre (truncated shows when more exist). A bbox covers the whole world. Add limit, cursor or bbox to get one page. The page adds nextCursor, limit and totalTrees. Send nextCursor as cursor until it is null. The MCP tool list_trees uses the same cursor.

Idempotency

Send an Idempotency-Key header on POST /api/visit (1-255 visible ASCII characters, a UUID is best). A repeat with the same key within 24 hours returns the first response and the header Idempotency-Replayed: true. It queues no second visit. The MCP tool plant_tree accepts the same key as idempotency_key.

Rate limits

Errors

Every API error is application/problem+json (RFC 9457). The detail field says how to fix the request.

{ "type": "about:blank", "title": "Bad Request", "status": 400, "detail": "limit must be an integer between 1 and 500." }
StatusWhen
400Invalid query parameter, body or Idempotency-Key
402MPP pay endpoint without a valid payment credential (business buyers only)
403MCP request with an untrusted Origin header
404Unknown API path, design or order
405Wrong method. The Allow header lists the correct methods
409Spot taken, sponsor layer full, or order not open
413Request body too large
422Blocked wish, name or link, or a missing consent
429Rate limit reached. Wait Retry-After seconds
503Forest state unavailable, or sponsorship sales not open yet. Wait Retry-After seconds

Versioning

The REST API is version 1. Responses carry API-Version: 1. The paths /api/v1/forest and /api/v1/visit are stable aliases. Changes inside a version are additive only. A breaking change gets a new path version. The old version then returns Deprecation (RFC 9745) and Sunset (RFC 8594) headers for at least 90 days before removal. The OpenAPI document states the same policy in x-versioning-policy.

MCP servers

Both servers use Streamable HTTP with JSON responses and no sessions. The docs server needs no authentication. The product server needs an OAuth token only for its account tools (see Authentication).

ServerURLToolsServer card
Producthttps://sharedforest.com/mcpget_forest_stats, get_forest, list_trees, plant_tree, explain_shared_forest, get_price_quote, design_tree, get_design, find_spots, start_sponsorship, get_order, my_orders, my_treesserver-card.json
Docshttps://sharedforest.com/mcp/docssearch_docs, read_docserver-card.json
curl -s -X POST https://sharedforest.com/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_forest_stats","arguments":{}}}'

Client config for Claude Code, Cursor or VS Code:

{ "mcpServers": { "shared-forest": { "type": "http", "url": "https://sharedforest.com/mcp" }, "shared-forest-docs": { "type": "http", "url": "https://sharedforest.com/mcp/docs" } } }

WebMCP

The homepage registers two in-page tools with document.modelContext (fallback navigator.modelContext): get_forest_stats and plant_tree. Browser agents call them without scraping the canvas.

SDKs and agent plugin

The public agent plugin repository holds zero-dependency SDKs for TypeScript (sdk/js) and Python (sdk/python) with one SharedForestClient: getForest, listTrees, plant, quote, designTree, findSpots, startSponsorship, getOrder and waitForOrder. The packages are not on npm or PyPI yet: install from the repository. It also holds AGENTS.md, the Agent Plugin manifest (plugin.json), MCP client config (mcp.json, server.json) and the skills. Any OpenAPI generator can read the OpenAPI document.

Sandbox

There is no separate sandbox. All read endpoints have no side effects. To test writes without planting, send {"plant": false}: it only adds growth energy.

Changelog

Discovery

Support

Arne Kellmann, contact@ghardenlab.com. See contact.