Wellbore Genius
Help
SimulationsSDK & REST API

SDK & REST API

Drop Wellbore Genius solvers into your notebook or pipeline. JSON over HTTPS, bearer-token auth, team-scoped. Mint a key under Settings → SDK API keys.

Authentication

Every request must include an Authorization header with a bearer token minted from Settings. Tokens look like dh_live_… and are shown only once at mint time. Revoke under Settings; last_used_at bumps on every successful call.

GET /api/public/sdk/v1/solver-spec

Returns the full solver-spec scoreboard — every coupling kernel's "Today" vs "Roadmap" stance. Stable JSON shape, mirrors the in-app /solver-spec page.

curl https://wellboregenius.com/api/public/sdk/v1/solver-spec \
  -H "Authorization: Bearer dh_live_..."
POST /api/public/sdk/v1/non-planar-3d/run

Drives the non-planar 3D fracture pipeline (DDM + tip kinking + optional out-of-plane tilt and stress-driven curvature) on a synthetic rectangular mesh. Returns per-step history + final mesh summary.

curl -X POST https://wellboregenius.com/api/public/sdk/v1/non-planar-3d/run \
  -H "Authorization: Bearer dh_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "halfLengthFt": 300,
    "halfHeightFt": 150,
    "elementSizeFt": 30,
    "ePrimePsi": 4500000,
    "defaultNetPressurePsi": 350,
    "defaultSigmaHPsi": 4200,
    "advanceFt": 25,
    "maxKinkDeg": 10,
    "steps": 8
  }'
Python client (thin wrapper)

A pip install downhole wrapper will ship separately. In the meantime, a dependency-free typed client (stdlib only) is bundled at /sdk/python/downhole_sdk.py. It includes typed dataclasses + Bearer auth for parent_child_analyze(). Or call the API directly:

# pip install requests
import requests

API_KEY = "dh_live_..."
BASE = "https://wellboregenius.com/api/public/sdk/v1"
H = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

def solver_spec():
    r = requests.get(f"{BASE}/solver-spec", headers=H)
    r.raise_for_status()
    return r.json()

def non_planar_3d_run(**kwargs):
    r = requests.post(f"{BASE}/non-planar-3d/run", headers=H, json=kwargs)
    r.raise_for_status()
    return r.json()

def parent_child_analyze(parents, child, reservoir, **kwargs):
    payload = {"parents": parents, "child": child, "reservoir": reservoir, **kwargs}
    r = requests.post(f"{BASE}/parent-child/analyze", headers=H, json=payload)
    r.raise_for_status()
    return r.json()

# Simulations CRUD ----------------------------------------------------------
def list_simulations(workspace_id=None, limit=200):
    params = {"limit": limit}
    if workspace_id:
        params["workspaceId"] = workspace_id
    r = requests.get(f"{BASE}/simulations", headers=H, params=params)
    r.raise_for_status()
    return r.json()["simulations"]

def create_simulation(workspace_id, name, **kwargs):
    payload = {"workspaceId": workspace_id, "name": name, **kwargs}
    r = requests.post(f"{BASE}/simulations", headers=H, json=payload)
    r.raise_for_status()
    return r.json()

def get_simulation(sim_id):
    r = requests.get(f"{BASE}/simulations/{sim_id}", headers=H)
    r.raise_for_status()
    return r.json()

def update_simulation(sim_id, **patch):
    r = requests.patch(f"{BASE}/simulations/{sim_id}", headers=H, json=patch)
    r.raise_for_status()
    return r.json()

def delete_simulation(sim_id):
    r = requests.delete(f"{BASE}/simulations/{sim_id}", headers=H)
    r.raise_for_status()
    return r.json()

