usesmileid
Official Smile ID server-side SDK for Python — V3 APIs.
This project is under active development. It is not yet published to PyPI, and the API is not stable. Do not use it in production yet.
The package and the importable module are both named usesmileid. Python 3.8 or later is required.
Install
pip install usesmileid
Getting started
Construct one client with your partner ID and API key. The SDK manages authentication for you: it fetches a short-lived token from the Smile ID API, caches it until it expires, and refreshes it automatically. You never handle tokens yourself.
import os
import usesmileid
smile = usesmileid.Client(
partner_id="1234",
api_key=os.environ["SMILE_API_KEY"],
environment="sandbox", # the default
)
Partner IDs are displayed zero-padded in the portal (for example 002) but must be passed without leading zeros (2).
Environment selection
The client targets the sandbox by default. Set environment="production" to go live:
sandbox→https://testapi.smileidentity.comproduction→https://api.smileidentity.com
Base URL override
Only sandbox and production are named environments. To reach any other Smile ID environment, pass base_url — it wins over environment:
smile = usesmileid.Client(
partner_id="2",
api_key=os.environ["SMILE_API_KEY"],
base_url="https://your-environment.example.com",
)
The value must be an absolute https URL with no query or fragment — anything else raises usesmileid.errors.ValidationError at construction. There is deliberately no way to turn this off: partner credentials and personal data travel on every request. environment must be "sandbox" or "production"; any other value is rejected at construction.
Callback URLs
Callback URLs must also be https. The SDK validates default_callback_url when you construct the client, and any per-request callback_url before it sends the request.
Other options
| Option | Default | Purpose |
|---|---|---|
default_callback_url |
unset | Used when a call omits callback_url; must be https |
timeout |
30 seconds | Per-request total timeout; each method also accepts a timeout override |
max_retries |
2 | Retries for idempotent operations only (see Retries below) |
http_client |
SDK default | Inject your own httpx.Client for testing or proxies |
Binary inputs
Every image parameter (selfie_image, liveness_images, document, document_back, comparison_image) accepts a file path (str or os.PathLike), raw bytes, or an open file object.
Consent and user details
All verification submissions need a consent record and the user's details. Build consent with the helper; pass user details as a dict or a usesmileid.UserDetails. At least one of email or phone_number is required — the SDK checks this before sending.
from datetime import datetime, timezone
consent = usesmileid.Consent.granted(
granted_at=datetime.now(timezone.utc),
notice_language="EN",
notice_privacy_policy_url="https://example.com/privacy",
)
user_details = {
"given_names": "Amina Fatou",
"last_name": "Clearwater",
"email": "amina.clearwater@example.com",
}
Non-production environments match test identities on given names, last name and email. An identity they do not recognise resolves to block.
The examples below assume smile, consent and user_details are defined as above.
Methods
Enhanced KYC
Verify an ID number against the issuing authority.
accepted = smile.enhanced_kyc.verify(
country="NG",
id_type="NIN",
id_number="12345678901",
user_details=user_details,
consent=consent,
)
print(accepted.job_id, accepted.is_accepted)
Document verification
Verify a selfie against a photo of an identity document. id_type is optional; the document type is auto-classified when omitted.
accepted = smile.documents.verify(
selfie_image="selfie.jpg",
liveness_images=["live1.jpg", "live2.jpg", "live3.jpg",
"live4.jpg", "live5.jpg", "live6.jpg"],
document="passport_front.jpg",
country="NG",
user_details=user_details,
consent=consent,
)
Enhanced document verification
Same as document verification, but id_type is required and the ID information is also checked against the issuing authority.
accepted = smile.documents.verify_enhanced(
selfie_image="selfie.jpg",
liveness_images=["live1.jpg", "live2.jpg", "live3.jpg",
"live4.jpg", "live5.jpg", "live6.jpg"],
document="license_front.jpg",
document_back="license_back.jpg",
country="NG",
id_type="DRIVERS_LICENSE",
user_details=user_details,
consent=consent,
)
Residency document verification
Same as document verification, plus the visa endorsed in the passport. visa is required and id_type must be PASSPORT (the default). Results take longer than document verification, so rely on the callback rather than polling.
accepted = smile.documents.verify_residency(
selfie_image="selfie.jpg",
liveness_images=["live1.jpg", "live2.jpg", "live3.jpg",
"live4.jpg", "live5.jpg", "live6.jpg"],
document="passport_front.jpg",
visa="visa_page.jpg",
country="ZA",
user_details=user_details,
consent=consent,
)
Biometric KYC
Verify a selfie against the photo on file with an ID authority.
accepted = smile.biometric_kyc.verify(
selfie_image="selfie.jpg",
liveness_images=["live1.jpg", "live2.jpg", "live3.jpg",
"live4.jpg", "live5.jpg", "live6.jpg"],
country="NG",
id_type="NIN",
id_number="12345678901",
user_details=user_details,
consent=consent,
)
Biometric enrollment
Register a user's selfie for later authentication.
accepted = smile.biometric.enroll(
selfie_image="selfie.jpg",
liveness_images=["live1.jpg", "live2.jpg", "live3.jpg",
"live4.jpg", "live5.jpg", "live6.jpg"],
user_details=user_details,
consent=consent,
user_id="user_01h8x9y2z3a4b5c6d7e8f9g0h1",
)
Biometric authentication
Authenticate a previously enrolled user. Set use_enrolled_image=True to re-use the enrolled image instead of uploading a new selfie.
accepted = smile.biometric.authenticate(
user_id="user_01h8x9y2z3a4b5c6d7e8f9g0h1",
selfie_image="selfie.jpg",
liveness_images=["live1.jpg", "live2.jpg", "live3.jpg",
"live4.jpg", "live5.jpg", "live6.jpg"],
user_details=user_details,
consent=consent,
)
Selfie comparison
Compare a selfie against another image (a document photo, ID photo or portrait).
accepted = smile.biometric.compare(
selfie_image="selfie.jpg",
comparison_image="id_photo.jpg",
comparison_image_type="ID_PHOTO", # DOCUMENT | ID_PHOTO | PORTRAIT
user_details=user_details,
consent=consent,
)
Check a verification's status
status = smile.verifications.retrieve("job_01h8x9y2z3a4b5c6d7e8f9g0h1")
print(status.status) # "processing", "not_found", or the decision
A running job reports status="processing". Once it finishes, status is the decision itself: clear, block, attention or error. message reads "Job completed" on every finished job, so read the decision from status, not from message.
A job that is not found returns a JobStatus with status="not_found" — it does not raise an error, so polling can distinguish "not found yet" cleanly.
Wait for a verification to complete
Polls the status endpoint while the job is processing or not_found, and returns as soon as it reaches a decision. status.is_complete is true for any decision. Raises usesmileid.errors.TimeoutError if the job does not finish in time.
status = smile.verifications.wait_until_complete(
"job_01h8x9y2z3a4b5c6d7e8f9g0h1",
interval=2.0, # seconds between polls
timeout=60.0, # give up after this many seconds
)
print(status.status) # "clear"
By default a not_found status is treated as "not found yet" and polling continues; pass treat_not_found_as_pending=False to return it immediately.
Replay a callback
Re-send the callback for a completed verification.
replayed = smile.verifications.replay(
"job_01h8x9y2z3a4b5c6d7e8f9g0h1",
callback_url="https://app.example.com/webhook", # optional override
)
Replaying a job that is still processing raises usesmileid.errors.ConflictError.
Report user fraud
Flag a user as fraudulent, or clear a previous flag. flag_fraud and clear_fraud are convenience wrappers over report_fraud.
smile.users.flag_fraud(
"user_01h8x9y2z3a4b5c6d7e8f9g0h1",
reason="FIRST_PARTY_FRAUD",
reported_by="risk@example.com",
)
smile.users.clear_fraud(
"user_01h8x9y2z3a4b5c6d7e8f9g0h1",
notes="Cleared after review",
reported_by="risk@example.com",
)
reason is required when flagging; notes is required when clearing or when reason="OTHER". The SDK checks these rules before sending.
List bank codes
No authentication required.
banks = smile.services.bank_codes(country="NG")
for bank in banks.bank_codes:
print(bank.code, bank.name)
List supported ID types
No authentication required.
id_types = smile.services.supported_id_types(country="NG")
for id_type in id_types.id_types:
print(id_type.type, id_type.label)
List supported documents
No authentication required.
documents = smile.services.supported_documents(country_code="NG")
for entry in documents.valid_documents:
print(entry.country.name, [d.code for d in entry.id_types])
Check ID type availability
status = smile.services.id_status(country="NG", id_type="NIN")
print(status.last_known_status, status.last_hour_success_rate)
Responses
Submission endpoints return an AcceptedResponse. Use response.is_accepted rather than comparing the raw status string — the API returns both "Accepted" and "accepted" depending on the endpoint, and is_accepted normalizes the difference.
Error handling
All errors raised by the SDK subclass usesmileid.errors.SmileIDError and expose status_code, status, message, code, request_id and raw_body.
import usesmileid.errors
try:
accepted = smile.enhanced_kyc.verify(...)
except usesmileid.errors.PaymentRequiredError:
... # top up your wallet
except usesmileid.errors.InvalidRequestError as err:
print(err.status_code, err.message)
except usesmileid.errors.SmileIDError as err:
... # everything else
| Error | Raised on |
|---|---|
InvalidRequestError |
HTTP 400, 415 |
ValidationError |
Client-side validation, before any request is sent |
AuthenticationError |
HTTP 401 (after one automatic token refresh) |
PaymentRequiredError |
HTTP 402 |
PermissionError |
HTTP 403 |
NotFoundError |
HTTP 404 |
ConflictError |
HTTP 409 |
PayloadTooLargeError |
HTTP 413 |
RateLimitError |
HTTP 429 |
APIError |
HTTP 5xx |
UnexpectedResponseError |
A success response whose body is not a JSON object |
ConnectionError |
Network failure or timeout, no HTTP response |
TimeoutError |
wait_until_complete deadline reached |
Retries
The SDK automatically retries idempotent operations only: status and services reads, and the internal token fetch. Retries cover connection errors and HTTP 408, 429 and 5xx, with exponential backoff, and honour the Retry-After header. HTTP 409 is never retried.
Submission calls (verification, enrollment, authentication, compare, replay, fraud reports) are never retried automatically, because a retry could create a duplicate job. A connection failure on these raises usesmileid.errors.ConnectionError and you decide whether to retry.
Telemetry
Every request carries three telemetry headers: SmileID-Source-SDK, SmileID-Source-SDK-Version and User-Agent. They identify the SDK and its version for observability. They are never used for authentication and carry no personal data.
Licence
This project is licensed under the MIT licence. See LICENSE for details.
Security
See SECURITY.md for how to report a vulnerability.
Metadata
Release files for usesmileid 12.1.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 | |
|---|---|---|---|
| usesmileid-12.1.0.tar.gz | 45.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| usesmileid-12.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 72.9 kB
Release files / usesmileid-12.1.0.tar.gz
| Download URL | usesmileid-12.1.0.tar.gz |
|---|---|
| Size | 45.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c7fc5ddc5ec195621328185009b651b26614aaf40f019420ca7f006fad256ff6
|
|
BLAKE2b-256 checksum How to use checksums |
e5a3d2c31c9566ed9ffdc1aee927ef72b87c1ff3ef206f104f858f0e844bd6cb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.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 Oct 1, 2026.
Transparency logRelease files / usesmileid-12.1.0-py3-none-any.whl
| Download URL | usesmileid-12.1.0-py3-none-any.whl |
|---|---|
| Size | 27.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
06ecd4ebb1ee5d45b70c8097f02b257f57e79675f74784ea813203baeaafe988
|
|
BLAKE2b-256 checksum How to use checksums |
3e1a2a5290be496e33c0250670677b40082db5da145d7add60badc811c5e0a79
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.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 Oct 1, 2026.
Transparency log