Results, Pagination, and Streaming

Regular JSON responses from the MOI Catalog Service use an envelope containing code, msg, data, and request_id. The SDK checks the HTTP status and business code, then returns only the decoded data on success. Application code normally handles a resource object directly rather than unwrapping another envelope.

Handle regular results and errors

Most Python RawClient methods return a dictionary, list, or None:

from moi import APIError, HTTPError

try:
    catalog = client.get_catalog({"id": 101})
    print(catalog["name"])
except APIError as exc:
    print(exc.code, exc.message, exc.request_id)
except HTTPError as exc:
    print(exc.status_code)

Go methods return a public response type and error:

catalog, err := client.GetCatalog(ctx, &sdk.CatalogInfoRequest{
	CatalogID: 101,
})
if err != nil {
	var apiErr *sdk.APIError
	var httpErr *sdk.HTTPError
	switch {
	case errors.As(err, &apiErr):
		log.Printf("code=%s request_id=%s", apiErr.Code, apiErr.RequestID)
	case errors.As(err, &httpErr):
		log.Printf("http_status=%d", httpErr.StatusCode)
	default:
		log.Printf("request failed: %v", err)
	}
	return
}
fmt.Println(catalog.CatalogName)

A non-2xx response is an HTTPError. A 2xx response whose business code does not indicate success is an APIError. The Go SDK compares OK case-insensitively; the Python SDK currently matches OK exactly. Decode failures, timeouts, and cancellation are other errors. Classify a failure before retrying; authentication, authorization, validation, and deterministic business errors should not be retried blindly.

LLM Proxy methods are a documented exception: they decode direct responses rather than the envelope above. Handle them according to their public response types.

Paginate according to the method

The SDKs do not provide one universal automatic paginator. Many Catalog lists place the one-based page and page_size fields in common_condition and return a resource list with total. Other methods use top-level fields or query parameters.

This example follows the public role-list shape:

page = 1
page_size = 100

while True:
    result = client.list_roles({
        "keyword": "",
        "common_condition": {
            "page": page,
            "page_size": page_size,
            "order": "desc",
            "order_by": "created_at",
            "filters": [],
        },
    })
    roles = result.get("role_list") or result.get("list") or []
    for role in roles:
        print(role["name"])

    total = result.get("total", 0)
    if len(roles) < page_size or (total and page * page_size >= total):
        break
    page += 1

Add a maximum-page guard to prevent an abnormal response from causing an infinite loop. Do not mechanically apply common_condition to every method. Workflow job lists use top-level page / page_size, knowledge lists use page_number / page_size, and LLM message lists can use after / limit. Follow the public request type and method documentation for the endpoint.

Download with FileStream

Methods that download table data, file previews, or GenAI results return FileStream instead of loading the entire response into memory. The caller must close it.

Python supports a context manager:

with client.download_table_data({"id": 301}) as stream:
    written = stream.write_to_file("/tmp/orders.csv")
    print(f"wrote {written} bytes")

You can also process chunks with iter_content(chunk_size). In Go, use defer:

stream, err := client.DownloadTableData(ctx, &sdk.TableDownloadDataRequest{
	ID: 301,
})
if err != nil {
	return err
}
defer stream.Close()

written, err := stream.WriteToFile("/tmp/orders.csv")

FileStream exposes response status and headers. For large files, stream to a destination rather than calling read() or io.ReadAll first.

Consume data-analysis SSE

analyze_data_stream / AnalyzeDataStream returns Server-Sent Events for data analysis. Events can include initialization, question classification, processing steps, and completion information. Read the event object’s type, step_type, step_name, and data fields.

Python:

from moi import with_stream_read_timeout

stream = client.analyze_data_stream(
    {
        "question": "Summarize sales by region",
        "config": {
            "data_category": "admin",
            "data_source": {"type": "all"},
        },
    },
    with_stream_read_timeout(60.0),
)
try:
    while True:
        event = stream.read_event()
        if event is None:
            break
        print(event.type, event.step_type, event.data)
finally:
    stream.close()

The streaming read timeout is the allowed delay between messages and defaults to 30 seconds. It is distinct from the total timeout for a regular HTTP call. Go provides the corresponding WithStreamReadTimeout option and ReadEvent method, while context cancellation terminates the overall call.

EOF means only that the transport ended. Decide business success from the completion event. If a timeout, cancellation, or parse failure occurs before completion, record the result as interrupted rather than successful.