---
url: /sdk-api-service.md
description: >-
  Invoke deployed services through the Kipu Quantum Hub gateway with the
  HubServiceClient - start executions, stream logs, and download result files
  using short-lived bearer tokens.
---

# HubServiceClient

> Part of the [Kipu Quantum Hub API SDK reference](./sdk-api.md) - see the landing page for [installation](./sdk-api.md#installation) and [Python credential helpers](./sdk-api.md#python-credential-helpers).

`HubServiceClient` talks to deployed services through the service gateway.
It takes a short-lived bearer `token` and, in Python, the async client additionally accepts `async_token: Callable[[], Awaitable[str]]` for async-driven refresh.

The default environment is a URL template - `https://gateway.hub.kipu-quantum.com/<context>/<service>/<version>` - that you **must** override by passing the concrete gateway URL for the service you are invoking (the placeholders are not substituted by the SDK).

All operations live under the `service_api` (Python) / `serviceApi` (TypeScript) namespace.

## Authentication

The bearer token is sent as `Authorization: Bearer <token>`.
Tokens are short-lived; the caller is responsible for rotating them before they expire.
In Python, pass either a `str` or a `Callable[[], str]` to `token`; the async client also accepts `async_token: Callable[[], Awaitable[str]]` for refresh that involves async I/O.
Python users can resolve tokens with the [Python credential helpers](./sdk-api.md#python-credential-helpers) and pass the value via `token`.

## Quickstart

Start a service execution and stream its logs.

::: tabs key:pythonTS

\== Python

```python
from qhub.api.service import HubServiceClient

service = HubServiceClient(
    base_url="https://gateway.hub.kipu-quantum.com/acme/vqe/v1",
    token="YOUR_GATEWAY_TOKEN",
)

execution = service.service_api.start_execution(
    request={"input": {"molecule": "H2"}},
)

for entry in service.service_api.get_logs(execution.id) or []:
    print(entry.timestamp, entry.severity, entry.message)
```

\== TypeScript

```ts
import { HubServiceClient } from "@quantum-hub/qhub-api/service";

const service = new HubServiceClient({
  baseUrl: "https://gateway.hub.kipu-quantum.com/acme/vqe/v1",
  token: "YOUR_GATEWAY_TOKEN",
});

const execution = await service.serviceApi.startExecution({
  input: { molecule: "H2" },
});

const logs = (await service.serviceApi.getLogs(execution.id)) ?? [];
for (const entry of logs) {
  console.log(entry.timestamp, entry.severity, entry.message);
}
```

:::

## Constructor

| Parameter                      | Required          | Description                                                                                                       |
| ------------------------------ | ----------------- | ----------------------------------------------------------------------------------------------------------------- |
| `token`                        | yes               | Bearer token, or a callable returning one. Sent as `Authorization: Bearer <token>`.                               |
| `async_token` (Python async only) | no             | Awaitable callable for async-driven token refresh; used instead of `token` on async requests when supplied.       |
| `base_url` / `baseUrl`         | yes (effectively) | Concrete gateway URL for the service; the default `environment` is a template that must be overridden.            |
| `environment`                  | no                | `HubServiceClientEnvironment.DEFAULT` (Python) / `HubServiceEnvironment.Default` (TypeScript) - template URL.     |
| `headers`                      | no                | Additional headers merged into every request.                                                                     |
| `timeout` / `timeoutInSeconds` | no                | Read timeout in seconds; defaults to 60 when no custom HTTP client is supplied.                                   |
| `max_retries` / `maxRetries`   | no                | Number of retries for transient failures; defaults to 2.                                                          |
| `follow_redirects`             | no (Python only)  | Passed through to `httpx.Client`; defaults to `True`.                                                             |
| `httpx_client` / `fetch`       | no                | Inject a preconfigured HTTP client (Python) or `fetch` implementation (TypeScript).                               |
| `logging`                      | no                | Logger instance or `{level, logger, silent}` config dict.                                                         |

Python also ships `AsyncHubServiceClient` with the same shape plus an `httpx.AsyncClient` hook and the `async_token` callable.

## Behaviour

* The default `environment` is an un-substituted template, so `base_url` / `baseUrl` is effectively required - see [Endpoints](#endpoints).
* `base_url` / `baseUrl` always wins over `environment`.
* `async_token` exists only on `AsyncHubServiceClient`; when supplied it is used instead of the synchronous `token` for async requests.
* `max_retries` applies to transient failures (network errors and 5xx responses); per-request overrides in `request_options` (Python) or per-call options (TypeScript) take precedence.

## Service Execution lifecycle

```
PENDING ──► RUNNING ──► SUCCEEDED
    │          │
    │          ├──► FAILED
    │          └──► CANCELLED
    └──► CANCELLED
```

`UNKNOWN` covers states the server has not yet resolved.
Terminal states are `SUCCEEDED`, `FAILED`, and `CANCELLED`.

## Operations

All methods live under the `service_api` (Python) / `serviceApi` (TypeScript) namespace.

| Method                                      | Description                                                         |
| ------------------------------------------- | ------------------------------------------------------------------- |
| `service_api.get_service_executions()`      | List executions started through this gateway.                       |
| `service_api.start_execution(request=body)` | Start a new execution; body is a JSON `dict` (reserved keys below). |
| `service_api.get_status(id)`                | Lightweight `ServiceExecution` snapshot.                            |
| `service_api.get_result(id)`                | Result response (HAL-style: `_links` + `_embedded`).                |
| `service_api.get_result_file(id, file)`     | Download a single result file as a byte stream.                     |
| `service_api.get_logs(id)`                  | Chronological list of log entries (oldest first).                   |
| `service_api.cancel_execution(id)`          | Request cancellation of a pending or running execution.             |

TypeScript exposes the same methods in camelCase (`serviceApi.getServiceExecutions()`, `serviceApi.startExecution({ ... })`, `serviceApi.getStatus(id)`, `serviceApi.getResult(id)`, `serviceApi.getResultFile(id, file)`, `serviceApi.getLogs(id)`, `serviceApi.cancelExecution(id)`).

### get\_service\_executions

List every service execution started through this gateway.

This method takes no arguments beyond per-request options.

::: tabs key:pythonTS

\== Python

```python
executions = service.service_api.get_service_executions()
for execution in executions:
    print(execution.id, execution.status)
```

\== TypeScript

```ts
const executions = await service.serviceApi.getServiceExecutions();
for (const execution of executions) {
  console.log(execution.id, execution.status);
}
```

:::

Returns `List[ServiceExecution]` / `ServiceExecution[]` - see [ServiceExecution](#serviceexecution).

### start\_execution

Start a service execution, processed asynchronously.
The returned `ServiceExecution` starts non-terminal; poll [get\_status](#get_status) until it reaches a terminal state, then read the result with [get\_result](#get_result).

| Parameter | Required | Type                                       | Default | Description                                                       |
| --------- | -------- | ------------------------------------------ | ------- | ----------------------------------------------------------------- |
| `request` | yes      | `RequestBody` (`Dict[str, Any]` / `Record<string, any>`) | —       | Free-form JSON body; two keys are reserved (see [Reserved request keys](#reserved-request-keys)). |

In Python `request` is keyword-only; in TypeScript the body is passed as the first positional argument.

::: tabs key:pythonTS

\== Python

```python
execution = service.service_api.start_execution(
    request={
        "input": {"molecule": "H2", "basis": "sto-3g"},
        "tags": ["demo"],
    },
)
print(execution.id, execution.status)
```

\== TypeScript

```ts
const execution = await service.serviceApi.startExecution({
  input: { molecule: "H2", basis: "sto-3g" },
  tags: ["demo"],
});
console.log(execution.id, execution.status);
```

:::

Returns `ServiceExecution` - see [ServiceExecution](#serviceexecution).

### get\_status

Lightweight snapshot of a single execution's lifecycle state.

| Parameter | Required | Type             | Default | Description                       |
| --------- | -------- | ---------------- | ------- | --------------------------------- |
| `id`      | yes      | `str` / `string` | —       | The id of a service execution.    |

::: tabs key:pythonTS

\== Python

```python
execution = service.service_api.get_status(execution_id)
print(execution.status)
```

\== TypeScript

```ts
const execution = await service.serviceApi.getStatus(executionId);
console.log(execution.status);
```

:::

Returns `ServiceExecution`; inspect its `status` (a [ServiceExecutionStatus](#enums)) against the [lifecycle](#service-execution-lifecycle).

### get\_result

Retrieve the result of a service execution as a HAL envelope.
The response carries `_embedded.status` (a full [ServiceExecution](#serviceexecution)) and `_links.status` (a [HalLink](#resultresponse) pointing back at the status resource); individual result files are downloaded with [get\_result\_file](#get_result_file).

| Parameter | Required | Type             | Default | Description                       |
| --------- | -------- | ---------------- | ------- | --------------------------------- |
| `id`      | yes      | `str` / `string` | —       | The id of a service execution.    |

::: tabs key:pythonTS

\== Python

```python
result = service.service_api.get_result(execution_id)
if result.embedded and result.embedded.status:
    print(result.embedded.status.status)
```

\== TypeScript

```ts
const result = await service.serviceApi.getResult(executionId);
console.log(result._embedded?.status?.status);
```

:::

Returns `ResultResponse` - see [ResultResponse](#resultresponse).

### get\_result\_file

Download a single named result file as a byte stream.
Use this for large outputs rather than parsing the full result into memory.

| Parameter | Required | Type             | Default | Description                       |
| --------- | -------- | ---------------- | ------- | --------------------------------- |
| `id`      | yes      | `str` / `string` | —       | The id of a service execution.    |
| `file`    | yes      | `str` / `string` | —       | The name of the result file.      |

::: tabs key:pythonTS

\== Python

```python
with open("result.bin", "wb") as out:
    for chunk in service.service_api.get_result_file(execution_id, "result.bin"):
        out.write(chunk)
```

\== TypeScript

```ts
import { Writable } from "node:stream";
import { createWriteStream } from "node:fs";

const file = await service.serviceApi.getResultFile(executionId, "result.bin");
await file.stream().pipeTo(Writable.toWeb(createWriteStream("result.bin")));
```

:::

Returns `Iterator[bytes]` (Python; `AsyncIterator[bytes]` on the async client) / `BinaryResponse` (TypeScript).

### get\_logs

Chronological list of log entries for an execution, oldest first.

| Parameter | Required | Type             | Default | Description                       |
| --------- | -------- | ---------------- | ------- | --------------------------------- |
| `id`      | yes      | `str` / `string` | —       | The id of a service execution.    |

::: tabs key:pythonTS

\== Python

```python
for entry in service.service_api.get_logs(execution_id) or []:
    print(entry.timestamp, entry.severity, entry.message)
```

\== TypeScript

```ts
const logs = (await service.serviceApi.getLogs(executionId)) ?? [];
for (const entry of logs) {
  console.log(entry.timestamp, entry.severity, entry.message);
}
```

:::

Returns `Optional[List[LogEntry]]` / `LogEntry[] | null | undefined` - see [LogEntry](#logentry).
Each entry's `severity` is a [LogEntrySeverity](#enums); the response may be absent when no logs have been emitted yet.

### cancel\_execution

Request cancellation of a pending or running execution.

| Parameter | Required | Type             | Default | Description                       |
| --------- | -------- | ---------------- | ------- | --------------------------------- |
| `id`      | yes      | `str` / `string` | —       | The id of a service execution.    |

::: tabs key:pythonTS

\== Python

```python
service.service_api.cancel_execution(execution_id)
```

\== TypeScript

```ts
await service.serviceApi.cancelExecution(executionId);
```

:::

No body returned.

## Reserved request keys

`start_execution` takes a free-form JSON body, but two top-level keys are reserved by the gateway:

* `input` - the service-specific payload; every deployed service expects this shape.
* `inputDataRefs` - references to mounted data-pool files, used when the input is too large to inline or lives in a shared data pool.

Everything else in the body is forwarded verbatim to the service.

## Advanced usage

### Polling for completion

::: tabs key:pythonTS

\== Python

```python
import time

TERMINAL = {"SUCCEEDED", "FAILED", "CANCELLED"}

def wait(execution_id: str, poll_interval: float = 2.0) -> str:
    while True:
        status = service.service_api.get_status(execution_id).status
        if status in TERMINAL:
            return status
        time.sleep(poll_interval)

final = wait(execution.id)
if final == "SUCCEEDED":
    result = service.service_api.get_result(execution.id)
```

\== TypeScript

```ts
const TERMINAL = new Set(["SUCCEEDED", "FAILED", "CANCELLED"]);

async function wait(executionId: string, pollMs = 2000): Promise<string> {
  for (;;) {
    const { status } = await service.serviceApi.getStatus(executionId);
    if (status && TERMINAL.has(status)) return status;
    await new Promise((r) => setTimeout(r, pollMs));
  }
}

const final = await wait(execution.id);
if (final === "SUCCEEDED") {
  const result = await service.serviceApi.getResult(execution.id);
}
```

:::

### Streaming a result file

Large result files are better consumed as a byte stream than loaded whole.

::: tabs key:pythonTS

\== Python

```python
with open("result.json", "wb") as out:
    for chunk in service.service_api.get_result_file(execution.id, "result.json"):
        out.write(chunk)
```

\== TypeScript

```ts
import { Writable } from "node:stream";
import { createWriteStream } from "node:fs";

const file = await service.serviceApi.getResultFile(execution.id, "result.json");
await file.stream().pipeTo(Writable.toWeb(createWriteStream("result.json")));
```

:::

## Endpoints

| Default base URL                                                     | Override                                                                                                                              |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `https://gateway.hub.kipu-quantum.com/<context>/<service>/<version>` | **Always override** - the template placeholders are not substituted by the SDK; pass the full concrete URL via `base_url` / `baseUrl`. |

## Errors

Service gateway responses do not surface per-status typed exceptions.
All Python errors extend `ApiError` (in `qhub.api.service.core.api_error`); all TypeScript errors extend `HubServiceError`.

Other failure modes:

* **Auth misconfiguration (TypeScript)** - constructing `HubServiceClient` without `token` throws `HubServiceError` with message `"Please provide 'token' when initializing the client"`.
* **Timeouts** - hitting `timeoutInSeconds` raises `HubServiceTimeoutError` in TypeScript; in Python, timeouts surface as the underlying `httpx.TimeoutException`.
* **Missing base URL** - because the default environment is an un-substituted template, calling any method without providing a concrete `base_url` / `baseUrl` will produce a 404 (or DNS) failure against the literal URL `https://gateway.hub.kipu-quantum.com/<context>/<service>/<version>`. In Python, omitting both `base_url` and `environment` raises `Exception("Please pass in either base_url or environment to construct the client")`.

All HTTP error instances expose `status_code`, `headers`, and `body` (Python) / `statusCode`, `rawResponse`, and `body` (TypeScript).

## Reference

### ServiceExecution

| Field                 | Python / wire name                              | Type                     | Description                           |
| --------------------- | ----------------------------------------------- | ------------------------ | ------------------------------------- |
| id                    | `id`                                            | `str`                    | Service execution identifier.         |
| created\_at            | `created_at` / `createdAt`                      | `str`                    | When the execution was created.       |
| started\_at            | `started_at` / `startedAt`                      | `str?`                   | When processing started.              |
| ended\_at              | `ended_at` / `endedAt`                          | `str?`                   | When processing ended.                |
| status                | `status`                                        | `ServiceExecutionStatus` | See [lifecycle](#service-execution-lifecycle). |
| type                  | `type`                                          | `ServiceExecutionType?`  | `MANAGED` or `WORKFLOW`.              |
| service\_id            | `service_id` / `serviceId`                      | `str?`                   | Catalog service this run belongs to.  |
| service\_definition\_id | `service_definition_id` / `serviceDefinitionId` | `str?`                   | Service definition (version).         |
| application\_id        | `application_id` / `applicationId`              | `str?`                   | Application that invoked the service. |
| tags                  | `tags`                                          | `List[str]?`             | Free-form tags.                       |

### ResultResponse

HAL envelope returned by [get\_result](#get_result).
Unknown top-level keys are preserved in both languages.

| Field    | Python / wire name        | Type                      | Description                                          |
| -------- | ------------------------- | ------------------------- | ---------------------------------------------------- |
| embedded | `embedded` / `_embedded`  | `ResultResponseEmbedded?` | `status` holds a full [ServiceExecution](#serviceexecution). |
| links    | `links` / `_links`        | `ResultResponseLinks?`    | `status` holds a `HalLink` to the status resource.   |

`HalLink` fields: `href` (`str`, required), and optional `templated` (`bool?`), `type` (`str?`), `deprecation` (`str?`), `name` (`str?`), `profile` (`str?`), `title` (`str?`), `hreflang` (`str?`).

### LogEntry

| Field     | Python / wire name | Type                   | Description                                                      |
| --------- | ------------------ | ---------------------- | ---------------------------------------------------------------- |
| message   | `message`          | `str`                  | Log message content.                                             |
| severity  | `severity`         | `LogEntrySeverity?`    | One of `DEBUG`, `NOTICE`, `INFO`, `WARNING`, `ERROR`.            |
| timestamp | `timestamp`        | `datetime` / `string`  | When the entry was logged; a `datetime` in Python, a `string` in TypeScript. |

### RequestBody

The body accepted by [start\_execution](#start_execution).
A free-form JSON object: `Dict[str, Any]` (Python) / `Record<string, any>` (TypeScript).
See [Reserved request keys](#reserved-request-keys) for the two top-level keys the gateway interprets.

### Enums

* `ServiceExecutionStatus` - `UNKNOWN | PENDING | RUNNING | SUCCEEDED | CANCELLED | FAILED`
* `ServiceExecutionType` - `MANAGED | WORKFLOW`
* `LogEntrySeverity` - `DEBUG | NOTICE | INFO | WARNING | ERROR`

### Field casing

Service-API DTOs use snake\_case in Python and camelCase in TypeScript for most fields, but preserve the HAL wire names (`_links`, `_embedded`) on `ResultResponse` in both languages.
