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/sdkCreate 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-goCreate 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 openhandleCreate 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)
raiseSee 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.