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.
Connect your AI agent
Copy the agent setup instructions into your local agent. They cover both MCP servers, both skills and the rules for using Shared Forest.
Quickstart
- Read the live forest:
GET /api/forest. - Plant a tree for your user:
POST /api/visitwith{"plant": true}. - 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.
- Discover: protected resource metadata (RFC 9728) and authorization server metadata (RFC 8414).
- Register:
POST /oauth/register(Dynamic Client Registration, RFC 7591). Redirect URIs: https, loopback http or a native app scheme. - Authorize: send the user to
/oauth/authorizewithresponse_type=code, PKCEcode_challenge_method=S256(required,plainis 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). - Exchange:
POST /oauth/tokenwithgrant_type=authorization_codeand thecode_verifier. Access tokens last 15 minutes. Refresh tokens rotate on every use. - Call:
Authorization: Bearer <access_token>onPOST /mcp. Orders from start_sponsorship belong to the user's account. The user still confirms and pays each order onconfirm_url. - Revoke:
POST /oauth/tokenwithtoken=<refresh_token>(RFC 7009).
Step-by-step walkthrough with curl: auth.md.
Endpoints
| Method and path | Purpose | Side effects |
|---|---|---|
GET /api/forest | Live snapshot of trees, groves and traffic stats | None |
POST /api/visit | Record a visit or plant a tree | Queues one visit |
GET /ask, POST /ask | NLWeb question endpoint | None |
POST /mcp | Product MCP server (JSON-RPC) | plant_tree queues a visit, design_tree and start_sponsorship create records |
POST /mcp/docs | Docs MCP server (JSON-RPC) | None |
GET /api/v1/pricing, GET /api/v1/pricing/quote | Sponsorship prices | None |
POST /api/v1/designs, GET /api/v1/designs/{id} | Design a sponsored tree | Creates a design (3 attempts per day) |
GET /api/v1/spots, POST /api/v1/holds, DELETE /api/v1/holds/{id} | Find, hold and release spots | Holds spots for 30 minutes |
POST /api/v1/orders, GET /api/v1/orders/{id} | Start and read a sponsorship order | Creates an unpaid order |
POST /api/v1/orders/{id}/confirm | Human confirmation (confirm page only) | Opens Stripe Checkout |
POST /api/v1/orders/{id}/extend | 30 more minutes for an unconfirmed agent order, once | Extends the holds |
POST /api/v1/orders/{id}/withdraw | Withdrawal 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:
limit: trees per page, 1-500. Turns on pagination.cursor: thenextCursorvalue of the previous page.bbox:minX,minY,maxX,maxYin world coordinates. Only trees inside the box.
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} requests anonymous growth energy and stores no identifier. {"plant": true} requests one free planting per visitor per UTC day. The daily allowance resets at 00:00 UTC. Bots never plant or add growth energy: 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 queued response confirms receipt, not a new tree. The live WebSocket feed sends a visit_result event with the same visitId. planted creates a tree record; absorbed adds a contribution to a grove; grew advances an existing tree; duplicate finds an already recorded planting; counted reports no planting. Read how to find your contribution before treating acceptance as completion.
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 premium trees in the forest for 12 months. Choose one-time payment, which ends after 12 months, or an annual subscription at about 20 % less with a 12-month minimum term. After the first year, the buyer can cancel at any time and get a day-based refund of unused prepaid time. An AI paints each design from the wish; the trees stand full grown from the first day. 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 are final; VAT is included. See pricing.
| Trees per order | One-time per tree | Annual plan per tree | Example final price |
|---|---|---|---|
| 1-4 | 9.00 EUR | 7.20 EUR/year | 1 tree: 9.00 EUR once or 7.20 EUR/year |
| 5-9 | 7.80 EUR | 6.24 EUR/year | 5 trees: 39.00 EUR once or 31.20 EUR/year |
| 10-24 | 6.90 EUR | 5.52 EUR/year | 10 trees: 69.00 EUR once or 55.20 EUR/year |
| 25-99 | 5.96 EUR | 4.77 EUR/year | 25 trees: 149.00 EUR once or 119.25 EUR/year |
| 100 | 4.90 EUR | 3.92 EUR/year | 100 trees: 490.00 EUR once or 392.00 EUR/year |
Steps:
- Quote: MCP
get_price_quoteorGET /api/v1/pricing/quote?quantity=5&billingChoice=one_time. The public REST price quote also acceptsbillingChoice=subscription. Agents can prepare only one-time orders; the buyer starts an annual subscription on the website. - Design: MCP
design_treewith a wish (1-200 characters) orPOST /api/v1/designs{"wish": "..."}. Returns adesign_id(dsn_...). Max 3 design attempts per caller per day (including rejected or interrupted generation). One design for all trees, or one per tree. - Place (optional): MCP
find_spotsorGET /api/v1/spots?quantity=5&near=4096,4096. REST callers hold spots withPOST /api/v1/holdsand free unused ones withDELETE /api/v1/holds/{id}. Omit spots and the server picks free ones. - Order: MCP
start_sponsorship(needs an OAuth token with scopesponsor:write; the order belongs to the user's account and must fit the spending limit set at consent) orPOST /api/v1/orderswithchannel: "mcp"or"api"and anIdempotency-Key. Agent orders are one-time. The order is unpaid. It expires 30 minutes after creation unless the human confirms.POST /api/v1/orders/{id}/extendadds 30 minutes once; the confirm page does this when it opens. The response hasconfirmUrl(https://sharedforest.com/sponsor/confirm/ord_...). - Hand over: give
confirmUrlto your user. The user checks the one-time payment, accepts the terms, gives the withdrawal consent and pays on Stripe. That page callsPOST /api/v1/orders/{id}/confirm. - Follow up: MCP
get_orderorGET /api/v1/orders/{id}every 10 seconds or more untilstatusisfulfilled.refund_pendingmeans paid but not plantable: the operator refunds.treeIdsthen 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 with obligation to pay" (§ 312j (3) BGB) and the consumer's own express consent before the display starts (§ 356 (5) BGB). The right of withdrawal ends only after full performance, not when the display starts. 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 design attempts per caller per day (including rejected or interrupted generation), 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
- 120 requests per minute per IP address on the API, the MCP servers and /ask.
- Every response carries
RateLimitandRateLimit-Policyheaders. - A
429response carriesRetry-Afterin seconds. - One free planting per visitor per UTC day. A repeated planting can return
duplicateand adds no second tree. Capacity limits can return a grove contribution or growth instead of a new tree. - Sponsorship caps (429): 3 design attempts per caller per day (including rejected or interrupted generation), 100 held spots per IP address, 3 open orders per email, 300 unconfirmed spots in total. Release unused holds with
DELETE /api/v1/holds/{id}. - MCP request bodies over 16 KiB and /ask bodies over 4 KiB return
413.
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." }
| Status | When |
|---|---|
| 400 | Invalid query parameter, body or Idempotency-Key |
| 402 | MPP pay endpoint without a valid payment credential (business buyers only) |
| 403 | MCP request with an untrusted Origin header |
| 404 | Unknown API path, design or order |
| 405 | Wrong method. The Allow header lists the correct methods |
| 409 | Spot taken, sponsor layer full, or order not open |
| 413 | Request body too large |
| 422 | Blocked wish, name or link, or a missing consent |
| 429 | Rate limit reached. Wait Retry-After seconds |
| 503 | Forest 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).
| Server | URL | Tools | Server card |
|---|---|---|---|
| Product | https://sharedforest.com/mcp | get_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_trees | server-card.json |
| Docs | https://sharedforest.com/mcp/docs | search_docs, read_doc | server-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
- 2026-09-23: relaunch. Public REST API, product and docs MCP servers, NLWeb /ask, cursor pagination and Idempotency-Key.
- 2026-09-23: tree sponsorship for agents. MCP tools get_price_quote, design_tree, get_design, find_spots, start_sponsorship and get_order. REST endpoints under /api/v1/ for pricing, designs, spots, holds and orders. Skill shared-forest-sponsor.
- 2026-09-23: the docs MCP server gets its own server card.
list_treesrequireslimit. The OpenAPI document states the versioning and deprecation policy.
Discovery
- llms.txt, API llms.txt, developers llms.txt
- Agent skill and sponsor skill
- Agent plugin repository
- Pricing
Support
Arne Kellmann, contact@sharedforest.com. See contact.