Construction software that runs on your own hardware. I hold none of your data. Why I build it this way →
Menu
  1. Home
  2. Get involved
  3. Developers
Contact
For developers

Nobody has to ask us for anything

Your server is yours and so are your keys. A plugin can be signed with your own key so it never passes through my hands. A markup is a PDF annotation any library reads, and a tool chest is JSON you can open in Notepad. There's no developer account, no approval, no store and no revenue share.

The full guide on GitHub

Four ways in

They answer different questions. Pick by what you're trying to do.

You want toUse
Add a command that reads a drawing and proposes markupsA plugin
Get quantities into your own systemThe API
Be told when a set changes, instead of askingChange notices
Let an assistant read your takeoffsThe assistant connector
Ship your own sizes, weights and columnsA tool chest

All of it runs on your own server. None of it needs me to be online, or to exist.

Plugins

A plugin is a WebAssembly module that Excalibur View runs in a sandbox. It can't open files, reach the network, see the screen or touch the drawing. It's handed the sheets and the markups as data and hands back what it found, with fixes somebody can click. Nothing changes until they do.

A plugin that isn't signed by a key the program trusts doesn't run. Not a warning: it doesn't run. You choose who signs it. Send it to me and it runs in any copy, or have your own plugin key added and it runs only in your shop, for tools that hold your costs or your standards.

Write one, from an empty folder to every seat

The API

On the Office server, under /api/v1.

Every Office server describes its own API at /api/v1/openapi.json. That description is built from the routes themselves, and a test fails the build if a route exists without being described, so it can't go stale. Point your tooling at it and generate a client.

Make a key under Studio → Office → Keys. An integration key reaches the API as the person who made it. Keys start hvk_, are shown once, and are revoked one at a time.

Authorization: Bearer hvk_…
RouteWhat it gives you
GET /projectsThe jobs
GET /projects/{id}/setsThe drawing sets in one
GET /sets/{id}/sheetsSheet numbers, titles and scales
GET /sets/{id}/markupsEvery markup, as its PDF annotation
GET /sets/{id}/takeoffThe quantities, totalled
GET /sets/{id}/takeoff.csvThe same, for a spreadsheet
GET /sets/{id}/markuplistThe markups list as the program shows it
POST /sets/{id}/copyA copy of a set to take off on
POST /sets/{id}/markupsPush markups up
GET /audit, /audit.csvWho did what (administrators)

Each set carries a revision that goes up whenever its markups change. Ask for /sets/{id}/markups?since=12 and you get only what changed after revision 12, rather than the whole set again.

A markup is a PDF annotation dictionary, not a format of mine. Your code can read it with any PDF library, and everything beside it in the API (subject, author, quantity, the custom columns) is there so you never have to parse PDF to filter and total. Each one keeps a stable name through the round trip, so you can update a markup you pushed rather than make a second.

Change notices

Office 0.6.9 and newer.

Give the server an address under Studio → Office → Change notices, or POST /notices, and it tells that address whenever a drawing set's markups change. Several changes close together are one notice, a couple of seconds after the first.

{"event":"markups.changed","set":"set_…","file":"S-101.pdf","revision":12,
 "project":"prj_…","number":"2640","reference":"erp:job:2640",
 "server":"Acme Steel","at":"2026-09-24T19:02:11Z"}

No drawing or markup goes with it; ask the API for what you want. reference is your own reference for the job, when the project was filed under one. Each notice is signed: check X-Excalibur-Signature: sha256=<hex> against HMAC-SHA256 of the body, keyed with the secret the server showed once when the address was added. One that isn't taken is tried three times and then dropped, so keep the last revision you saw and catch up by asking. POST /notices/{id}/test sends a ping.

A Sealed server sends notices only to addresses inside your own network.

The assistant connector

Excalibur View speaks the Model Context Protocol, so an assistant that does can answer questions about a job: the takeoff, the shop list, what changed between issues. Through the office server it uses an assistant key, which reaches that connector and nothing else, and is read only. It can tell you what a drawing says; it can't change one.

In Sealed the connector is switched off entirely, because an assistant runs on somebody else's computers.

Tool chests

A chest is plain JSON in a .evtools file you own. Generate one from your own catalogue and every estimator in the shop has your sizes, your weights, your names and your colours.

{
  "format": "excalibur-view-tools",
  "version": 1,
  "name": "Our Steel",
  "columns": [
    { "slot": 0, "name": "LBS Per FT", "kind": "Number", "decimals": 3 },
    { "slot": 1, "name": "Total LBS", "kind": "Formula",
      "formula": "[Length] * [LBS Per FT]", "decimals": 1 }
  ],
  "sets": [
    { "title": "Wide Flange",
      "tools": [
        { "subject": "W14X38", "kind": "Length", "colour": "#1f4fb8",
          "columns": ["38", "", "", "", "", ""],
          "template": "<the PDF annotation, base64>" }
      ] }
  ]
}

The columns travel with the chest. In Revu, custom columns belong to the PDF or to the profile you have loaded: Save to Profile adds them "to all PDFs opened or created in Revu", and a column isn't written into the PDF until something is entered in it (Bluebeam's documentation). Here the weight column is part of the chest the tool came from, so a tool's weight always has a column to land in.

Revu profiles (.bpx) and tool sets (.btx) are read on the way in and saved as one of these. How importing works.

Things that won't change under you

  • A markup is a PDF annotation. Any PDF library reads it.
  • A tool chest is JSON you can read.
  • The API describes itself, and the build fails if a route is missing from the description.
  • The plugin contract is versioned. A plugin built for a newer one than the program knows is refused with a reason, not run and misread.
  • The source is public under the Elastic License 2.0, so you can read exactly what each of these does. github.com/cngmoney88/excalibur-view

Tell me what you're connecting

If the API or the plugin contract can't do it yet, I'd rather hear that than have you work around it.