Skip to main content

RadMah AI Python SDK

Typed client for the RadMah AI platform. Create jobs, upload datasets, stream results, and retrieve cryptographic evidence bundles — all from Python.

Requires: Python 3.10 or newer. radmah_sdk.__version__ reports the installed version.


Install

pip install radmah-sdk

From this repository during development:

pip install -e ./sdk

Authentication

Get a scoped API key from Settings → API Keys in the RadMah AI dashboard (the key is shown once, at creation). Keys are prefixed sl_live_.

from radmah_sdk import RadMahClient

client = RadMahClient(api_key="sl_live_abcdef...")

Base URL defaults to https://api.radmah.ai. Enterprise customers running their own deployment point the client at that deployment's API URL:

client = RadMahClient(
    api_key="sl_live_...",
    base_url="https://radmah.example.com",  # your deployment's API URL
    timeout=60.0,      # per-request timeout seconds
    max_retries=3,     # transparent retry on 5xx / connection errors
)

Or via env var:

export RADMAH_API_URL=https://radmah.example.com

A deployment on your private network (an RFC 1918 address, an IPv6 unique-local address or a name under .internal) also needs an explicit opt-in, so that a base URL reaching your program from somewhere else cannot aim your API key at an internal service: pass allow_private_base_url=True, or export RADMAH_ALLOW_PRIVATE_BASE_URL=1 when the code does not pass that argument (an explicit allow_private_base_url=False always refuses). Use https:// (set RADMAH_CA_BUNDLE or ca_bundle_path= to your CA's PEM file when its certificate comes from your own CA): plain http:// is accepted, but to any host other than this machine it sends the key unencrypted, and the SDK issues a radmah_sdk.InsecureBaseURLWarning. Cloud instance-metadata endpoints are always refused, in every spelling the transport accepts (Unicode dots, numeric and IPv6 forms). A server's region redirect never moves the key from a public host into your network or from https:// to http://. A refused base URL raises radmah_sdk.BaseURLError, and an unusable CA bundle radmah_sdk.CABundleError naming its setting and path; both are ValueErrors.


Quickstart — reviewed Fabricate generation

from radmah_sdk import RadMahClient

with RadMahClient(api_key="sl_live_...") as client:
    preview = client.create_fabricate_preview(
        "I need 10,000 account records with a unique account_id, "
        "a created_on date, an active flag, and a non-negative balance.",
        requested_records=10_000,
        seed=42,
    )
    preview_id = preview["preview_id"]
    state = client.wait_fabricate_preview(preview_id, timeout=300)

    # Review state["contract_versions"] before approval. Apply a refinement
    # with client.refine_fabricate_preview(...) when the contract needs one.
    approval = client.approve_fabricate_preview(preview_id)
    job = client.get_job(approval["job_id"])
    job = job.wait(timeout=300)

    if job.status == "succeeded":
        # The SDK requires exactly one server-declared primary artifact.
        df = job.to_dataframe()
        print(df.head())
        evidence = client.get_evidence(job.id)
        print("Evidence contract hash:", evidence.contract_hash)

Every job produces a cryptographically sealed evidence bundle. Ask the platform to re-verify it with client.verify_job(job.id), or verify it yourself offline — without trusting the API — by recomputing the hashes over the downloaded bytes:

from radmah_sdk.verify import verify_evidence_bundle

result = verify_evidence_bundle(client.get_evidence_data(job.id))
print(result.ok, result.kind, result.failures)

Core operations

Jobs

from radmah_sdk import SynthesisReleasePolicy

release_policy = SynthesisReleasePolicy(
    minimum_overall_fidelity=0.75,
    minimum_column_fidelity=0.75,
    minimum_bivariate_fidelity=0.60,
    maximum_membership_advantage=0.20,
    maximum_linkage_risk=0.05,
    require_zero_exact_copies=True,
    block_live_identifiers=False,  # optional; off by default
)

