Developer Portal Service — Jira-Ready Epics & User Stories
Status: Draft Owner: Product + Developer Relations (DevRel) Last Updated: 2026-04-20 Service prefix: DEV Scope: Public developer portal, official server SDKs (Node, Python, Java, .NET, Go, PHP), mobile SDKs (Android, iOS, Flutter), Postman/OpenAPI/snippet generator, and the managed Verify API (SMS/Voice/WhatsApp).
Epic Summary
| Epic ID | Title | Stories | Points |
|---|---|---|---|
| EP-DEV-01 | Public Developer Portal (docs, sandbox, key self-serve, consumption analytics) | US-DEV-001 – US-DEV-008 | 47 |
| EP-DEV-02 | Official Server SDKs (Node, Python, Java, .NET, Go, PHP) | US-DEV-009 – US-DEV-014 | 48 |
| EP-DEV-03 | Mobile SDKs (Android, iOS, Flutter) | US-DEV-015 – US-DEV-019 | 34 |
| EP-DEV-04 | Postman Collection, OpenAPI Pre-Baked, Code Snippet Generator | US-DEV-020 – US-DEV-023 | 24 |
| EP-DEV-05 | Verify API (managed OTP across SMS/Voice/WhatsApp) | US-DEV-024 – US-DEV-029 | 45 |
Total stories: 29
EP-DEV-01 · Public Developer Portal
Context: The portal is the front door for every developer building on the Ghasi backbone — docs, sandbox, self-serve API keys, consumption analytics, and trust signals (status, incidents).
US-DEV-001 · Public Documentation Site Renders from OpenAPI
Type: Feature | Points: 5
Description: As an external developer, I want to browse versioned API reference documentation rendered from the canonical OpenAPI 3.1 spec so that I can understand every endpoint without reading source code.
Acceptance Criteria:
- Canonical
openapi/v1.jsonartifact rendered in Redoc at/reference/v1 - Sidebar navigation lists every tag; deep-linking via
#/operations/{operationId}anchors - Swagger UI at
/reference/v1/tryfor interactive sandbox-only "Try it" calls - CI rejects publishes that fail
spectral lint - Multi-version routes
/reference/v{N.M}generated per release
US-DEV-002 · Locale Switching for Documentation (English / Pashto / Dari)
Type: Feature | Points: 5
Description: As an Afghan developer reading docs in their preferred language, I want to switch the portal between English, Pashto, and Dari so that I can learn the platform in my native language.
Acceptance Criteria:
- Language switcher persists choice in
localStorageandpref-langcookie - Pashto/Dari trigger RTL layout (snapshot tested)
- Untranslated guides show banner + render English fallback
- First-visit language honours
Accept-Language - Reference uses
x-i18n.{locale}extension when present
US-DEV-003 · Self-Serve Sandbox API Key Creation
Type: Feature | Points: 5
Description: As a logged-in developer, I want to generate a sandbox API key from the portal in one click so that I can start integrating without filing a support ticket.
Acceptance Criteria:
-
POST /devportal/v1/keysmodal collects label + scope checkboxes - Calls
auth-serviceCreateApiKey(env=sandbox)via gRPC; returns 201 with{ keyId, prefix, secret, env, scopes, createdAt } - Secret shown exactly once with copy-to-clipboard + confirmation; never persisted in
devportalPostgres - 6th sandbox key returns 409
KEY_QUOTA_EXCEEDED -
devportal.key.createdNATS event emitted within 5 s
US-DEV-004 · Self-Serve Production API Key with Owner Approval
Type: Feature | Points: 8
Description: As a tenant developer needing production credentials, I want to request a production API key that the tenant owner approves in-portal so that production credentials are issued under owner control without a manual ticket.
Acceptance Criteria:
- Production key requests trigger pending row + owner notification (in-portal + email) with one-click approve/deny
- On approve, key is created via
auth-serviceand shown to requester once on next portal load - 7-day expiry on unanswered requests (
status=EXPIRED+ notify) - Owner deny returns
status: DENIEDwith reason - Self-owners get auto-approval (synchronous secret)
US-DEV-005 · API Key Revocation Propagates Within 60 Seconds
Type: Feature | Points: 5
Description: As a developer who suspects a leaked key, I want to revoke a key in one click and have it stop working everywhere within 60 s so that compromise blast-radius is bounded.
Acceptance Criteria:
-
DELETE /devportal/v1/keys/{keyId}invokesauth-serviceRevokeApiKeyand emitsdevportal.key.revoked - Synthetic prober confirms P95 ≤ 60 s, P99 ≤ 120 s propagation
- Revoked-key requests return 401
API_KEY_REVOKED - Double-revoke returns 200
alreadyRevoked: true, no duplicate event - Revoked keys cannot be reactivated (must create a new key)
US-DEV-006 · Sandbox Endpoint Returns Deterministic Synthetic DLR
Type: Feature | Points: 8
Description:
As a developer integrating against sandbox, I want to call POST https://api-sandbox.ghasi.af/v1/sms/send and receive a deterministic DLR so that I can write reliable integration tests.
Acceptance Criteria:
- Sandbox returns same response shape as production
-
+93700000001→ DELIVERED in 2 s -
+93700000002→ UNDELIVABSENT_SUBSCRIBER -
+93700000003→ no DLR for 30 m, then EXPIRED - Other recipients → DELIVERED with random 200–1500 ms latency
- Production key against sandbox returns 403
ENV_MISMATCH
US-DEV-007 · Per-Key Consumption Analytics Dashboard
Type: Feature | Points: 8
Description: As a developer monitoring my integration, I want to see per-key request volume, error rate, P95 latency, and SMS segment count over a chosen time range so that I can detect anomalies and plan capacity.
Acceptance Criteria:
-
/dashboard/keys/{keyId}renders 4 charts for ranges 1h / 24h / 7d / 30d - gRPC
QueryConsumptiontoanalytics-service; Redis cache 30 s - CSV export ≤ 10 000 rows with
[ts, requests, errors, p50, p95, p99, segments] - Analytics outage shows empty state + retry; no toast spam
- Range > 90 days → 400
RANGE_TOO_LARGE
US-DEV-008 · Status Page Embed and Incident Banner
Type: Feature | Points: 3
Description: As a developer worried about an outage, I want to see real-time platform status on every portal page so that I know whether an issue is mine or the platform's.
Acceptance Criteria:
- Status indicator polls
status.ghasi.af/api/v2/status.jsonevery 60 s - Active incident ≥ minor renders banner with title + link
- Status feed unreachable → no banner, log warn, no broken UI
- Per-incident dismissal cookie respected
EP-DEV-02 · Official Server SDKs
Context: First-class server SDKs for Node, Python, Java, .NET, Go, and PHP — generated from OpenAPI, post-processed for idiom, gated by DevRel manual review per release.
US-DEV-009 · Node.js SDK Published to npm
Type: Feature | Points: 8
Description:
As a Node.js developer, I want @ghasi/sdk from npm with client.sms.send(...) so that I do not write HTTP plumbing.
Acceptance Criteria:
- Published
@ghasi/sdk@1.M.Pwith TypeScript types, ESM + CJS dual build, changelog -
client.sms.send({to, from, body})returnsPromise<{messageId, status: "QUEUED"}> - Transient 5xx → 3 retries with exponential backoff (200/400/800 ms)
- 4xx → typed
GhasiApiError(code, httpStatus, requestId) - Strict TS consumer compiles with
tsc --noEmit - DevRel manual approval gate before npm publish
US-DEV-010 · Python SDK Published to PyPI
Type: Feature | Points: 8
Description:
As a Python developer, I want pip install ghasi-sdk and client.sms.send(...) so that I have an idiomatic Pythonic client.
Acceptance Criteria:
- Wheels for 3.10/3.11/3.12 + sdist published
- Sync
GhasiClientandAsyncGhasiClient(httpx async) both exposed - Errors:
AuthError,RateLimitError(retry_after) -
mypy --strictpasses against the SDK - PEP 484 type hints throughout
US-DEV-011 · Java SDK Published to Maven Central
Type: Feature | Points: 8
Description:
As a Java developer, I want af.ghasi:ghasi-sdk on Maven Central with client.sms().send(...) so that I have a typed, supported client.
Acceptance Criteria:
- Maven Central artifact + sources JAR + Javadoc JAR
- JDK 17 thread-safe
GhasiClient.builder().apiKey(...).build() - Spring Boot starter
ghasi-sdk-spring-boot-starterregistersGhasiClientbean - Errors:
GhasiApiExceptionwith code/status/requestId - Sonatype OSSRH validation passes
US-DEV-012 · .NET SDK Published to NuGet
Type: Feature | Points: 8
Description:
As a .NET developer, I want Ghasi.Sdk from NuGet and await client.Sms.SendAsync(...) so that I have a first-class .NET client.
Acceptance Criteria:
-
Ghasi.Sdkpublished targetingnet8.0with source-link -
services.AddGhasiClient(o => o.ApiKey = "...")DI registration -
CancellationTokencancels in-flight HTTP - Source-generated
System.Text.Jsonserialisers (Blazor WASM-safe) - Typed
GhasiApiException
US-DEV-013 · Go SDK Published as a Module
Type: Feature | Points: 8
Description:
As a Go developer, I want af.ghasi/sdk-go and context-aware client.SMS.Send(ctx, ...) so that I have an idiomatic Go client.
Acceptance Criteria:
- Tagged
v1.M.Pon GitHub, resolvable bygo get - Every method takes
context.Contextfirst -
*ghasi.APIErrorfor 4xx withCode,Status,RequestID -
go vetandstaticcheckzero-finding -
WithRetryPolicyconfigurable backoff
US-DEV-014 · PHP SDK Published to Packagist
Type: Feature | Points: 8
Description:
As a PHP developer, I want composer require ghasi/sdk with Laravel and Symfony bindings so that common PHP frameworks integrate cleanly.
Acceptance Criteria:
- PHP 8.2+, PSR-12 compliant, published to Packagist
- Laravel:
GhasiServiceProvider+Ghasifacade auto-discovered - Symfony: Flex recipe + bundle service registration
-
Ghasi\Exception\ApiExceptionwith code/status/requestId - PHPStan level 8 zero errors
EP-DEV-03 · Mobile SDKs
Context: Native mobile SDKs (Android, iOS, Flutter) optimised for the Verify API — auto-OTP-read, accessibility, locale, reduced-motion.
US-DEV-015 · Android SDK with SMS Retriever Auto-Read
Type: Feature | Points: 8
Description: As an Android developer integrating Verify, I want an Android SDK that auto-reads SMS OTP using SMS Retriever / SMS User Consent APIs so that users do not manually copy codes.
Acceptance Criteria:
-
af.ghasi:android-sdk:1.M.PGradle dependency -
GhasiVerify.start(activity, verificationId)uses SMS Retriever (noREAD_SMSpermission) -
onCodeReceived(code)fires within 5 s - Falls back to SMS User Consent API when needed
-
minSdk 24, compileSdk 34; shipsconsumer-rules.pro
US-DEV-016 · iOS SDK with OTP AutoFill Heuristics
Type: Feature | Points: 8
Description: As an iOS developer integrating Verify, I want a native Swift SDK with one-tap OTP autofill leveraging iOS heuristics so that users get the iOS standard experience.
Acceptance Criteria:
- SwiftPM via
https://github.com/ghasi-af/ios-sdk -
GhasiVerify.start(verificationId)returns Combine publisher ofVerifyState - iOS QuickType OTP suggestion via
From: GhasiSMS heuristics - iOS 15+
-
GhasiOTPField()SwiftUI view with.oneTimeCodecontent type -
GhasiErrortyped errors via publisher failure
US-DEV-017 · Flutter SDK Published to pub.dev
Type: Feature | Points: 8
Description:
As a Flutter developer, I want flutter pub add ghasi_sdk with unified Verify across Android and iOS so that I do not maintain native bridges.
Acceptance Criteria:
-
GhasiClient(apiKey: ...)exposessms,verify,keys - Verify uses platform channel to native flows on each OS
- pub.dev quality score ≥ 130/140
- Sandbox-only HTTP testing works without platform channels
- Flutter 3.16+, Dart 3.2+
US-DEV-018 · Mobile SDKs Surface Consistent Verify State Machine
Type: Feature | Points: 5
Description:
As a mobile developer, I want the same VerifyState enum across Android, iOS, and Flutter so that cross-platform behaviour is predictable.
Acceptance Criteria:
- Shared seven values:
PENDING, AUTO_FILLED, USER_ENTERED, APPROVED, FAILED, CANCELLED, EXPIRED - State diagram documented + linked from each SDK README
- CI contract test asserts identical sequence per platform on a deterministic sandbox
verificationId
US-DEV-019 · Mobile SDKs Honour Reduced-Motion and Locale
Type: Feature | Points: 5
Description: As a mobile developer building inclusive apps, I want provided UI components to respect OS reduced-motion and active locale so that my app inherits accessibility for free.
Acceptance Criteria:
- Reduced-motion disables non-essential animation
- Locale
ps/fa/enswitches strings; missing → English fallback - RTL layout for Pashto/Dari with correct caret behaviour
-
GhasiVerify.localize(map)overrides bundled catalogue
EP-DEV-04 · Postman, OpenAPI, Snippet Generator
US-DEV-020 · Pre-Baked Postman Collection Per Release
Type: Feature | Points: 5
Description: As a developer evaluating the API, I want to click "Run in Postman" so that I can poke at endpoints in 30 seconds.
Acceptance Criteria:
- Postman v2.1 JSON generated per release at immutable URL
/postman/v1.M.P/collection.json - Imports two environments (sandbox/production)
-
Authorization: Bearer {{apiKey}}collection variable - Example responses (200/4xx/5xx) per request
- Old release URLs remain accessible
US-DEV-021 · OpenAPI 3.1 Bundle Downloadable and Validated
Type: Feature | Points: 3
Description: As a developer using their own toolchain, I want to download the canonical OpenAPI 3.1 JSON/YAML so that I can generate clients in unsupported languages.
Acceptance Criteria:
-
GET /openapi/v1.jsonand/openapi/v1.yamlwith correctContent-Type - Spec passes
spectral lintandopenapi-cli validate -
openapi-generatorJava client builds in CI - Historical versions immutable per release
US-DEV-022 · Per-Endpoint Code Snippet Generator (10+ Languages)
Type: Feature | Points: 8
Description: As a developer reading the docs, I want copy-paste snippets in my chosen language so that I do not translate from cURL manually.
Acceptance Criteria:
- Languages: cURL, Node, Python, Java, .NET, Go, PHP, Ruby, Kotlin, Swift
- Snippets use official SDK when available; raw HTTP otherwise
- "Copy" button on hover; clipboard copy works
- Endpoint changes regenerate snippets and purge CDN
- Placeholders bracketed (e.g.
<YOUR_API_KEY>)
US-DEV-023 · SDK Release Pipeline with DevRel Manual Gate
Type: Feature | Points: 8
Description: As a DevRel engineer, I want one GitHub Actions pipeline to publish all six server SDKs from one tag with a manual gate so that releases are atomic and auditable.
Acceptance Criteria:
- One tag
v1.M.Ptriggers per-language build, test, RC artifacts -
release-approvalGitHub environment requires DevRel reviewer - On approval: parallel publish to npm, PyPI, Maven Central, NuGet, GitHub releases (Go), Packagist
- Per-language failure: Slack alert + auto-create follow-up issue, mark partial release
-
devportal.sdk.releasedevent per language with{language, version, artifactUri, sha256}
EP-DEV-05 · Verify API (Managed OTP)
Context: A Twilio-Verify-equivalent product that abstracts code generation, storage, dispatch, and channel fallback behind one contract. Delegates SMS to
sms-orchestrator, voice tovoice-otp-gateway, WhatsApp tochannel-router-service-> WA BSP.
US-DEV-024 · Verify API: Start SMS Verification
Type: Feature | Points: 8
Description:
As a tenant integrating Verify, I want to call POST /v1/verify/sms so that the platform sends an OTP without me implementing code generation, storage, and SMS dispatch.
Acceptance Criteria:
- Request
{to, locale, codeLength, ttlSeconds, templateId?}returns 202{verificationId, status: "PENDING", channel: "sms", expiresAt} - Code is
crypto.randomInt, length 4/6/8; stored asHMAC-SHA-256(code, server-pepper)in Redis with TTL - SMS dispatched via
sms-orchestratorusing approved Verify template - Invalid
to→ 400INVALID_RECIPIENT - Per-recipient cap exceeded → 429
RECIPIENT_RATE_LIMIT -
devportal.verify.requestedevent emitted (recipient hashed)
US-DEV-025 · Verify API: Check OTP
Type: Feature | Points: 5
Description:
As a tenant validating a user-entered code, I want POST /v1/verify/check so that I do not store OTPs or implement constant-time comparison.
Acceptance Criteria:
- Match + unexpired + attempts ≤ 5 → 200
APPROVED; session deleted - Mismatch → 200
PENDINGwithattemptsRemaining; attempts++ - attempts > 5 → 200
FAILEDMAX_ATTEMPTS; session deleted - Expired → 200
EXPIRED; emitdevportal.verify.failed -
crypto.timingSafeEqualconstant-time compare - On approval, emit
devportal.verify.approved
US-DEV-026 · Verify API: Voice Channel
Type: Feature | Points: 8
Description:
As a tenant whose user cannot receive SMS, I want POST /v1/verify/voice so that the platform places an outbound call reading the OTP twice.
Acceptance Criteria:
- 202
{verificationId, status: "PENDING", channel: "voice", expiresAt} - TTS in
ps/fa/enreads each digit individually with 500 ms gap, repeated twice -
voice-otp-gatewayinvocation; up to 2 retries with 30 s spacing - Pickup completion →
DELIVERED - Voice unavailable → 503
VOICE_UNAVAILABLE; no session created
US-DEV-027 · Verify API: WhatsApp Channel
Type: Feature | Points: 8
Description:
As a tenant whose users prefer WhatsApp, I want POST /v1/verify/whatsapp so that OTP is delivered via the WhatsApp Cloud API.
Acceptance Criteria:
- 202 with
channel: "whatsapp" - Sends Meta-approved template
verify_otp_v1viachannel-router-service→ WA BSP adapter - Not-on-WhatsApp →
FAILEDwith reasonNOT_ON_WHATSAPPwithin 30 s - Delivery webhook updates session; emits
devportal.verify.delivered - Regulator-blocked region → 451
CHANNEL_BLOCKED_BY_REGULATOR
US-DEV-028 · Verify API: Channel Fallback Cascade
Type: Feature | Points: 8
Description:
As a tenant who wants maximum delivery rate, I want channels: ["whatsapp","sms","voice"] cascade so that I do not orchestrate fallback myself.
Acceptance Criteria:
-
POST /v1/verifyacceptschannels[]; 202 returnscurrentChannel,channelOrder - Per-channel timeout (default 60 s) advances to next; max 3 attempts
-
GET /v1/verify/{verificationId}returns full attempt history - Approval on any successful channel ends session
- All channels failed →
FAILED ALL_CHANNELS_FAILED+ emitdevportal.verify.failed
US-DEV-029 · Verify API: Per-Tenant Anti-Abuse and Spend Cap
Type: Feature | Points: 8
Description: As a platform operator, I want per-tenant Verify start rate limits and a daily spend cap so that OTP-grinding and runaway usage are bounded.
Acceptance Criteria:
- Per-tenant rate exceeded → 429
TENANT_RATE_LIMIT(default 100/min) - Daily spend cap exceeded → 402
DAILY_SPEND_CAP_EXCEEDED+devportal.verify.spend_cap_hitevent (default $500/day) -
fraud-intel-serviceflag → 403FRAUD_BLOCKwith reference - Per-recipient cap (5/hour, 20/day) → 429
RECIPIENT_RATE_LIMIT - All blocks logged with full context; counters exposed to Prometheus