HF PanelDocsv1.0.0

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. A POST is 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 without Retry-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

bash
composer config repositories.hfpanel path ./third_party/hfpanel     # the copied integrations/sdk/php folder
composer require hfpanel/hfpanel:1.0.0
php
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

bash
pip install ./integrations/sdk/python
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

bash
cd integrations/sdk/typescript && npm install && npm run build
npm install /path/to/integrations/sdk/typescript     # in your project
ts
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

bash
go mod edit -require=hfpanel@v1.0.0 -replace=hfpanel=./third_party/hfpanel   # after copying the folder
go
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

bash
cd integrations/sdk/java && mvn -q package        # target/hfpanel-1.0.0.jar
java
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:

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

bash
dotnet add reference third_party/HfPanel/src/HfPanel/HfPanel.csproj   # or dotnet pack and add the package
csharp
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

bash
cd integrations/sdk/ruby && gem build hfpanel.gemspec && gem install ./hfpanel-1.0.0.gem
ruby
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

toml
[dependencies]
hfpanel = { path = "third_party/hfpanel" }     # the copied integrations/sdk/rust folder
rust
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

swift
// Package.swift
dependencies: [.package(path: "third_party/HFPanel")],     // the copied integrations/sdk/swift folder
swift
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

yaml
dependencies:
  hfpanel:
    path: third_party/hfpanel          # the copied integrations/sdk/dart folder
dart
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

bash
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
bash
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.