SDKs
Typed clients for the token API in PHP, Python, TypeScript, Go, Java, Kotlin, C#, Ruby, Rust, Swift, Dart and the shell, with safe retries built in.
HF Panel comes with ready-made clients for the token API in every widely used language, and a command-line client for shell scripts. Each has one method per endpoint, typed request bodies and answers, and the same behaviour:
- answers are unwrapped from
{"ok": true, "data": …}; a refusal raises an error with the HTTP status and the API's own sentence; - calls are tried again (2 times by default) on 429 with
Retry-After, 502, 503, 504 and network errors. APOSTis retried only with an Idempotency-Key; when you give none the client makes one up, so a retry never creates something twice. The sign-in lock-out (429 withoutRetry-After) is not retried; - the last answer's request limit headers are kept, and so are its warnings.
They are in the HF Panel source under integrations/sdk/, each with a README, an example program and tests. The
methods are generated from the OpenAPI description, so every client has every endpoint.
| Language | Folder | Package | Needs |
|---|---|---|---|
| PHP | integrations/sdk/php |
Composer hfpanel/hfpanel |
PHP 8.1+, the curl extension or PHP's own streams; any PSR-18 client optional |
| Python | integrations/sdk/python |
hfpanel |
Python 3.9+, standard library only |
| TypeScript / JavaScript | integrations/sdk/typescript |
hfpanel-api |
Node.js 18+ or any runtime with fetch |
| Go | integrations/sdk/go |
module hfpanel |
Go 1.22+, no dependencies |
| Java and Kotlin | integrations/sdk/java |
Maven hfpanel:hfpanel |
Java 11+ (java.net.http), no dependencies |
| C# / .NET | integrations/sdk/csharp |
NuGet HfPanel |
.NET 6+ (HttpClient, System.Text.Json) |
| Ruby | integrations/sdk/ruby |
gem hfpanel |
Ruby 3.0+, standard library only |
| Rust | integrations/sdk/rust |
crate hfpanel |
Rust 1.71+, ureq and serde (blocking, no async runtime) |
| Swift | integrations/sdk/swift |
Swift package HFPanel |
Swift 5.7+, URLSession with async/await |
| Dart / Flutter | integrations/sdk/dart |
package hfpanel |
Dart 3, package:http |
| Shell | integrations/sdk/bash |
command hfpanel |
bash and curl; jq optional |
Every example below uses the API's address https://api.yourdomain.com:2087/v1 and a token from the HF_TOKEN
environment variable. Make the token under your name at the top right › Password & security › API tokens
(see Make a token).
Warning: keep API tokens on your server. Never put a token in code that runs in a visitor's browser or in an app that customers install.
PHP
composer config repositories.hfpanel path ./third_party/hfpanel # the copied integrations/sdk/php folder
composer require hfpanel/hfpanel:1.0.0
use HfPanel\Client;
use HfPanel\HFPanelError;
$hf = new Client('https://api.yourdomain.com:2087/v1', getenv('HF_TOKEN'));
foreach ($hf->listAccounts() as $acc) {
echo $acc['username'], ' ', $acc['status'], "\n";
}
try {
$made = $hf->createAccount(username: 'sarahbakes', email: 'sarah@example.com', package: 'Starter',
idempotencyKey: 'order-1001');
echo $made['password'], "\n";
$hf->createMailbox('sarahbakes', address: 'info@example.com', quotaMb: 1024);
} catch (HFPanelError $e) {
echo "HF Panel said {$e->status}: {$e->getMessage()}\n";
}
Without Composer, require 'hfpanel/autoload.php';. Answers are arrays.
Python
pip install ./integrations/sdk/python
from hfpanel import Client, HFPanelError
hf = Client("https://api.yourdomain.com:2087/v1", os.environ["HF_TOKEN"])
for acc in hf.list_accounts():
print(acc["username"], acc["status"])
try:
made = hf.create_account(username="sarahbakes", email="sarah@example.com", package="Starter",
idempotency_key="order-1001")
print(made["password"])
hf.create_mailbox("sarahbakes", address="info@example.com", quota_mb=1024)
except HFPanelError as e:
print("HF Panel said", e.status, e.message)
TypeScript
cd integrations/sdk/typescript && npm install && npm run build
npm install /path/to/integrations/sdk/typescript # in your project
import { Client, HFPanelError } from "hfpanel-api";
const hf = new Client("https://api.yourdomain.com:2087/v1", process.env.HF_TOKEN!);
for (const acc of await hf.listAccounts()) console.log(acc.username, acc.status);
try {
const made = await hf.createAccount(
{ username: "sarahbakes", email: "sarah@example.com", package: "Starter" },
{ idempotencyKey: "order-1001" },
);
console.log(made.password);
await hf.createMailbox("sarahbakes", { address: "info@example.com", quota_mb: 1024 });
} catch (e) {
if (e instanceof HFPanelError) console.error(`HF Panel said ${e.status}: ${e.message}`);
}
Go
go mod edit -require=hfpanel@v1.0.0 -replace=hfpanel=./third_party/hfpanel # after copying the folder
c := hfpanel.New("https://api.yourdomain.com:2087/v1", os.Getenv("HF_TOKEN"))
ctx := context.Background()
accounts, err := c.ListAccounts(ctx)
for _, acc := range accounts {
fmt.Println(acc.Username, acc.Status)
}
// optional fields are pointers: hfpanel.String, hfpanel.Int, hfpanel.Bool
_, err = c.CreateAccount(ctx, hfpanel.CreateAccountRequest{Username: "sarahbakes", Email: "sarah@example.com",
Package: "Starter"}, hfpanel.WithIdempotencyKey("order-1001"))
_, err = c.CreateMailbox(ctx, "sarahbakes", hfpanel.CreateMailboxRequest{
Address: "info@example.com", QuotaMB: hfpanel.Int(1024)})
var e *hfpanel.APIError
if errors.As(err, &e) {
log.Printf("HF Panel said %d: %s", e.Status, e.Message)
}
Java and Kotlin
cd integrations/sdk/java && mvn -q package # target/hfpanel-1.0.0.jar
Client hf = new Client("https://api.yourdomain.com:2087/v1", System.getenv("HF_TOKEN"));
for (Account acc : hf.listAccounts()) {
System.out.println(acc.getUsername() + " " + acc.getStatus());
}
try {
Map<String, Object> made = hf.createAccount(
new CreateAccountRequest("sarahbakes", "sarah@example.com", "Starter"), "order-1001");
System.out.println(made.get("password"));
hf.createMailbox("sarahbakes", new CreateMailboxRequest("info@example.com").quotaMb(1024));
} catch (HFPanelError e) {
System.err.println("HF Panel said " + e.getStatus() + ": " + e.getMessage());
}
The same library from Kotlin:
val hf = Client("https://api.yourdomain.com:2087/v1", System.getenv("HF_TOKEN"))
hf.listAccounts().forEach { println("${it.username} ${it.status}") }
try {
val made = hf.createAccount(CreateAccountRequest("sarahbakes", "sarah@example.com", "Starter"), "order-1001")
println(made["password"])
} catch (e: HFPanelError) {
println("HF Panel said ${e.status}: ${e.message}")
}
C# / .NET
dotnet add reference third_party/HfPanel/src/HfPanel/HfPanel.csproj # or dotnet pack and add the package
using var hf = new Client("https://api.yourdomain.com:2087/v1", Environment.GetEnvironmentVariable("HF_TOKEN")!);
foreach (var acc in await hf.ListAccountsAsync())
Console.WriteLine($"{acc.Username} {acc.Status}");
try
{
var made = await hf.CreateAccountAsync(
new CreateAccountRequest { Username = "sarahbakes", Email = "sarah@example.com", Package = "Starter" }, "order-1001");
Console.WriteLine(made.GetProperty("password").GetString());
await hf.CreateMailboxAsync("sarahbakes", new CreateMailboxRequest { Address = "info@example.com", QuotaMb = 1024 });
}
catch (HFPanelError e)
{
Console.WriteLine($"HF Panel said {e.Status}: {e.Message}");
}
Ruby
cd integrations/sdk/ruby && gem build hfpanel.gemspec && gem install ./hfpanel-1.0.0.gem
require 'hfpanel'
hf = HFPanel::Client.new('https://api.yourdomain.com:2087/v1', ENV.fetch('HF_TOKEN'))
hf.list_accounts.each { |acc| puts "#{acc['username']} #{acc['status']}" }
begin
made = hf.create_account(username: 'sarahbakes', email: 'sarah@example.com', package: 'Starter',
idempotency_key: 'order-1001')
puts made['password']
hf.create_mailbox('sarahbakes', address: 'info@example.com', quota_mb: 1024)
rescue HFPanel::HFPanelError => e
puts "HF Panel said #{e.status}: #{e.message}"
end
Rust
[dependencies]
hfpanel = { path = "third_party/hfpanel" } # the copied integrations/sdk/rust folder
use hfpanel::{Client, CreateAccountRequest, CreateMailboxRequest};
let hf = Client::new("https://api.yourdomain.com:2087/v1", &std::env::var("HF_TOKEN")?);
for acc in hf.list_accounts()? {
println!("{:?} {:?}", acc.username, acc.status);
}
let made = hf.create_account(
&CreateAccountRequest { username: "sarahbakes".into(), email: "sarah@example.com".into(),
package: "Starter".into(), ..Default::default() },
Some("order-1001"),
);
match made {
Ok(made) => println!("{}", made["password"]),
Err(e) => eprintln!("HF Panel said {}: {}", e.status, e.message),
}
hf.create_mailbox("sarahbakes", &CreateMailboxRequest { address: "info@example.com".into(),
quota_mb: Some(1024), ..Default::default() }, None)?;
Swift
// Package.swift
dependencies: [.package(path: "third_party/HFPanel")], // the copied integrations/sdk/swift folder
import HFPanel
let hf = Client(baseURL: "https://api.yourdomain.com:2087/v1", token: ProcessInfo.processInfo.environment["HF_TOKEN"]!)
for acc in try await hf.listAccounts() {
print(acc.username ?? "", acc.status ?? "")
}
do {
let made = try await hf.createAccount(username: "sarahbakes", email: "sarah@example.com", package: "Starter",
idempotencyKey: "order-1001")
print(made["password"]?.stringValue ?? "")
_ = try await hf.createMailbox("sarahbakes", address: "info@example.com", quotaMb: 1024)
} catch let e as HFPanelError {
print("HF Panel said \(e.status): \(e.message)")
}
Dart and Flutter
dependencies:
hfpanel:
path: third_party/hfpanel # the copied integrations/sdk/dart folder
import 'package:hfpanel/hfpanel.dart';
final hf = Client('https://api.yourdomain.com:2087/v1', Platform.environment['HF_TOKEN']!);
for (final acc in await hf.listAccounts()) {
print('${acc.username} ${acc.status}');
}
try {
final made = await hf.createAccount(username: 'sarahbakes', email: 'sarah@example.com', package: 'Starter',
idempotencyKey: 'order-1001');
print(made['password']);
await hf.createMailbox('sarahbakes', address: 'info@example.com', quotaMb: 1024);
} on HFPanelError catch (e) {
print('HF Panel said ${e.status}: ${e.message}');
}
Shell
sudo install -m 755 integrations/sdk/bash/hfpanel /usr/local/bin/hfpanel
export HF_API=https://api.yourdomain.com:2087/v1 HF_TOKEN=hf_xxx
hfpanel accounts list
hfpanel accounts create --username sarahbakes --email sarah@example.com --package Starter \
--idempotency-key order-1001
hfpanel email create-mailbox sarahbakes --address info@example.com --quota-mb 1024
if ! hfpanel accounts get nobody; then # exit 1, "HF Panel said 404: No such account." on stderr
echo "not there"
fi
hfpanel help lists every command and hfpanel help accounts create one command's arguments. A command is the
endpoint's section and action (accounts create, dns add-record, cron-jobs add) or the operation's own name
(create-account). With jq installed the answer's data is printed pretty; any language that can run a program can
use it.
Method names
The methods are named after the endpoints' operation ids in the description. createAccount is:
| Language | Method |
|---|---|
| PHP, Java, Kotlin, TypeScript, Swift, Dart | createAccount |
| Python, Ruby, Rust | create_account |
| Go | CreateAccount |
| C# | CreateAccountAsync |
| Shell | hfpanel accounts create (or hfpanel create-account) |
Path parameters come first, in path order, then the body. The API explorer lists every endpoint.