Getting Started — validate your first license in 5 minutes¶
This is the fastest path from a clean checkout to a validated license: run the server → create a product → create a plan → issue a license → validate it from an SDK. Everything below uses the public REST API and the client SDKs — the same surface a real integrator hits.
Why an SDK for the last step? License validation is a challenge/response: the server issues a geometric-proof challenge, the client solves it and submits the proof. The SDKs do this for you in one
validate()call, so you don't hand-roll the proof. See Geometric Proof, explained.
0. Prerequisites¶
- Rust 1.70+ (with
cargo) - PostgreSQL 12+ reachable (the devcontainer already runs one on
:5432)
1. Run the server (migrations run automatically)¶
- Health: http://localhost:8000/health
- Swagger UI: http://localhost:8000/swagger-ui/
- OpenAPI JSON: http://localhost:8000/api-docs/openapi.json
Auth. Management endpoints (create product/plan/license) always require a key — there is no open mode. Send it as
Authorization: ApiKey <key>; the headerX-API-Keyis not read. The key must be an admin key: being inSAWABONA_API_KEYS__KEYSgets you past the door, and these routes then checkSAWABONA_API_KEYS__ADMIN_KEYS(orSAWABONA_ADMIN_API_KEY) as well.
config/development.tomlships two fixtures:test-operator-key, which is an admin key, andtest-admin-key, which — despite its name — is a plain one. The commands below use the operator key and work against a local instance as written. Both are public in this repository, and the server refuses to start with either of them outside development.A few routes are open by design because the licence key is itself the credential:
/api/v1/licenses/validate, the geometric-proof challenge and proof,/heartbeat, and the public/api/v1/catalogue.
2. Create a product¶
curl -sX POST http://localhost:8000/api/v1/products \
-H "Authorization: ApiKey test-operator-key" \
-H "Content-Type: application/json" \
-d '{"name":"My App","slug":"my-app","version":"1.0.0","description":"Demo product"}'
Copy the returned id (a UUID) → PRODUCT_ID.
The slug also becomes the product segment of every licence key, so it is
limited to 1-8 characters, lowercase letters, digits and inner hyphens, no
underscore — a key is six underscore-separated segments and an underscore here
adds a seventh. For a longer name, pass a short key_slug beside it:
{"name":"My Application","slug":"my-application","key_slug":"myapp", …}.
3. Create a plan for that product¶
curl -sX POST http://localhost:8000/api/v1/plans \
-H "Authorization: ApiKey test-operator-key" \
-H "Content-Type: application/json" \
-d '{"name":"Pro","product_id":"'"$PRODUCT_ID"'","slug":"pro","tier":"pro","price_monthly":4900}'
Copy the returned plan id → PLAN_ID. (price_monthly is in minor units, e.g. cents.)
4. Issue a license¶
The server generates the key — you do not supply one. The plaintext key is returned exactly once (it is stored hashed); copy it now.
curl -sX POST http://localhost:8000/api/v1/licenses \
-H "Authorization: ApiKey test-operator-key" \
-H "Content-Type: application/json" \
-d '{"product_id":"'"$PRODUCT_ID"'","plan_id":"'"$PLAN_ID"'","max_activations":5,"duration_days":365}'
// response (illustrative)
{
"id": "…",
"key": "saw_default-tenant_my-app_live_a1b2c3d4e5f6a7b8c9d0_f0e1", // shown once — copy it
"key_hash": "…", // only the hash is stored
"status": "active"
}
The
live/testsegment in the key reflects the server's environment.
5. Validate it from an SDK¶
Pick your language — every SDK exposes the same validate() surface and handles
the geometric-proof challenge internally. Python:
from sawabona_sdk import LicenseClient
with LicenseClient(
"http://localhost:8000",
"saw_default-tenant_my-app_live_a1b2c3d4e5f6a7b8c9d0_f0e1", # the key from step 4
"my-app", # product slug
) as client:
result = client.validate()
print(result.is_valid()) # True
print(result.features) # plan features
print(client.has_feature("pro"))
The same flow exists for Rust, TypeScript, Go, Java, C#, C, C++ and Ada — see the SDK index.
Validating directly over HTTP (
POST /api/v1/licenses/validate) is possible but requires building and submitting a geometric proof yourself; the SDKs are the supported path.
Retiring things (there is no hard delete — by design)¶
Products, plans and licenses are never hard-deleted over the API: licenses reference products/plans by foreign key and audit/billing lineage must survive. Use the soft lifecycle instead:
- License →
POST /api/v1/licenses/{id}/revoke - Plan →
PATCH /api/v1/plans/{id}with{"is_active": false} - Product →
PATCH /api/v1/products/{id}with{"is_active": false}
New licenses cannot be issued against an inactive product or plan; existing
licenses are unaffected. Reactivate by patching is_active back to true.
Next steps¶
- SDK index — all client libraries + per-language quickstarts
- License service guide — revoke, rotate, transfer, heartbeats, offline tokens
- API reference & Swagger UI — full endpoint surface
- Payment providers — wire Stripe / Paddle / Paystack / Flutterwave to auto-issue licenses from webhooks
- Geometric proof, explained — how validation actually works
- Production security checklist — before you go live