Python SDK
eps-sdk is a standard-library-only client for every EPS API. One generic
call(slug, params) covers all of them: the endpoint catalog, each endpoint's
params and which of them are required are baked into the package from the same
surface these docs are built from, so the client validates your input before
it signs and sends anything.
Backend only. access_key signs every request — it must never reach a browser,
a mobile app, or a notebook you share.
Install
pip install eps-sdk
- Package
eps-sdk- Requires
- Python 3.9 or newer
- Dependencies
- None — standard library only (
urllib,hmac,hashlib,mimetypes). - Source
- GitHub
Your first call
Read the two keys from the environment, pick an environment, and call an endpoint by its slug:
import osfrom eps_sdk import EpsClientclient = EpsClient(developer_key=os.environ["EPS_DEVELOPER_KEY"],access_key=os.environ["EPS_ACCESS_KEY"],initiator_id="9962981729",environment="sandbox",)result = client.call("pan-lite", {"pan_number": "ABCDE1234F","name": "Rajesh Kumar","dob": "1994-08-29",})print(result)
initiator_id and user_code are near-constant per developer, so set them once
on the client. They are injected into every call — pass either in params to
override it for a single call, or pass None to clear it.
Client options
EpsClient is a dataclass, so every option is a keyword argument.
| Option | Type | Required | Notes |
|---|---|---|---|
developer_key | str | Yes | Your EPS developer key, sent as the developer_key header. |
access_key | str | Yes | Server-side secret used to sign every request. Never ships to a browser. |
environment | "sandbox" | "production" | Yes | Selects the base URL from the embedded surface. |
initiator_id | str | No | Default initiator_id (registered mobile of the API user) injected into every call. |
user_code | str | No | Default user_code (retailer/agent code) injected into every call. |
timeout | float | No | Whole-request budget passed through to urlopen. (seconds, default 30.0) |
API surface
The SDK is deliberately small. There is no method per endpoint — call() takes
the slug, and the baked surface supplies the method, path, and validation rules.
resolve_target() is the same pipeline stopping one step short of the network,
which is the fastest way to see exactly what would be sent.
| Member | Signature | What it does |
|---|---|---|
EpsClientclass | EpsClient(developer_key, access_key, environment, initiator_id=None, user_code=None, timeout=30.0) | The client — a dataclass, so keyword arguments read well. |
callmethod | client.call(slug: str, params: Mapping[str, Any] | None = None) -> Any | Validates, signs and sends one endpoint call; returns the decoded envelope. |
resolve_targetmethod | client.resolve_target(slug, params=None) -> Target | The signed method/url/body/headers for a call, without sending it. Useful for debugging. |
build_headersmethod | client.build_headers(multipart: bool = False) -> dict[str, str] | The four auth headers for a single request. |
sign_secret_keyfunction | sign_secret_key(access_key: str, timestamp: str) -> str | The raw signing primitive, exported for debugging. |
MULTIPART_JSON_FIELDconstant | MULTIPART_JSON_FIELD = "form-data" | Name of the single form field carrying the JSON envelope on file-upload endpoints. |
Targettype | @dataclass Target(method, url, body, headers, multipart) | What resolve_target returns. |
Authentication
You never compute a signature yourself. On every request the client derives
secret-key = base64(HMAC-SHA256(timestamp, base64(access_key))) and sends it
with secret-key-timestamp and developer_key. sign_secret_key is exported
only so you can reproduce a signature while debugging a 403.
File uploads
A single type: "file" param flips the whole request to multipart/form-data.
You still pass every parameter flat; on the wire the SDK packs them the way the
API expects — one form field named form-data holding all the non-file params
as a single JSON object, plus one part per upload. A None param is dropped (a
form field has no null encoding), while a None nested inside a dict value is
preserved.
File params accept:
- A path
stroros.PathLike(the MIME type is guessed, falling back toapplication/octet-stream) - An in-memory
(filename, bytes)tuple
Errors and timeouts
A non-2xx response raises — an auth or infrastructure failure is never
returned as if it were a result. The decoded envelope is still on the exception,
so you can read status and message off .body.
| Type | Raised when | Fields |
|---|---|---|
EpsHttpError | Any non-2xx response. | .status, .url, .body (decoded envelope or None), .raw (bytes) |
EpsError | Unknown slug, missing required param, wrong param type, bad config, or a 2xx body that is not JSON. EpsHttpError subclasses it. | — |
urllib.error.URLError | Transport failure or timeout. Surfaced raw. | — |
| HTTP status | Meaning |
|---|---|
200 | OK — response returned by our system. |
403 | Forbidden — incorrect secret-key or timestamp. |
404 | Not Found — wrong request URL. |
405 | Method Not Allowed — incorrect HTTP method. |
415 | Unsupported Media Type — wrong Content-Type header. |
500 | Internal Server Error — connectivity or URL misconfiguration. |
Environments
Switch with the environment argument; there is no base-URL override.
| Environment | Base URL | Notes |
|---|---|---|
sandboxUAT / Sandbox | https://staging.eko.in/ekoapi/v3 | Self-serve credentials available immediately on signup. |
productionProduction | https://api.eko.in/ekoicici/v3 | Credentials issued after organizational KYC. |
Python-specific notes
- Paths are checked for existence during validation, so a typo fails before the request is signed.
Every endpoint
call() accepts every slug in the EPS catalog. Each endpoint's reference page
shows its parameters, response fields and a ready-to-paste Python snippet.