INTEGRATIONS
Scripting SDK
Reference for Converge scripts - the converge object, every engine function, print, table and plot, units, limits and a worked example.
Overview
Scripts let you run any Converge engine from JavaScript inside the app, loop over cases, and show the results as tables and plots. You write them on Integrations > Scripts. Each run executes in a fresh, sandboxed Web Worker in your browser, with no network access, and the worker is destroyed when the run ends.
The same engine functions are available over the REST API and to an MCP client, with the same inputs and outputs.
How a script runs
Your script is the body of an async function, so you can use await at the top level and return a value. These names are in scope:
| Name | What it is |
|---|---|
converge |
The SDK: one namespace per module, plus helpers. |
print(...args) |
Writes a line to the output. Same as console.log. |
console |
log, info, warn, error, debug. |
table(data, title?) |
Adds a table tab to the output. |
plot(series, options?) |
Adds a plot tab to the output. |
A return value is shown when the run ends. Every engine call is synchronous and returns plain data (objects, arrays, numbers).
Run with the run button or Ctrl/Cmd + Enter. Choose Time limit (5, 10, 30 or 60 s; 10 s by default). Save to project stores the script in the current project, up to 100 scripts per project; saved scripts are kept in project snapshots and exports.
The converge object
Engine functions
Every engine function has the form:
converge.<namespace>.<function>(input, options?)input: one object. Unknown keys are an error that lists the valid keys.options.units:'field'(default) or'metric', for this call only.
An invalid input throws an Error whose message names each bad field, for example converge.well.nodal: invalid input (fluid.api: ...).
Helpers
| Signature | Returns |
|---|---|
converge.setUnits(units) |
Sets 'field' or 'metric' for the rest of the run. Returns the unit system. |
converge.units() |
The current unit system for the run. |
converge.functions() |
Array of { name, module, title } for every engine function. |
converge.describe(name) |
{ name, module, title, description, inputSchema, example }. inputSchema is JSON Schema. |
converge.example(name) |
A copy of the documented example input for name, ready to modify and pass back. |
describe and example take the full dotted name, for example converge.example('well.nodal'). They throw for an unknown name.
Units
Field units by default: psia, degF, ft, STB/d, Mscf/d, scf/STB. Pass { units: 'metric' } as the second argument, or call converge.setUnits('metric') once, to send and receive bara, degC, m, sm3/d and sm3/sm3. The main outputs come back in the same system.
converge.setUnits('metric');
const z = converge.pvt.gasProperties({ psia: 200, TF: 90, gasSG: 0.7 }); // 200 bara, 90 degCA metric result carries a units object: units.system is 'metric' and units.converted lists the output keys that were converted, with their unit. Keys not listed are still in field units.
Function reference
Inputs listed are the main keys. Call converge.describe(name) for the full schema, defaults and limits.
pvt (Black-oil PVT)
| Function | Main inputs | Returns |
|---|---|---|
pvt.blackOilTable(input) |
fluid (black-oil: api, gasSG, gor scf/STB, reservoirT degF, optional waterSalinity ppm, pbMeasured psia); pressures psia or range { pMin, pMax, n }; optional TF |
pb psia, rows (Rs, Bo, oil viscosity, density, co, Bg, Z, gas viscosity, water properties, IFTs), warnings |
pvt.gasProperties(input) |
psia, TF, gasSG |
Z (Sutton + Dranchuk-Abou-Kassem), Bg rb/scf, viscosity cp (Lee-Gonzalez-Eakin), density lbm/ft3 |
pvt.waterProperties(input) |
psia, TF, salinityPpm (default 30,000) |
McCain water FVF, viscosity, density |
eos (Compositional PVT)
fluid is { kind: 'eos', eos, components, plus, split, ... } or { kind: 'spe5' } for the SPE5 benchmark oil. eos is 'PR', 'PR78' (default) or 'SRK'.
| Function | Main inputs | Returns |
|---|---|---|
eos.summary(input) |
fluid, reservoirTF, optional separator stages |
Saturation pressure, fluid type, separator test, CGR for gases, component table |
eos.flash(input) |
fluid, psia, TF |
Vapour fraction, x/y, K-values, phase properties |
eos.envelope(input) |
fluid, qualities (vapour fractions, default none) |
Bubble and dew branches, critical point, cricondenbar, cricondentherm, quality lines |
eos.blackOilTable(input) |
fluid, TF, pMax, optional separator |
Separator-corrected black-oil table, pb, API, gas SG, separator GOR, warnings |
well (Well performance)
| Function | Main inputs | Returns |
|---|---|---|
well.ipr(input) |
kind ('oil' or 'gas'), pr psia, Tres degF, ipr model, fluid, points (default 40) |
IPR curve, AOF, total skin and its parts for Darcy-type models |
well.vlp(input) |
well case, fluid, optional rates |
Flowing bottomhole pressure per rate, VLP minimum |
well.nodal(input) |
well case, fluid |
IPR, VLP, every intersection with stability, operating point, wellhead pressure, traverse |
mbal (Material balance)
| Function | Main inputs | Returns |
|---|---|---|
mbal.analyse(input) |
fluid, tank, history rows |
Havlena-Odeh terms, aquifer influx We bbl, diagnostic plots, drive indices when N or G is given |
mbal.historyMatch(input) |
fluid, tank, history, optional fitM, samples (default 150), seed |
Best-fit in-place volume (and m, aquifer), residuals, pressure match, P90/P50/P10 |
dca (Decline & RTA)
points are { t, q } with t in days since first production and q in the rate basis. Nominal declines are 1/day. rateUnit is 'STB/d' (default), 'Mscf/d' or 'sm3/d'.
| Function | Main inputs | Returns |
|---|---|---|
dca.fit(input) |
kind ('arps' default, 'modified-hyperbolic', 'duong', 'sepd'), points, optional excluded, autoOutliers, bBounds, terminalEffectiveAnnual, qEconomic, tMaxDays, rateUnit |
Fitted model, fit statistics; with qEconomic, eur with eurUnit and the time cap (default 50 years) |
dca.compare(input) |
points, qEconomic, optional tMaxDays, rateUnit |
Every model ranked by AIC with its EUR, R2 and warnings |
dca.forecast(input) |
model, times days, or tMaxDays (default 3,652.5) and stepDays (default 30.4375) |
Rate and cumulative at each time |
dca.eur(input) |
model, qEconomic, optional tMaxDays (at most 100 years), t, rateUnit |
EUR, time at the limit, rate at t; a warning when the limit is at or above qi |
welltest (Well test)
| Function | Main inputs | Returns |
|---|---|---|
welltest.synthetic(input) |
reservoir, pi psia, q STB/d, tpHr, shutInHr, pointsPerPeriod (default 40), boundary |
Drawdown and build-up pressure record, plus the build-up ready for the next two functions |
welltest.horner(input) |
dtHr, pws, tpHr, pwf, fluid { q, B, mu, h, phi, ct, rw }, optional window |
k md, skin, p* and p1hr psia, skin pressure drop, flow efficiency, radius of investigation, r2 |
welltest.derivative(input) |
tHr, dp, smoothing (default 0.1) |
Pressure change and Bourdet derivative against time |
sim (Simulation)
| Function | Main inputs | Returns |
|---|---|---|
sim.run(input) |
preset ('spe1', 'buckley-leverett', 'tank') or your own case; optional cells, days, timeLimitMs (default 20,000) |
Field and well rates and cumulatives per report step, average pressure, initial in-place, run statistics, warnings |
sim.buckleyLeverett(input) |
sw, krw, kro, muW, muO cp |
Front saturation, average saturation behind the front, breakthrough pore volumes |
network (Network)
| Function | Main inputs | Returns |
|---|---|---|
network.solve(input) |
Optional network { nodes, branches } (default: built-in four-well example), fluid, maxIter |
Node pressures, branch rates, constraint checks, totals |
integrated (Integrated forecast)
| Function | Main inputs | Returns |
|---|---|---|
integrated.run(input) |
Optional case (default: built-in example), fluid, endDays |
Forecast per step, plateau length days, EUR, end reason |
econ (Economics)
| Function | Main inputs | Returns |
|---|---|---|
econ.metrics(input) |
cashFlows per period, discountRates (default [0.1]), periodYears (default 1), discounting (default 'mid-period') |
NPV per rate, IRR (null when the cash flow never changes sign), payback years |
econ.cashFlow(input) |
case: the Economics data model (production, prices, opex, capex, fiscal, discountRates, ...) |
Full cash flow, taxes, government take, metrics, warnings. See Economics. |
data (Data)
These two run under the Integrations licence.
| Function | Main inputs | Returns |
|---|---|---|
data.parseTable(input) |
text (CSV, tab, semicolon or space separated), optional delimiter |
Row count, first 20 rows, detected columns, units, date order, frequency and layout |
data.validate(input) |
asOf ISO date, wells (each with id, name, type, frequency, optional initialPressure, bubblePoint psia, and records) |
Validation report: each issue with severity and suggested fix |
table and plot
table(data, title?)
data can be:
- an array of objects (columns are the union of the keys),
- an array of arrays (columns
col1,col2, ...), { columns, rows },- a plain object (shown as key / value rows).
Nested values are shown as compact JSON, cut at 120 characters.
plot(series, options?)
series is { name?, x, y } or an array of them; plot(x, y, options?) with two number arrays also works. x and y must have the same length. Pairs with a null or non-finite value are skipped.
options: title, xTitle, yTitle, logX, logY.
Limits
| Limit | Value |
|---|---|
| Time limit | 10 s default, 60 s maximum |
| Script length | 100,000 characters |
| Output lines | 2,000 per run; each line cut at 20,000 characters |
| Tables | 20 per run, 5,000 rows each |
| Plots | 20 per run, 20,000 points in total per plot |
When the time limit is reached, the worker is terminated, which stops even an endless loop.
Sandbox rules
- No
importanywhere in the script, not even inside a string or a comment. Everything you need is onconverge. - No network or storage:
fetch,XMLHttpRequest,WebSocket,indexedDB,navigatorand similar are removed. - No code built from strings:
eval,Functionand string handlers forsetTimeoutandsetIntervalare removed. - Nothing survives between runs.
Seats
When you are signed in, the page takes the licence seat for each module a script uses before it runs, and returns the seats when the run ends. It finds them by reading converge.<namespace> in your code. Write calls out literally: a namespace reached another way (for example through a variable) throws converge.<ns> is not available in this run.
Worked example
This script fits a decline to two years of monthly rates, ranks every model, and plots a five-year forecast. It is one of the built-in examples.
// Monthly rates (days, STB/d). Replace with your own.
const points = [];
for (let i = 0; i < 24; i++) {
points.push({ t: i * 30.4, q: 1200 / Math.pow(1 + 0.9 * 0.004 * i * 30.4, 1 / 0.9) * (1 + 0.03 * Math.sin(i)) });
}
const fit = converge.dca.fit({ kind: 'arps', points, qEconomic: 20 });
const m = fit.model;
print(`Arps: qi = ${m.qi.toFixed(0)} STB/d, Di = ${m.Di.toFixed(5)} 1/d, b = ${m.b.toFixed(2)}, R2 = ${fit.r2Rate.toFixed(3)}`);
print('EUR to 20 STB/d:', Math.round(fit.eur.eur).toLocaleString(), 'STB');
const ranked = converge.dca.compare({ points, qEconomic: 20 });
table(ranked.map((r) => ({ model: r.kind, 'EUR (STB)': Math.round(r.eur), AIC: r.aic, 'R2 rate': r.r2Rate })), 'Models ranked by AIC');
const f = converge.dca.forecast({ model: fit.model, tMaxDays: 5 * 365.25, stepDays: 30.4 });
plot(
[
{ name: 'data', x: points.map((p) => p.t), y: points.map((p) => p.q) },
{ name: 'forecast', x: f.t, y: f.q },
],
{ title: 'Rate and forecast', xTitle: 'Time (days)', yTitle: 'Rate (STB/d)', logY: true },
);A second pattern: start from a documented example and vary one input.
const base = converge.example('well.nodal');
const rows = [];
for (const wct of [0, 0.2, 0.4, 0.6, 0.8]) {
const well = { ...base.well, ratios: { ...base.well.ratios, wct } };
const op = converge.well.nodal({ well, fluid: base.fluid }).nodal.operating;
rows.push({ 'water cut': wct, 'liquid (STB/d)': op ? op.q : null, 'pwf (psia)': op ? op.pwf : null });
}
table(rows, 'Operating point vs water cut');