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
- Sign in at app.1wire.be.
- Open the user menu at the top right, choose Integrations (below Users) and open the REST API tab.
- Give the token a recognisable name, for example the name of your ERP integration, and click Create token.
- Copy the token straight away. The full token is shown only once.
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:
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
401with aWWW-Authenticate: Bearer realm="1draad API"header. - All responses carry
Cache-Control: no-store.
3. List projects
Returns all non-deleted projects in the account, from every user, newest first (created_at
descending, then id descending).
Query parameters
| Parameter | Default | Meaning |
|---|---|---|
page | 1 | Page number, a positive integer. |
per_page | 50 | Projects per page, from 1 to 100. |
query | empty | Case-insensitive search across project name, reference, client details, installation address and EAN. Truncated after 200 characters. |
updated_since | none | ISO 8601 timestamp, for example
2026-10-23T09:00:00Z. Returns only projects changed at or after that moment. |
curl "https://app.1wire.be/api/v1/projects?per_page=100&query=ERP-101" \
-H "Authorization: Bearer $DENDRAAD_API_TOKEN"
{
"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
| Field | Type | Meaning |
|---|---|---|
id | integer | Stable project identifier. Use it to link projects to your own records. |
name | string | Project name. |
reference | string | null | Your own reference, for example a file or order number from your ERP. |
client_name | string | null | Client name. |
installation_ean | string | null | EAN code of the installation. |
created_at, updated_at | date-time | ISO 8601 in
UTC, with microseconds. updated_at changes with every saved change, including changes to
the diagram. |
app_url | uri | Link that opens the project in the editor. Works only for a signed-in user with access to the account. |
share_url | uri | null | Public 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
Returns all fields from the list, plus the project's administrative details and four contact records.
| Field | Type | Meaning |
|---|---|---|
account_id | integer | Account the project belongs to. |
owned_by_id | integer | null | User who owns the project. |
client_language | string | null | Client language:
nl, fr or en. |
installation_kind | string | domestic or
non_domestic. |
installation_same_as_client | boolean | The installation address is the same as the client's address. |
current_type | string | null | Current type as entered, free
text, for example 3 x 400V + N ~ 50Hz. |
client | Contact | Client. |
installation_location | Contact | Installation location. |
installer | Contact | Installer. |
controller | Contact | Inspection 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_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.
{
"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
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.
{
"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:
| Field | Meaning |
|---|---|
id, name | Identifier and name of the diagram. |
root_component_ids | The components at the top of the tree, in order. |
components | All components of the diagram (see below). |
annotations | Notes 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_connections | Backup power links between an inverter
(converter_component_id) and a backup board (switchboard_component_id). |
Components
{
"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
}
| Field | Meaning |
|---|---|
id | Identifier of the component within the project. |
type | Stable component type from the editor, for example battery or
switchboard. Use this field in your code. |
label | Readable English name of the type. Meant for display, not for filtering. |
description | The description exactly as typed, or null. |
properties | Saved settings with snake_case keys: variants, electrical values,
quantity, … A text component keeps its text in properties.content. |
parent_id, child_ids | Position in the diagram's tree. The order of
child_ids is the order of the branches. |
connection | Saved ports of inverters and controls, and the side and slot of branches and backup boards. An empty object means nothing is saved. |
switchboard_id | The nearest parent distribution board. |
circuit_id, circuit_label, circuit_name | The circuit: a reference to the component that defines it, the letter on the drawing and the circuit name. |
phase | The circuit's phase, for example L1. |
subcircuit_id, subcircuit_label, subcircuit_name | The subcircuit, in the same way. The label is usually a number. |
null.
Units
property_units tells you the unit of known settings:
| Setting | Unit |
|---|---|
amperage | A |
delta_current | mA (residual current) |
cross_section | mm² (cable cross-section) |
length | m |
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:
- Note the time before you start.
- Page through all of
GET /projects?updated_since=…with the time of the previous run, and deduplicate onid. - For each changed project, fetch the details or the diagram if you need them.
- Store the time from step 1 as the starting point for the next run. Because
updated_sinceis inclusive, you miss nothing; a project that arrives twice is simply overwritten.
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()];
}
}
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
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.
- Add the URL above as a remote MCP server (Streamable HTTP) in your AI client. Choose OAuth if the client asks how to authenticate.
- Sign in with your existing 1wire account when the client asks.
- 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 mcp add --transport http 1wire https://app.1wire.be/mcp
# Then in Claude Code: /mcp, choose 1wire and sign in.
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.bedoes not work onapp.1draad.beorapp.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).
{
"mcpServers": {
"1wire": {
"url": "https://app.1wire.be/mcp",
"headers": { "Authorization": "Bearer YOUR_API_TOKEN" }
}
}
}
Tools
All tools are read-only and return the same JSON response as the matching REST endpoint.
| Tool | Arguments | Matches |
|---|---|---|
list_projects | Optional: page, per_page,
query, updated_since, with the same meaning as in the REST API. | GET /projects |
get_project | Required: project_id (integer). | GET /projects/{id} |
get_project_schematic | Required: 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: trueand the sameerrorobject as the REST API (not_foundorinvalid_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 aWWW-Authenticateheader 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-25and the earlier versions of the official MCP Ruby SDK. Sendinitializefirst, thennotifications/initialized, and afterwards pass the negotiated version in theMCP-Protocol-Versionheader. An unsupported version returns HTTP400. - Every POST carries
Content-Type: application/jsonandAccept: application/json, text/event-stream. Responses are JSON; a notification gets an empty response with HTTP202. - The server is stateless: no session ID, no persistent SSE stream.
GETandDELETEreturn HTTP405. - Native clients without an
Originheader are welcome. With anOriginheader, only the three app domains are accepted; others return HTTP403. Calling it directly from a web page on another domain (CORS) therefore does not work.
OAuth
| Endpoint | Purpose |
|---|---|
GET /.well-known/oauth-protected-resource/mcp | MCP resource and authorization server discovery. |
GET /.well-known/oauth-authorization-server | Authorization server metadata. |
POST /oauth/register | Dynamic client registration (RFC 7591). |
GET /oauth/authorize | Sign-in and consent. |
POST /oauth/token | Code exchange and token refresh. |
POST /oauth/revoke | Connection 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, exactredirect_urisandtoken_endpoint_auth_method(none,client_secret_basicorclient_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
resourceparameter, for examplehttps://app.1wire.be/mcp. Token requests useapplication/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.
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.
{ "error": { "code": "not_found", "message": "Project not found." } }
| Status | Code | When |
|---|---|---|
400 | invalid_parameters | Invalid page or
per_page, or an updated_since that is not an ISO 8601 timestamp. |
401 | unauthorized | Token missing, invalid or revoked. |
404 | not_found | The 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
propertiesandconnection.
Questions about the API, or missing a piece of data for your integration? Let us know through the contact form.