1. Overview

The 1wire API gives you the latest saved version of the projects in your account. Each API token belongs to one account and gives access to all projects in that account, whichever user created them.

Base URL

All endpoints in this documentation live under:

https://app.1wire.be/api/v1

The API is also available on the Dutch and French domains (app.1draad.be and app.1filaire.be). The domain has no effect on the data you get back.

What v1 does not do

  • Changes that have not been saved yet, or that are stored offline in the browser, are not in the API.
  • You cannot create, change or delete projects: the API is read-only.
  • You manage tokens in the app, not through the API.

OpenAPI specification

The full machine-readable contract is at app.1wire.be/api/v1/openapi.yaml. Load it into Postman, Insomnia or a code generator to build a client.

MCP for AI assistants

You can also open the same data to an AI assistant through MCP. You simply sign in with your account; no API token needed.

2. Authentication

Create an API token

  1. Sign in at app.1wire.be.
  2. Open the user menu at the top right, choose Integrations (below Users) and open the REST API tab.
  3. Give the token a recognisable name, for example the name of your ERP integration, and click Create token.
  4. Copy the token straight away. The full token is shown only once.
Treat the token like a password. Every token has full API access to all projects in the account; there are no per-token permissions. Store it in your integration's secret settings, never in source code or in a URL.

A token belongs to the account, not to the user who created it. It stays valid until someone revokes it or the account is deleted, even if the user who created it leaves the account. Every user in the account can see, create and revoke all tokens. The same page shows when each token was last used.

Sending the token

Send the token in the Authorization header with every request:

Shell
curl https://app.1wire.be/api/v1/projects \
  -H "Authorization: Bearer $DENDRAAD_API_TOKEN"
  • A token in a query parameter is not accepted, and a signed-in browser session does not give access to the API.
  • If the token is missing, invalid or revoked, you get 401 with a WWW-Authenticate: Bearer realm="1draad API" header.
  • All responses carry Cache-Control: no-store.

3. List projects

GET/projects

Returns all non-deleted projects in the account, from every user, newest first (created_at descending, then id descending).

Query parameters

ParameterDefaultMeaning
page1Page number, a positive integer.
per_page50Projects per page, from 1 to 100.
queryemptyCase-insensitive search across project name, reference, client details, installation address and EAN. Truncated after 200 characters.
updated_sincenoneISO 8601 timestamp, for example 2026-10-23T09:00:00Z. Returns only projects changed at or after that moment.
Request
curl "https://app.1wire.be/api/v1/projects?per_page=100&query=ERP-101" \
  -H "Authorization: Bearer $DENDRAAD_API_TOKEN"
Response · 200
{
  "data": [
    {
      "id": 123,
      "name": "Home battery installation",
      "reference": "ERP-101",
      "client_name": "Sarah Janssens",
      "installation_ean": "541234567890123456",
      "created_at": "2026-10-23T09:00:00.000000Z",
      "updated_at": "2026-10-23T10:00:00.000000Z",
      "app_url": "https://app.1wire.be/projects/123/edit",
      "share_url": null
    }
  ],
  "pagination": { "page": 1, "per_page": 100, "total_count": 1, "total_pages": 1 }
}

Project fields

FieldTypeMeaning
idintegerStable project identifier. Use it to link projects to your own records.
namestringProject name.
referencestring | nullYour own reference, for example a file or order number from your ERP.
client_namestring | nullClient name.
installation_eanstring | nullEAN code of the installation.
created_at, updated_atdate-timeISO 8601 in UTC, with microseconds. updated_at changes with every saved change, including changes to the diagram.
app_urluriLink that opens the project in the editor. Works only for a signed-in user with access to the account.
share_urluri | nullPublic share link, only if sharing is already enabled. An API request never enables sharing.

Links use the domain of the project's export language: app.1draad.be, app.1filaire.be or app.1wire.be.

Pagination

The list uses numbered pages. If projects are added or removed while you page through, a project can shift to another page and show up twice or not at all. Always deduplicate on id, and see Syncing for a robust approach.

4. Project details

GET/projects/{id}

Returns all fields from the list, plus the project's administrative details and four contact records.

