SDKs and client libraries

Use the official TypeScript, Go, and Python SDKs through the same typed resource graph.

Openhandle publishes official TypeScript, JavaScript, Go, and Python SDKs. Every API-reference page lets you switch between cURL, the TypeScript SDK, and Go for the same deterministic Test request.

TypeScript SDK

Install @openhandle/sdk:

pnpm add @openhandle/sdk

Create one client and select resources synchronously. Only terminal operations such as get, list, search, and fetch perform network requests.

import { OpenHandle } from '@openhandle/sdk';

const openhandle = new OpenHandle({ apiKey: process.env.OPENHANDLE_TEST_KEY! });
const profile = openhandle.instagram.profile('northstar_forge_test');

const response = await profile.get();
const posts = await profile.posts.list({ freshness: '24h' });

Profile strings are usernames. Use an explicit object for a platform ID or URL:

openhandle.instagram.profile({ id: '25025320' });
openhandle.instagram.profile({ url: 'https://www.instagram.com/openai/' });

IDs are opaque strings; numeric ID values are rejected before a request is made.

Named types

The package exports camel-cased models and concise types for every operation. Use them when a wrapper, cache, callback, or public function needs an explicit type annotation:

import type {
    InstagramProfile,
    InstagramProfilePostsOptions,
    InstagramProfilePostsPage,
    InstagramProfileResponse,
} from '@openhandle/sdk';

async function getProfile(): Promise<InstagramProfileResponse> {
    return openhandle.instagram.profile('northstar_forge_test').get();
}

function readProfile(profile: InstagramProfile) {
    return profile.handle;
}

Operation names omit terminal get and list words, so instagram.profile.posts.list maps to InstagramProfilePostsOptions and InstagramProfilePostsPage.

Pagination

List operations return a typed page. next() is lazy and returns null after the final page.

let page = await profile.posts.list({ freshness: '24h' });

while (true) {
    for (const post of page.data) {
        console.log(post.id);
    }
    const nextPage = await page.next();
    if (!nextPage) break;
    page = nextPage;
}

Each fetched page is one request. See pagination for cursor and upstream-switch rules.

Errors

The SDK retries explicitly retryable failures by default. Catch OpenHandleError and branch on code, never message.

import { OpenHandleError } from '@openhandle/sdk';

try {
    await profile.get();
} catch (error) {
    if (error instanceof OpenHandleError) {
        console.error(error.code, error.requestId, error.retryable);
    }
    throw error;
}

See envelope and errors for the public error catalog.

Go SDK

Install openhandle-go. The SDK requires Go 1.27 or newer.

go get github.com/openhandlehq/openhandle-go

Create one client and select resources synchronously. Only terminal operations such as Get, List, Search, and Fetch perform network requests.

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    openhandle "github.com/openhandlehq/openhandle-go"
)

func main() {
    client, err := openhandle.New(os.Getenv("OPENHANDLE_TEST_KEY"))
    if err != nil {
        log.Fatal(err)
    }

    profile := client.Instagram.Profile("northstar_forge_test")
    response, err := profile.Get(context.Background(), &openhandle.InstagramProfileOptions{
        Freshness: openhandle.FreshnessTwentyFourH,
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(response.Data.Handle)
}

Raw strings use the selected resource's natural shorthand. Explicit reference types remove ambiguity for IDs and URLs.

client.Instagram.Profile("openai")
client.Instagram.Profile(openhandle.Username("12356"))
client.Instagram.Profile(openhandle.ID("25025320"))
client.Instagram.Profile(openhandle.URL("https://www.instagram.com/openai/"))

Pagination

Pages expose the opaque next cursor. Next is lazy and returns nil after the final page.

page, err := client.Instagram.Profile("northstar_forge_test").Posts.List(
    context.Background(),
    &openhandle.InstagramProfilePostsOptions{
        Freshness: openhandle.FreshnessTwentyFourH,
    },
)

for page != nil && err == nil {
    for _, post := range page.Data {
        fmt.Println(post.ID)
    }
    page, err = page.Next(context.Background())
}
if err != nil {
    log.Fatal(err)
}

Each fetched page is one request. See pagination for cursor and upstream-switch rules.

Errors

The SDK retries explicitly retryable failures by default. Use errors.As to read *openhandle.Error. Branch on Code, never Message.

var apiError *openhandle.Error
if errors.As(err, &apiError) {
    log.Printf("code=%s request_id=%s retryable=%t", apiError.Code, apiError.RequestID, apiError.Retryable)
}

See envelope and errors for the public error catalog.

Python SDK

Install openhandle. The package requires Python 3.10 or newer and ships full type annotations.

pip install openhandle

Create one client and select resources synchronously. Only terminal operations such as get, list, search, and fetch perform network requests.

import os

from openhandle import OpenHandle

openhandle = OpenHandle(api_key=os.environ["OPENHANDLE_TEST_KEY"])
profile = openhandle.instagram.profile("northstar_forge_test")

response = profile.get()
posts = profile.posts.list(freshness="24h")

print(response.data["handle"], len(posts.data))

Profile strings are usernames. Use an explicit keyword for a platform ID or URL:

openhandle.instagram.profile(id="25025320")
openhandle.instagram.profile(url="https://www.instagram.com/openai/")

IDs are opaque strings; numeric ID values are rejected before a request is made.

Named models

Responses stay typed through the generated models in openhandle.models:

from openhandle import OpenHandle, Response
from openhandle.models import InstagramProfile


def get_profile(openhandle: OpenHandle) -> Response[InstagramProfile]:
    return openhandle.instagram.profile("northstar_forge_test").get()

Pagination

List operations return a typed page. next() is lazy and returns None after the final page. items() iterates lazily across pages, one request per page.

page = profile.posts.list(freshness="24h")
while page is not None:
    for post in page.data:
        print(post["id"])
    page = page.next()

for post in profile.posts.items(freshness="24h"):
    print(post["id"])

Each fetched page is one request. See pagination for cursor and upstream-switch rules.

Async client

AsyncOpenHandle exposes the same resource graph with async terminal operations:

from openhandle import AsyncOpenHandle

async with AsyncOpenHandle(api_key=os.environ["OPENHANDLE_TEST_KEY"]) as openhandle:
    response = await openhandle.instagram.profile("northstar_forge_test").get()

    async for post in openhandle.instagram.profile("northstar_forge_test").posts.items():
        print(post["id"])

Errors

The SDK retries explicitly retryable failures by default. Catch OpenHandleError and branch on code, never message.

from openhandle import OpenHandleError

try:
    profile.get()
except OpenHandleError as error:
    print(error.code, error.request_id, error.retryable)
    raise

See envelope and errors for the public error catalog.

Test and Live

The client does not take an environment option. The API key selects Test or Live: oh_test_ keys use deterministic synthetic data at a $0.000 actual charge, while oh_live_ keys use public live data and normal billing. Moving to Live only requires changing the key and identifiers.

On this page