# Fidelity thresholds use an equal-sized independent source baseline under the
# same untouched audit reference. Linkage limits apply to measured excess above
# the empirical source rate. Raw, baseline, and release values remain in the
# evidence bundle.
#
# block_live_identifiers=True refuses delivery when any delivered TEXT value is
# shaped like an email address, US SSN, NHS number, payment card number, IBAN or
# telephone number outside the formally reserved test ranges (example.com-style
# domains, 555-01xx numbers, SSN area 000). A refused job fails with
# SYNTHESIS_RELEASE_POLICY_FAILED, publishes nothing, and names each column,
# count and identifier format (never a value) in
# job.result_summary["synthesis_release_failure"]. Numeric columns are not
# checked. With the control off (the default) the data is delivered and any
# detection is reported in
# job.result_summary["release_policy_evaluation"]["value_safety"] and sealed as
# an observational record, which evaluate_value_safety_bundle() reports with
# enforcement="observational". It is a control you choose, not a guarantee that
# delivered data contains no identifiers.

client.jobs.create(
    kind="synthesize",
    dataset_id=dataset_id,
    rows=10_000,
    seed=42,
    release_policy=release_policy,
)
client.jobs.list(status="succeeded", offset=0, limit=50)
client.jobs.get(job_id)
client.jobs.cancel(job_id)
client.jobs.rerun(job_id)

The generic jobs endpoint accepts synthesize, synthesis_probe (see Quick probe), simulate, analyze, verify, train, fit, lift, and scenario_fabricate. Prompt-to-data Fabricate deliberately uses create_fabricate_preview, optional refine_fabricate_preview, and approve_fabricate_preview; it cannot be submitted through the generic jobs endpoint.

Datasets

# Upload from a path (auto-detects CSV / Parquet / JSON / JSONL).
# A display name without an extension inherits the real source extension.
dataset = client.upload_dataset("./data.csv", name="Transactions")

# Or from bytes.
dataset = client.upload_dataset_bytes(
    filename="data.csv",
    content=b"...",
)

# The server parses and stores the whole file before it answers, so a large
# upload waits up to an hour for its answer by default (the client's
# `timeout` still bounds connecting). Set `timeout=` in seconds, or
# RADMAH_UPLOAD_TIMEOUT; `rady datasets upload --timeout` does the same.
dataset = client.upload_dataset("./large.json", timeout=7200)

client.list_datasets()
client.get_dataset(dataset_id)
client.delete_dataset(dataset_id)

Saved generators: export and import

A job that trained a Synthesis model can export it as a signed generator package (.rsg). The file carries no executable content: it is a pickle-free tensor payload in a header signed by your workspace's key, and it can be imported only into the workspace that exported it, for a dataset holding the data the model was trained on.

exported = client.export_synthesis_model(job_id, "generator.rsg")
print(exported.sha256, exported.fitted_state_hash)

# Import it for a dataset uploaded from the same source file. The returned
# job verifies the package and the dataset; the generator is reusable once
# it has succeeded.
imported = client.import_synthesis_model("generator.rsg", dataset_id).wait()

client.list_synthesis_models(dataset_id, include_imported=True)
client.jobs.create(
    kind="synthesize",
    dataset_id=dataset_id,
    rows=10_000,
    seed=42,
    checkpoint_source_job_id=imported.id,
    release_policy=release_policy,
)

Generating from the imported generator with the same seed, rows and release policy delivers byte-identical data to generating from the model it was exported from.

Quick probe

Before paying for a full Synthesize run, probe it. A quick probe trains on a sample of your dataset with the exact settings of the run you plan (rows, mode, training controls, time cap, release policy, numeric constraints, compute) and reports estimates: what the probe itself took, the full run's wall time (at the epoch ceiling, which is not an upper bound: the full run can take longer; and at the point the sample converged, or None with the reason), whether the run would hit the 4-hour job limit or your training cap, its peak memory against the full run's worker memory and admission floor, and each release bar's likely outcome (likely_pass, uncertain or likely_refuse; predictive utility is advisory). A figure carries a range only where calibration supports one; otherwise its label is uncalibrated or outside_calibrated_range and it has no range. Every figure is an estimate, never a release decision, and the probe delivers no rows and no model. Its arguments and defaults are the ones the planned run takes.