if __name__ == "__main__":
    print(solver_spec()["count"], "scoreboard rows")

    np3d = non_planar_3d_run(
        halfLengthFt=300, halfHeightFt=150, elementSizeFt=30,
        ePrimePsi=4_500_000, defaultNetPressurePsi=350,
        defaultSigmaHPsi=4200, advanceFt=25, maxKinkDeg=10, steps=8,
    )
    print("np3D steps:", len(np3d["history"]), "final mesh:", np3d["finalMesh"])

    pc = parent_child_analyze(
        parents=[
            {"id":"P1","heel":{"x":0,"y":660},"toe":{"x":10000,"y":660},"drawdownPsi":1000},
            {"id":"P2","heel":{"x":0,"y":-660},"toe":{"x":10000,"y":-660},"drawdownPsi":1000},
        ],
        child={"id":"C1","heel":{"x":0,"y":0},"toe":{"x":10000,"y":0},"stageCount":50},
        reservoir={"biotAlpha":0.85,"poissonRatio":0.22},
    )
    print("parent-child worst stage:", pc["summary"]["worstStageId"])

    sim = create_simulation("default", "bakken_baseline", tags=["history-match"])
    update_simulation(sim["simulation"]["id"], status="completed",
                      runtimeSeconds=1843, results={"peakBhpPsi": 9120})
    print("simulations on team:", len(list_simulations()))
PowerShell (Windows)

Dependency-free scripts driven by built-in Invoke-RestMethod. Set $Env:DOWNHOLE_API_KEY once, then run:

PowerShell SDK — request & response schemas

Every PowerShell example script hits one of the endpoints below. Field types, ranges, defaults, and required/optional flags are enforced server-side by Zod — a body that violates any rule comes back as 400 { error: { code: "bad_request", message } } with the exact failing path.

GET /api/public/sdk/v1/solver-spec (Get-SolverSpecReport, Invoke-SolverSpec; MATLAB: getSolverSpecReport.m)

No request body. Response is a stable envelope; the CSV writer in Get-SolverSpecReport.ps1 pins the column order.

// 200 OK
{
  "scoreboard": [
    {
      "id": string,                              // e.g. "perf-erosion-clearance"
      "title": string,
      "status"?: "shipped" | "next-up",          // omitted ≡ "shipped"
      "coupling"?: string,                       // e.g. "Two-way, in Newton loop"
      "newtonWired"?: boolean,
      "newtonWiredDetail"?: string,
      "today": string,                           // 1-2 sentence "what runs today"
      "roadmap": string                          // 1-2 sentence "what's next"
    }
  ],
  "count": number,                               // = scoreboard.length
  "generatedAt": string                          // ISO 8601 UTC
}

POST /api/public/sdk/v1/parent-child/analyze (Invoke-ParentChildAnalyze)

Body — all objects .strict():

{
  "parents": [                                   // 1..20 entries
    {
      "id": string,                              // 1..64 chars, required
      "label"?: string,                          // ≤128 chars
      "heel": { "x": number, "y": number },      // ft, finite
      "toe":  { "x": number, "y": number },      // ft, finite
      "drawdownPsi": number,                     // 0..20 000
      "drainageRadiusFt"?: number                // >0, ≤20 000
    }
  ],
  "child": {
    "id": string,                                // 1..64 chars
    "label"?: string,
    "heel": { "x": number, "y": number },
    "toe":  { "x": number, "y": number },
    "stageCount": integer                        // 1..200
  },
  "reservoir": {
    "biotAlpha": number,                         // 0..1.5
    "poissonRatio": number                       // 0.01..0.49
  },
  "asymmetrySensitivity"?: number                // 0..10, default engine value
}

Response (200 OK):

{
  "stages": [
    {
      "stageIndex": integer,                     // 0-based
      "stageLabel": string,                      // "Stage N", 1-based
      "midpoint": { "x": number, "y": number },  // ft
      "depletionPsi": number,                    // psi
      "dSigmaHPsi": number,                      // psi
      "asymmetryPct": number,                    // -100..+100
      "nearestParentId": string | null,
      "nearestParentFt": number,                 // ft
      "bashingRisk": "low" | "watch" | "high"
    }
  ],
  "summary": {
    "highCount": integer,
    "watchCount": integer,
    "lowCount": integer,
    "worstStageId": string | null,               // stageLabel of max Δp
    "worstDepletionPsi": number,
    "meanDepletionPsi": number,
    "meanDSigmaHPsi": number
  },
  "generatedAt": string
}

POST /api/public/sdk/v1/non-planar-3d/run (Invoke-NonPlanar3dRun; MATLAB: runNonPlanar3d.m)

