Provider contract audit
Audit date: 2026-10-10. Verification means comparison with retrievable official documentation, not a live API transaction. No credentials, SMS submissions, shipment creation, or captured production responses were used. Unknown means unresolved, not supported by inference from existing mocks or third-party wrappers.
The numbered task sections are historical snapshots; their progress statements describe that task's completion point. The latest capability/verification summary appears at the end of this page. Later SDK changes do not retroactively establish unverified upstream contracts.
Evidence and current implementation matrix
Paths below are relative to the base URL. Unless explicitly marked verified, these are implementation inventory, not claims about upstream behavior. Last independently verified date is “unverified” where no authoritative specification was retrieved.
| Provider | SDK endpoints | SDK request fields/auth | SDK response handling | Official evidence and last verification |
|---|---|---|---|---|
| AlphaSMS | https://api.sms.net.bd/sendsms |
Form api_key, msg, to, optional sender_id |
Object with error; documented success 0 and data.request_id; documented failures below |
Official API, 2026-10-10; verified published contract only |
| BulkSMSBD | http://bulksmsbd.net/api/smsapi |
Form api_key, type, number, senderid, message |
Integer response_code; legacy success 202 retained; all other integers raise generic ProviderError |
Provider site redirected to login; /api and /api.php retrieval failed; unverified |
| Steadfast | https://portal.packzy.com/api/v1/create_order, /status_by_cid/{id} |
Headers Api-Key/Secret-Key; JSON invoice, recipient_name/phone/address, cod_amount, note | Raw JSON; integer body status >=400 mapped by adapter | Official business page confirms API offering, not this wire contract; unverified |
| Pathao | https://api-hermes.pathao.com/aladdin/api/v1/issue-token, /orders, /orders/{id}/info, /stores; sandbox host courier-api-sandbox.pathao.com |
Token request client_id/secret and password or refresh grant; orders use Bearer auth and store_id, merchant_order_id, recipient fields/geography, delivery_type, item_type, special_instruction, item_quantity/weight, amount_to_collect | Token access_token/refresh_token/expires_in; resource responses returned raw | Official developer panel returned JavaScript shell; unverified |
| RedX | https://openapi.redx.com.bd/v1.0.0-beta/parcel, legacy /parcels/tracking/{id}; sandbox host sandbox.redx.com.bd |
API-ACCESS-TOKEN Bearer; customer name/phone/address, RedX delivery_area/name and delivery_area_id, invoice, cash collection, weight, instruction, separate declared value, optional pickup_store_id | Raw JSON; create tracking_id; legacy tracking/error guards remain unverified | Official developer specification bundle, 2026-10-10: create fields/endpoint/auth verified; exact create weight units/types and legacy tracking contract unresolved; see task 6 |
| GreenWeb | https://api.greenweb.com.bd/api.php |
Form token, to, message; json=1 | Per-recipient SENT/FAILED JSON list or Ok:/Error: text lines | Official English manual, linked via the portal's homepage; 2026-10-10; published JSON/text format verified, standalone text-error mappings remain legacy/unverified |
HTTP status classifications in the adapters are SDK policies, not verification of each provider's application codes. Unverified adapters still require a future authoritative audit; existing tests are implementation regressions, not certifications.
AlphaSMS classification policy
Source: the Common Errors and Send SMS Errors sections of https://sms.bd/api, checked 2026-10-10. Recorded paraphrased semantics and test expectations are in tests/fixtures/alphasms_contract.json; fixtures are synthetic and retain their provenance.
| Codes | Jukto classification | Meaning |
|---|---|---|
| 0 | Return response | Accepted submission, not proof of delivery |
| 403, 405 | ProviderAuthenticationError | Permission/access rejection |
| 410, 411 | ProviderAuthenticationError | Merchant/reseller account unavailable |
| 404 | ResourceNotFoundError | Missing resource |
| 409 | ProviderServerError | Upstream internal failure |
| 400, 412–416, 420 | InvalidRequestError | Parameter, scheduling, sender, message, recipient, or content rejection |
| 417 | InsufficientBalanceError | Credit depletion |
| Other integer codes | ProviderError | No documented semantics established |
These exception classes are Jukto policy, not names supplied by the provider. Permission denial and account expiration use the existing authentication category; they do not imply a token refresh will resolve the problem. A 409 application code is not assumed to be an HTTP conflict.
The official JSON example has an integer error; its C# examples compare against string zero. The adapter accepts canonical nonnegative ASCII decimal strings as a compatibility policy and applies the same code semantics. This is not proof that every code has been observed as a string. Bools, floats, whitespace-padded/zero-padded strings, missing codes, and non-object envelopes raise generic ProviderError. Unknown integer codes retain their code and body.
The docs also list schedule and content_id (required for bulk SMS); these are not exposed by the current adapter. Full success-data schema validation and JSON decoding normalization remain task 4, not completed here.
Unresolved BulkSMSBD contract
Official error table, envelope schema, string-code support, and success code could not be retrieved without an account. Do not substitute another company's BulkSMS.com API or a third-party wrapper as authority. No new semantics were inferred.
The old 1002/1003/1004/1005/1006/1007/1008/1010 classifications and message-substring inference have been removed. All non-202 integer codes now raise ProviderError with the original body and code. Integer 202 is retained solely for legacy compatibility and remains explicitly unverified, including its acceptance/delivery meaning. String 202 and floats are not silently coerced. Unknown envelopes fail closed.
Resolution requires a provider-issued specification/API-page export or sanitized responses with recorded endpoint, API version, date, and provenance. No credentials should be included.
RedX shipment contract: task 1 findings (superseded in part by task 6)
At the task 1 baseline, the adapter placed the full address into delivery_area, ignores recipient_area, multiplies item_weight by 1000, and sets value equal to COD. None of these choices was independently verified in this audit.
Obtain the official create-parcel schema defining geographic name/ID fields and lookup relationship, parcel_weight units/range/rounding, value meaning and requiredness, cash_collection_amount semantics, pickup-store requirements, response envelope, and error table. Do not invent delivery_area_id or change weight units based on unofficial examples. Task 1 made no RedX payload change. The task 6 section below records newly retrieved primary evidence, corrections, and remaining ambiguities.
Compatibility changes in this task
- AlphaSMS 405 now means authentication/access failure, 417 means insufficient balance, 416 means invalid recipients, 404 means missing resource, and 409 means provider server failure.
- Undocumented AlphaSMS application codes 401/402/406/422 now raise generic ProviderError instead of guessed specific classes. Actual HTTP status handling is unchanged.
- BulkSMSBD application error codes previously classified specifically now raise generic ProviderError. Callers should catch ProviderError and retain the vendor code until the official contract is available.
- Unexpected AlphaSMS/BulkSMSBD code envelopes no longer return success-like dictionaries. Existing success responses remain raw dictionaries.
status_codestill carries application codes for backwards compatibility; separating HTTP status and vendor code remains task 8.- No retries or automatic failover were added. A server error does not establish that resubmitting SMS is safe.
Task 1 is partially complete: matrix and AlphaSMS audit are complete; BulkSMSBD and RedX authoritative verification and the remaining providers' schema evidence are unresolved. Tasks 2 onward were not started.
Task 2: credential transport policy
All six clients now reject cleartext endpoints by default, validate endpoints again before sending, enable TLS certificate verification explicitly, and refuse all redirects (including same-origin redirects). Invalid schemes, relative URLs, and embedded URL credentials are rejected. This policy also covers Pathao token issue, refresh, and authenticated replay. No client reuse or retry redesign was added.
Every concrete client accepts the keyword-only allow_insecure_http=True option
for deliberate legacy/local use. It permits HTTP only; it does not disable HTTPS
certificate verification or enable redirect following. Credentials and SMS content
sent over HTTP are unencrypted.
BulkSMSBD's legacy HTTP constant remains unchanged pending authoritative verification of HTTPS POST support. Consequently, construction with its default HTTP endpoint now raises JuktoConfigurationError unless explicitly opted in. HTTPS override URLs are accepted by the transport policy but are not certified provider integrations.
A credential-free curl --head --max-time 20 --proto '=https' request to the exact
https://bulksmsbd.net/api/smsapi path on 2026-10-10 succeeded with default certificate
verification. It returned HTTP 200, Apache, and Content-Type application/json.
This establishes TLS reachability only: HEAD carries no response body and does not
verify SMS POST compatibility or application success. The public documentation
fetch for that URL failed; the provider homepage still exposes a login panel.
The endpoint-verification checkbox remains open until provider-issued documentation
or authorized, provenance-recorded sandbox evidence confirms HTTPS SMS submission.
Compatibility: existing custom HTTP endpoints and default BulkSMSBD construction must now opt in explicitly. Prefer confirmed HTTPS endpoints. Endpoint policy failures raise JuktoConfigurationError before HTTP requests are made.
Task 3: logging and exception privacy
Jukto adapters emit only fixed provider/operation/event metadata and HTTP status
where available. They do not log request or response payloads, URLs, headers,
recipient data, SMS/OTP content, token responses, usernames, or underlying exception
text. No diagnostic payload-logging option is retained. enable_debug_logging()
changes the Jukto logger's level/handler, not this metadata-only policy.
mask_sensitive() now replaces complete known credential values with ***,
regardless of value type, including nested dictionaries/lists. It returns a copy
and does not reveal credential prefixes/suffixes. This helper is not a universal
personal-data sanitizer: unknown fields and scalar strings are unchanged, and
adapters no longer use it to justify logging arbitrary payloads.
SDK-generated provider exception messages, str, and repr withhold provider
messages and underlying network-error text because these can echo credentials,
URLs, OTPs, or recipient data. Raw upstream chains are suppressed in normal
formatted tracebacks. Exception types, provider identifiers, and codes remain
available; HTTP/application classification and retry behavior are unchanged.
error.response_body intentionally retains the original parsed body or raw text
for deliberate investigation. Treat it as sensitive: do not print it, serialize
it into public API responses, attach it to issue reports, or log it without your
own reviewed sanitization. Explicit inspection of exception context/request objects
can likewise reveal sensitive data. Application-created JuktoError messages are
not automatically scrubbed; callers must not put secrets in those messages.
This policy covers SDK-generated diagnostics on the jukto logger. It does not
sanitize independently enabled HTTPX/httpcore logs, application logs, debugger
locals, or external error-reporting tools. Configure those separately. Existing
malformed-response failures outside JuktoError remain task 4; this is not a claim
that arbitrary upstream exceptions have all been normalized.
Compatibility: provider message text no longer appears in .message, str(error),
repr(error), or ordinary chained traceback output. Consumers needing that text
must deliberately inspect response_body; do not parse exception message strings
for error classification. Masking now yields *** instead of credential fragments.
Task 4: malformed responses and SMS batch outcomes
UnexpectedProviderResponseError extends ProviderError/JuktoError and identifies
JSON decoding or response-schema failures. Messages and normal tracebacks remain
metadata-only; response_body preserves sensitive raw text or parsed evidence.
It does not establish whether an order or SMS side effect occurred. Reconcile
before retrying. HTTP failures retain HTTP-based classification even if the error
body is a JSON scalar or array. Only JSONDecodeError/UnicodeDecodeError are caught
by the common decoder; internal TypeError and other programming failures propagate.
AlphaSMS requires its documented submission data.request_id on success. Its
known application errors retain task 1 mappings. Oversized or noncanonical code
strings and missing success evidence fail closed. BulkSMSBD still accepts the
unverified legacy integer 202 only; no additional success schema was invented.
Steadfast/RedX now guard their existing SDK-consumed success fields: Steadfast status 200 with consignment evidence or delivery_status, RedX tracking_id or tracking.status. Pathao guards its existing data envelopes with consignment_id for create, order_status for tracking, or object/list store data. These are conservative SDK compatibility policies, not authoritative upstream schema certification. Unknown/missing envelopes and Pathao error/type indicators raise UnexpectedProviderResponseError. The official Pathao developer panel still exposes only a JavaScript shell; specific HTTP-200 application-error mappings remain unresolved and the corresponding checklist item is open. No provider error code meanings were inferred from HTTP conventions.
Pathao token issue/refresh also reject invalid JSON/object/access-token/expiry structures. Malformed refresh responses no longer trigger password-auth fallback; malformed replay responses and internal programming failures are no longer swallowed as the original 401. The broader refresh/replay policy, state atomicity, locking, and clock redesign remain task 5. Existing missing-token responses now raise an unexpected-response error rather than authentication failure or fallback success.
GreenWeb evidence and compatibility
The official portal links to bdbulksms.com as its homepage. Its English manual at https://bdbulksms.com/bulk-sms-api-bd-english.php documents the existing GreenWeb endpoint, HTTPS, json=1, SENT/FAILED JSON entries, and Ok:/Error: text lines. Provenance is recorded in tests/fixtures/greenweb_contract.json. Regression entries are synthetic; no SMS transactions or demo-token submissions were performed. Do not copy that manual's disabled TLS verification examples; Jukto's TLS policy remains enabled.
JSON is parsed before checking status. Echoed message or statusmsg content is not searched for error words. Lists are inspected in full, including malformed entries; unrecognized status values are unknown, never assumed successful. The documented json=1 option is now sent. Legacy single-object SENT/FAILED/error envelopes remain compatible. Historical standalone text errors are restricted to anchored prefixes and explicitly remain unverified; arbitrary plain text is rejected. Documented Ok:/Error: lines are evaluated individually, preserving mixed text outcomes.
SMSBatchResult subclasses list, preserving ordered raw entries. Its sent_indices,
failed_indices, and unknown_indices refer to response positions, not guaranteed
positions in the original recipient request. For JSON, correlate using the original
to field when present, not guessed ordering. SENT is provider output, not independent
delivery confirmation. Raw entries and returned successful text may contain personal
data and must not be logged.
Successful JSON lists return SMSBatchResult (normal list operations still work).
Batches with failed or unknown entries raise SMSBatchError, a ProviderError subclass,
with .results and .response_body. Entirely unrecognized batches raise
UnexpectedProviderResponseError while retaining the raw body. No automatic retry
or failover is performed. Preserve successful entries and reconcile unknown outcomes;
do not resend the whole batch. Single successful text responses retain the legacy
string return shape; multi-line text responses use the batch representation.
from jukto import SMSBatchError, UnexpectedProviderResponseError
try:
results = client.send_sms(numbers, message)
except SMSBatchError as error:
sent_positions = error.results.sent_indices
failed_positions = error.results.failed_indices
unknown_positions = error.results.unknown_indices
# Persist outcomes privately; reconcile before any targeted retry.
except UnexpectedProviderResponseError:
# Submission outcome is unknown. Do not automatically resend.
pass
Pathao token lifecycle and replay (task 5, 2026-10-10)
Token exchange and publication are serialized by a per-client reentrant lock.
Waiters recheck expiry under that lock; concurrent late 401 responses reuse a newer
cache generation rather than invalidating it. The cache and lock belong to one
client instance in one process: workers, other instances, and forked processes do
not coordinate. Construct clients inside each worker. Do not mutate token cache
attributes from application threads or persist token_expiry.
token_expiry is now a monotonic deadline, not a Unix timestamp. Lifetime starts
before the exchange request, subtracting network latency conservatively. The refresh
margin is min(60 seconds, 10% of lifetime), allowing short-lived tokens to be reused.
A nonblank access token and finite positive expires_in are required; no undocumented
3600-second default is supplied. A supplied refresh token must be a nonblank string;
omission preserves the previous refresh token during refresh and clears it during
password issuance. All fields are validated before publishing the new cache state.
Malformed responses and transport failures leave the prior state untouched.
Only HTTP 400 with JSON error: "invalid_grant" during a refresh grant triggers one
password-grant fallback. This classification follows RFC 6749 sections 5.2 and 6,
which defines rejection of an invalid/expired/revoked refresh token, and permits
omission or rotation of the refresh token on successful refresh. Pathao's exact
adoption of this error envelope, expiry schema, and omission behavior remains
unverified: its developer portal could not be
retrieved in this audit. These are conservative SDK policies grounded in OAuth,
not Pathao certification. Generic 400/401/403, 429/500, timeout, network, and malformed
refresh responses propagate; they never silently switch to password authentication.
GET operations replay at most once after HTTP 401, refreshing or issuing a token.
The final attempt's HTTP/timeout/network/schema error is propagated with its own
response data. No retries occur for timeouts, network failures, 5xx, or validation
errors. Shipment creation no longer automatically replays: callers receive the 401.
For integrations with independently verified guarantees that a 401 shipment request
has no side effects, PathaoClient(..., retry_unauthorized_orders=True) restores the
legacy single replay. This option is an explicit application assumption; Pathao's
no-side-effect-on-401 and shipment idempotency guarantees remain unresolved. An
order reference is not evidence of idempotency. Reconcile uncertain outcomes before
application retries. No live shipment or credentialed authentication was performed.
Offline lifecycle regressions cover final retry failures, transient refresh outages, malformed token state, rotation, short lifetimes, clock jumps, concurrent expiry, concurrent stale 401, and default refusal to replay shipment creation.
Shipment input validation and migration (task 6, 2026-10-10)
All three adapters validate the Parcel again at submission, including mutations made after construction, before any HTTP client or Pathao token exchange. Nonblank string invoice/name/phone/address, positive integer quantity, finite positive numeric weight, finite nonnegative numeric COD, and configured nonblank credentials are SDK input policies. Booleans, numeric strings, NaN/infinity, unrepresentable numeric magnitudes, and nonstring notes are rejected with JuktoValidationError. Error text names fields without echoing their values. This does not verify deliverability, phone syntax, geographic membership, vendor limits, or monetary precision.
Pathao evidence
The provider-maintained pathao-bridge.php makeDto serializer conditionally includes city/zone/area only when supplied, rather than filling them with 1. Jukto now follows that behavior: None omits the field; explicit values must be positive integers. No cross-provider geography translation occurs. Parcel.store_id uses the client default only for None; explicit zero no longer silently overrides it. Store ID must be positive. Delivery/item type must be positive integers; accepted enum values, weight units/range, address-dependent omission acceptance, phone constraints, and complete provider requirements remain unresolved. A maintained integration is narrower evidence than a normative API specification. No new vendor limits or enums are guessed. Existing item_weight numeric serialization is retained.
RedX evidence and corrections
The official developer page exposes its published
specification in this versioned JavaScript bundle.
Its Create Parcel specification confirms /parcel, API-ACCESS-TOKEN: Bearer ...,
a delivery-area name and integer ID, independent cash collection and declared
value for compensation, and optional pickup_store_id. Consequently create_order
now uses that endpoint/header and requires explicit declared_value,
redx_delivery_area, and redx_delivery_area_id. It sends optional
redx_pickup_store_id only when supplied. It does not use Pathao's city/zone/area
or store_id. Example prepaid inputs:
parcel = Parcel(
invoice_id="INV-1", recipient_name="Example Customer",
recipient_phone="01700000000", recipient_address="Example street address",
cod_amount=0, declared_value=1200,
redx_delivery_area="Banani", redx_delivery_area_id=12, item_weight=0.5,
)
Area names/IDs above are illustrative; obtain matching values from RedX's own
/areas specification/service. Jukto validates shape, not existence or the pairing.
Declared value is never inferred from COD: existing RedX callers must now provide
it, including when COD is zero. These new optional Parcel fields are appended,
preserving existing positional arguments. Pathao/Steadfast do not serialize declared
value because a supported field has not been verified for those adapters.
Remaining RedX ambiguity: the create table labels weight and value as strings,
while its curl example uses numeric weight/value; the parcel-detail specification
describes returned weight in grams, but the create table ambiguously mentions kg/g.
The officially linked WooCommerce plugin
(version downloaded from WordPress) uses integer weights and numeric amount/value
on its distinct /wordpress/parcel endpoint, so it is corroborating evidence, not
proof of identical types on /parcel. Jukto retains the legacy SDK convention:
item_weight in kilograms converted to integer grams, numeric cash/value. It now
rejects fractional grams instead of truncating them or inventing a 500g fallback.
Create weight units/range/rounding and exact accepted JSON number/string types still
require provider confirmation. Offline tests assert this SDK policy, not live
compatibility. RedX tracking endpoint/response changes are outside task 6; the legacy
/parcels/tracking/{id} route and guards remain unverified. The shared RedX auth
header is now the documented API-ACCESS-TOKEN for both operations.
Evidence record and limits
tests/fixtures/shipment_contracts.json records source provenance and unresolved
contracts. Retrieved public-source SHA-256 digests:
- RedX bundle:
323ce7e69b14334da721aaab12b94c889b4099d393897ef64e560eae8381f70d - Pathao bridge:
0e8c93123adaea0b0d8fb8f1613972842038a732f0d594a49e2fd472fc940a4f
Steadfast's specification remained inaccessible; its existing payload is unchanged, and local validation is explicitly SDK policy. Exact provider phone formats, lengths, amount precision/currency, and maximum quantity/weight remain unresolved across these providers. No regex, region defaults, minimum monetary value, or undocumented vendor limits were added. All checks were offline; no shipments were created.
HTTP client ownership and lifecycle (task 7, 2026-10-10)
All six synchronous providers now share one transport implementation. It validates actual target URLs, creates an owned httpx.Client lazily on the first valid operation, and reuses it until close(). Provider-specific response inspection and exceptions remain in the adapters. Pathao token exchange and resource replay use the same pool. First-use initialization is synchronized; parallel requests are not serialized by the initialization lock. Connection reuse follows HTTPX's client model; offline tests prove client reuse, not a measured network performance improvement.
Use a context manager or call close() when the application finishes with a provider:
import httpx
from jukto import AlphaSMSClient
with AlphaSMSClient(
api_key="example-key",
timeout=httpx.Timeout(20, connect=5, read=15, write=10, pool=3),
limits=httpx.Limits(max_connections=20, max_keepalive_connections=10,
keepalive_expiry=15),
) as sms:
# Existing send_sms calls use this provider's single pool.
pass
The constructor's existing scalar timeout remains supported; httpx.Timeout exposes connect/read/write/pool settings. limits accepts httpx.Limits for owned pools, with HTTPX defaults when omitted. These settings are captured at construction. Configure a new provider rather than mutating its timeout attribute. Owned clients retain certificate verification and refuse redirects. There are no SDK transport retries; Pathao's operation-specific, bounded 401 replay is unchanged.
For injection, pass keyword-only http_client to any provider:
with httpx.Client() as application_client:
with AlphaSMSClient("example-key", http_client=application_client) as sms:
pass
# application_client is still open here; its owner closes it.
Injected clients are borrowed. Provider close/context exit marks the provider closed without closing that HTTPX client. Borrowed pool configuration belongs to its owner: combining http_client with provider limits raises JuktoConfigurationError. The SDK timeout (including the default 30 seconds) overrides the borrowed request timeout. Redirects are explicitly disabled per request and client-level HTTPX auth is disabled so it cannot replace provider authentication. A separately closed borrowed client is rejected on use. Invalid injection types are configuration errors.
The application controls an injected client's TLS verification, proxies, custom transport retries, event hooks, default headers/params, and cookies; Jukto cannot certify those settings. Use a dedicated provider client configured for verified TLS and no automatic retries of side-effecting requests. HTTPX merges client defaults into requests and retains cookies, including in owned pools. Do not share a borrowed client carrying unrelated credentials or tenant state across providers. Hooks and custom logging also remain application-controlled.
close() is idempotent. Closing an unused owned provider does not create a pool. Closed providers cannot be reused or re-entered; construct a new instance. Context exit also closes owned resources when application code raises. Finish all in-flight operations before close(); concurrent shutdown/request use is unsupported. Create clients inside each worker process; do not fork or persist a live pool. These APIs are synchronous and block the calling thread; native async remains task 11.
Migration: previously every operation closed its temporary pool automatically. Long-lived application integrations now need explicit provider shutdown or context management. Existing operation signatures and provider response shapes are unchanged. No live requests were used to test transport ownership or pooling.
Typed domain contracts and migration (task 8, 2026-10-10)
The raw APIs remain compatible: create_order/check_status/send_sms keep their existing dictionaries, strings and lists. New create_shipment/check_shipment_status and submit_sms methods return ShipmentCreationResult, ShipmentStatusResult and SMSSubmissionResult. Common fields are provider, provider ID/request ID, Outcome, original provider status, and raw. Outcome has ACCEPTED, DELIVERED, FAILED, PARTIAL and UNKNOWN. Results are frozen containers, but raw data is neither copied nor sanitized; it can contain personal information. Raw/status/recipient fields and submission request IDs are excluded from result repr. Use raw only in deliberate, access-controlled diagnostics. Provider IDs normalize string/integer IDs to strings. Unknown or invalid identifiers do not establish acceptance.
examples/normalized_shipments.py
is one typed consumer for all three shipment providers, with no provider-specific
response indexing. ACCEPTED means submission/creation evidence, not delivery,
fulfillment or payment. Pathao's maintained creation consumer and RedX's published
tracking_id response support this inference; Steadfast creation remains UNKNOWN
because its success contract has not been verified. All polling vocabularies remain
UNKNOWN, including familiar strings such as Delivered: no cross-endpoint status
mapping is assumed. Original status and IDs remain accessible for application
policies and reconciliation. RedX's new typed status method uses its documented
/parcel/info/{id} and parcel.status; legacy check_status keeps its prior route.
RedX's published webhook mapped-status meanings are not assumed to certify polling
states. This intentionally does not offer verified delivered/returned/failed
shipment classifications yet.
AlphaSMS's published submission envelope supports ACCEPTED
and a submission request_id, with no per-recipient delivery claims. GreenWeb's
published recipient output supports
SENT → ACCEPTED and FAILED → FAILED. Unrecognized entries remain UNKNOWN and mixed
outcomes become PARTIAL. SMSBatchError preserves the original list-compatible
results and adds normalized_result; SMSBatchResult.normalized exposes the same
models. Recipient identities are only response-supplied to values; response_index
is not an input-position correlation or resend instruction. Text-only responses
cannot supply an independently verified recipient identity. BulkSMSBD's legacy
202 remains UNKNOWN in the typed API. Full provenance is recorded in
tests/fixtures/result_contracts.json; no live delivery was verified.
Error metadata and ambiguity
JuktoError adds http_status, provider_error_code, request_id, retry_after, outcome and reconciliation_required. Legacy status_code remains unchanged for compatibility, including its historical mixture of application and HTTP codes. New code should use the separate fields. HTTP status comes from the final observed response; application codes come only from existing adapter envelopes, retaining original numeric/string representation. HTTP errors without such a field have no provider code. Request ID uses the response's X-Request-ID when supplied, and Retry-After is retained as the original header string, not parsed into an automatic delay. Neither header's presence or vendor support is guaranteed; unknown provider code semantics remain unknown. Header values and response bodies are excluded from exception str/repr. Request-local context isolates metadata between threads and clears it before each attempt so a final timeout cannot inherit an earlier 401's headers.
When a shipment/SMS POST was attempted, a timeout, connection failure, 5xx or malformed response conservatively sets UNKNOWN and reconciliation_required. This flag means reconcile with the provider, not blindly resend or switch providers. Authentication exchange alone and read-only calls do not count as shipment/SMS attempts. Published AlphaSMS rejection codes in HTTP 200 responses establish FAILED, except server code 409 and unknown codes. GreenWeb batch errors expose all outcomes; unknown entries require reconciliation. Other vendor rejection/idempotency guarantees remain unverified and therefore do not clear uncertainty. A local validation/configuration error before sending does not require reconciliation. No automatic retry, backoff, failover or deduplication was added. Applications must persist operation references and provider evidence, query their provider/dashboard after ambiguous outcomes, and apply their own durable deduplication/reconciliation policies. References are not provider idempotency keys.
Options, capabilities and monetary values
Parcel accepts optional PathaoOptions and RedXOptions. Typed options take precedence over legacy flat fields for their own provider only. PathaoOptions scopes store, city/zone/area and delivery/item type; RedXOptions scopes area name/ID and pickup store. Legacy fields still work when the corresponding options object is absent. capabilities describes the SDK's implemented operations/options, not production certification, cross-provider interchangeability or delivery-status verification. Only RedX exposes verified declared-value fields in the current adapter; only GreenWeb exposes per-recipient submission output. No geography translation occurs.
Monetary input annotations accept Decimal, plus int/float for backward compatibility. Use Decimal constructed from a decimal string for new integrations. Validation compares finite, nonnegative Decimal values without converting through binary float. For RedX, Decimal amounts are serialized as exact fixed-point strings per the published create table. Existing int/float JSON number serialization is retained as explicit legacy compatibility. The RedX table and curl examples disagree on accepted number/string types; actual acceptance and monetary precision still need vendor confirmation. Pathao/Steadfast Decimal integers serialize as exact JSON integers; fractional Decimals fail locally until a lossless supported wire format is verified. Legacy floats retain their previous serialization and binary-float limitations. No currency, rounding, decimal scale or minor-unit conversion is guessed. Nonzero declared goods value can remain independent of zero COD.
The package includes py.typed and a mypy development dependency/configuration. Run
python -m mypy to check the SDK and normalized consumer example. The SMS interface
now truthfully permits raw dictionary/list/string results; HTTP status handlers
are annotated NoReturn. These checks do not certify every raw provider dictionary:
the raw escape hatch deliberately uses Any. Multi-version CI, wheel installation
checks and publication gates remain the next CI/release task (currently task 10 in
the local checklist).
SDK capabilities and verification summary
This summary describes implemented methods, not independently certified provider behavior. Detailed evidence and dates are in the table above and task audit notes. All clients are synchronous; payments, delivery receipt processors and generic side-effect retries are not implemented.
| Provider | SDK operations/options | Normalized semantics and unresolved contracts |
|---|---|---|
| Steadfast | Shipment creation/status | Creation and polling UNKNOWN; endpoint/schema and status vocabulary unverified |
| Pathao | Shipment creation/status, store/geography options, process-local OAuth lifecycle | Creation with ID ACCEPTED; polling UNKNOWN; provider-maintained DTO evidence supports omission of absent geography, but full error/status/replay guarantees remain unverified |
| RedX | Shipment creation/status, area options, declared value | Documented create fields/auth and typed parcel-info path; creation with ID ACCEPTED, polling UNKNOWN; exact weight units/types, Decimal wire acceptance and legacy raw tracking unresolved |
| AlphaSMS | SMS submission | Published code 0 plus request ID ACCEPTED; audited failure codes; no delivery confirmation API |
| GreenWeb | SMS submission and per-response-entry outcomes | SENT is ACCEPTED submission; FAILED/unknown preserved; partial batches raise SMSBatchError; request/response ordering and legacy standalone text-error mappings unresolved |
| BulkSMSBD | Legacy SMS submission | Legacy code 202 UNKNOWN; application-code meanings and HTTPS POST support unresolved; default HTTP refused |
provider.capabilities exposes SDK method availability, including shipment/SMS,
per-recipient submission, geography options and declared value. It does not certify
provider constraints, routing validity, delivery status or idempotency. See the
logistics guide and SMS guide before adopting
these capabilities in an application.
Native async additions (task 12, 0.1.0a1 alpha)
The six Async*Client classes share the same pure payload builders, response
parsers, classifications and normalized result helpers as sync clients. All
verification gaps above apply equally to async operations. No new provider
endpoint, success vocabulary or idempotency guarantee was inferred for this task.
See native async for asyncio scope, cancellation and lifecycle.
First payment adapter (task 13, experimental alpha)
SSLCOMMERZ hosted BDT checkout is the only payment integration. Sync/async clients share audited parsers and validation. See the separate payment contract matrix for official v4/signature provenance, requirements and unresolved sandbox/refund contracts. No live compatibility or provider idempotency is certified. Standalone bKash/Nagad remain planned.