quote = client.estimate_cost(
    "synthesis_probe",
    rows=10_000,
    dataset_id=dataset_id,
    epochs=400,
)
probe = client.probe_synthesis(
    dataset_id,
    10_000,
    epochs=400,
    seed=42,
    release_policy=release_policy,
    max_credits=quote["credits_required"],
).wait(timeout=3600)

report = client.get_synthesis_probe_report(probe.id)
print(report.deterministic_sha256)  # same image, data, settings, seed: same digest
ceiling = report.estimates.time_at_ceiling_seconds
print(ceiling.point, ceiling.label, ceiling.calibrated_interval)
for row in report.release_predictions:
    print(row.bar, row.sample_value, row.calibrated_interval, row.status)
for line in report.limitations:
    print(line)  # fixed statements; show them as given

# The report file the probe's evidence bundle hashes, digest-checked.
report_bytes = client.download_synthesis_probe_report(probe.id)

# Start the full run from the probe with the SAME settings; the run then
# records the probe's estimates against what it measured.
run = client.jobs.create(
    kind="synthesize",
    dataset_id=dataset_id,
    rows=10_000,
    seed=42,
    options={"epochs": 400},
    release_policy=release_policy,
    probe_job_id=probe.id,
).wait(timeout=3600)
print(run.probe_validation.comparable, run.probe_validation.quantities)

A probe refuses seed data and model reuse with SYNTHESIS_PROBE_UNSUPPORTED_CONTROL, and a dataset below the Synthesis row floor with DATASET_TOO_SMALL_FOR_SYNTHESIS_TRAINING, before any job or charge exists. From the shell, rady synthesize --probe takes every rady synthesize flag, and --json prints the same public report.

Inference on a saved model

A saved generator (a succeeded train or synthesize job, or an imported package's job) can answer questions about your own rows. Upload a table with some or all of the model's source columns, then run one of five operations:

Operation What each row gets
impute Its missing cells filled; every present cell is delivered unchanged. By default (statistic="most_likely") each categorical cell gets the most likely fitted category given the row's present categorical cells and the bands of its present numeric cells (flow-only cells, for example dates, and positions within a numeric band are not used) and each number or date the conditional median of samples model draws; statistic="mean" gives decimals the conditional mean; statistic="draw" fills each row with one joint model draw (keeps the data's spread and relationships — use it, with several seeds, for multiple imputation). imputed_cells.csv lists each filled cell and its method.
predict The target's most likely category and its probability, or, for a numeric target, the median (default) or mean (decimal targets only) of its sampled values.
predict_proba The probability of each fitted category of the target, or of each interval of the bins you supply for a numeric target.
log_prob The row's log-likelihood under the model, in nats: the log-probability that the model delivers exactly this row as written (categories and nulls as delivered, each number at its delivered precision), with its Monte-Carlo standard error and which parts were exact or estimated.
state_log_prob The log-probability, in nats, of the row's fitted discrete state only: its categories, missing flags, numeric range bands and character states as the model holds them.
model_job_id = trained_job.id           # or an imported generator's job id
profile = client.inference_profile(model_job_id)
print(profile.enabled, profile.default_samples, profile.probability_tolerance)
for entry in profile.columns:            # what each column supports
    print(entry.column, entry.representation, entry.operations)

target = profile.columns[0].column       # a column the profile allows as a target

# input is an uploaded dataset id, or a local file uploaded first.
job = client.predict_proba(model_job_id, "./my_rows.csv", target, seed=42).wait()
print(job.inference.results.rows_exact, job.inference.results.rows_estimated)
print(job.inference.privacy_statement)

result = client.inference_result(job.id)  # checked against seal.json first
print(result.sha256, result.columns)
for row in result.rows[:5]:
    print(row)                            # strings, None for an empty cell

