This repository powers the backend API for ApexChainx, an SLA automation and outage settlement platform.
It is responsible for:
- managing outages and RCA
- calculating SLA performance
- exposing analytics and audit data
- brokering contract-aware payout logic
- handling authentication, payments, wallet state, jobs, disputes, and webhooks through the API surface
- Framework: FastAPI
- Language: Python (3.11+)
- Database: PostgreSQL via SQLAlchemy
- Auth: lightweight in-repo auth store
- Blockchain: Soroban-aware backend bridge with configurable execution mode
- Validation: Pydantic v2
- Async: FastAPI + Celery-oriented modules
Responsible for:
- creating outages
- updating outage status
- tracking resolution
- storing metadata (location, services, subscribers)
Key endpoints:
- GET /outages
- POST /outages
- PUT /outages/{id}
Core business logic.
Responsible for:
- calculating MTTR
- determining SLA compliance
- triggering penalties or rewards
- invoking smart contracts
Key endpoints:
- GET /sla/status/{outage_id}
- POST /sla/calculate
- POST /sla/execute-payment
Important:
- SLA depends on severity thresholds
- Payment logic is tightly coupled with SLA
Responsible for:
- exposing payment records tied to SLA outcomes
- tracking transaction status
- storing transaction history
Key endpoints:
- POST /payments/process-sla
- GET /payments/history
Flow: Outage → SLA Calculation → Smart Contract → Payment → Record stored
Responsible for:
- creating lightweight wallet records
- retrieving balances and status
- linking wallets to users
Key endpoints:
- POST /wallets/create
- GET /wallets/{user_id}
- GET /wallets/{address}/balance
SECURITY CRITICAL:
- private keys are NEVER returned via API
- private keys are NEVER logged or exposed
- only public keys and balance information are accessible
- wallet operations require proper authentication
Responsible for:
- MTTR calculations
- SLA compliance metrics
- payment analytics
Key endpoints:
- GET
/api/v1/sla/analytics/dashboard - GET
/api/v1/sla/analytics/trends - GET
/api/v1/sla/performance/aggregation
Responsible for:
- recording all state-changing operations
- correlating events via
X-Correlation-ID - immutable append-only log
Key endpoints:
- GET /api/v1/audit
Responsible for:
- login
- registration
- JWT issuance
Key endpoints:
- POST /auth/login
- POST /auth/register
- API Layer → routes (FastAPI endpoints)
- Service Layer → business logic and adapters
- Repository Layer → SQLAlchemy interaction
- External Layer → contract bridge, Celery, and webhook integrations
Treat the following as active routed runtime modules:
authauditjobsoutagespaymentsslasla_disputewalletswebhooks
Treat the following as lighter-weight or environment-dependent:
authandwalletsare functional but currently backed by in-repo storesjobsandwebhooksdepend on worker infrastructure for full operational behavior- contract execution depends on
CONTRACT_EXECUTION_MODE(localorcontract)
Treat the following as non-routed or legacy helper paths:
app/services/outage_store.py
- Outage created
- Outage resolved
- MTTR calculated
- SLA evaluated
- Contract adapter or local adapter invoked
- Payment record generated
- Transaction stored in DB
- All monetary actions must go through the SLA system
- Payments must be idempotent
- Wallet operations must avoid private key exposure
- SLA must be deterministic and reproducible
- API responses must follow a consistent structure
CRITICAL SECURITY REQUIREMENTS:
- Private Key Protection: Private keys for Stellar wallets are never exposed via API responses, logs, or documentation
- Credential Management: All sensitive credentials (database passwords, API keys, JWT secrets) must use environment variables
- Documentation Safety: Examples must use placeholder values clearly marked as non-production
- Logging Security: Never log sensitive information including partial keys, tokens, or passwords
- Environment Separation: Testnet and mainnet credentials must be completely separate
- Access Control: All financial operations require proper authentication and authorization
- Audit Trail: All payment and SLA operations must be logged for audit purposes
Documentation Standards:
- Use
[REDACTED]or[EXAMPLE]for sensitive placeholder values - Include security warnings for any blockchain or financial operations
- Show secure patterns (environment variables, secure key management)
- Distinguish clearly between testnet and mainnet examples
Codex should focus on generating issues for:
- endpoint validation consistency
- error handling standardization
- docs alignment with routed runtime
- contributor clarity around active vs dormant modules
- separate routes from business logic
- validate all inputs with Pydantic
- keep business logic out of controllers
- prefer reusable services and repositories
- treat the routed runtime as source of truth when docs drift
When generating issues:
- scope each issue to ONE domain
- include:
- title
- description
- acceptance criteria
- affected modules/files
- dependencies (Blocked By)
- prefer small, atomic tasks
- group issues into:
- foundational
- feature
- integration
- optimization
This repo depends on:
- apexchainx-fe → consumes API
- apexchainx-contracts → executes SLA logic
Important:
- any change in SLA logic may affect contracts
- any API shape change affects frontend
Generate a structured backlog of issues that:
- improves backend reliability
- ensures correctness of SLA + payments
- prepares system for production scale
- maintains clean separation of concerns
Responsible for:
- filing and tracking disputes against SLA outcomes
- linking disputes to originating SLA records
- providing audit trail for contested settlements
Key endpoints:
- POST /api/v1/sla/disputes
- GET /api/v1/sla/disputes
- GET /api/v1/sla/disputes/{dispute_id}
| Term | Definition |
|---|---|
| MTTR | Mean Time to Resolve — the primary SLA compliance metric |
| SLA | Service Level Agreement — defines penalty/reward thresholds |
| Correlation ID | UUID injected per request for cross-system tracing |
| Contract adapter | Soroban bridge activated when CONTRACT_EXECUTION_MODE=contract |
| Local adapter | Default in-process SLA execution path |
The auth domain includes token family tracking and per-user rate limiting. Rate limit state is stored in the database via migration 0008_auth_rate_limiting. Abuse triggers a short-lived backoff enforced in app/core/rate_limiter.py.
SLA settlement and payment execution are idempotent by design. Duplicate execution attempts for the same outage_id are detected and return the existing record rather than creating a second payment. Payment deduplication is enforced at the database level via migration 0011_payment_deduplication.
Every request receives an X-Correlation-ID header injected by app/middleware/correlation.py. The value propagates through audit log entries, SLA records, and payment records so that the full lifecycle of any request can be reconstructed from logs alone.
Requests pass through two middleware layers before reaching route handlers:
- Correlation middleware (
app/middleware/correlation.py) — injects or propagatesX-Correlation-ID - Payload size guard (
app/middleware/payload_size.py) — rejects bodies exceedingMAX_REQUEST_BODY_SIZE_BYTES
Both are applied globally to all routes.
- Each domain has a dedicated repository class under
app/repositories/ - Repositories accept a SQLAlchemy
Sessionand never call each other directly - Business logic belongs in
app/services/, not in repositories - Route handlers call services; services call repositories
- Unit tests for service logic use mocked repositories
- Integration tests hit in-memory or test-database sessions
- Config validation tests use
pytestwith monkeypatched environment variables - All tests live under
tests/and usepytestas the runner
All error responses follow a consistent envelope:
{
"detail": "Human-readable error message",
"correlation_id": "550e8400-e29b-41d4-a716-446655440000"
}Validation errors from Pydantic return 422 Unprocessable Entity with a detail array describing each field failure.
To add a new domain to the routed runtime:
- Create
app/models/{domain}.py— Pydantic request/response schemas - Create
app/models/orm/{domain}.py— SQLAlchemy ORM model - Create
app/repositories/{domain}_repository.py— DB access - Create
app/services/{domain}_service.py— business logic - Create
app/api/v1/endpoints/{domain}.py— FastAPI route handlers - Register the router in
app/api/v1/router.py - Write an Alembic migration under
alembic/versions/ - Add tests under
tests/
MTTR (Mean Time to Resolve) is computed as the difference in minutes between the outage created_at timestamp and its resolved_at timestamp. The computed MTTR is compared against severity-specific thresholds defined in app/services/sla/config.py:
- If MTTR ≤ threshold → reward outcome
- If MTTR > threshold → penalty outcome
Both penalty amount and reward amount are configurable per severity tier.
Webhook delivery is handled by app/tasks/webhook_tasks.py as a Celery task. When CELERY_TASK_ALWAYS_EAGER=true, tasks execute synchronously in-process (no Redis required). In production, set CELERY_TASK_ALWAYS_EAGER=false and run a Celery worker alongside the API process.
The migration 0012_sla_latest_backfill.py populates the is_latest flag on existing SLA records. This flag allows the analytics layer to efficiently query only the most recent SLA result per outage without a subquery on every request.
When SLA policy thresholds change, existing resolved outages can be recomputed in bulk via POST /api/v1/sla/bulk-recompute. The operation is idempotent — re-running it with the same outage IDs produces the same result. Each recompute emits an audit event.
SLA results are stamped with the policy version that was active at computation time. This allows historical results to be compared against current policy without ambiguity. Policy version is stored on the sla_results table.
All timestamps are stored and compared in UTC. The SLA calculator normalises input timestamps to UTC before computing MTTR. Clock skew between the submitting client and the server is not compensated — use NTP-synchronized clients in production.
MTTR boundary computation is deterministic: given the same created_at and resolved_at, the same MTTR and SLA outcome are always produced. This property enables safe recompute and contract parity testing.
created → updated → resolved
↓
(SLA computed)
↓
penalty or reward outcome
↓
(optional) payment triggered
An outage can only be resolved once. Resolved outages are immutable — subsequent updates return 409 Conflict.
Payment deduplication is enforced at the database level via a unique constraint on (outage_id, payment_type) added in migration 0011_payment_deduplication. Attempting to execute a second payment for the same outage returns the existing record with 409 Conflict.
The auth system uses token families to detect refresh token reuse attacks. Each refresh creates a new token family member. Using a previously rotated refresh token invalidates the entire family, forcing re-login. This is implemented in app/repositories/token_family_repository.py.
app/repositories/session_repository.py manages active user sessions. Sessions are linked to token families and expire after the JWT TTL. Logout invalidates the session record immediately, preventing further token refresh even within the TTL window.
app/utils/analytics_exporter.py handles CSV and JSON serialisation of SLA analytics. It reads from the sla_analytics_snapshots table populated by migration 0007_sla_analytics_snapshots. Export operations are read-only and do not mutate any records.
app/services/wallet_registry.py manages the mapping between entity IDs and Stellar public keys. In the current release this uses an in-memory store. Persistence via the wallets database table (migration 0010_wallet_persistence) is the planned next step.
app/services/job_cleanup.py prunes completed and failed job records older than a configurable retention window. This prevents unbounded growth of the jobs table. The cleanup is triggered as a periodic Celery beat task when a worker is running.
app/services/webhook_signing.py provides HMAC-SHA256 signing for outgoing webhook payloads. The signing key is the webhook secret stored per-registration. Signature version metadata is written to the webhook_signature_metadata column added in migration 0013_webhook_secret_metadata.
app/services/metrics.py exposes internal counters for SLA evaluations, payment executions, webhook delivery attempts, and audit log entries. Metrics are in-process only; no external metrics endpoint is exposed in the current release.
| Task | Command |
|---|---|
| Start API | uvicorn app.main:app --reload |
| Run migrations | alembic upgrade head |
| Check migration state | alembic current |
| Run all tests | pytest tests/ |
| Run one test file | pytest tests/test_outage_lifecycle.py -v |
| Start Celery worker | celery -A app.tasks.celery_app worker --loglevel=info |
To run background tasks in worker mode:
celery -A app.tasks.celery_app worker --loglevel=infoRequires CELERY_BROKER_URL and CELERY_RESULT_BACKEND to be set. Set CELERY_TASK_ALWAYS_EAGER=false to route tasks to the worker instead of executing in-process.
app/repositories/outage_repository.py exposes:
create(db, data)— persist new outageget(db, outage_id)— fetch by IDupdate(db, outage_id, data)— partial updateresolve(db, outage_id, mttr, resolved_at)— mark resolved (atomic)list(db, filters, limit, offset)— paginated list with filter supportsearch(db, query, filters)— full-text and filter search
app/repositories/sla_repository.py exposes:
create(db, data)— persist SLA resultget_latest(db, outage_id)— fetch most recent result per outagelist(db, filters, limit, offset)— paginated result listbulk_create(db, records)— bulk insert for recompute operationsmark_latest(db, sla_id)— updateis_latestflag
app/repositories/payment_repository.py exposes:
create(db, data)— persist payment recordget(db, payment_id)— fetch by IDget_by_outage(db, outage_id)— fetch payment linked to outageupdate_status(db, payment_id, status, tx_hash)— update after confirmationlist(db, filters, limit, offset)— paginated list with date and status filters
| Layer | Allowed | Not Allowed |
|---|---|---|
| Route handler | Call services, return responses | Query DB directly, business logic |
| Service | Business logic, call repositories | Import other services' repositories |
| Repository | SQLAlchemy queries only | Business logic, HTTP calls |
| Utility | Pure functions, no DB/HTTP | Side effects |
- Request schemas are named
{Domain}Create,{Domain}Update - Response schemas are named
{Domain}Response,{Domain}ListResponse - ORM models live in
app/models/orm/and are never exposed directly to route handlers - All datetime fields use
datetimetype (notstr) — FastAPI serialises to ISO 8601 automatically
Enums used across the API are defined in app/models/enums.py. Always reference the enum class, not raw strings, in service and repository code to benefit from type safety and refactoring support.
Key enums:
OutageStatus:open,resolvedSLAOutcome:penalty,rewardPaymentStatus:pending,confirmed,failedSeverityLevel:low,medium,high,critical
Key event types emitted by app/services/audit_log.py:
| Event Type | Trigger |
|---|---|
outage.created |
New outage persisted |
outage.resolved |
Outage resolved with MTTR |
sla.computed |
SLA outcome calculated |
sla.recomputed |
Bulk recompute executed |
payment.initiated |
Stellar payment submitted |
payment.confirmed |
On-chain confirmation received |
dispute.filed |
Dispute created |
dispute.resolved |
Dispute closed |
auth.login |
Successful login |
auth.logout |
Session invalidated |
auth.failed |
Failed login attempt |
ORM models live in app/models/orm/. Each model:
- extends
Basefromapp/db/base_class.py - uses
__tablename__matching the migration table name - defines
idas UUID primary key - includes
created_atwith server defaultnow() - never exposes SQLAlchemy internals to API responses
Sessions are managed via a FastAPI dependency injected into route handlers:
from app.db.session import get_db
from sqlalchemy.orm import Session
from fastapi import Depends
@router.get("/outages")
def list_outages(db: Session = Depends(get_db)):
...Sessions are committed and closed automatically by the dependency. Do not call db.commit() in repositories — commit in services or use explicit transactions.
app/core/config.py uses Pydantic Settings to load and validate all environment variables at startup. Access settings via the get_settings() function (cached singleton). Never read os.environ directly in application code — always go through get_settings().
app/core/lock.py provides a lightweight advisory lock mechanism used to prevent concurrent SLA recompute operations on the same outage. Uses a database-level advisory lock via PostgreSQL pg_try_advisory_lock. Do not use Python threading primitives for cross-process synchronisation.
app/utils/cache.py provides a simple in-process LRU cache for SLA policy configuration reads. Cache entries expire after a configurable TTL. Invalidate the cache after any policy configuration update to ensure the next SLA computation uses the new values.
app/utils/logging.py configures structured JSON logging. All log entries include:
timestamp(UTC)levelmessagecorrelation_id(if available in request context)service: alwaysapexchainx-be
Do not use print() in application code. Use the logger from app/utils/logging.py.
app/utils/explorer.py constructs Stellar Expert URLs for transactions and accounts based on the active STELLAR_NETWORK setting. Use this utility when building payment records rather than constructing URLs manually.
app/utils/wallet_address.py validates and normalises Stellar public key format. Call validate_stellar_address(address) before persisting any wallet address. Returns False for addresses that are not valid G-type Stellar public keys.
app/utils/correlation.py provides helpers for reading and setting the correlation ID in the current request context. Use get_correlation_id() inside services when you need to attach the ID to audit events or log entries outside of the request-response cycle.