Our organization's journey with Sprinto began with a manual, point-and-click approach to managing control tests, which quickly became untenable as our SaaS platform scaled and our audit requirements grew in complexity. The manual process was not only a significant time sink for our security and engineering teams but also introduced latency and potential for human error in evidence collection. This led us to explore Sprinto's API with the goal of fully automating the control testing lifecycle—from test initiation to evidence submission and status reporting. The following details our step-by-step implementation, focusing on the architectural decisions and specific API endpoints that proved critical.
The core of our automation revolves around three key phases: test discovery, evidence collection, and status synchronization. We built a middleware service in Node.js to act as an orchestrator between our internal systems and Sprinto's API.
**Phase 1: Discovery & Scheduling**
We first needed to programmatically identify which controls required testing and on what schedule. We leveraged the `GET /controls` endpoint to fetch our control list, filtering on metadata like `testFrequency` and `lastTestDate`. This list is cached and compared daily to generate a queue of controls due for testing.
```javascript
// Example: Fetching controls due for testing
const fetchControlsDue = async () => {
const response = await sprintoClient.get('/controls', {
params: { status: 'active', fields: 'id,name,testFrequency,lastTestDate' }
});
const controls = response.data;
const today = new Date();
return controls.filter(control => {
const dueDate = addToDate(control.lastTestDate, control.testFrequency);
return dueDate <= today;
});
};
```
**Phase 2: Automated Evidence Collection**
For each control due, our system triggers the corresponding internal audit. For example, a control regarding "SSH key rotation" would invoke our internal credential management system's API. The output (e.g., a JSON log of key ages) is then formatted as evidence and submitted via `POST /controls/{controlId}/tests`. A crucial learning was the necessity of adhering precisely to Sprinto's evidence schema, including proper `fileType` and `collectedAt` timestamps.
**Phase 3: Status Synchronization & Error Handling**
After evidence submission, we poll the test status using `GET /controls/{controlId}/tests/{testId}` until it reaches a terminal state ('passed', 'failed', 'requires_input'). This status is then logged to our internal observability platform and a ticket is created in our GRC board if manual intervention is needed. We implemented idempotent retry logic with exponential backoff for all API calls to handle rate limits and transient failures.
**Key Challenges and Solutions:**
* **Idempotency:** Sprinto's API does not provide native idempotency keys for test creation. We mitigated duplicate tests by generating a deterministic UUID based on the control ID and the calendar week, storing it to prevent re-submission within the same period.
* **Evidence Formatting:** Certain controls required screenshots or signed PDFs as evidence. We automated this by using a headless browser to generate screenshots of our internal admin panels and a PDF service for report generation, uploading them as multipart/form-data.
* **Webhook Limitations:** While we initially hoped to rely on webhooks for test status updates, the available events were insufficient for our real-time needs. Thus, the polling mechanism described above was necessary.
The result has been a 90% reduction in manual effort for control testing, with tests now running on a precise schedule and evidence collected consistently. Our middleware service has become a pivotal component of our platform engineering stack, bridging our operational reality with compliance requirements. The implementation demands a rigorous approach to error handling and data mapping, but the payoff in scalability and reliability is substantial.
— Harper
— Harper
That initial approach of fetching all controls and filtering client-side worked for us in the prototype phase, but we ran into performance issues as our control library expanded into the thousands. We ended up requesting the Sprinto team add query parameters to the `GET /controls` endpoint for server-side filtering, specifically for `nextTestDueBefore` and `controlType`. This cut our discovery payload by about 70% and made the scheduling logic more deterministic.
I'm curious about your Node.js orchestrator's state management, particularly for handling test runs that might be interrupted or need to be retried. Did you implement idempotency keys when calling the evidence submission endpoints, or rely on a different idempotency pattern? We've found the need for it when our internal evidence-gathering scripts occasionally time out.