filled = client.impute(model_job_id, dataset_id).wait()
print(client.inference_result(filled.id).imputed_cells[:5])

The same methods are on client.jobs and, as coroutines, on AsyncRadMah (wait on a job there with await client.wait_for_job(job.id)). The rady CLI runs them as rady synthesis-model impute | predict | predict-proba | log-prob | state-log-prob | profile.

Samples and precision. predict, predict_proba, log_prob and state_log_prob draw samples model states per row. The default is DEFAULT_INFERENCE_SAMPLES = 400: the draws that resolve any probability to within ±0.05 at 95% confidence even in the worst case (p = 0.5), since the 95% half-width is at most 1/√n. Input rows × samples (× the model's log_prob_draw_cost for log_prob) may not exceed INFERENCE_DRAW_BUDGET (10,000,000) per job; impute with statistic="draw" uses one draw per row. That product is the quoted and charged quantity for every row: a row computed exactly, or one that stops early, is not cheaper.

Exact, estimated and sampled rows. Every result row carries a status:

  • exact — computed from the fitted state with no sampling, so it does not change with the seed;
  • estimated — a Monte-Carlo estimate; the row reports its effective sample size and 95% half-width (1/√ESS), and the record gives the smallest ESS and the worst half-width in job.inference.results;
  • sampled — reduced from samples seeded model draws of the row (a numeric target; the result reports the draws used), or, for impute, a row whose missing cells were filled by a model draw;
  • outside_fitted_support — the row gets no score or prediction (never -inf or NaN), and outside_support_reasons says why. Most reasons mean the row's known values have no support in the fitted model (for combination_never_delivered, the model's exact discrete-state probability of the row is zero). For log_prob, not_reproduced_in_sample_budget means something weaker: the row's draws never reproduced its values after its full sample budget. That is not a proof of probability zero — the row's probability is below what that many draws can detect, and more samples may score it.

job.inference.coverage names every column and how it was used: as the target, as evidence, only through its range band, or not at all.

What log_prob computes. The model draws a discrete state (categories, missing flags, numeric range bands, character states) from an exact chain-rule model, then a latent vector from its base prior, transports it with a flow (the ODE integrated by the same Euler steps the generator ships), and decodes it. log_prob evaluates that same process for your row:

  • the discrete part is the chain's exact probability of the row's states (discrete_part = exact), or, when a chain column is unknown, an unbiased estimate over its possible states (estimated);
  • the continuous part is the flow's density, by the change-of-variables formula along the ODE, integrated over every latent value that decodes to the row's delivered cells; latent coordinates the row does not pin (nuisance coordinates, and the missingness indicator of a nullable numeric) are marginalised by importance sampling, so this part is always estimated when the flow is read;
  • the divergence in the change-of-variables formula is computed exactly from the full Jacobian for models whose latent has up to 64 coordinates (divergence = exact_jacobian) and by the Hutchinson/Skilling estimator beyond (hutchinson_skilling_estimate, with divergence_variance).

Precision. log_prob estimates rows together and stops each one at a declared precision: LOG_PROB_TARGET_STANDARD_ERROR = 0.025 nats (a 95% half-width of ±0.05 on the log-likelihood, so the likelihood itself is known to about ±5%). Rows draw 32, then 64, 128, … up to samples (default 400); samples_used and target_met say where each row stopped. The quote and the charge are for the most draws a row may take, on every row: input rows × samples × profile.log_prob_draw_cost, whether a row is exact, stops early or is outside the fitted support. A log_prob draw counts as profile.log_prob_draw_cost model draws — the priced cost for that model: 1 for a model with no flow (discrete state only), 2 when the model's fitted transport is affine (its learned residual was not accepted at training), more when every Euler step needs the residual's Jacobian. Current servers always state it; if an older server leaves it None, do not assume 1 — quote the job (client.quote_inference(...)) and use its credits_required.