FieldTypeMeaning
account_idintegerAccount the project belongs to.
owned_by_idinteger | nullUser who owns the project.
client_languagestring | nullClient language: nl, fr or en.
installation_kindstringdomestic or non_domestic.
installation_same_as_clientbooleanThe installation address is the same as the client's address.
current_typestring | nullCurrent type as entered, free text, for example 3 x 400V + N ~ 50Hz.
clientContactClient.
installation_locationContactInstallation location.
installerContactInstaller.
controllerContactInspection body.

Every contact record has the same fields: name, street, postal_code, city, country, company_number, fixed_phone, mobile_phone and email. Anything not filled in stays null or empty.

Installation address: if installation_same_as_client is true, use the details in client as the installation address. installation_location then holds only what was stored separately, and may be empty or out of date.
Response · 200 (shortened)
{
  "data": {
    "id": 123,
    "name": "Home battery installation",
    "reference": "ERP-101",
    "client_name": "Sarah Janssens",
    "installation_ean": "541234567890123456",
    "created_at": "2026-10-23T09:00:00.000000Z",
    "updated_at": "2026-10-23T10:00:00.000000Z",
    "app_url": "https://app.1wire.be/projects/123/edit",
    "share_url": null,
    "account_id": 7,
    "owned_by_id": 15,
    "client_language": "en",
    "installation_kind": "domestic",
    "installation_same_as_client": true,
    "current_type": "3 x 400V + N ~ 50Hz",
    "client": {
      "name": "Sarah Janssens",
      "street": "Avenue Louise 120",
      "postal_code": "1050",
      "city": "Brussels",
      "country": "Belgium",
      "company_number": null,
      "fixed_phone": null,
      "mobile_phone": "0470 12 34 56",
      "email": "[email protected]"
    },
    "installation_location": { "name": null, "street": null, ... },
    "installer": { ... },
    "controller": { ... }
  }
}

5. Read the single-line diagram

GET/projects/{id}/schematic

Returns a readable view of the saved diagrams: every component with its settings, the circuit it belongs to, and the notes. The response comes from one saved version of the project and changes nothing in the project.

Response · 200 (shortened)
{
  "data": {
    "project_id": 123,
    "updated_at": "2026-10-23T10:00:00.000000Z",
    "property_units": { "amperage": "A", "delta_current": "mA", "cross_section": "mm²", "length": "m" },
    "schematics": [
      {
        "id": "schema-1",
        "name": "Schema 1",
        "root_component_ids": ["board-789"],
        "components": [ ... ],
        "annotations": [
          { "id": "note-1", "anchor_component_id": "battery-123", "text": "Installed in the garage" }
        ],
        "backup_connections": [
          { "id": "backup-1", "converter_component_id": "converter-456", "switchboard_component_id": "board-789" }
        ]
      }
    ]
  }
}

Diagrams

A project can contain several diagrams. Each diagram in schematics has:

FieldMeaning
id, nameIdentifier and name of the diagram.
root_component_idsThe components at the top of the tree, in order.
componentsAll components of the diagram (see below).
annotationsNotes with id, anchor_component_id and text. A free-standing text has anchor_component_id: null; text that merely sits close to a component is not treated as linked.
backup_connectionsBackup power links between an inverter (converter_component_id) and a backup board (switchboard_component_id).

Components

Example component
{
  "id": "battery-123",
  "type": "battery",
  "label": "Battery",
  "description": "Brand: Example\nS/N: BAT-001",
  "properties": { "quantity": 1, "label": "B1" },
  "parent_id": "converter-456",
  "child_ids": [],
  "connection": {},
  "switchboard_id": "board-789",
  "circuit_id": "breaker-234",
  "circuit_label": "B",
  "circuit_name": "Home battery circuit",
  "phase": "L1",
  "subcircuit_id": "battery-123",
  "subcircuit_label": 1,
  "subcircuit_name": null
}
FieldMeaning
idIdentifier of the component within the project.
typeStable component type from the editor, for example battery or switchboard. Use this field in your code.
labelReadable English name of the type. Meant for display, not for filtering.
descriptionThe description exactly as typed, or null.
propertiesSaved settings with snake_case keys: variants, electrical values, quantity, … A text component keeps its text in properties.content.
parent_id, child_idsPosition in the diagram's tree. The order of child_ids is the order of the branches.
connectionSaved ports of inverters and controls, and the side and slot of branches and backup boards. An empty object means nothing is saved.
switchboard_idThe nearest parent distribution board.
circuit_id, circuit_label, circuit_nameThe circuit: a reference to the component that defines it, the letter on the drawing and the circuit name.
phaseThe circuit's phase, for example L1.
subcircuit_id, subcircuit_label, subcircuit_nameThe subcircuit, in the same way. The label is usually a number.
No interpretation. The API returns only what is saved. Brands, serial numbers or capacities are not extracted from the description, no editor defaults are filled in, and nothing is matched to items in your ERP. In older projects the circuit fields may be missing; they are then null.

