Skip to content

Run an off-target module over HTTP

Use this workflow when your organization has a published, approved off-target prediction module and you need an HTTP integration. Module identifiers, input fields, pricing and runtimes belong to that deployed version; this page does not prescribe a universal scientific model or claim a production benchmark. For Python applications, the SDK prepare-and-run flow handles the corresponding requests.

This guide targets the prepared-run API used by SDK 5.7. Confirm that the chosen environment has that API before continuing. If quoting does not return admissionId, requestFingerprint and preparedRequest, stop and ask the administrator to complete the rollout. Do not fall back to an unreviewed start.

Credentials and environment

Use the exact API base URL for your environment, without a trailing /v1. Supply HLA_API_BASE_URL and HLA_API_KEY through your process configuration and secret store. A personal automation key needs the relevant read/write permissions, module execution scope and access to the selected module and data. An API key cannot choose a different organization by changing a request field. See API-key permissions and scopes.

All paths below are the API-key /v1/api/... paths. A signed-in user's bearer session uses the corresponding /v1/... paths through the SDK. Do not place bearer tokens in X-API-Key, or reuse a module-run credential as a personal key.

Discover the actual module and inputs

Read the accessible module inventory. It is paginated; inspect pagination.pages and repeat with subsequent page values when needed:

curl --fail-with-body --silent --show-error \
  -H "X-API-Key: $HLA_API_KEY" \
  "$HLA_API_BASE_URL/v1/api/modules?limit=100&page=1" > modules.json
python -m json.tool modules.json

Choose the intended off-target module and set MODULE_ID to its full UUID. Read its definition, including published version, input schema, execution requirements and any declared data inputs:

curl --fail-with-body --silent --show-error \
  -H "X-API-Key: $HLA_API_KEY" \
  "$HLA_API_BASE_URL/v1/api/modules/$MODULE_ID" > module.json
python -m json.tool module.json

Create inputs.json as a JSON object matching that version's schema. A previous module accepting peptide, top_n or catalog_id does not prove a redeployed module accepts those fields. Validate peptide, HLA and catalog semantics against the selected module's documentation; an empty result is not evidence of safety. Do not run a paid module merely to discover catalogs: use GET /v1/api/data-catalogs. Its items array contains Catalog identities and any ready currentVersion.

A Catalog UUID selects a catalog; it does not pin its data. Reproducible selected data requires the module's supported governed data-context binding and exact Catalog Version, or an explicitly declared versioned input. Supplying an arbitrary catalogVersionId inside module parameters does not add that capability. See saved views and run inputs.

Save a plan, then confirm it separately

Install requests in your virtual environment and save this complete example as off_target_http.py. It makes authenticated calls only when you run a command. The optional --data-context file must contain a supported saved-view selection; this script does not invent one or transform unrelated catalogs into a join.

import argparse
import json
import os
import time
from pathlib import Path
from uuid import UUID

import requests


def request(method, base, path, payload=None):
    response = requests.request(
        method,
        base + path,
        headers={"X-API-Key": os.environ["HLA_API_KEY"]},
        json=payload,
        timeout=(10, 40),
    )
    if not response.ok:
        raise RuntimeError(f"HTTP {response.status_code}: {response.text}")
    return response.json()


def read_object(path):
    value = json.loads(Path(path).read_text())
    if not isinstance(value, dict):
        raise ValueError(f"{path} must contain a JSON object")
    return value


