Documentation
The API
One namespace, one permission check, and no route that reads or writes a twin without proving the caller is allowed to touch it. This is the API the interface itself uses, documented because you are entitled to know what your browser is doing.
Namespace cfct/v1. The only machine tokens are feed keys, scoped to one twin and to posting; everything else is a signed in session, and none of it is versioned for third parties.
Read this before you build anything on it
This is the application talking to itself. It is documented so you can see what the front end does, script your own account, and check that nothing is happening you did not ask for. It is not a public platform API and should not be treated as one yet.
- Authentication is your signed in session: the session cookie plus the header the application page carries in CFCT.headers. That is it
- The first exception is the feed: a key minted on the integrations screen lets a finance system post a file or a month of actuals to one twin, in the X-Counterfirm-Key header, and nothing else
- The second is provisioning: a SCIM token made on the account screen lets a company identity provider add, change and remove people on that account, as a bearer token, and cannot open a twin. There is no machine account that can read a twin
- The namespace is cfct/v1, but it is not versioned in the sense that matters to an integrator. Routes and response shapes can change in a point release, and they will
- Rate limits are plan limits on runs and sweeps per month, not request limits per second
If you want a stable integration surface, say so on the contact form. It is a reasonable thing to want and it is on the roadmap rather than in the product, and this page is not going to imply otherwise.
Calling it from the page
The app is given its own base url and session header on the application page, as a global called CFCT. From the browser console on that page, this runs a saved scenario and prints the change in operating profit against doing nothing.
const res = await fetch( CFCT.rest + '/twins/12/scenarios/48/run', {
method: 'POST',
credentials: 'same-origin',
headers: Object.assign( { 'Content-Type': 'application/json' }, CFCT.headers ),
body: JSON.stringify( { reps: 200, horizon: 12 } )
} );
const run = await res.json();
console.log( run.summary.deltas.ebitda.vs_baseline );Leave out the credentials or the header and you get a 401. The response is the full run: the median series, the p10 and p90 at every month, the aggregated events with their frequencies, and the brief.
The routes
Everything under /twins/{id} checks that the twin exists and that the caller owns it or holds a seat on it, and returns 404 or 403 before doing anything else.
| Route | Methods | What it does |
|---|---|---|
| /boot | GET | The only route that does not require a login. Returns the version, whether you are signed in, your plan and limits, the lever dictionary, the scenario templates, the fifteen agent classes, the thirteen source kinds, the metric keys and, if you have a plan, your twins. |
| /twins | GET, POST | List your twins, or create one. Creating derives the assumption ledger, builds the graph, generates agents and adds the starter scenarios in one call. |
| /twins/demo | POST | Build the worked example: the invented Harborline Components, with its sources, facts, graph, agents and scenarios. |
| /twins/{id} | GET, POST, DELETE | The overview with recent and pending jobs; change the name, industry, currency or settings; or delete the twin and everything under it. Changing and deleting are for the owner only. A new industry rebuilds the twin at once and the answer says so. |
| /twins/{id}/rebuild | POST | Re-extract, rebuild the graph, regenerate agents and refresh the ledger. Pass ai to include the optional model pass over chunks the rules could not read. Locked assumptions survive. |
| /twins/{id}/sources | GET, POST | List sources with the gaps the twin still has, or add one. POST takes either a multipart file or a text field for pasted notes, plus a kind and a title. |
| /twins/{id}/sources/{sid} | GET, DELETE | One source with a preview of its first chunk, or delete it. Deleting removes the file from disk along with its chunks and its facts. |
| /twins/{id}/sources/{sid}/file | GET | The file as it was uploaded, as a download. The folder it sits in refuses web requests, so this is the only way to fetch it. |
| /twins/{id}/graph | GET | The whole knowledge graph: nodes, edges, types, and which nodes were inferred rather than read. |
| /twins/{id}/agents | GET | Every agent with counts by class, plus the six personality axes. |
| /twins/{id}/agents/{aid} | GET, POST | One agent, or edit it. Name, role, summary, objectives, constraints, personality, parameters, and whether it is enabled at all. |
| /twins/{id}/assumptions | GET | The full ledger with the confidence breakdown by origin. |
| /twins/{id}/assumptions/{aid} | POST | Set one assumption, which locks it against future rebuilds, or pass unlock to hand it back to the deriver. Either way the readiness score is recomputed. |
| /twins/{id}/scenarios | GET, POST | List scenarios with the templates and the lever dictionary, or save one. Naming a template with no levers fills the levers from the template against this twin. |
| /twins/{id}/scenarios/{sid} | DELETE | Delete a scenario. Its past runs stay. |
| /twins/{id}/scenarios/{sid}/run | POST | Run it. Replications and horizon are optional and are capped by your plan. Returns the finished run with its brief. |
| /twins/{id}/runs | GET | Every run on this twin, newest first. |
| /twins/{id}/runs/{rid} | GET | One run: the series with its bands, the baseline, the aggregated events and the brief. |
| /twins/{id}/runs/{rid}/narrate | POST | Ask the configured language model to rewrite the finished brief in better English. Returns an error explaining that the engine brief stands on its own if no model is configured. The numbers are not touched. |
| /twins/{id}/sensitivity/{sid} | POST | Move each load bearing assumption to the ends of its own band, one at a time, and report how far cumulative operating profit swings. Ranked by swing. |
| /twins/{id}/compare | POST | Up to eight scenarios against one baseline on one set of seeds. |
| /twins/{id}/sweeps | GET, POST | List sweeps, or create one: a lever, a range, a number of steps, replications, a horizon and an objective. A second lever makes it a grid. |
| /twins/{id}/sweeps/{wid} | GET, DELETE | A sweep with its points so far and the best point found, or delete it. |
| /twins/{id}/sweeps/{wid}/advance | POST | Do a few more seconds of work on a sweep and return the updated curve. This is how a two thousand simulation sweep progresses without a request timing out. |
| /twins/{id}/comments | GET, POST | The discussion on one decision, analysis, scenario or run (kind and sid), with the people who can be named; POST adds a comment, and an @name sends that person a note if they can open the twin. |
| /twins/{id}/comments/{cid} | DELETE | Remove a comment: your own, or any on a twin you own. |
| /twins/{id}/goal | POST | What would it take: a goal (metric and a target or a change, or a question in words) and optionally one lever. Every lever is walked across its range on the same seeds and the smallest move that reaches the goal comes first, with the share of runs that make it, what else moves and the ledger rows it stands on. Counts as a sweep. |
| /twins/{id}/ask | GET, POST | The question history, or ask a question. Answers come from the graph, the ledger and the runs, and carry the rows that produced them. |
| /account | GET, POST | Your profile, plan, limits, usage this month and seats. POST saves profile fields and, if the site allows it, your own model key. |
| /account/export | GET | Everything held about you in one JSON document: profile, usage, seats, and every twin you own with its record, ledger, graph, agents, scenarios, runs, questions, decisions and log. Secrets are left out. |
| /account/close | POST | Close your account. With no body it closes in thirty days and cancels the plan now. With now set to true and confirm set to DELETE, everything is deleted in this request. |
| /account/reopen | POST | Keep an account that is set to close. |
| /account/security | GET | Whether two step sign in is on, how many recovery codes are left, where this account is signed in (device, a shortened address, when), and the quiet sign out limit the site sets. |
| /account/twostep/start | POST | Begin two step sign in: a new secret, returned as a key and as a QR code drawn on the server. Nothing changes until it is confirmed. |
| /account/twostep/confirm | POST | Confirm with the first code from the authenticator app. Switches it on and returns ten recovery codes, once. |
| /account/twostep/off | POST | Switch it off with a current code or a recovery code. Refused while the site requires it. |
| /account/twostep/recovery | POST | Ten new recovery codes for a current code; the old ones stop working. |
| /account/sessions/end | POST | Sign out everywhere else: every session on this account except the one making the request. |
| /tick | POST | Do a slice of queued background work, between two and twenty seconds of it. The browser calls this while you watch a job run. |
| /cron | GET | No login. Takes a key parameter which must match the site cron token, and runs a 25 second slice of the queue. This is the route an external scheduler calls on a private deployment. |
The braces are path parameters and are numeric ids: id is the twin, sid a source or scenario depending on the route, aid an agent or an assumption, rid a run, wid a sweep.
The routes added since version two
The same rule on every one: signed in, the twin exists, and the caller owns it or holds a seat on it. Where a seat is not enough the row says which role it takes.
| Route | Methods | What it does |
|---|---|---|
| /health | GET | No login. Answers ok with the version and the time, for a monitor to poll; anything else means the application is not answering. |
| /twins/{id}/intake | GET | The upload guide for this twin: what to send, in what order, the readiness score per area and the documents that would move an answer most. /intake/{sid} is the report on one file. |
| /template/{kind} | GET | A starter spreadsheet for one kind of file, with a real example row. |
| /twins/{id}/knowledge | GET | The industry profile this twin stands on, stated as a prior rather than a finding. |
| /twins/{id}/graph/{nid} | GET | One node of the graph: what it is, what it touches and how, the facts the record holds on it with the file and the line, the agent that stands for it and what the network makes of it. |
| /twins/{id}/explain | GET | The Why chain behind one number: the rule, the line, the file. |
| /twins/{id}/neural | GET, POST | The trained network and its scores, or train it again. /neural/{nid} is one node with the path its signal took. |
| /twins/{id}/sweeps/{wid}/surrogate | POST | Fit a curve to a sweep with at least eight finished points, so the rest of the range can be read between them. |
| /twins/{id}/brain | GET, POST | Earlier analyses, or ask a question that needs working out: read, planned, run inside a time budget, written up. /brain/plan shows the plan without running it. /brain/{aid} reads or deletes one. |
| /twins/{id}/decisions | GET, POST | The decision register, or record one from an analysis or a run. /decisions/{did} reads, edits or deletes it; /decisions/{did}/status moves it through review, which takes a reviewer; /decisions/{did}/outcome closes it with what happened. /decisions/pack assembles a board pack. |
| /twins/{id}/actuals | GET, POST, DELETE | The months of actuals laid on the pinned forecast, add a month, or remove one. /repin pins the baseline again; /recalibrate proposes the ledger values the actuals imply. |
| /twins/{id}/stress | GET, POST | The last stress battery, or run all fourteen shocks. /stress/scenario/{key} opens one shock as a scenario. |
| /twins/{id}/snapshots | GET, POST | Every frozen copy of the twin, or take one. /snapshots/{sid} reads or deletes it; /snapshots/{sid}/diff/{other} says what moved between two. |
| /twins/{id}/people | GET | Who holds which role on this twin. |
| /twins/{id}/record | GET | Everything that has happened on the twin, with who did it. |
| /twins/{id}/integrations | GET | Feed keys and hooks on this twin. /keys mints a key and /keys/{kid} revokes it; /hooks adds a URL and /hooks/{hid} pauses, tests or removes it. Owner only. |
| /feed/{id} | POST | A machine posting a file with a feed key rather than a session; /feed/{id}/actuals posts a month of actuals the same way. The key is scoped to one twin and can do nothing else. |
| /twins/{id}/alerts | GET, POST | The inbox, swept first if it is more than ten minutes old, or sweep now. /alerts/settings sets the rules and thresholds (reviewer); /alerts/digest previews or sends the digest; /alerts/{aid} acknowledges, snoozes, resolves or reopens one. |
| /twins/{id}/proposals | GET, POST | Open and decided proposals to change a ledger number, or make one with a reason. /proposals/{pid} approves, rejects or withdraws it, never by the person who proposed it; /proposals/rule switches four eyes on for every change an analyst makes (owner). |
| /twins/{id}/house | POST | Apply the house view to this ledger, with a snapshot first (reviewer). |
| /portfolio | GET | Every twin you own or hold a seat on, in one book: totals, runway, attention, the house view and the rates. |
| /portfolio/ask | POST | One question put to every company in the book, each answered on its own record. |
| /portfolio/house | GET, POST | The house view kept per account: the numbers about the world every company shares. |
| /portfolio/fx | GET, POST | The book's base currency and a rate per other currency, typed or fetched daily from the European Central Bank. /portfolio/fx/refresh fetches now. |
| /prefs | GET, POST | The forty four settings, saved against the account. |
| /account/seats | POST | Give somebody a seat on your twins with a role; /account/seats/{sid} changes the role or removes the seat. |
| /twins/{id}/scenarios/{sid}/watch | POST | Watch a scenario: mode actuals runs it again when new actuals arrive, weekly runs it every week, and an empty mode stops watching. A changed answer goes on the record, into notifications and out on the scenario.changed hook. |
| /twins/{id}/connectors | GET, POST | The connections on a twin with their state and last pull, and the systems on offer; POST connects one with a kind and a key or link, or returns the address to sign in at for QuickBooks and Xero. Owner only to connect. |
| /twins/{id}/connectors/{cid} | DELETE | Remove a connection: the token is revoked with the provider where it has one, and deleted here. Owner only. |
| /twins/{id}/connectors/{cid}/sync | POST | Pull now rather than wait for the day. Returns the counts brought in, or the reason it failed. |
| /notifications | GET | Your notifications, newest first, with the count not yet read. |
| /notifications/read | POST | Mark the ones named in ids as read, or all of them when ids is left out. |
| /account/sso | GET, POST, DELETE | Company sign in for your account: the domain, the provider issuer, the client ID and secret, the role new people get, and whether it is required. The secret is never returned. DELETE removes it. |
| /account/sso/verify | POST | Look for the DNS record that proves the domain and mark it proved when it is there. |
| /account/sso/scim | POST | A new SCIM token, shown once. The old one stops working. |
| /scim/v2/ServiceProviderConfig | GET | What this SCIM service supports, for the identity provider. Takes the SCIM token rather than a session. |
| /scim/v2/Users | GET, POST | The people on the account as SCIM users, filtered by userName eq, or add one. Adding gives them a seat with the role the connection names, or the one in roles. |
| /scim/v2/Users/{uid} | GET, PUT, PATCH, DELETE | One person. Setting active to false suspends the seat and ends every session; DELETE removes the seat. The account itself is never deleted from here. |
Boardroom and Enterprise for the feed, hooks, the portfolio and the API itself; Operator and above for alerts, proposals and the governance routes; Explore and above for the stress battery. A route on a plan you do not have answers 402 with the plan it needs.
Errors
Errors are JSON with a code, a message written for a human, and a status.
- 401 means you are not signed in
- 402 means you are signed in with no plan on the account, and the message is whatever the site owner set as the gate message
- 403 means either the twin is not yours, or you have used this month's runs or sweeps, or you are at your plan limit for twins or files
- 404 means the twin, source, agent, run or sweep does not exist, or does exist and belongs to someone else, which is reported the same way on purpose
- 400 means the request was empty or malformed, such as an add source call with neither a file nor any text
- 502 means the optional language model was asked for something and did not answer. The engine output is unchanged
If you want it on your own server
On Enterprise the whole application, this API included, runs as a private deployment on a server you control, with your own database behind it.