MCP for AI agents
Let an AI agent, such as Claude, get solar output estimates for Swedish roofs, and read your organisation's prospects and offers in Numsolar.
What it is
MCP (Model Context Protocol) is an open standard that AI agents use to call tools on other services. Numsolar is an MCP server: you give your agent our address, and the agent can then call our tools by itself when a question needs them, for example "How much power do 9 kWp on a south roof in Stockholm give in a year?" or "Which of our offers did the client accept this week?".
Technically: JSON-RPC 2.0 over HTTP, one POST
per call, no streaming. The
methods are initialize, ping, tools/list
and tools/call. A plain-text version of this page for AI agents is at /llms.txt.
Units and conventions
- Energy in kWh;
_kwh_kwpis kWh per kWp of panels. - Panel power in kWp (equipment: panels in W, batteries in kWh, inverters in kW).
- Angles in degrees.
tilt_deg: 0 = flat, 90 = vertical. -
Azimuth (
azimuth_deg) is the compass direction the roof face points to: 0 = north, 90 = east, 180 = south, 270 = west, clockwise. - Coordinates (lat, lng) are WGS 84 decimal degrees. For a Swedish town use its centre: the data grid is 0.25° × 0.5°, so a town centre is accurate enough for an estimate. get_started_link also takes a street address.
-
The REST API
/api/v1/pv-yieldcalls the anglestiltandazimuth; the numbers are the same. - Money in SEK incl. VAT.
-
A result is JSON in
result.content[0].text, with the key numbers first. The same object is inresult.structuredContent.
Two endpoints
POST /mcp/public
Public estimates (no key). Anonymous. Solar output estimates for any roof in Sweden, and a link where a homeowner continues. Uses only our own precomputed data: no account and no cost. We keep only a short call log, without the values you send.
URL: https://numsolar.com/mcp/public. Its tools:
POST /mcp
Your organisation's data (key needed). Needs an API key of your organisation. Read only: your prospects, offers, equipment and price templates, plus the public estimate tools. A key on /mcp gives all 10 tools, so your agent needs only this one server. A key never sees another organisation's data.
URL: https://numsolar.com/mcp. Its tools:
-
estimate_yield -
estimate_roof_faces -
yield_matrix -
get_started_link -
list_prospects -
get_prospect -
list_offers -
get_offer -
list_equipment -
list_price_templates
Get a key
Only /mcp needs a key. /mcp/public works without one.
- Open the settings page. Sign in to Numsolar as an admin of your organisation and open Settings › API keys for AI agents. Open API keys for AI agents
- Create a key. Click Create key. Give the key a name (for example "Our sales agent") and tick what it may read.
- Copy the key into your agent. Copy the key at once: we show it only once. Your agent sends it in the header Authorization: Bearer ns_live_XXXXXXXX_YOUR_SECRET_KEY. Revoke the key on the same page when you no longer need it.
What a key may read
You tick one or more of these when you create the key. A tool that needs a scope the key
does not have answers insufficient_scope.
| Scope | What the agent may read |
|---|---|
prospects:read |
Prospects: name, contact details, address, status (tools list_prospects, get_prospect). |
offers:read |
Offers: status, system size, price incl. VAT, tax deduction (tools list_offers, get_offer). |
catalog:read |
Catalogue: equipment and price templates (tools list_equipment, list_price_templates). |
prices:read |
Internal cost and margin: adds purchase cost, margin and discount to the offer and equipment tools. Give it only to an agent you trust with your purchase prices. |
Connect
Claude Code and other agents that read an mcpServers
config (for example .mcp.json): put your key in place of ns_live_XXXXXXXX_YOUR_SECRET_KEY.
{
"mcpServers": {
"numsolar": {
"headers": {
"Authorization": "Bearer ns_live_XXXXXXXX_YOUR_SECRET_KEY"
},
"type": "http",
"url": "https://numsolar.com/mcp"
}
}
}
claude mcp add --transport http numsolar https://numsolar.com/mcp --header "Authorization: Bearer ns_live_XXXXXXXX_YOUR_SECRET_KEY"
Public estimates only, no key. Apps that connect by URL only (for example a custom connector
in Claude Desktop) can use this address; /mcp
needs an app that can send the Authorization
header.
{
"mcpServers": {
"numsolar": {
"type": "http",
"url": "https://numsolar.com/mcp/public"
}
}
}
Try it with curl
curl -s -X POST 'https://numsolar.com/mcp/public' \
-H 'content-type: application/json' \
-d '{"id":1,"jsonrpc":"2.0","method":"tools/call","params":{"arguments":{"azimuth_deg":180,"kwp":9.24,"lat":59.33,"lng":18.07,"tilt_deg":35},"name":"estimate_yield"}}'
curl -s -X POST 'https://numsolar.com/mcp' \
-H 'content-type: application/json' \
-H 'authorization: Bearer ns_live_XXXXXXXX_YOUR_SECRET_KEY' \
-d '{"id":1,"jsonrpc":"2.0","method":"tools/call","params":{"arguments":{"limit":2},"name":"list_prospects"}}'
An agent starts with initialize. The answer tells it what the server is and how to read the numbers:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "my-agent", "version": "1.0"}}
}
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"capabilities": {
"tools": {"listChanged": false}
},
"instructions": "Numsolar estimates solar PV output for Swedish roofs and gives installers access to their own prospects and offers. Public tools are estimates, not quotes. Coordinates are WGS 84 decimal degrees; a town centre is accurate enough. get_started_link also takes a street address. Results are JSON in content[0].text. Money is SEK incl. VAT. Azimuth: 0 = north, 90 = east, 180 = south, clockwise. Status filters take the organisation's own status names, as list_* shows them.",
"protocolVersion": "2025-06-18",
"serverInfo": {"name": "numsolar", "version": "0.4.0"}
}
}
Tools
Call a tool with tools/call. The answer has the result as JSON text in result.content[0].text; the examples below show that JSON.
estimate_yield
Estimate yearly and monthly PV output in kWh for one roof face in Sweden (nearby land in Norway, Denmark and Finland also gets a value, with lower accuracy). Returns kWh per kWp of panels (and total kWh when kwp is given), the best angle at the point and the data source. Angles in degrees: tilt_deg 0 = flat, 90 = vertical; azimuth_deg is a compass bearing, 0 = north, 90 = east, 180 = south, 270 = west (clockwise). An estimate, not a quote. lat and lng are WGS 84 decimal degrees; for a Swedish town its centre is accurate enough (the data grid is 0.25° × 0.5°).
Endpoint: /mcp/public.
Also on /mcp with a key.
No key needed.
| Argument | Type | Description |
|---|---|---|
azimuth_degrequired
|
number | Direction the roof face points, in compass degrees from 0 to 360 (0 = north, 90 = east, 180 = south, 270 = west). |
latrequired
|
number | Latitude in degrees, e.g. 59.33 (Stockholm). |
lngrequired
|
number | Longitude in degrees, e.g. 18.07 (Stockholm). |
tilt_degrequired
|
number | Roof tilt in degrees, from 0 (flat) to 90 (vertical). |
kwp
|
number | Panel power on this face in kWp (optional); adds total kWh. |
shading
|
number | Share of sunlight that reaches the panels, 0–1. Default 1.0 (no shade). |
system_loss
|
number | Fraction of energy lost in cables and inverter, 0–0.6. Default 0.14. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "estimate_yield", "arguments": {"lat": 59.33, "lng": 18.07, "tilt_deg": 35, "azimuth_deg": 180, "kwp": 9.24}}
}
{
"annual_kwh": 9084.8,
"annual_kwh_kwp": 983.2,
"monthly_kwh": [162.2, 337.4, 824.5, 1127.6, 1276.1, 1330.5, 1262.0, 1109.9, 851.1, 501.4, 193.1, 109.3],
"monthly_kwh_kwp": [17.5, 36.5, 89.2, 122.0, 138.1, 144.0, 136.6, 120.1, 92.1, 54.3, 20.9, 11.8],
"optimal": {"tilt_deg": 45, "azimuth_deg": 180, "annual_kwh_kwp": 988.7},
"disclaimer": "Estimate, not a quote. Tax deduction follows Skatteverket rules.",
"attribution": "Contains PVGIS 5.3 data © European Union, 2001–2026 (CC BY 4.0), transposed with Numsolar solar-core poa v2",
"input": {"lat": 59.33, "lng": 18.07, "tilt_deg": 35.0, "azimuth_deg": 180.0, "kwp": 9.24, "shading": 1.0, "system_loss": 0.14},
"cell": {
"lat": 59.25,
"lng": 18.0,
"elevation_m": 48.0,
"era5_corrected": false,
"grid_deg": [0.25, 0.5],
"interpolation": "bilinear",
"radiation_db": "PVGIS-SARAH3"
},
"dataset": {
"coverage": "SE bbox 55.0–69.5N 10.5–24.5E",
"generated_at": "2026-10-07T20:47:05Z",
"grid_deg": [0.25, 0.5],
"radiation_db": "PVGIS-SARAH3",
"sources": ["PVGIS 5.3 SARAH3 2005–2023", "PVGIS 5.3 ERA5 2005–2023 (north of 65N, bias-corrected towards SARAH3)"],
"version": "se-2026.10"
},
"model": {"albedo": 0.2, "anchor_scale": 0.978, "horizon": "free", "poa_model": "v2", "representative_days": 12, "sky_model": "hay_davies", "steps_per_day": 24, "system_loss_default": 0.14},
"ghi_annual_kwh_m2": 995.5,
"orientation_factor": 0.994,
"poa_annual_kwh_m2": 1171.5
}
estimate_roof_faces
Estimate the total yearly and monthly PV output in kWh for a house with 1 to 8 roof faces in Sweden, and the output of each face. Each face has tilt_deg (0 = flat, 90 = vertical), azimuth_deg (compass degrees: 0 = north, 90 = east, 180 = south, 270 = west, clockwise) and kwp (panel power in kWp). An estimate, not a quote. lat and lng are WGS 84 decimal degrees; for a Swedish town its centre is accurate enough (the data grid is 0.25° × 0.5°).
Endpoint: /mcp/public.
Also on /mcp with a key.
No key needed.
| Argument | Type | Description |
|---|---|---|
facesrequired
|
list of objects | 1 to 8 roof faces with panels, each with tilt_deg, azimuth_deg and kwp. |
faces[].azimuth_degrequired
|
number | Direction the roof face points, in compass degrees from 0 to 360 (0 = north, 90 = east, 180 = south, 270 = west). |
faces[].kwprequired
|
number | Panel power on this face in kWp. |
faces[].tilt_degrequired
|
number | Roof tilt in degrees, from 0 (flat) to 90 (vertical). |
latrequired
|
number | Latitude in degrees, e.g. 59.33 (Stockholm). |
lngrequired
|
number | Longitude in degrees, e.g. 18.07 (Stockholm). |
shading
|
number | Share of sunlight that reaches the panels, 0–1. Default 1.0 (no shade). |
system_loss
|
number | Fraction of energy lost in cables and inverter, 0–0.6. Default 0.14. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "estimate_roof_faces", "arguments": {"lat": 57.71, "lng": 11.97, "faces": [{"tilt_deg": 27, "azimuth_deg": 90, "kwp": 4.2}, {"tilt_deg": 27, "azimuth_deg": 270, "kwp": 4.2}]}}
}
{
"annual_kwh": 6322.8,
"monthly_kwh": [74.0, 178.3, 478.5, 805.5, 1027.4, 1045.8, 985.8, 782.1, 534.6, 266.5, 96.6, 47.4],
"faces": [
{
"annual_kwh": 3160.2,
"annual_kwh_kwp": 752.4,
"monthly_kwh": [37.9, 89.6, 233.4, 407.3, 509.0, 523.2, 493.6, 391.9, 267.2, 132.4, 49.9, 24.4],
"monthly_kwh_kwp": [9.0, 21.3, 55.6, 97.0, 121.2, 124.6, 117.5, 93.3, 63.6, 31.5, 11.9, 5.8],
"optimal": {"tilt_deg": 40, "azimuth_deg": 180, "annual_kwh_kwp": 958.4},
"input": {"lat": 57.71, "lng": 11.97, "tilt_deg": 27.0, "azimuth_deg": 90.0, "kwp": 4.2, "shading": 1.0, "system_loss": 0.14},
"cell": {
"lat": 57.75,
"lng": 12.0,
"elevation_m": 4.0,
"era5_corrected": false,
"grid_deg": [0.25, 0.5],
"interpolation": "bilinear",
"radiation_db": "PVGIS-SARAH3"
},
"ghi_annual_kwh_m2": 987.5,
"orientation_factor": 0.785,
"poa_annual_kwh_m2": 900.4
},
{
"annual_kwh": 3162.6,
"annual_kwh_kwp": 753.0,
"monthly_kwh": [36.1, 88.8, 245.1, 398.3, 518.4, 522.6, 492.2, 390.2, 267.4, 134.1, 46.7, 23.0],
"monthly_kwh_kwp": [8.6, 21.1, 58.4, 94.8, 123.4, 124.4, 117.2, 92.9, 63.7, 31.9, 11.1, 5.5],
"optimal": {"tilt_deg": 40, "azimuth_deg": 180, "annual_kwh_kwp": 958.4},
"input": {"lat": 57.71, "lng": 11.97, "tilt_deg": 27.0, "azimuth_deg": 270.0, "kwp": 4.2, "shading": 1.0, "system_loss": 0.14},
"cell": {
"lat": 57.75,
"lng": 12.0,
"elevation_m": 4.0,
"era5_corrected": false,
"grid_deg": [0.25, 0.5],
"interpolation": "bilinear",
"radiation_db": "PVGIS-SARAH3"
},
"ghi_annual_kwh_m2": 987.5,
"orientation_factor": 0.786,
"poa_annual_kwh_m2": 901.2
}
],
"disclaimer": "Estimate, not a quote. Tax deduction follows Skatteverket rules.",
"attribution": "Contains PVGIS 5.3 data © European Union, 2001–2026 (CC BY 4.0), transposed with Numsolar solar-core poa v2",
"input": {"lat": 57.71, "lng": 11.97, "shading": 1.0, "system_loss": 0.14},
"dataset": {
"coverage": "SE bbox 55.0–69.5N 10.5–24.5E",
"generated_at": "2026-10-07T20:47:05Z",
"grid_deg": [0.25, 0.5],
"sources": ["PVGIS 5.3 SARAH3 2005–2023", "PVGIS 5.3 ERA5 2005–2023 (north of 65N, bias-corrected towards SARAH3)"],
"version": "se-2026.10"
},
"model": {"albedo": 0.2, "anchor_scale": 0.978, "horizon": "free", "poa_model": "v2", "representative_days": 12, "sky_model": "hay_davies", "steps_per_day": 24, "system_loss_default": 0.14}
}
yield_matrix
Return yearly PV output in kWh per kWp for every roof angle at one point in Sweden, and the best angle. Rows are tilts 0–90 degrees in 5-degree steps (0 = flat); columns are compass azimuths 0–350 degrees in 10-degree steps (0 = north, 90 = east, 180 = south, 270 = west, clockwise). Limited to 10 calls per clock minute; use estimate_yield for one angle. lat and lng are WGS 84 decimal degrees; for a Swedish town its centre is accurate enough (the data grid is 0.25° × 0.5°).
Endpoint: /mcp/public.
Also on /mcp with a key.
No key needed.
| Argument | Type | Description |
|---|---|---|
latrequired
|
number | Latitude in degrees, e.g. 59.33 (Stockholm). |
lngrequired
|
number | Longitude in degrees, e.g. 18.07 (Stockholm). |
shading
|
number | Share of sunlight that reaches the panels, 0–1. Default 1.0 (no shade). |
system_loss
|
number | Fraction of energy lost in cables and inverter, 0–0.6. Default 0.14. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "yield_matrix", "arguments": {"lat": 59.33, "lng": 18.07}}
}
{
"annual_kwh_kwp": [
[798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7, 798.7],
[753.5, 754.1, 756.0, 759.2, 763.5, 768.8, 775.0, 781.8, 789.2, 796.6, 804.2, 811.4, 818.2, 824.4, 829.7, 834.0, 837.2, 839.1, 839.8, 839.1, 837.2, 834.1, 829.7, 824.4, 818.2, 811.4, 804.1, 796.6, 789.1, 781.8, 775.0, 768.8, 763.5, 759.2, 756.1, 754.1]
],
"optimal": {"tilt_deg": 45, "azimuth_deg": 180, "annual_kwh_kwp": 988.7},
"tilts_deg": [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90],
"azimuths_deg": [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260, 270, 280, 290, 300, 310, 320, 330, 340, 350],
"disclaimer": "Estimate, not a quote. Tax deduction follows Skatteverket rules.",
"attribution": "Contains PVGIS 5.3 data © European Union, 2001–2026 (CC BY 4.0), transposed with Numsolar solar-core poa v2",
"input": {"lat": 59.33, "lng": 18.07, "shading": 1.0, "system_loss": 0.14},
"cell": {
"lat": 59.25,
"lng": 18.0,
"elevation_m": 48.0,
"era5_corrected": false,
"grid_deg": [0.25, 0.5],
"interpolation": "bilinear",
"radiation_db": "PVGIS-SARAH3"
},
"dataset": {
"coverage": "SE bbox 55.0–69.5N 10.5–24.5E",
"generated_at": "2026-10-07T20:47:05Z",
"grid_deg": [0.25, 0.5],
"radiation_db": "PVGIS-SARAH3",
"sources": ["PVGIS 5.3 SARAH3 2005–2023", "PVGIS 5.3 ERA5 2005–2023 (north of 65N, bias-corrected towards SARAH3)"],
"version": "se-2026.10"
},
"model": {"albedo": 0.2, "anchor_scale": 0.978, "horizon": "free", "poa_model": "v2", "representative_days": 12, "sky_model": "hay_davies", "steps_per_day": 24, "system_loss_default": 0.14}
}
get_started_link
Return a Numsolar web link where the homeowner continues with a real roof model and installer quotes. Give address (a street address in Sweden), or lat and lng in degrees. It creates no account and starts no calculation. The link opens the Numsolar wizard with the address filled in.
Endpoint: /mcp/public.
Also on /mcp with a key.
No key needed.
| Argument | Type | Description |
|---|---|---|
address
|
string | Street address in Sweden. |
lat
|
number | Latitude in degrees (use with lng instead of address). |
lng
|
number | Longitude in degrees (use with lat instead of address). |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "get_started_link", "arguments": {"address": "Storgatan 1, Uppsala"}}
}
{
"url": "https://numsolar.com/get-started?address=Storgatan+1%2C+Uppsala",
"message": "Open this link in a browser: the homeowner checks the address, sees a 3D model of the roof and the estimated output, and can ask installers for quotes."
}
list_prospects
List your organisation's prospects (homeowners who may buy), newest change first, with name, city and status; use get_prospect for contact details and offers.
Endpoint: /mcp.
Scope:
prospects:read
| Argument | Type | Description |
|---|---|---|
cursor
|
string | next_cursor from the previous page; leave out for the first page. |
limit
|
integer | Rows per page, 1 to 100 (default 20). |
status
|
string | Only prospects with this status name, as shown in Numsolar. |
updated_since
|
string | Only rows changed at or after this time, ISO 8601 (for example 2026-11-01 or 2026-11-01T08:00:00Z), UTC. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "list_prospects", "arguments": {"limit": 2}}
}
{
"items": [
{
"city": "Uppsala",
"id": "00000000-0000-4000-8000-000000000001",
"name": "Anna Svensson",
"number": "P-1024",
"status": "Offer sent",
"updated_at": "2026-11-03T09:30:00Z"
},
{
"city": "Västerås",
"id": "00000000-0000-4000-8000-000000000003",
"name": "Erik Holm",
"number": "P-1023",
"status": "New",
"updated_at": "2026-11-02T14:05:00Z"
}
],
"next_cursor": "WyIyMDI2LTExLTAzVDA5OjMwOjAwWiIsIjAwMDAiXQ",
"truncated": false
}
get_prospect
Get one prospect of your organisation by id, with name, e-mail, phone, address and status, and its offers when the key may read offers.
Endpoint: /mcp.
Scope:
prospects:read
| Argument | Type | Description |
|---|---|---|
idrequired
|
string | The prospect id from list_prospects. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "get_prospect", "arguments": {"id": "00000000-0000-4000-8000-000000000001"}}
}
{
"address": "Storgatan 1",
"city": "Uppsala",
"co_customer_name": null,
"co_customer_ssn": null,
"country": "Sweden",
"created_at": "2026-10-28T08:12:00Z",
"email": "anna.svensson@example.com",
"first_name": "Anna",
"id": "00000000-0000-4000-8000-000000000001",
"last_name": "Svensson",
"number": "P-1024",
"offers": [
{
"currency": "SEK",
"id": "00000000-0000-4000-8000-000000000002",
"kwp": 10.4,
"number": "O-2031",
"price_after_deduction": 136000.0,
"prospect_id": "00000000-0000-4000-8000-000000000001",
"status": "Sent",
"title": "Villa Svensson, 10.4 kWp",
"updated_at": "2026-11-03T09:30:00Z"
}
],
"phone": "+46 70 000 00 00",
"property_identifier": null,
"ssn": null,
"status": "Offer sent",
"updated_at": "2026-11-03T09:30:00Z",
"zip": "753 20"
}
list_offers
List your organisation's offers, newest change first, with status, system size in kWp and price after the tax deduction in SEK incl. VAT; use get_offer for the details.
Endpoint: /mcp.
Scope:
offers:read
| Argument | Type | Description |
|---|---|---|
cursor
|
string | next_cursor from the previous page; leave out for the first page. |
limit
|
integer | Rows per page, 1 to 100 (default 20). |
status
|
string | Only offers with this status name, as shown in Numsolar and in each offer's status. |
updated_since
|
string | Only rows changed at or after this time, ISO 8601 (for example 2026-11-01 or 2026-11-01T08:00:00Z), UTC. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "list_offers", "arguments": {"limit": 2}}
}
{
"items": [
{
"currency": "SEK",
"id": "00000000-0000-4000-8000-000000000002",
"kwp": 10.4,
"number": "O-2031",
"price_after_deduction": 136000.0,
"prospect_id": "00000000-0000-4000-8000-000000000001",
"status": "Sent",
"title": "Villa Svensson, 10.4 kWp",
"updated_at": "2026-11-03T09:30:00Z"
},
{
"currency": "SEK",
"id": "00000000-0000-4000-8000-000000000004",
"kwp": 8.2,
"number": "O-2030",
"price_after_deduction": 112500.0,
"prospect_id": "00000000-0000-4000-8000-000000000003",
"status": "New",
"title": "Villa Holm, 8.2 kWp",
"updated_at": "2026-11-03T09:30:00Z"
}
],
"next_cursor": "WyIyMDI2LTExLTAzVDA5OjMwOjAwWiIsIjAwMDAiXQ",
"truncated": false
}
get_offer
Get one offer of your organisation by id: status, package and items, system size in kWp, estimated yearly output in kWh, price after the tax deduction, the deduction and VAT in SEK incl. VAT, and the client's share link.
Endpoint: /mcp.
Scope:
offers:read
| Argument | Type | Description |
|---|---|---|
idrequired
|
string | The offer id from list_offers. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "get_offer", "arguments": {"id": "00000000-0000-4000-8000-000000000002"}}
}
{
"approved": false,
"cost_of_goods": 78000.0,
"currency": "SEK",
"deduction": 24000.0,
"discount": 0.0,
"id": "00000000-0000-4000-8000-000000000002",
"items": [
{
"category": "solar_panel",
"cost": 1450.0,
"quantity": 24.0,
"title": "Solar panel 435 W"
},
{
"category": "inverter",
"cost": 14500.0,
"quantity": 1.0,
"title": "Inverter 10 kW"
},
{
"category": "installation",
"cost": 22000.0,
"quantity": 1.0,
"title": "Installation"
}
],
"kwp": 10.44,
"margin": 34000.0,
"number": "O-2031",
"offer_date": "2026-11-03",
"package": {
"id": "00000000-0000-4000-8000-000000000005",
"title": "Solar 10 kWp"
},
"price_after_deduction": 136000.0,
"prospect_id": "00000000-0000-4000-8000-000000000001",
"prospect_name": "Anna Svensson",
"share_url": "https://numsolar.com/o/EXAMPLESHARETOKEN",
"status": "Sent",
"tax": 0.0,
"title": "Villa Svensson, 10.4 kWp",
"updated_at": "2026-11-03T09:30:00Z",
"valid_until": "2026-12-03",
"vat": 32000.0,
"yearly_kwh": 9650.0
}
list_equipment
List your organisation's equipment catalogue, newest change first, with panel power in W, battery capacity in kWh and inverter power in kW; the purchase cost needs the prices:read scope.
Endpoint: /mcp.
Scope:
catalog:read
| Argument | Type | Description |
|---|---|---|
cursor
|
string | next_cursor from the previous page; leave out for the first page. |
limit
|
integer | Rows per page, 1 to 100 (default 20). |
updated_since
|
string | Only rows changed at or after this time, ISO 8601 (for example 2026-11-01 or 2026-11-01T08:00:00Z), UTC. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "list_equipment", "arguments": {"limit": 2}}
}
{
"items": [
{
"active": true,
"battery_kwh": null,
"category": "solar_panel",
"cost": 1450.0,
"currency": "SEK",
"description": "Monocrystalline, black frame",
"id": "00000000-0000-4000-8000-000000000007",
"inverter_kw": null,
"panel_w": 435,
"title": "Solar panel 435 W",
"updated_at": "2026-11-03T09:30:00Z",
"warranty_years": 25
},
{
"active": true,
"battery_kwh": 10.0,
"category": "battery",
"cost": 42000.0,
"currency": "SEK",
"description": null,
"id": "00000000-0000-4000-8000-000000000008",
"inverter_kw": null,
"panel_w": null,
"title": "Battery 10 kWh",
"updated_at": "2026-11-03T09:30:00Z",
"warranty_years": 10
}
],
"next_cursor": "WyIyMDI2LTExLTAzVDA5OjMwOjAwWiIsIjAwMDAiXQ",
"truncated": false
}
list_price_templates
List your organisation's price templates (the price rules an offer uses), newest change first, with status draft or active and currency.
Endpoint: /mcp.
Scope:
catalog:read
| Argument | Type | Description |
|---|---|---|
cursor
|
string | next_cursor from the previous page; leave out for the first page. |
limit
|
integer | Rows per page, 1 to 100 (default 20). |
status
|
string: draft or active | Only templates with this status: draft or active. |
updated_since
|
string | Only rows changed at or after this time, ISO 8601 (for example 2026-11-01 or 2026-11-01T08:00:00Z), UTC. |
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "list_price_templates", "arguments": {"limit": 2}}
}
{
"items": [
{
"currency": "SEK",
"id": "00000000-0000-4000-8000-000000000006",
"package_id": "00000000-0000-4000-8000-000000000005",
"status": "active",
"title": "Standard Sweden",
"updated_at": "2026-11-03T09:30:00Z",
"version": 3
}
],
"next_cursor": null,
"truncated": false
}
Errors
When a tool cannot answer, the HTTP status is 200 and result.isError
is true. The JSON text has code, a one-sentence message
that says how to fix the call and, when one argument is wrong, field:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "estimate_yield", "arguments": {"lat": 59.33, "lng": 18.07, "tilt_deg": 120, "azimuth_deg": 180}}
}
{
"code": "invalid_args",
"message": "tilt_deg must be a roof tilt in degrees from 0 (flat) to 90 (vertical).",
"field": "tilt_deg"
}
| Code | Status | Retry-After | When |
|---|---|---|---|
invalid_args |
200, isError | – | An argument is missing, has the wrong type or is out of range. field names it (for example tilt_deg or faces.1.kwp) and message says the allowed values. |
out_of_coverage |
200, isError | – | The point is outside the area with yield data (latitude 55.0–69.5, longitude 10.5–24.5, land only). The data is made for Sweden; nearby land in Norway, Denmark and Finland also gets a value, with lower accuracy. |
unknown_tool |
200, isError | – | No tool with this name on this endpoint. The organisation tools are only on /mcp; call tools/list for the names. |
insufficient_scope |
200, isError | – | The key may not use this tool. message names the missing scope: create a key that may read it. |
not_found |
200, isError | – | No record with this id in your organisation. An id of another organisation also gives not_found. |
rate_limited |
200, isError | retry_after_seconds | Over a limit (see Limits). Nothing was done. Wait retry_after_seconds, then call again. |
busy |
200, isError | retry_after_seconds | The server runs its maximum of calls at once, or the yield data is still loading. Nothing was done. Wait retry_after_seconds. |
disabled |
503 | – | The server for AI agents is switched off. HTTP 503 with the JSON-RPC error -32000 and data.code disabled. |
internal_error |
200, isError | – | Something failed on our side. The error has request_id: send it to us if the error stays. |
Errors before a tool runs
These are JSON-RPC errors (error.code, error.message) or HTTP
statuses. A request without an id
is a notification: the answer is HTTP 202 with no body.
| Code | Status | Retry-After | When |
|---|---|---|---|
-32700 |
400 | – | The body is not valid JSON. |
-32600 |
200 | – | The body is not one JSON-RPC 2.0 request object, or it is a batch (a JSON array). Send one request per HTTP POST. |
-32601 |
200 | – | Unknown method. The methods are initialize, ping, tools/list and tools/call. |
-32602 |
200 | – | tools/call without params.name, or params.arguments is not an object. |
-32001 |
401 | – | /mcp only: the key is missing, wrong or revoked, or its organisation is closed. The answer has WWW-Authenticate: Bearer and data.code unauthorized. |
-32001 |
429 | seconds until the minute ends | /mcp only: 20 calls with a wrong key from your IP address in one clock minute; a call with a valid key still works. data.code is rate_limited. |
-32001 |
429 | seconds until the minute ends | Too many requests of any kind from your IP address in one clock minute (see Limits). data.code is rate_limited. |
-32600 |
405 | – | A GET, PUT, PATCH, DELETE (or OPTIONS on /mcp). This server does not stream (no SSE): use POST. The Allow header lists the methods. |
-32600 |
413 | – | The body is larger than 256 KB. |
-32000 |
503 | – | The server for AI agents is switched off (data.code disabled). |
Limits
- Requests per IP address: 120 requests per clock minute on /mcp/public and 600 on /mcp, of any kind (also initialize, tools/list, ping and bad requests). Over that: HTTP 429 with Retry-After.
- Per IP address on /mcp/public: 60 tool calls per clock minute and 1000 a day (UTC). yield_matrix: also at most 10 per clock minute.
- Per key on /mcp: 120 tool calls per clock minute and 10000 a day (UTC), the public tools included (yield_matrix: also at most 10 per clock minute). Each key has its own count: your other keys keep working.
- Calls with a wrong key: 20 per clock minute per IP address. After that, a call from that address with a missing or wrong key gets HTTP 429 until the minute ends; a call with a valid key still works.
- Calls at the same time: 8 tool calls at once for the whole server. Over that, a call gets busy with retry_after_seconds.
- Request size: 256 KB per request body. A larger body gets HTTP 413.
Minute limits restart at each clock minute (:00). Over a limit, a tool call answers
rate_limited
with retry_after_seconds, and nothing is done.
Need more? Write to contact@numsolar.com.
A plain-text summary of this page for AI agents: /llms.txt.