🚀 HakiAPI
Build production-grade Python API SDKs — not boilerplate.
Authentication · Retries · Pagination · Typed Exceptions · Session Management
Stop rewriting authentication, retries, and pagination for every API client you build.
Installation • Quick Start • Features • Architecture • Create Your Own Client • Roadmap
Why HakiAPI?
Every API client grows the same infrastructure, in the same order. You start with a simple HTTP call. Then you add authentication. Then retries. Then pagination. Then timeout and exception handling. Then session management. A month later, you've rebuilt the same plumbing you already wrote for the last five projects.
HakiAPI extracts all of that into one reusable core, so every client you build on top of it inherits production-ready behavior automatically. Instead of writing infrastructure, you write business logic.
Without HakiAPI vs. with HakiAPI
Raw requests |
HakiAPI | |
|---|---|---|
| Retry on 429/500/502/503/504 | Write your own urllib3.Retry + HTTPAdapter wiring |
Built into BaseAPIClient automatically |
| Auth (Bearer / API Key / HMAC) | Reimplement per project | 4 reusable AuthBase strategies, drop-in |
| Rate limits, timeouts, 4xx/5xx | Manually check response.status_code everywhere |
Raised as typed, catchable exceptions |
| Pagination | Write a while loop per API's pagination style |
Auto-detects Link-header and cursor/token pagination, iterated lazily |
| New service client | Copy-paste session + error-handling code | Subclass BaseAPIClient, define endpoints, done |
A real example: fetching your 3 latest unread emails
Talk is cheap — here's the same task, side by side. No auth boilerplate, no manual pagination, no hand-rolled URLs.
❌ Without HakiAPI
import requests
import os
token = os.environ["GMAIL_OAUTH_TOKEN"]
headers = {"Authorization": f"Bearer {token}"}
base_url = "https://gmail.googleapis.com/gmail/v1/users/me"
params = {"q": "is:unread"}
unread_messages = []
# Keep pulling pages until we have 3 messages or run out of pages
while True:
response = requests.get(f"{base_url}/messages", headers=headers, params=params)
response.raise_for_status()
data = response.json()
for msg in data.get("messages", []):
unread_messages.append(msg)
if len(unread_messages) >= 3:
break
if len(unread_messages) >= 3 or "nextPageToken" not in data:
break
params["pageToken"] = data["nextPageToken"]
# Fetch the full body for each message
for msg in unread_messages:
msg_data = requests.get(f"{base_url}/messages/{msg['id']}", headers=headers).json()
print(msg_data.get("snippet"))
✅ With HakiAPI
import os
from hakiapi import GmailClient
token = os.environ["GMAIL_OAUTH_TOKEN"]
with GmailClient(token=token) as gmail:
# Auth, retries, and pagination are handled for you
unread_messages = gmail.messages.search(query="is:unread", max_pages=1)
for count, msg in enumerate(unread_messages):
if count >= 3:
break
full_msg = gmail.messages.get(message_id=msg["id"])
print(full_msg.get("snippet"))
What HakiAPI handles for you here:
- Pagination — Google-style
nextPageToken(and Link-header pagination elsewhere) is followed automatically, with amax_pagessafety valve so you never accidentally loop forever. - Resource routing —
client.resource.action()instead of hand-building URLs and re-typing the same base path everywhere. - Type safety — built with static analysis in mind, so tools like Ruff and Pyright stay useful instead of fighting
dictsoup.
✨ Features
| Feature | Details |
|---|---|
| 🔐 Multiple auth strategies | BearerTokenAuth, HeaderApiKeyAuth, QueryApiKeyAuth, HmacAuth |
| 🔁 Automatic retries | Exponential backoff on 429/500/502/503/504, mounted transparently on every session |
| 📄 Automatic pagination | Auto-detects Link-header (GitHub-style) and cursor/token (meta.next_token) pagination, lazily iterated with an optional max_pages safety valve |
| ⚠️ Typed exception hierarchy | RateLimitError, AuthenticationError, ClientError, ServerError, RequestTimeoutError, all rooted in HakiAPIError |
| 🌐 Persistent HTTP sessions | Connection pooling via requests.Session, closed automatically with with |
| 🧩 Extensible base client | Subclass BaseAPIClient, inherit everything above for free |
| 📦 Ready-to-use clients | GitHub, Gmail, GoogleCalendar |
| 🧪 Fully tested | 220+ tests, full coverage of core + clients |
| 🐍 Python 3.10+ | Fully type-hinted |
Installation
pip install hakiapi
Requires Python 3.10+.
Quick Start
GitHub
from hakiapi.clients.github import GitHubClient
with GitHubClient() as github:
user = github.get_user("torvalds")
print(f"{user['name']} — {user['public_repos']} public repos")
No authentication plumbing. No retry logic. No session handling. Just Python.
Automatic pagination
Forget page numbers, while loops, and manually checking for a next page — HakiAPI follows Link headers, cursor pagination, and token pagination automatically, lazily:
with GitHubClient() as github:
for repo in github.get_all_user_repos("torvalds"):
print(repo["name"])
A full example
Aggregate every programming language used across a user's public repositories, entirely through the paginator:
from hakiapi.clients.github import GitHubClient
with GitHubClient() as github:
target_user = "Gugilla-Aakash"
user_data = github.get_user(target_user)
print(f"User: {user_data.get('name')}")
print(f"Public Repos: {user_data.get('public_repos')}")
lang_stats = github.get_aggregate_user_languages(target_user, params={"per_page": 5})
total_bytes = sum(lang_stats.values())
print("\nLanguage Breakdown (by byte allocation):")
for lang, byte_count in sorted(lang_stats.items(), key=lambda i: i[1], reverse=True):
percentage = (byte_count / total_bytes) * 100 if total_bytes else 0
print(f"- {lang}: {byte_count} bytes ({percentage:.2f}%)")
Output shape is illustrative — real numbers depend on the account queried.
Gmail
from hakiapi.clients.gmail import GmailClient
with GmailClient(token="your-oauth-token") as gmail:
for message in gmail.get_all_messages():
print(message["id"])
Authentication, pagination, and retries — already handled.
Exception Handling
Never check HTTP status codes manually again:
from hakiapi.core.exceptions import AuthenticationError, RateLimitError, ServerError
try:
github.get_user("torvalds")
except RateLimitError as e:
print(f"Rate limited — retry after {e.retry_after}s")
except AuthenticationError:
print("Invalid credentials.")
except ServerError:
print("GitHub is currently unavailable.")
Every exception inherits from HakiAPIError, so you can catch broadly or narrowly — each carries status_code and the original response object.
HakiAPIError
├── ClientError (4xx)
│ ├── RateLimitError (429, carries retry_after)
│ └── AuthenticationError (401 / 403, carries auth_method)
├── ServerError (5xx)
└── RequestTimeoutError (network-level timeout, no status code)
Supported Authentication
| Strategy | Use case |
|---|---|
BearerTokenAuth |
Standard Authorization: Bearer <token> (GitHub, Gmail, most OAuth2 APIs) |
HeaderApiKeyAuth |
Custom header-based API keys (e.g. X-API-Key: <key>) |
QueryApiKeyAuth |
Query-string API keys, appended without dropping existing params |
HmacAuth |
HMAC-SHA256 request signing — signs method, path, timestamp, and body; sets the key, timestamp, and signature headers |
Every strategy is a reusable AuthBase instance — pass it once into BaseAPIClient.__init__, and every request is signed automatically.
Automatic Retry
Retries happen transparently on 429, 500, 502, 503, and 504, via urllib3's Retry mounted on the session's HTTPAdapter:
- Exponential backoff
- Connection reuse across retries
- Timeout handling surfaced as
RequestTimeoutError - No configuration required for standard usage — override
total_retries,backoff_factor, orstatus_forcelistif you need to
Architecture
GitHubClient, GmailClient, ...
│
▼
BaseAPIClient
┌──────────────┬─────────┬──────────┐
│ │ │ │
Authentication Retry Pagination Exceptions
(auth.py) (retry.py) (paginator.py) (exceptions.py)
│
▼
requests
Every service client inherits production-ready infrastructure automatically — nothing to wire up per client.
Create Your Own Client
Creating a new SDK is intentionally simple: subclass BaseAPIClient, point it at a base URL, and define your endpoints as plain methods.
from hakiapi import BaseAPIClient
class WeatherClient(BaseAPIClient):
def __init__(self, **kwargs):
super().__init__(base_url="https://api.open-meteo.com/v1", **kwargs)
def get_weather(self, latitude: float, longitude: float):
return self.get(
"forecast",
params={
"latitude": latitude,
"longitude": longitude,
"current_weather": True,
},
)
if __name__ == "__main__":
# Hyderabad, Telangana, India
with WeatherClient() as client:
weather = client.get_weather(latitude=17.385, longitude=78.4867)
print(weather["current_weather"])
Output
{
'time': '2026-07-19T10:00',
'interval': 900,
'temperature': 30.6,
'windspeed': 13.9,
'winddirection': 271,
'is_day': 1,
'weathercode': 51
}
Authentication, retries, pagination, sessions, and exceptions are already included — you only write the endpoint logic.
Project Structure
hakiapi/
├── core/
│ ├── auth.py # BearerTokenAuth, HeaderApiKeyAuth, QueryApiKeyAuth, HmacAuth
│ ├── retry.py # Exponential-backoff HTTPAdapter factory
│ ├── paginator.py # Link-header + cursor/token pagination, lazily iterated
│ ├── base_client.py # Session management, request lifecycle, error mapping
│ └── exceptions.py # Typed exception hierarchy
│
└── clients/
├── github.py # GitHubClient
└── gmail.py # GmailClient
└── google_calendar.py # GoogleCalendarClient
Design Principles
- Infrastructure should be written once.
- API clients should remain lightweight.
- Explicit is better than magical.
- Strong typing improves maintainability.
- Production readiness should be the default, not an afterthought.
- Developer experience matters as much as correctness.
Testing
pip install hakiapi[dev]
pytest
- ✅ 214 tests passing
- ✅ Core framework covered (auth, retry, paginator, base client, exceptions)
- ✅ GitHub client covered
- ✅ Gmail client covered
Roadmap
Completed
- Base API framework (
BaseAPIClient) - Authentication system (Bearer, Header, Query, HMAC)
- Retry engine with exponential backoff
- Automatic pagination (Link header + cursor/token)
- Typed exception hierarchy
- GitHub client
- Gmail client
- Google Calendar client
Planned
- Stripe client
- Twitter/X client
- Async client (
httpx-based) - OAuth2 helpers
- Plugin system
- More service clients
Contributing
Contributions are welcome — bug fixes, documentation, tests, or new clients. Please open an issue before proposing major changes so we can discuss the approach first.
License
MIT License — see LICENSE for details.
Metadata
Release files for hakiapi 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hakiapi-2.0.0.tar.gz | 43.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hakiapi-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 67.4 kB
Release files / hakiapi-2.0.0.tar.gz
| Download URL | hakiapi-2.0.0.tar.gz |
|---|---|
| Size | 43.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
456c12bc77e68bdcc4a317ad56485e367877d30a378322c8f13d30c20bc814fc
|
|
BLAKE2b-256 checksum How to use checksums |
da4280682f07b2deb7dbf5ace39ec5b16e0a897e0e463ad138bd969ed6d41c93
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 23, 2026.
Transparency logRelease files / hakiapi-2.0.0-py3-none-any.whl
| Download URL | hakiapi-2.0.0-py3-none-any.whl |
|---|---|
| Size | 24.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0245fee88957587ebf5930318a59c0a07fd6a39d5459643aeee6c892a26383ed
|
|
BLAKE2b-256 checksum How to use checksums |
8c81d83d60cfea39c4faf90f93c3f5a6ec8f9c1739b84340e0c3b84369f221cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 23, 2026.
Transparency log