Each row reports log_prob, standard_error (the Monte-Carlo standard error of that log), effective_sample_size, discrete_log_prob / continuous_log_prob where the split is exact, and scored_columns / marginalised_columns. A number is scored at its delivered precision — a date to the day, a decimal to the model's output decimals — so the score is the probability of that cell, not a density of an unrounded value. A row the model can never deliver is outside_fitted_support with no score, and outside_support_reasons says why, per column (combination_never_delivered only when the model's exact discrete-state probability of the row is zero). A row whose draws never reproduced it after its full sample budget is also outside_fitted_support, with the reason not_reproduced_in_sample_budget: that is not a proof of probability zero — its probability is below what that many draws can detect, and more samples may score it. The common case of a proved zero is a date: when a date column is mostly repeated dates, the model learns it as a schedule (the dates seen at least twice) and only ever produces those dates, so a row with any other date has likelihood zero (date_not_in_fitted_schedule). A date column learned as continuous time scores any date within its fitted range. Mark such a column unknown (unknown_columns) to score the rest of the row. A row whose flow inversion or divergence estimate cannot be trusted is estimation_failed. Each sample inverts the flow step by step; when the model's learned residual transport was not accepted at training the transport is affine and that inversion and its determinant are closed-form, otherwise each step takes the residual's Jacobian (exact up to 64 latent coordinates, estimated beyond), which costs more per sample than the other operations.

Refusals. A request the API cannot run is refused before any job exists with HTTP 422 and a SYNTHESIS_INFERENCE_* code (for example SYNTHESIS_INFERENCE_TARGET_REQUIRED, _BINS_INVALID, _SAMPLES_OUT_OF_RANGE, _COLUMN_UNKNOWN, _NOT_ENABLED), raised as ValidationError. The SDK checks the parts that need no knowledge of the model — fields that do not apply to the operation, the target, the bin edges and the sample count — itself, with the same codes, before anything is uploaded. For a local file it also reads the model's inference profile first and refuses, before uploading anything, what the profile already decides: inference not enabled on the plan (_NOT_ENABLED), a model inference cannot use, or a column the model does not have (_COLUMN_UNKNOWN). If the API refuses the job after the SDK uploaded your file for it, the SDK deletes that uploaded dataset (best effort) and the error's message and detail["uploaded_input_deleted"] say whether it was deleted; after a server or network failure the upload is kept (the job may exist) and the error names its dataset id. Details name columns and counts only, never a cell value.

Quotes. client.quote_inference(model_job_id, dataset_id, operation, ...) (also on client.jobs and AsyncRadMah) prices a request before submitting it and reserves nothing; it sends POST /v1/client/jobs/quote with the same body the client area quotes (kind, checkpoint_source_job_id, inference) and is equivalent to client.estimate_cost(kind="synthesis_inference", checkpoint_source_job_id=..., inference={...}). A quote needs an uploaded dataset id: it never uploads a file. Pass its credits_required as max_credits to submit bounded by it. The rady inference commands show it with --quote.

Privacy. These results are model inference on your own rows. They are not a privacy-protected or differentially private release, the release policy does not apply to them, and the job reads no source rows (job.inference.source_rows_read is False). Treat the output with the same care as the rows you uploaded.

Account + auth helpers

client.signup(email=..., password=..., org_name=...)
client.login(email=..., password=...)             # returns {access_token, refresh_token, mfa_pending?}
client.mfa_setup()                                 # TOTP provisioning URI
client.mfa_verify(code)                            # 6-digit TOTP OR 8-char backup code (xxxx-xxxx)

client.create_api_key(name="ci")                   # {raw_key, prefix, ...}
client.list_api_keys()
client.rotate_api_key(key_id)
client.revoke_api_key(key_id)

Platform introspection

client.health()            # service liveness
client.version()           # build + engine versions
client.get_openapi()       # full OpenAPI spec
client.get_error_catalog() # machine-readable error_code → message map

Async client

For workloads that run many jobs in parallel or interleave with other I/O (Jupyter, notebook pipelines, aiohttp servers), use AsyncRadMahClient:

