SDK Docs
vertical-ai-sdk reference
Everything the /sdk overview page doesn't have room for — every domain client, every StandardsPack shape, the async client, error handling, and the gaps we haven't closed yet.
Getting the SDK
The SDK is shared with API customers on request; it isn't on the public package index. Email hello@vaico.io and we'll send the current release with your key. Python 3.11 or newer. It depends only on pydantic and httpx — no compute engine ships client-side, every method is a real HTTP call to the API. YAML pack files need PyYAML (the [yaml] extra); there are no other extras.
Quickstart
from vertical_ai_sdk import Client
with Client(api_key="vaico_live_...") as client:
result = client.overhead.nesc.grade(inp, source_ref="my-tool")
# a typed GradeResult, not a dict -- result.grade, result.rule_refs, result.provenanceUse with Client(...) as client: so the underlying HTTP connection closes when you're done, or call client.close() yourself in a longer-lived process.
Domains
Every API-key route on the API has a typed method — 308 across 18 domains, listed below by the part of the system they serve (the same groups as /domains). They're generated from the live API schema, so new routes show up in the SDK without anyone hand-wrapping them. See /docs for the REST reference.
| client. | scope | routes | covers |
|---|---|---|---|
| Overhead Lines — Distribution and transmission lines: compliance, structures, and where to route them. | |||
| overhead | overhead (+ nesc, go95, csa) | 39 | NESC / GO-95 / CSA clearance, grade of construction, loading, sag-tension, anchors, poles, grounding, vegetation |
| utm | utm | 50 | Transmission structures: pole capacity, crossarms, guying, galloping, reference lookups, staged line design with verified fixes· Early Access |
| siting | siting | 5 | GIS least-cost corridor and route siting for new lines· Early Access |
| replacement_cost | replacement-cost | 3 | Planning-level pole replacement costs and RUS compatible-unit bills of materials |
| Pole Design & Field Data — Read and write the pole-design tools' own file formats, and turn field capture into engineering data. | |||
| ocalc | ocalc | 7 | O-Calc PPLX / .pplld parse and generate |
| spida | spida | 4 | SPIDAcalc project JSON parse and generate |
| pls | pls | 2 | PLS-POLE .pol / .loa parse |
| field_capture | field-capture | 9 | Drone-capture ingestion, classification, fault detection· Early Access |
| Underground — Underground distribution: vaults, conduit, cable, and riser transitions. | |||
| underground | uvm | 32 | Conduit fill and pulling, ampacity and derating, vaults, risers, duct banks, fault withstand, verified fixes |
| Substations & Generation — Station clearances and grounding, generation and DER interconnection, and battery storage. | |||
| usm | usm | 31 | Substation clearances, grounding and bonding, staged design with verified fixes· Early Access |
| ugm | ugm | 24 | Generation and DER interconnection, staged plant design with verified fixes· Early Access |
| der | der | 6 | Battery energy storage sizing and duration |
| Circuits, Buildings & Accessibility — The electrical fundamentals every other domain leans on, plus the building and sidewalk edges of the system. | |||
| circuit | circuit | 27 | Fault current, conductor and cable ampacity, voltage drop, grounding grids, flicker, phase balance, sheath bonding |
| ubm | ubm | 14 | Building electrical, with verified fixes· Early Access |
| ada | ada | 27 | PROWAG public right-of-way accessibility checks, with verified fixes· Early Access |
| AI-Factory & Datacenter — Cooling, redundancy, and equipment data for AI-factory design. | |||
| thermal | thermal | 12 | Liquid and air cooling, fluid sizing |
| controls | controls | 8 | Redundancy and availability math, valve-actuator sizing |
| datacenter | datacenter | 8 | AI-factory equipment reference data |
Two styles of method
Generated methods (all domains) take one typed request model — or a plain dict — and return a typed result. GET routes take keyword arguments, and file-upload routes take bytes or a Path. Hand-written methods (overhead, underground, circuit, ADA) add friendlier signatures and the jurisdiction facades, and override the generated method of the same name; every other route in those domains still works through the generated one.
from vertical_ai_sdk.der._generated import CalculateStateOfChargeRequest
soc = client.der.calculate_state_of_charge(
CalculateStateOfChargeRequest(present_energy_kwh=50.0, usable_energy_capacity_kwh=100.0, source_ref="my-tool")
) # soc.soc_percent == 50.0
client.utm.lookup_pole_class_horizontal_load(pole_class="H6") # GET routes: keyword arguments
client.spida.parse_spida_file(file=Path("project.json")) # uploads: bytes or Pathclient.overhead — jurisdiction facades
Facades narrow the domain to one code and pre-fill the shared jurisdiction parameter:
client.overhead.nesc.grade(inp, source_ref="...") # NESC Rule 242
client.overhead.go95.grade(inp, source_ref="...") # GO-95 Rule 42, aliased .cpuc
client.overhead.csa.grade(inp, source_ref="...") # CSA C22.3 No.1 Table 27
client.overhead.csa.grade_proximity(inp, source_ref="...") # Table 28
client.overhead.csa.grade_joint_use(inp, source_ref="...") # Table 29Physics that was never jurisdiction-exclusive (sag/tension, anchor strength, line balance, grounding, vegetation, ROW width, setting depth) stays on client.overhead directly.
client.underground
Conduit fill, jam ratio and pulling tension, ampacity and derating, vault sizing and working clearances, riser transitions, fault-current withstand and duct-bank design. One real asymmetry worth knowing: the source harness's own routes are declared as plain dictionaries, so every method here returns a dict[str, Any] instead of a typed model — that's the real server contract, not a shortcut. A number of other routes across the API are the same, and return the parsed JSON.
client.circuit
Hand-written methods for conductor and underground-cable ampacity, voltage drop, transformer hottest-spot, available fault current, grounding-grid resistance and ground potential rise; the remaining circuit routes (soil-model fits, mesh and step voltage, relay trip time, flicker, phase balance, sheath bonding, line constants) use the generated methods. CircuitStandardsPack adds your own ampacity, voltage-drop and transformer-loading limits.
client.ada
All 26 PROWAG routes. No jurisdiction facades — PROWAG is one federal standard, not several. Every result carries its own regulatory_status (a federal legal fact computed server-side), separate from anything a StandardsPack would customize.
StandardsPack
Generic physics and cited regulatory floors are computed server-side. A utility's own proprietary layer on top of that is a pack you write and run entirely on your own machines — it never touches the network. Packs exist for overhead, underground, circuit and ADA. Five shapes, each grounded in a real gap or a production standards schema:
1. Margin on a code floor
NESC/GO-95/CSA clearances and tension limits stay legally fixed — you add a conservative margin on top, you don't override the floor itself.
from vertical_ai_sdk.overhead.standards import OverheadStandardsPack
my_standard = OverheadStandardsPack(clearance_margin={"primary_over_road": 0.5})
verdict = my_standard.classify_clearance(required=result.required, actual=result.required, margin_key="primary_over_road")
# verdict.effective_minimum is NESC's floor + 0.5m, not the raw code minimum2. Stricter-than-code limits
Grounding resistance, vegetation clearance, conductor ampacity and voltage drop. Your published number applies only when it's tighter than the code or computed value; a looser table can never weaken the floor.
from vertical_ai_sdk.overhead.standards import GroundingLimitOverride, OverheadStandardsPack
pack = OverheadStandardsPack(grounding_limit_overrides={
OverheadStandardsPack.grounding_limit_key("transformer_pole"):
GroundingLimitOverride(application="transformer_pole", max_resistance_ohm=10.0, source="Standard 4.2"),
})
verdict = pack.classify_grounding_resistance(15.0, "transformer_pole")
# verdict.compliant is False: 15 ohms passes the 25-ohm code limit but fails your own 103. Utility-table substitution
Setting depth and anchor pull-out capacity work differently from a margin: a published table entry replaces the server's computed value outright. Both close real, disclosed gaps named directly in production engineering code but never built server-side.
from vertical_ai_sdk.overhead.standards import OverheadStandardsPack, PoleSettingDepthOverride
my_standard = OverheadStandardsPack(setting_depth_overrides={
OverheadStandardsPack.setting_depth_override_key(40.0, "average_soil"):
PoleSettingDepthOverride(height_ft=40.0, soil_condition="average_soil", depth_ft=6.5, source="Construction Standard Sec. 3.2"),
})
ansi = client.overhead.derive_setting_depth(height_ft=40.0, species="douglas_fir", source_ref="...")
verdict = my_standard.classify_setting_depth(ansi, height_ft=40.0, soil_condition="average_soil")
# verdict.effective_setting_depth_ft is your own 6.5ft -- not an "ANSI + X" margin, a direct substituteclassify_anchor_capacity works the same way against calculate_anchor_holding_capacity.
4. Pure reference data
Framing assemblies, angle limits by conductor/insulator/framing combination, span-length policy, sag-tension table rows, transformer loading limits — data with no code anchor at all. Defaults to "no limit configured," never inventing a threshold on your behalf.
pack = OverheadStandardsPack(max_span_ft={"336 ACSR": 300.0})
verdict = pack.classify_span("336 ACSR", span_ft=350.0)
# verdict.compliant is False -- 350ft exceeds your own 300ft policy5. Jurisdiction edition tracking
Which edition of a code your own AHJs enforce, and what they've locally amended — your own operational knowledge that no domain API can know on your behalf. Lives on the base StandardsPack class, so every domain pack inherits it.
from vertical_ai_sdk.standards import JurisdictionEdition, LocalAmendment, StandardsPack
pack = StandardsPack(
edition_registry={"CA-Example City": {"NEC": JurisdictionEdition(edition_adopted="2023", has_local_amendments=True)}},
local_amendments={"CA-Example City": [LocalAmendment(clause="Table 110.26(A)(1) Condition 2", value=48.0, unit="in", source="Ordinance 9-1")]},
)
pack.resolve_adoption_status("CA-Example City", "NEC", loaded_edition="2023") # -> ADOPTED_WITH_LOCAL_AMENDMENTSPack files
A pack's data can live in a JSON or YAML file instead of Python, so a standards group can maintain it without writing code. Unknown keys are rejected — a typo like clearence_margin raises an error instead of silently configuring nothing. Files carry data only; overriding a classify_* method still means subclassing in Python.
pack = OverheadStandardsPack.from_file("our_standards.yaml") # .json, .yaml or .yml
pack.to_file("backup.json")
schema = OverheadStandardsPack.json_schema() # validate uploads, drive an editorAsync
import asyncio
from vertical_ai_sdk import AsyncClient
async def main():
async with AsyncClient(api_key="vaico_live_...") as client:
soc, limit = await asyncio.gather(
client.der.calculate_state_of_charge(request),
client.utm.lookup_pole_class_horizontal_load(pole_class="H6"),
)AsyncClient has the same 18 domain attributes, the same typed methods (each one async def), the same retry policy and the same exceptions as Client; only the I/O is awaited. The hand-written ergonomic methods and the jurisdiction facades are sync-only — every route is still reachable asynchronously through the generated methods. A StandardsPack is plain data and works with either client.
Errors & retries
Every non-2xx response raises a subclass of VerticalAiApiError, carrying the real status code and response body. Catch the base class to handle any API failure, or a subclass to react to one case:
from vertical_ai_sdk import RequestValidationError, ScopeError, TransportError
try:
client.der.calculate_charge_duration({})
except RequestValidationError as exc:
for err in exc.errors: # FieldError(loc="body.charge_power_kw", message="Field required")
print(err.loc, err.message)
except ScopeError: # 403: the key is valid but its scopes don't reach this route
...
except TransportError: # no response at all, after the retries
...AuthenticationError401 ·ScopeError403 ·NotFoundError404 ·RequestValidationError400/422 (with parsed.errors) ·RateLimitError429 (.retry_after) ·ServerError5xx.TransportErrorcovers connection and timeout failures; the original exception is chained as its cause.- Scopes do not nest — a key scoped to
overheadalone does not reach thenesc,go95orcsaroutes. AScopeErrornames the scope it needed.
Retries. Connection errors, timeouts and 429/502/503/504 are retried automatically — two retries by default, jittered exponential backoff, and the server's Retry-After honoured. Every route is stateless compute, so retrying is safe. A 4xx request error is never retried, and neither is a 500 (an internal error that would simply repeat). Tune or disable it with Client(key, max_retries=0).
Unit conventions
There is no single fixed unit across every clearance route — lookup_nesc_table232_1_clearance returns meters, lookup_go95_table1_clearance returns inches. Every clearance result carries its own unit field — check it, don't assume. classify_clearance is deliberately unit-agnostic: it operates on whatever numbers you pass it, and keeping required/actual/your configured margin in the same unit is on you.
What's not in the SDK yet
Disclosed, not hidden:
- Hand-written ergonomics exist for overhead, underground, circuit and ADA. The other 13 domains work through the generated request-model methods — complete, but not yet given friendlier signatures. We add those where real usage shows they matter.
- Async is generated-methods-only; the hand-written methods and jurisdiction facades are sync-only.
- Standards coverage: 7 of the 16 production rule-template categories have a pack hook today (anchor capacity, grounding resistance, vegetation clearance, sag-tension, conductor ampacity, service voltage drop, transformer loading) and 2 more are partial (angle limits, framing). Not yet: pole strength factors, pole loading overrides, pole attachment separation, loading-area definitions, climbing and working space, avian protection.
- Underground's session-stateful tools (about 45 vault and circuit construction tools that are MCP-only) are session-scoped server-side and not REST-shaped, so a stateless client SDK doesn't cover them.
- Typed results depend on the server: routes the API declares as plain dictionaries return the parsed JSON rather than a model.
- Equipment framing (transformer/switch/riser/cutout-fuse mounting) and insulation/environmental-district hardware selection — real convergences found while surveying utility standards, each needing its own schema design.
Version history
- 0.12
AsyncClient— every route, same retries and errors as the sync client.- 0.11
- Exception taxonomy (scope, validation with field paths, rate limit, server, transport) and automatic retries.
- 0.10
- Every API route wrapped — 308 typed methods across all 18 domains, generated from the live API schema, with a drift check that fails when the API gains a route the SDK doesn't have.
- 0.9
CircuitStandardsPack: your own ampacity, voltage-drop and transformer-loading limits.- 0.8
client.circuitadded.- 0.7
- Grounding-resistance, vegetation-clearance and sag-tension pack hooks.
- 0.6
- Pack files: load and save a
StandardsPackas JSON or YAML, with a JSON Schema. - 0.5
- Continuous integration, live end-to-end tests of every wrapped route, and an API-schema drift check.
- 0.4
- Anchor-capacity utility-table override, closing a gap named but never built in production engineering code.
- 0.3
- Pole-setting-depth utility-table override;
FramingAssemblyangle-limit fields, surveyed from real RUS/Entergy/PG&E/SDG&E overhead standards. - 0.2
client.adaadded; jurisdiction edition/local-amendment tracking generalized onto the baseStandardsPack.- 0.1
- First releases —
overhead+underground, theStandardsPackmechanism, jurisdiction facades (.nesc/.go95/.csa), full CSA grade-of-construction (Tables 27/28/29), and no compute engine shipped client-side.