Sub-processors
Effective from: [FOUNDER FILL — YYYY-MM-DD]
Last updated: [FOUNDER FILL — YYYY-MM-DD]
Version: 1
This page lists every external service that processes personal data on our behalf. It is referenced from §5 of the Privacy Policy. A change to this list is a documentation update — it does not re-version the Privacy Policy.
Each row cites the source file or symbol in code so the disclosure cannot drift away from the integration that actually runs.
Active sub-processors
| Sub-processor | Purpose | Data shared | Region | Source in code |
|---|---|---|---|---|
| OpenAI / Gemini / HUMAIN (per environment, operator-selected) | AI generation (SWOT, PESTEL, TOWS, objectives, KPIs, recommendations, risks) | Allow-listed organization profile fields (SafeOrganizationProfile) + aggregated organization context (department names + counts, prior-cycle attainment, plan-structure counts) + the public regional pack. Never per-person names/emails/user-ids, customer lists, partner lists, or legal_name. | US (OpenAI) / US+EU (Gemini) / KSA (HUMAIN) | apps/api/ai_runtime.py (AIProvider enum + generate_json_response); apps/api/ai_endpoints.py:_get_organization_context is the redactor |
| Moyasar | Payment processing — checkout intent and gateway-signed webhook for status changes | Gateway reference, amount, currency, status. Card PANs, CVCs, expiry dates never touch our infrastructure; the Moyasar hosted form handles card entry directly. | KSA | apps/api/billing.py (POST /v1/billing/checkout-intents, POST /v1/billing/webhooks/moyasar) |
| Taqnyat / Unifonic / Twilio (operator-selected) | SMS one-time-code delivery | Phone number + bilingual OTP message. Message body never logged. | Taqnyat: KSA. Unifonic: KSA. Twilio: US. | apps/api/integrations.py:KNOWN_SMS_ENDPOINTS (line 299); send path: apps/api/otp_delivery.py:send_otp_code |
| SMTP relay (operator-configured) | Transactional email — verification, password reset, deadline notifications, daily digest, OTP fallback | Email address, recipient name, message body. No marketing email is sent. | Per operator configuration | apps/api/email_service.py; admin-managed credentials Fernet-encrypted via apps/api/secrets_crypto.py |
| Object storage — S3 / GCS / MinIO (operator-configured) | Report generation outputs, avatar uploads, logo uploads | Files uploaded by users; each upload is magic-byte validated before storage so spoofed extensions are rejected. | Per operator configuration | apps/api/storage.py (allowed types: ALLOWED_REPORT_TYPES, ALLOWED_AVATAR_TYPES, ALLOWED_LOGO_TYPES); credentials encrypted at rest |
| PostgreSQL 16 (Cloud SQL or self-hosted, per environment) | Primary datastore — accounts, organizations, strategic plans, KPIs, audit logs, all relational data | Everything in apps/api/models.py. | Per operator configuration | apps/api/database.py (DATABASE_URL) |
| Redis 7 (managed or self-hosted, per environment) | Sessions, rate-limit counters, queue (arq), AI provider circuit breaker, WebSocket tickets, MFA counters | Session identifiers, integer counters. No PII bodies are stored in Redis. | Per operator configuration | settings.py:REDIS_URL; usage via apps/api/redis_client.py |
| OpenTelemetry collector (operator-supplied, optional) | Distributed tracing and metrics scrape target | Non-PII metadata — request path, status code, duration, span attributes. Body capture and header capture are explicitly disabled at instrumentation time. | Per operator configuration | apps/api/tracing.py (lines 23–24 pin the no-body-no-header posture); apps/api/observability.py redactor strips password, secret, token, authorization substrings before any log line is written |
Current provider selection per environment
| Environment | LLM provider | SMS provider | SMTP relay |
|---|---|---|---|
| Development / local | OpenAI or Gemini (operator choice) | None / MailHog catcher | MailHog (catches mail; sends nothing) |
| Staging | OpenAI | [FOUNDER FILL — Taqnyat / Unifonic / Twilio] | [FOUNDER FILL — SES / SendGrid / etc.] |
| Production | [FOUNDER FILL — OpenAI / Gemini / HUMAIN] | [FOUNDER FILL] | [FOUNDER FILL] |
When the production LLM provider is set to AIProvider.HUMAIN, the cross-border transfer disclosure in §6 of the Privacy Policy narrows to no cross-border transfer (HUMAIN is hosted in KSA).
How to track changes
- A new sub-processor — open a PR adding a row to the table above.
The Privacy Policy automatically picks up the change because it references this page rather than enumerating sub-processors itself.
- A change of region or scope — same PR mechanism. Material changes
trigger the same 30-day in-app notice + audit log row as a Privacy Policy change (an AuditLog(event_type="legal.subprocessors_updated") on deploy of the change).
*See also:*