import asyncio
from radmah_sdk import AsyncRadMah

async def main():
    async with AsyncRadMah(api_key="sl_live_...") as client:
        preview = await client.create_fabricate_preview(
            "I need 250 inventory records with a unique item_id and quantity "
            "between 0 and 500.",
            requested_records=250,
            seed=42,
        )
        preview_id = preview["preview_id"]
        await client.wait_fabricate_preview(preview_id)
        approval = await client.approve_fabricate_preview(preview_id)
        print("Final job:", approval["job_id"])

asyncio.run(main())

A quick probe from the async client:

async def probe(dataset_id, release_policy):
    async with AsyncRadMah(api_key="sl_live_...") as client:
        job = await client.probe_synthesis(
            dataset_id, 10_000, seed=42, release_policy=release_policy,
        )
        await client.wait_for_job(job.id, timeout=3600)
        return await client.get_synthesis_probe_report(job.id)

The async surface mirrors the sync one method-for-method.


Error handling

All API errors surface as RadMahError with a structured payload:

from radmah_sdk import RadMahError

try:
    client.approve_fabricate_preview("unknown-preview-id")
except RadMahError as e:
    print(e.status_code)      # HTTP status
    print(e.error_code)       # machine-readable (CONTRACT_NOT_FOUND, etc.)
    print(e.message)          # human-readable
    print(e.detail)            # optional context dict

Common error codes: INVALID_API_KEY, CONTRACT_NOT_FOUND, CREDIT_LIMIT_EXCEEDED, CONCURRENT_JOB_LIMIT, AGENT_SESSION_LIMIT, GOAL_OUT_OF_SCOPE (ADS only).

Pull the full catalogue at runtime: client.get_error_catalog().


Agentic Data Scientist (ADS)

ADS turns a natural-language goal into a planned, human-approved, self-healing multi-step run that seals a cryptographic evidence trail. The SDK exposes the full lifecycle (same surface on AsyncRadMah, awaited):

# 1. Create + wait for the plan to reach the approval gate.
project = client.create_agent_project(
    goal="Profile the customers dataset, engineer tenure + spend "
         "features, train a churn classifier, evaluate on a 20% holdout.",
    title="Churn baseline — Q3",
)
project = client.wait_for_agent_project(project.id, timeout=120)

# 2. Approve the plan (HAGP gate) — execution begins.
if project.status == "awaiting_approval":
    quote = project.cost_summary or {}
    client.approve_agent_project(
        project.id,
        max_credits=quote["total_estimated_credits"],
        plan_sha256=quote["approval_plan_sha256"],
    )

# 3. Poll to a terminal state, routing any mid-run decision gate.
while True:
    project = client.wait_for_agent_project(project.id, timeout=600)
    s = (project.status or "").lower()
    if s in ("complete", "failed", "blocked"):
        break
    if s == "awaiting_patch_approval":
        client.submit_patch_decision(project.id, "accepted")
    elif s == "awaiting_replan_approval":
        client.submit_replan_decision(project.id, "accepted")
    elif s == "awaiting_step_approval":
        gate = project.pending_step_approval or {}
        consent = gate.get("consent") or {}
        if gate.get("trigger_reason") == "execution_consent":
            # Running the simulation: bind the exact cost you were shown.
            client.submit_step_approval(
                project.id, "approved",
                max_credits=consent["maximum_credits"],
                consent_fingerprint=consent["fingerprint"],
            )
        elif gate.get("trigger_reason") == "pack_preparation_consent":
            # Preparing a new reusable scenario pack.
            client.submit_step_approval(
                project.id, "approved", max_credits=consent["maximum_credits"],
            )
        else:
            client.submit_step_approval(project.id, "approved")
    elif s == "awaiting_escalation_review":
        client.submit_escalation_decision(project.id, "retry")

# 4. Read the sealed output + verify the replay bundle offline.
if project.status == "complete":
    output = client.get_agent_project_output(project.id)
    verdict = client.verify_replay_bundle(project.id)   # BLAKE3 + Ed25519, client-side
    assert verdict["ok"]