Units

property_units tells you the unit of known settings:

SettingUnit
amperageA
delta_currentmA (residual current)
cross_sectionmm² (cable cross-section)
lengthm

Identity and consistency

A component is unique within the combination (project_id, schematic_id, component_id). If you fetch project details and the diagram in separate requests, compare updated_at to make sure they belong to the same saved version.

6. Syncing

To keep your own system up to date, regularly request the projects that changed since the previous run:

  1. Note the time before you start.
  2. Page through all of GET /projects?updated_since=… with the time of the previous run, and deduplicate on id.
  3. For each changed project, fetch the details or the diagram if you need them.
  4. Store the time from step 1 as the starting point for the next run. Because updated_since is inclusive, you miss nothing; a project that arrives twice is simply overwritten.
JavaScript (Node.js 18+)
const BASE_URL = "https://app.1wire.be/api/v1";
const headers = { Authorization: `Bearer ${process.env.DENDRAAD_API_TOKEN}` };

// All projects changed at or after `since`, deduplicated on id.
async function changedProjects(since) {
  const projects = new Map();
  for (let page = 1; ; page++) {
    const url = new URL(`${BASE_URL}/projects`);
    url.search = new URLSearchParams({ page, per_page: 100, updated_since: since });
    const response = await fetch(url, { headers });
    const body = await response.json();
    if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`);

    body.data.forEach((project) => projects.set(project.id, project));
    if (page >= body.pagination.total_pages) return [...projects.values()];
  }
}
Deleted projects: updated_since does not return deleted projects. To track deletions, compare the full list (without updated_since) with your own data from time to time. A deleted project also returns 404 on GET /projects/{id}.

7. MCP for AI assistants

POST/mcp

The same data is also available as an MCP server (Model Context Protocol). An AI assistant that supports MCP, such as Claude or ChatGPT, can then look up your projects and read your diagrams itself, for example to draw up a material list or answer a question about an installation.

https://app.1wire.be/mcp

The server uses the Streamable HTTP transport and is also available on app.1draad.be and app.1filaire.be. You can connect in two ways: by signing in with your 1wire account (recommended), or with an API token.

Connect by signing in (recommended)

No token needed. You sign in and approve each AI client yourself.

  1. Add the URL above as a remote MCP server (Streamable HTTP) in your AI client. Choose OAuth if the client asks how to authenticate.
  2. Sign in with your existing 1wire account when the client asks.
  3. Check the account and the callback address, then click Allow read-only access. The client then appears under Integrations → AI assistants (MCP) → Connected AI clients.
Claude Code
claude mcp add --transport http 1wire https://app.1wire.be/mcp
# Then in Claude Code: /mcp, choose 1wire and sign in.
Codex (OpenAI)
codex mcp add 1wire --url https://app.1wire.be/mcp
codex mcp login 1wire
# Opens your browser to sign in.

ChatGPT: in Settings, turn on Developer mode under Apps → Advanced settings. Then choose Apps → Create, enter the MCP URL, choose OAuth as the authentication and sign in. Whether and where this is available depends on your ChatGPT plan; in a workspace an admin may need to allow it first.

  • The client can read all saved projects, contact details and diagrams in the account. It cannot change projects or enable sharing.
  • You approve every connection yourself, even if the same client has connected before. The client name comes from the client itself and is not verified: only approve connections you started.
  • Any user in the account can revoke a connection with Disconnect. The client loses access straight away; other connections and API tokens keep working.
  • A connection belongs to the user who approved it. If that user leaves the account, the connection ends too. After 90 days it always expires and you connect again.
  • Sign-in applies per domain: a connection through app.1wire.be does not work on app.1draad.be or app.1filaire.be, and it does not give access to the REST API.

Connect with an API token

Client can't sign in but supports a custom Authorization header? Send an API token as a Bearer token instead, as with the REST API (see Authentication).

mcp.json (for example Cursor)
{
  "mcpServers": {
    "1wire": {
      "url": "https://app.1wire.be/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_TOKEN" }
    }
  }
}
Tip: create a separate token for your AI assistant, for example named "AI assistant". If you revoke it, the assistant loses access straight away without breaking your other integrations.

Tools

All tools are read-only and return the same JSON response as the matching REST endpoint.

ToolArgumentsMatches
list_projectsOptional: page, per_page, query, updated_since, with the same meaning as in the REST API.GET /projects
get_projectRequired: project_id (integer).GET /projects/{id}
get_project_schematicRequired: project_id (integer).GET /projects/{id}/schematic

The result is returned both in structuredContent and as JSON text in content, so clients without support for structured results can read it too. Descriptions and notes in a project are user data; the server asks AI clients not to treat them as instructions.

Errors

  • An unknown, deleted or foreign project, or an invalid argument, returns a tool result with isError: true and the same error object as the REST API (not_found or invalid_parameters).
  • An unknown tool or method returns a JSON-RPC protocol error.
  • Without a valid API token or OAuth access token you get HTTP 401, with a WWW-Authenticate header that points to the server's OAuth metadata.

Technical details

You only need this if you build an MCP client yourself.

Transport

  • Supports MCP protocol version 2025-11-25 and the earlier versions of the official MCP Ruby SDK. Send initialize first, then notifications/initialized, and afterwards pass the negotiated version in the MCP-Protocol-Version header. An unsupported version returns HTTP 400.
  • Every POST carries Content-Type: application/json and Accept: application/json, text/event-stream. Responses are JSON; a notification gets an empty response with HTTP 202.
  • The server is stateless: no session ID, no persistent SSE stream. GET and DELETE return HTTP 405.
  • Native clients without an Origin header are welcome. With an Origin header, only the three app domains are accepted; others return HTTP 403. Calling it directly from a web page on another domain (CORS) therefore does not work.

OAuth

EndpointPurpose
GET /.well-known/oauth-protected-resource/mcpMCP resource and authorization server discovery.
GET /.well-known/oauth-authorization-serverAuthorization server metadata.
POST /oauth/registerDynamic client registration (RFC 7591).
GET /oauth/authorizeSign-in and consent.
POST /oauth/tokenCode exchange and token refresh.
POST /oauth/revokeConnection revocation.
  • Only the authorization-code flow with PKCE (S256), required for every client. The only scope is mcp:read.
  • Register your client with client_name, exact redirect_uris and token_endpoint_auth_method (none, client_secret_basic or client_secret_post). Callback addresses must use HTTPS, except HTTP on a loopback address for native clients. Client ID Metadata Documents are not supported yet.
  • Pass the MCP URL as the resource parameter, for example https://app.1wire.be/mcp. Token requests use application/x-www-form-urlencoded.
  • An access token is valid for one hour. Every refresh returns a new access token and a new refresh token; store both and never refresh twice at the same time. Reusing an old refresh token revokes the whole connection.
Calling a tool with curl
curl https://app.1wire.be/mcp \
  -H "Authorization: Bearer $DENDRAAD_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  --data '{"jsonrpc":"2.0","id":2,"method":"tools/call",
           "params":{"name":"list_projects","arguments":{"query":"ERP-101","per_page":10}}}'

8. Errors

On an error you get a JSON object with a fixed code and a message. Base your logic on the code, not on the text.

Response · 404
{ "error": { "code": "not_found", "message": "Project not found." } }
StatusCodeWhen
400invalid_parametersInvalid page or per_page, or an updated_since that is not an ISO 8601 timestamp.
401unauthorizedToken missing, invalid or revoked.
404not_foundThe project does not exist, was deleted or belongs to another account. For security reasons the API does not say which.

9. Versions and compatibility

Changes that could break existing integrations will arrive in a new namespace (/api/v2). Within v1, things can still be added. Write your integration so it tolerates:

  • new fields in a response,
  • new component types in type,
  • new settings in properties and connection.

Questions about the API, or missing a piece of data for your integration? Let us know through the contact form.