{
  "halfLengthFt": number,                        // >0, ≤5 000
  "halfHeightFt": number,                        // >0, ≤2 000
  "elementSizeFt": number,                       // >0, ≤200
  "ePrimePsi": number,                           // >0, ≤50 000 000
  "defaultNetPressurePsi"?: number,              // -5 000..20 000, default 300
  "defaultSigmaHPsi"?: number,                   // 0..30 000,  default 3 500
  "advanceFt": number,                           // >0, ≤500
  "maxKinkDeg": number,                          // >0, ≤45
  "steps": integer,                              // 1..50
  "sigmaHMaxAzimuthRad"?: number,                // any finite; constant across steps
  "outOfPlaneTiltDeg"?: number,                  // -45..45
  "stressDrivenCurvatureEnabled"?: boolean,
  "maxCurvatureDegPerFt"?: number                // >0, ≤10 (only if stress-driven enabled)
}

Response (200 OK):

{
  "history": [                                   // length = steps
    {
      "step": integer,
      "advanceFt": number,
      "kinkAppliedDeg": number,
      "meanApertureIn": number,
      "meanNetPressurePsi": number,
      "tipCount": integer
    }
  ],
  "finalMesh": {
    "vertexCount": integer,
    "faceCount": integer,
    "boundingBoxFt": {
      "min": { "x": number, "y": number, "z": number },
      "max": { "x": number, "y": number, "z": number }
    }
  },
  "generatedAt": string
}

POST /api/public/sdk/v1/marketplace/bundle (Export-MarketplacePresets, Import-MarketplacePresets; MATLAB: exportMarketplacePresets.m, importMarketplacePresets.m)

Accepts a single envelope, a bare array, or a bundle object — all three shapes are normalized to { version: 1, entries }.

// Any of these three request shapes:
{ "version": 1, "entries": [ envelope, envelope, ... ] }   // ≤500 entries
[ envelope, envelope, ... ]                                // bare array, ≤500
envelope                                                   // single object

// envelope shape (all objects .strict()):
{
  "version": 1,                                  // literal 1
  "id": string,                                  // 1..200 chars
  "kind":                                        // enum
    | "basin.parent-child"
    | "basin.pressure-advisor"
    | "vendor.slurry"
    | "vendor.breaker"
    | "vendor.proppant"
    | "calibration.net-pressure-knobs"
    | "calibration.dfit"
    | "other",
  "title": string,                               // 1..200 chars
  "description"?: string,                        // ≤2 000, default ""
  "basin"?: string,                              // ≤120
  "vendor"?: string,                             // ≤120
  "tags"?: string[],                             // ≤32 items, each 1..60
  "author": string,                              // 1..120
  "publishedAtIso": string,                      // 1..64
  "payload": unknown                             // kind-specific; opaque to server
}

Response (200 OK):

{
  "bundle": { "version": 1, "entries": envelope[] },   // canonical, id-deduped, ≤500
  "report": {
    "input": integer,                            // # candidates received
    "accepted": integer,                         // # kept in bundle.entries
    "rejected": integer,                         // # failed envelope validation
    "deduped": integer,                          // # dropped as duplicate id
    "overflow": integer                          // # trimmed by the 500 cap
  },
  "rejected": [ { "index": integer, "reason": string } ],
  "generatedAt": string
}

Common error envelope

// 400 bad_request | 401 unauthorized | 500 internal_error
{
  "error": {
    "message": string,                           // Zod flatten() JSON on 400
    "code": "bad_request" | "unauthorized" | "internal_error" | "analyzer_error" | null
  }
}

CORS: every endpoint answers OPTIONS with 204 and the standard Access-Control-Allow-* headers, so browser callers work too.

MATLAB / Simulink

MATLAB ↔ Wellbore Genius runs over the Python SDK via py.* (no MEX, no Java). Works on R2019b+ across Linux / macOS / Windows.

  • /sdk/matlab/README.md pyenv setup + hello-solver.
  • Repository examples under examples/matlab/: run_parent_child_example.m, run_fracpro_export_example.m, run_end_to_end_example.m, and the reusable simulink_parent_child_block.m.
  • Zero-dependency webread scripts that mirror the PowerShell workflow: setDownholeApiKey.m (auth, mirrors Set-DownholeApiKey.ps1) and getSolverSpecReport.m (GET /solver-spec + JSON/CSV writer + FailOnNextUp CI gate, mirrors Get-SolverSpecReport.ps1 — CSV columns byte-comparable).
POST /api/public/sdk/v1/parent-child/analyze

Runs the analytical parent–child interference engine. Returns per-stage Δp, Δσ_h, asymmetry %, nearest parent, and bashing-risk chip — plus the rolled-up summary.