elif project.status == "blocked":
    client.resume_project(project.id, "retry_failed")    # or skip_failed / restart_step

Simulation costs are approved in two parts. A SCADA simulation's price depends on its scenario pack, so it is never approved blind:

  1. Pack preparation. If a compatible pack already exists it is reused at no cost. Otherwise the plan's cost_summary["stages"] shows the one-off cost of preparing a new, reusable pack, and approving the plan authorises only that — the plan's total_estimated_credits is the maximum you authorise now, and total_expected_credits is the expected figure.
  2. Execution. Once the pack exists, the run is priced from it and the project pauses in awaiting_step_approval with pending_step_approval["trigger_reason"] == "execution_consent". The consent block carries expected_credits, maximum_credits, the workload basis and a fingerprint; approve with max_credits equal to the maximum shown and consent_fingerprint. The run cannot be charged above the credits you approve: authorising less than its maximum stops it before it starts (nothing is charged) and the cost is asked for again. If the cost changed after it was shown the API answers 409 and nothing is approved. When the pack already exists at planning time, the execution is priced in the plan and approving the plan covers it.

client.estimate_job(contract, prompt_text=...) gives the same two-part price before you create a project (stages, with basis equal to "bounded_estimate"). rady ads approve and rady ads decide <id> approve show both costs and bind them the same way.

Other lifecycle methods: list_agent_projects, get_agent_project, cancel_agent_project, delete_agent_project, stream_agent_project (SSE), get_evidence_chain, verify_trail, get_evidence_entries, get_replay_bundle, list_agent_memories / delete_agent_memory (CSSM), list_agent_tools, and execute_agent_tool (run one planner tool directly). The rady ads CLI drives the same surface from a shell. ADS-only error codes: AGENT_SESSION_LIMIT, GOAL_OUT_OF_SCOPE.


Cryptographic evidence

Successful Fabricate, Synthesize, and simulation generation paths expose a BLAKE3-sealed multi-file evidence bundle. The exact file count varies by generation and output shape. Retrieve and verify programmatically:

evidence = client.get_evidence(job.id)
print(evidence.seal_hash)          # binding hash over the bundle's artifacts
client.verify_job(job.id)          # re-computes every artifact hash server-side

Artifacts include the sealed contract JSON, the run manifest, the generated data, the constraint / determinism / privacy / utility reports, and the hash manifest. The full schema is in the documentation (https://docs.radmah.ai/sdk).


More

For issues, contact support@radmah.ai.

Licence

Proprietary (LicenseRef-Proprietary); see the LICENSE file shipped with the package. You may read the source and use the unmodified package to access the RadMah AI platform under your account agreement; you may not copy, modify, redistribute or reuse it.

Metadata

Release files for radmah-sdk 1.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for radmah-sdk 1.3.0
File Size Uploaded
radmah_sdk-1.3.0.tar.gz 205.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for radmah-sdk 1.3.0
File Interpreter ABI Platform
radmah_sdk-1.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 396.1 kB

Release files / radmah_sdk-1.3.0.tar.gz

Download URL radmah_sdk-1.3.0.tar.gz
Size 205.7 kB
Tags Source
SHA-256 checksum
How to use checksums
8b9c0feef9c327aeddb5778d06e8d24b5c13b7fe4f5d5ba03e49e123e9175406
BLAKE2b-256 checksum
How to use checksums
78a90fbe7e6fa4d37476295ef54015a11fbc507789b967ab9c428ab8b22af796
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / radmah_sdk-1.3.0-py3-none-any.whl

Download URL radmah_sdk-1.3.0-py3-none-any.whl
Size 190.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
911a9191aaf45417cc8493711617e09dd7bbf17529552d025e9224022132e714
BLAKE2b-256 checksum
How to use checksums
a9ccf1c70f8618785377d730f02b3b7c241b34aac0fc38c3c30a0914efd55d96
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

1.4.0

2 release files

This release

1.3.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page