The Developer API lets you integrate AuthForge with any payment provider, CRM, or internal tool. This guide covers the patterns for building your own integration.
Prerequisites
- An AuthForge account with an app created
- A Developer API key
- A backend that can make HTTP requests
Authentication
All API requests require your API key in the Authorization header:
Authorization: Bearer af_live_your_key_here
Common patterns
Generate a license after payment
The most common integration: create a license when a customer pays, deliver the key.
async function generateLicense({ appId, email, plan, orderId }) {
// Create the license
const response = await fetch("https://api.authforge.cc/v1/licenses", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
appId,
quantity: 1,
maxHwidSlots: plan === "enterprise" ? 5 : 1,
label: orderId,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`AuthForge API error: ${error.error}; ${error.message}`);
}
const { licenses } = await response.json();
const licenseKey = licenses[0].licenseKey;
// Optionally set license variables for the plan tier
if (plan !== "basic") {
await setLicenseVariables(licenseKey, plan);
}
// Deliver to customer
await sendEmail(email, licenseKey);
return licenseKey;
}
Bulk license generation
Generate multiple keys at once (up to 100 per request):
async function generateBulkLicenses(appId, quantity, options = {}) {
const response = await fetch("https://api.authforge.cc/v1/licenses", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
appId,
quantity,
expiresAt: options.expiresAt || null,
maxHwidSlots: options.maxHwidSlots || 1,
label: options.label,
}),
});
const { licenses } = await response.json();
return licenses.map((l) => l.licenseKey);
}
// Generate 50 keys for a reseller
const keys = await generateBulkLicenses("your-app-id", 50, {
expiresAt: "2027-01-01T00:00:00Z",
label: "Reseller batch #42",
});
License lookup and status check
Check a license’s status before taking action:
async function checkLicense(licenseKey) {
const response = await fetch(
`https://api.authforge.cc/v1/licenses/${licenseKey}`,
{
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
},
}
);
if (!response.ok) {
if (response.status === 404) return null;
throw new Error("API error");
}
return await response.json();
}
// Usage
const license = await checkLicense("A3K9-BFWX-7NP2-QHDT");
if (license) {
console.log(`Status: ${license.status}`);
console.log(`Expires: ${license.expiresAt || "never"}`);
console.log(`Devices: ${license.hwidList.length}/${license.maxHwidSlots}`);
}
Customer support: HWID reset
When a customer gets a new machine:
async function resetHwid(licenseKey) {
const response = await fetch(
`https://api.authforge.cc/v1/licenses/${licenseKey}`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ action: "reset-hwid" }),
}
);
return response.ok;
}
Pagination: iterate all licenses
async function getAllLicenses(appId) {
const allLicenses = [];
let cursor = null;
do {
const url = new URL("https://api.authforge.cc/v1/licenses");
url.searchParams.set("appId", appId);
url.searchParams.set("limit", "200");
if (cursor) url.searchParams.set("cursor", cursor);
const response = await fetch(url.toString(), {
headers: {
Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
},
});
const data = await response.json();
allLicenses.push(...data.licenses);
cursor = data.cursor;
} while (cursor);
return allLicenses;
}
Error handling
Always handle API errors gracefully:
async function safeApiCall(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
const body = await response.json().catch(() => ({}));
switch (body.error) {
case "no_credits":
console.error("Out of credits; purchase more in the dashboard");
break;
case "rate_limited":
// Retry with exponential backoff
await sleep(1000);
return safeApiCall(url, options);
case "forbidden":
console.error("App doesn't belong to this account");
break;
default:
console.error(`API error: ${body.error}; ${body.message}`);
}
throw new Error(body.error || "unknown_error");
}
return response.json();
}
Webhooks for event-driven workflows
Instead of polling, use webhooks to react to license events:
app.post("/webhooks/authforge", express.raw({ type: "application/json" }), (req, res) => {
// Verify signature (see webhooks docs)
const event = JSON.parse(req.body);
switch (event.event) {
case "license.validated":
updateLastSeen(event.data.licenseKey);
break;
case "license.hwid_bound":
logNewDevice(event.data.licenseKey, event.data.hwid);
break;
}
res.sendStatus(200);
});
Language examples
Python
import requests
API_KEY = os.environ["AUTHFORGE_API_KEY"]
BASE_URL = "https://api.authforge.cc/v1"
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def create_license(app_id, **kwargs):
response = requests.post(
f"{BASE_URL}/licenses",
headers=HEADERS,
json={"appId": app_id, **kwargs},
)
response.raise_for_status()
return response.json()["licenses"]
Go
func createLicense(appID string, quantity int) ([]License, error) {
body, _ := json.Marshal(map[string]interface{}{
"appId": appID,
"quantity": quantity,
})
req, _ := http.NewRequest("POST", "https://api.authforge.cc/v1/licenses", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("AUTHFORGE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result struct {
Licenses []License `json:"licenses"`
}
json.NewDecoder(resp.Body).Decode(&result)
return result.Licenses, nil
}
Next steps
- API Reference; Full endpoint documentation
- Commerce; Managed Stripe checkout → licenses
- Custom Stripe webhooks; Self-hosted Stripe webhook + Developer API
- Tiered Licensing; Feature gating with license variables