Skip to content
All projects
Production · Attra TechnologiesCase study

CIO 360.

A monitoring, automation & SOAR platform — one operational view across an organization's security stack.

CIO 360 consolidates a fragmented stack of security, identity, backup and compliance tools — Microsoft Entra ID, DefensX, Dropsuite, Secure Connect and more — into a single dashboard, then orchestrates security responses on top. I built the entire web application as its sole developer, and I own the REST API contracts that the Laravel backend and native iOS app both build against.

Role
Sole web developer · API contract owner
Timeline
Jan 2024 — Present
Team
Me + external backend & iOS team (3 engineers)
Users
CIOs, CISOs and MSPs
React.jsReduxREST API contractsLaravel (integration)iOS (integration)MongoDBMySQL

01The problem

The problem

Mid-size organizations run their security operations across a dozen disconnected vendor tools: identity in Microsoft Entra ID, browser security in DefensX, backup in Dropsuite, network access in Secure Connect, and more. Each tool has its own dashboard, its own alert format and its own login. Nobody — least of all the CIO who answers for all of it — has one place to see the state of the whole stack, and responding to an incident means hopping between consoles.

The business goal for CIO 360 was direct: give CIOs, CISOs and MSPs a single operational view across all of those tools, and let them trigger automated responses (the SOAR part — Security Orchestration, Automation and Response) from the same place they detect problems.

02My role

My role

I joined as the platform's only web developer. There was no existing frontend codebase, no component library and no API documentation — the backend was being built in parallel by an external three-person team, alongside a native iOS app.

That shaped the job into three overlapping responsibilities: build the entire React web application from an empty repository; design and document the REST API contracts both clients would consume; and act as the primary technical interface between our product side and the external engineering team — writing scope documents, running daily progress meetings and unblocking integration issues.

03System architecture

System architecture

The platform is a classic multi-client architecture: a React single-page application and a native iOS app, both talking to a Laravel REST API that aggregates data from the vendor integrations. Relational platform data lives in MySQL; high-volume, loosely-structured vendor feed payloads land in MongoDB before being normalized.

Because two clients with different teams and release cadences consume the same API, the contract layer is the load-bearing wall of the system. My most consequential architectural decision was to treat the API contract as a designed artifact — written, reviewed and agreed before implementation — rather than letting the backend's code define it implicitly.

Contract-first, not code-first

Every endpoint was specified in a shared document — URL, method, request shape, response shape, error semantics — before either side wrote code. When web, iOS and backend disagree about a field, the document decides, not whoever shipped first.

One response envelope

All endpoints return the same envelope: a status, the data payload, and a structured error object with a machine-readable code. Both clients share one error-handling path instead of special-casing endpoints.

Normalize at the contract, not in the client

A dozen vendor feeds arrive in a dozen shapes. Rather than each client re-interpreting raw vendor payloads, the contract defines one canonical shape per domain (alerts, identities, backups) and the backend maps vendors into it. Web and iOS render identical data with zero vendor-specific logic.

04API contract design

API contract design

The contract documents cover request/response schemas, pagination, filtering, and — the part that pays for itself daily — explicit error semantics. Every failure mode an endpoint can produce is enumerated with a stable code, so clients can distinguish "vendor integration is down" from "your token expired" from "this tenant has no data yet" and show the right UI for each.

The example below is representative of the real contracts (the production API is closed-source): a normalized alert feed endpoint, with the envelope and error semantics both clients rely on.

Representative contract excerpt — normalized alerts feed
GET /api/v1/alerts?source=entra&severity=high&page=2

200 OK
{
  "status": "ok",
  "data": {
    "items": [
      {
        "id": "alrt_8f3a...",
        "source": "entra_id",        // canonical source key, not vendor-raw
        "severity": "high",           // normalized: low | medium | high | critical
        "category": "identity",
        "title": "Risky sign-in detected",
        "occurred_at": "2025-11-04T09:21:00Z",
        "entity": { "type": "user", "ref": "usr_29c1..." },
        "actions": ["disable_user", "require_mfa"]   // SOAR actions available
      }
    ],
    "page": { "current": 2, "per_page": 25, "total": 214 }
  },
  "error": null
}