curl -X POST https://wellboregenius.com/api/public/sdk/v1/parent-child/analyze \
  -H "Authorization: Bearer dh_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "parents": [
      {"id":"P1","heel":{"x":0,"y":660},"toe":{"x":10000,"y":660},"drawdownPsi":1000},
      {"id":"P2","heel":{"x":0,"y":-660},"toe":{"x":10000,"y":-660},"drawdownPsi":1000}
    ],
    "child": {"id":"C1","heel":{"x":0,"y":0},"toe":{"x":10000,"y":0},"stageCount":50},
    "reservoir": {"biotAlpha":0.85,"poissonRatio":0.22}
  }'

Response schema (200 OK)

stages[] — one entry per child stage, ordered heel→toe:

  • stageIndex (number) — 0-based index.
  • stageLabel (string) — 1-based UI label, e.g. "Stage 12".
  • midpoint ({x, y}) — stage centroid in plan view [ft].
  • depletionPsi (number) — depletion-induced Δp at the stage [psi].
  • dSigmaHPsi (number) — Eaton Δσ_h from depletion [psi].
  • asymmetryPct (number, −100..+100) — signed half-length skew toward the more-depleted side.
  • nearestParentId (string | null) — id of closest parent, or null if none.
  • nearestParentFt (number) — perpendicular distance to nearest parent [ft].
  • bashingRisk ("low" | "watch" | "high") — frac-hit severity chip.

summary — roll-up across all stages:

  • highCount, watchCount, lowCount (number) — stages in each risk bucket.
  • worstStageId (string | null) — stageLabel of the highest-Δp stage.
  • worstDepletionPsi (number) — max Δp across stages [psi].
  • meanDepletionPsi (number) — mean Δp across stages [psi].
  • meanDSigmaHPsi (number) — mean Δσ_h across stages [psi].

Example response

{
  "stages": [
    {
      "stageIndex": 0,
      "stageLabel": "Stage 1",
      "midpoint": { "x": 100, "y": 0 },
      "depletionPsi": 312.4,
      "dSigmaHPsi": 187.6,
      "asymmetryPct": 4.2,
      "nearestParentId": "P1",
      "nearestParentFt": 660,
      "bashingRisk": "watch"
    },
    {
      "stageIndex": 1,
      "stageLabel": "Stage 2",
      "midpoint": { "x": 300, "y": 0 },
      "depletionPsi": 540.1,
      "dSigmaHPsi": 324.0,
      "asymmetryPct": -8.7,
      "nearestParentId": "P2",
      "nearestParentFt": 660,
      "bashingRisk": "high"
    }
  ],
  "summary": {
    "highCount": 1,
    "watchCount": 1,
    "lowCount": 0,
    "worstStageId": "Stage 2",
    "worstDepletionPsi": 540.1,
    "meanDepletionPsi": 426.25,
    "meanDSigmaHPsi": 255.8
  }
}
Simulations CRUD

Team-scoped simulation rows. Same shape used by the in-app Cloud simulations page. Status must be one of not_yet_submitted, queued, running, completed, failed, cancelled.

# List
curl https://wellboregenius.com/api/public/sdk/v1/simulations \
  -H "Authorization: Bearer dh_live_..."

# Create
curl -X POST https://wellboregenius.com/api/public/sdk/v1/simulations \
  -H "Authorization: Bearer dh_live_..." \
  -H "Content-Type: application/json" \
  -d '{"workspaceId":"default","name":"bakken_baseline","tags":["history-match"]}'

# Retrieve (includes results JSON)
curl https://wellboregenius.com/api/public/sdk/v1/simulations/<id> \
  -H "Authorization: Bearer dh_live_..."

# Update status / attach results
curl -X PATCH https://wellboregenius.com/api/public/sdk/v1/simulations/<id> \
  -H "Authorization: Bearer dh_live_..." \
  -H "Content-Type: application/json" \
  -d '{"status":"completed","runtimeSeconds":1843,"results":{"peakBhpPsi":9120}}'

# Delete
curl -X DELETE https://wellboregenius.com/api/public/sdk/v1/simulations/<id> \
  -H "Authorization: Bearer dh_live_..."
Roadmap
  • POST /sensitivity/run — OFAT factor sweeps
  • Published pip install downhole with typed dataclasses