def main():
    parser = argparse.ArgumentParser()
    commands = parser.add_subparsers(dest="action", required=True)
    prepare = commands.add_parser("prepare")
    prepare.add_argument("module_id")
    prepare.add_argument("inputs")
    prepare.add_argument("plan_file")
    prepare.add_argument("--data-context")
    prepare.add_argument("--mode", choices=("interactive", "async"), default="interactive")
    confirm = commands.add_parser("confirm")
    confirm.add_argument("plan_file")
    result = commands.add_parser("result")
    result.add_argument("run_id")
    args = parser.parse_args()
    base = os.environ["HLA_API_BASE_URL"].rstrip("/")
    if not base.startswith("https://"):
        raise ValueError("Use your environment's HTTPS API base URL")

    if args.action == "prepare":
        module_id = str(UUID(args.module_id))
        payload = {"mode": args.mode, "parameters": read_object(args.inputs)}
        if args.data_context:
            payload["dataContext"] = read_object(args.data_context)
        plan = request("POST", base, f"/v1/api/modules/{module_id}/quote", payload)
        if not all(plan.get(k) for k in ("admissionId", "requestFingerprint", "preparedRequest")):
            raise RuntimeError("API returned no complete prepared plan; do not start")
        # Exclusive creation prevents accidentally replacing a reviewed plan.
        with Path(args.plan_file).open("x") as handle:
            json.dump({"apiBaseUrl": base, "plan": plan}, handle, indent=2)
        print(json.dumps(plan, indent=2))
        return

    if args.action == "confirm":
        saved = read_object(args.plan_file)
        if saved["apiBaseUrl"] != base:
            raise ValueError("Plan belongs to a different API environment")
        plan = saved["plan"]
        payload = dict(plan["preparedRequest"])
        payload.update(admissionId=plan["admissionId"], requestFingerprint=plan["requestFingerprint"])
        accepted = request("POST", base, "/v1/api/module-runs", payload)
        run_id = accepted.get("runId") or (accepted.get("_run") or {}).get("runId")
        if not run_id:
            raise RuntimeError("No Run ID returned; retain the plan for recovery")
        print(json.dumps({"runId": run_id, "response": accepted}, indent=2))
        return

    run_id = str(UUID(args.run_id))
    deadline = time.monotonic() + 600
    while time.monotonic() < deadline:
        status = request("GET", base, f"/v1/api/module-runs/{run_id}")
        state = str(status.get("status", "")).lower()
        if state in {"completed", "failed", "cancelled"}:
            output = request("GET", base, f"/v1/api/module-runs/{run_id}/result")
            print(json.dumps(output, indent=2))
            if (
                state != "completed"
                or not output.get("success")
                or output.get("error")
                or output.get("artifactErrors")
            ):
                raise SystemExit("Run failed or result artifacts are incomplete; inspect output")
            return
        time.sleep(5)
    raise TimeoutError(f"Stopped waiting for {run_id}; the server run may still be active")


if __name__ == "__main__":
    main()

Prepare only; this saves admission metadata but does not start compute:

python off_target_http.py prepare "$MODULE_ID" inputs.json plan.json
python -m json.tool plan.json

Review the module version, parameters, mode, profile and any frozen data context in plan.preparedRequest, plus estimated_cost, currency and expiresAt in plan. If you accept those values, confirm the saved plan:

python off_target_http.py confirm plan.json > run.json
python -m json.tool run.json

Copy the returned runId into RUN_ID, then retrieve its terminal result:

python off_target_http.py result "$RUN_ID" > result.json
python -m json.tool result.json

Handle uncertainty without creating another run

A successful start may return an inline result or a background acceptance. Either way, retain the returned Run ID. A network timeout is an unknown outcome, not proof that no run exists. Retry confirmation of the same unchanged plan with the same user/organization credentials; an admitted plan recovers its original Run ID. Never recover by choosing the newest run in a list.

If an unused plan expires, its price changes or the request changes, prepare a new plan and review it again before confirming. Do not automatically reprepare or remove the admission identity. The plan file contains scientific inputs and should be stored with the same access controls as the analysis.

A polling timeout does not cancel compute. Continue inspecting the same Run ID. On 429, honor Retry-After before retrying the read; the example stops and reports the response rather than claiming a fixed per-key rate. Concurrency, credit and query budgets vary by environment and organization.

/result returns 409 before a terminal state. At completion, inspect platform success, error, artifactErrors and the selected module's output contract. The result envelope preserves Run ID, module version, inputs/context and artifact references. It does not guarantee scientific validity, replay availability, or that an arbitrary off-target module supports pinned governed data.