502 Bad Gateway (vendor unreachable)
{
  "status": "error",
  "data": null,
  "error": {
    "code": "SOURCE_UNAVAILABLE",
    "source": "entra_id",
    "retryable": true,
    "message": "Entra ID integration did not respond."
  }
}

One envelope, canonical field names across all vendor sources, and error codes clients can branch on. "actions" is what wires detection to SOAR response.

05Frontend architecture

Frontend architecture

The web app is a React SPA with Redux as the single source of truth for server state. The store is organized by domain (alerts, integrations, tenants, automation) rather than by page, so the same normalized data feeds the overview dashboard, the per-tool drill-downs and the response workflows without duplication.

Every remote read goes through one async lifecycle convention — idle, loading, success, error with the contract's error code attached — which means every screen gets loading states, empty states and per-failure-mode error states from shared components instead of ad-hoc handling.

Domain-sliced Redux store

State is keyed by what the data is, not where it's shown. Dashboard widgets and detail pages select from the same slices, so a status change propagates everywhere at once.

One async convention

A single request-lifecycle pattern wraps every API call. Adding a new endpoint means writing a thunk and a selector — loading, error and retry UI come free.

Error UI driven by error codes

Because the contract enumerates failure modes, the UI can be honest: a down integration shows a "source unavailable" card with retry, not a generic spinner that never resolves.

06Challenges & trade-offs

Challenges & trade-offs

The hardest problems were coordination problems wearing technical clothes. The three that taught me the most:

A dozen vendors, one schema

Vendor APIs disagree about everything — severity scales, timestamp formats, what counts as an "alert". Designing the canonical shapes meant deciding, per field, what to preserve, what to coerce and what to drop. Trade-off accepted: some vendor-specific richness is flattened in the normalized feed, in exchange for clients that never contain vendor logic. Raw payloads stay retrievable for the cases that need them.

Building against an API that didn't exist yet

Backend and frontend were built in parallel. The contract documents made that possible: I built the entire UI against the agreed shapes with local fixtures, and integration became verification rather than discovery. When reality diverged from the document, the divergence was visible and arguable — that alone justified the process.

Three workstreams, one release train

Web, iOS and backend shipped together. I ran the daily syncs and kept a running integration-status document. The lesson: most "integration bugs" are two teams holding different definitions of the same word. Writing definitions down early is cheaper than debugging them later.

07Auth & security considerations

Auth & security considerations

A platform whose whole purpose is security operations has no room for a sloppy client. The web app follows token-based authentication against the Laravel API, with the session lifecycle — expiry, refresh, forced logout — handled through the same contract-defined error codes as everything else, so an expired token degrades into a clean re-authentication flow rather than a broken screen.

What a user can see and trigger is role-driven: a CIO's read-heavy overview and an operator's response actions are different capability sets, and the UI derives what it renders from what the API says the account can do — the client never assumes permissions the server hasn't granted. SOAR response actions are the sharpest edge in the product (a button that disables a user is not a button to render casually), so destructive actions are explicit, confirmed and driven by the per-alert action list the API returns.

08Delivery & how it shipped

Delivery & how it shipped

The platform is live in production with real organizations monitoring their stacks through it. I shipped the web app from empty repository to launch and continue to own it — features, fixes and the contract documents — while coordinating each release across the backend and iOS workstreams through daily progress meetings.

I also lean on AI-assisted development tooling deliberately: as a sole developer, it compresses the cycle between "decided" and "shipped" — scaffolding, debugging, reviewing my own code — while the architecture and contract decisions stay mine.

09What I'd do differently

What I'd do differently

With another pass, I'd push type safety across the contract boundary — generating TypeScript types from the contract documents so the compiler catches divergence instead of integration testing. I'd also introduce automated contract tests on the backend so the documents are enforced, not just agreed.

The larger lesson stands regardless: on multi-client systems, the interface is the product. Time spent making the contract precise is the highest-leverage engineering time there is.

If you only remember four things

  • Sole developer of a production React platform — empty repo to live users.
  • Contract-first API design consumed by two client platforms.
  • Normalized a dozen vendor feeds into canonical schemas with explicit error semantics.
  • Primary technical interface between product and a 3-person external team.