Introduction
In Week 10, we introduced GraphQL mutations and implemented rkTriggerSnapshot(), our first helper that actively performs an operation in Rubrik Security Cloud instead of just retrieving data.
In Week 9, we built the Snapshot Explorer, a command-line application that retrieves snapshot history and exports it as CSV and HTML reports.
In Week 8, we built rkGetSnapshots() to retrieve the complete recovery-point history of a protected workload with cursor-based pagination.
Until then, most of our interactions with RSC were read operations. We retrieved workloads. We examined protection status. We explored snapshot history. We generated operational reports.
Then we introduced mutations and started asking Rubrik Security Cloud to do something.
That introduced another important concept: the request ID. When Rubrik Security Cloud accepts an asynchronous operation, receiving a successful API response does not necessarily mean that the requested operation has completed. It means that the request has been accepted. The actual operation may still be queued, running, or eventually fail.
So the request ID returned in Week 10 was not simply another value to display on screen. It is our link to what happens next.
This week, we are going to put that request ID to work. We will extend the framework so that it can:
- Retrieve the status of an asynchronous operation
- Poll that operation while it is running
- Detect successful completion
- Detect failures
- Prevent endless polling with a timeout
- Provide useful real-time feedback to the operator
To do this, we will introduce two new reusable helpers: rkGetRequestStatus() and rkWaitForRequest().
Together, they close an important gap in the framework. We will no longer simply ask Rubrik Security Cloud to perform an operation. We will be able to follow that operation through to completion.
From API Response to Operation Completion
Consider the workflow introduced in Week 10. We submit a mutation to Rubrik Security Cloud:

At this point, our PHP script knows that RSC accepted the request. But it does not yet know whether the underlying operation succeeded.
The real workflow looks more like this:

This asynchronous model is common in cloud APIs. Operations such as snapshot creation, restore operations, exports, and other infrastructure tasks may require seconds, minutes, or considerably longer to complete.
Keeping the original HTTP connection open while the entire operation executes would be inefficient. Instead, the API accepts the operation and provides an identifier that allows us to track it independently.
That identifier is what makes monitoring possible.
Why We Need Two Functions
We could build a single function that receives a request ID and waits until the operation finishes. But that would make the framework unnecessarily restrictive.
Sometimes we only want to know the current state of an operation. Other times we want our script to wait until the operation reaches a final state. These are two different responsibilities. So we will keep them separate.
The first function, rkGetRequestStatus(), performs one status lookup.
The second, rkWaitForRequest(), uses the first function repeatedly until the operation completes or a timeout is reached.
This gives us a simple relationship:

This separation will become particularly useful when we start building web interfaces, where we do not want a PHP request sitting inside a blocking polling loop. But more about that later.
Building rkGetRequestStatus()
Our first helper has a simple responsibility. Given a request ID and the Rubrik cluster UUID associated with the VM, ask Rubrik Security Cloud for the current state of that request.
For VMware operations, vSphereVMAsyncRequestStatus requires both values. The request ID comes from the mutation, while the clusterUuid must be retained from the VM discovery information.
Conceptually, the function looks like this:
// ----------------------------------------
// Function rkGetRequestStatus : Retrieve the current status of an asynchronous RSC request
// ----------------------------------------
function rkGetRequestStatus( $requestId, $clusterUuid )
{
/*
* Build the GraphQL request used to retrieve
* the current state of the asynchronous operation.
*/
$variables = [
"id" => $requestId,
"clusterUuid" => $clusterUuid
];
/*
* Execute the request through the common
* Rubrik Security Cloud framework.
*/
$response = rkGraphQLRequest(
$query,
$variables
);
/*
* Normalize the RSC response before returning it
* to the calling application.
*/
return [
"requestId" => $requestId,
"status" => $status,
"message" => $message
];
} The exact GraphQL query and returned fields depend on the asynchronous operation being monitored and the RSC API schema exposed to the tenant.
The important part from the framework perspective is not to expose all of that complexity to the calling script. Instead, rkGetRequestStatus() should return a predictable structure. For example:
[
"requestId" => "YOUR_REQUEST_ID",
"status" => "RUNNING",
"message" => "Operation is currently running"
]The caller does not need to understand the underlying GraphQL response. It only needs to understand our framework. This is exactly the abstraction model we have been building throughout this series.
The GraphQL query and reusable PHP implementation used in this section are available in the GitHub repository:
The GraphQL query retrieves the asynchronous VMware request status, while the PHP helper normalizes the response into a structure that can be reused by the rest of our framework.
Normalizing Status Information
One challenge when working with APIs is that different operations do not always expose status information in exactly the same way. One API response might use RUNNING, while another may expose additional information about individual tasks or stages.
Our framework should avoid forcing every calling script to understand those differences. Instead, we can normalize the result into a small number of states that are meaningful to our application. For example:
- QUEUED
- RUNNING
- SUCCEEDED
- FAILED
- CANCELED
- UNKNOWN
This makes the rest of the framework much easier to build. The application can simply ask:
if ($status["status"] === "SUCCEEDED")
{
// Operation completed successfully.
}rather than parsing the complete GraphQL response every time.
Practitioner Tip "Keep the raw API response available while developing your helper. Normalization makes application code easier to maintain, but the original response is extremely useful when troubleshooting unexpected API behaviour." |
Testing a Single Status Request
Before implementing polling, we should test the simplest possible scenario. Take the request ID returned by the operation introduced in Week 10 and pass it directly to our new helper.
For example:
$requestId = "YOUR_REQUEST_ID";
$status = rkGetRequestStatus(
$requestId,
$clusterUuid
);
echo "Request ID : " . $status["requestId"] . PHP_EOL;
echo "Status : " . $status["status"] . PHP_EOL;
echo "Message : " . $status["message"] . PHP_EOL;A typical terminal output could look like:
Request ID : 12345678-1234-1234-1234-123456789012
Status : RUNNING
Message : Operation is currently runningRunning the same script a little later might return:
Request ID : 12345678-1234-1234-1234-123456789012
Status : SUCCEEDED
Message : Operation completed successfullyWe now have something we did not have at the end of Week 10. We can determine what happened after the mutation was accepted.
From Status Check to Status Monitoring
Checking a request manually is useful for troubleshooting. It is not particularly useful for automation. If our PHP script triggers an operation, we normally want it to follow that operation automatically.
The obvious solution is polling. The logic is straightforward:

This becomes our second helper: rkWaitForRequest().
Building rkWaitForRequest()
The function receives four important pieces of information:
- The request ID
- The Rubrik cluster UUID
- The polling interval
- The maximum amount of time we are prepared to wait
A simplified implementation looks like this:
// ----------------------------------------
// Function rkWaitForRequest : Monitor an asynchronous RSC request until completion or timeout
// ----------------------------------------
function rkWaitForRequest(
$requestId,
$clusterUuid,
$pollInterval = 5,
$timeout = 300
)
{
$startTime = time();
while (true)
{
$status = rkGetRequestStatus(
$requestId,
$clusterUuid
);
echo sprintf(
"[%s] Request %s : %s%s",
date("H:i:s"),
$requestId,
$status["status"],
PHP_EOL
);
if ($status["status"] === "SUCCEEDED")
{
return $status;
}
if (
$status["status"] === "FAILED" ||
$status["status"] === "CANCELED"
)
{
return $status;
}
if ((time() - $startTime) >= $timeout)
{
return [
"requestId" => $requestId,
"status" => "TIMEOUT",
"message" => "Maximum monitoring time exceeded"
];
}
sleep($pollInterval);
}
}There is nothing particularly complicated here. And that is a good thing. The function performs one job: wait until the asynchronous operation reaches a terminal state.
Why the Timeout Matters
It may be tempting to write:
while ($status !== "SUCCEEDED")
{
// Keep polling.
}Don't.
Several things could happen. The operation could fail. The request could enter an unexpected state. Connectivity with RSC could be interrupted. The API could temporarily become unavailable. Or the operation could simply take much longer than expected.
Without a timeout, our script could continue running indefinitely. For automation, that is a dangerous assumption.
The timeout gives the calling application a clear boundary. For example:
$result = rkWaitForRequest(
$requestId,
$clusterUuid,
5,
300
);This means:
Poll every : 5 seconds
Maximum : 300 secondsAfter five minutes, the framework stops waiting and returns control to the application.
Importantly, TIMEOUT does not necessarily mean that the RSC operation itself failed. It means that our monitoring window expired before we observed a terminal state. That distinction matters.
Practitioner Tip "Never treat a monitoring timeout as proof that the underlying operation failed. A timeout tells you that your application stopped waiting. The operation may still be running in Rubrik Security Cloud." |
Putting Week 10 and Week 11 Together
Now we can connect the two pieces.
Week 10 gave us something conceptually similar to:
$result = rkTriggerOperation(...);
$requestId = $result["requestId"];Week 11 allows us to continue:
$result = rkTriggerOperation(...);
$requestId = $result["requestId"];
echo "Operation accepted." . PHP_EOL;
echo "Request ID: " . $requestId . PHP_EOL;
echo PHP_EOL;
$status = rkWaitForRequest(
$requestId,
$clusterUuid,
5,
300
);
echo PHP_EOL;
echo "Final status: " . $status["status"] . PHP_EOL;The complete workflow is now:

That is a significant improvement over simply printing Mutation accepted. We can now follow an asynchronous operation through its lifecycle.
Handling Failure Properly
Success is only half of the workflow. Automation also needs to provide useful information when something goes wrong.
Instead of simply returning FAILED, our normalized response should preserve any useful error information provided by RSC. For example:
[
"requestId" => $requestId,
"status" => "FAILED",
"message" => "Operation failed",
"error" => $errorMessage
]The calling application can then decide what to do:
if ($status["status"] === "FAILED")
{
echo "Operation failed." . PHP_EOL;
if (!empty($status["error"]))
{
echo "Reason: " . $status["error"] . PHP_EOL;
}
exit(1);
}This is especially important when the framework is used from scheduled jobs, monitoring systems, CI/CD workflows, or other automation. A script that silently stops is difficult to troubleshoot. A script that returns a clear status, meaningful error information, and a non-zero exit code can be integrated into much larger operational workflows.
Polling Responsibly
Polling every few seconds may seem harmless. But imagine running dozens or hundreds of simultaneous operations. An aggressive polling interval can quickly generate unnecessary API traffic.
For most long-running infrastructure operations, checking every few seconds is more than sufficient. For example:
$pollInterval = 5;or even:
$pollInterval = 10;depending on the operation.
There is little operational value in asking RSC for the same status several times per second when the underlying operation may take minutes. Although it is outside the scope of this project, Rubrik also supports webhooks, enabling RSC to trigger an alert when the event is completed.
Practitioner Tip "Poll according to the expected duration of the operation, not according to how quickly your code can send requests. Faster polling rarely makes the operation finish faster; it usually just creates more API traffic." |
Blocking Versus Non-Blocking Monitoring
Our rkWaitForRequest() helper is intentionally blocking. The PHP process waits until one of three things happens: SUCCESS, FAILURE, or TIMEOUT.
For command-line automation, that is often exactly what we want. A script can trigger an operation and wait for the result before continuing.
But this model has limitations. Imagine a web page where an operator clicks Restore VM. We do not want the browser to sit waiting while a PHP request remains open for several minutes.
Instead, a web application should work differently:

Notice something interesting? We already have the most important building block for that architecture: rkGetRequestStatus().
The web interface does not need rkWaitForRequest(). It can call rkGetRequestStatus() asynchronously and perform the polling from the browser.
This is precisely why we separated the two responsibilities earlier.
What We Added to the Framework
Our framework has gained two new capabilities this week.
rkGetRequestStatus()
Performs a single request-status lookup. Useful for:
- CLI tools
- Web interfaces
- Dashboards
- External monitoring
- Troubleshooting
rkWaitForRequest()
Monitors an operation until it reaches a terminal state. Useful for:
- Command-line automation
- Scheduled scripts
- Workflow orchestration
- Operations that depend on previous operations completing
The relationship remains simple:

One helper retrieves information. The other adds workflow logic around it.
The Framework Is Starting to Behave Differently
It is worth looking at how far the framework has evolved.
Initially, we were essentially building a PHP client capable of communicating with Rubrik Security Cloud. Then we started adding reusable functions around the API. Those functions allowed us to retrieve inventory and protection information. Snapshot data gave us operational visibility. Reporting allowed us to transform that information into something useful. Mutations allowed us to take action. And now asynchronous monitoring allows us to determine the outcome of those actions.
The framework is no longer simply exchanging GraphQL requests and responses. It is beginning to understand workflows.
That distinction becomes important for what comes next.
Commit of the Week
graphql-rsc/
├── graphql/
│ └── query_vsphereVMAsyncRequestStatus.graphql
│
├── core/
│ └── rkRequestStatus.php
│
└── examples/
└── MonitorRequest.phpWhat This Week Really Delivers
At the end of Week 10, our framework could trigger an asynchronous operation and retrieve its request ID. At the end of Week 11, that request ID becomes operationally useful.
The framework can now:
- Retrieve the current state of an asynchronous request
- Normalize operation status
- Monitor an operation until completion
- Detect successful operations
- Detect failed or canceled operations
- Protect automation against endless polling
- Provide live status information to calling applications
More importantly, we now have both sides of an asynchronous workflow:

We can initiate an action. And we can determine what happened afterward. That completes one of the most important foundations required for automation.
What's Next
Over the past eleven weeks, we have built this framework one capability at a time. We started with authentication and our first GraphQL requests. Then we progressively added workload discovery, SLA information, protection status, snapshot history, operational reporting, mutations, and finally asynchronous operation monitoring. Each week deliberately solved one part of the problem.
Now there is only one thing left to do: put everything together.
Week 12 will be the final article in this series. And rather than introducing another isolated API capability, we are going to use what we have already built to create something much closer to a real application.
We will build an interactive PHP restore workflow that allows an operator to:

Behind this apparently simple workflow will be many of the components developed throughout the series. Workload discovery will populate the VM selection. Snapshot retrieval will expose available recovery points. The mutation framework will initiate the restore. The request ID will connect the operation to the monitoring functions introduced this week. And the web interface will bring those capabilities together into a single operational workflow.
This is an important final step. During the series, we deliberately avoided trying to build a complete application too early. Instead, we created small, reusable building blocks and validated each one independently.
In Week 12, we will see why. The result will no longer feel like a collection of GraphQL examples. It will be a working demonstration of what can be built on top of Rubrik Security Cloud when those individual capabilities are combined into a reusable framework.
And with that, our twelve-week journey from a first authenticated API call to a functional PHP application will come to an end.
Next and Final: Week 12: Bringing It All Together: Building an Interactive Restore Portal with PHP and Rubrik Security Cloud
Contributed by

Frederic Lhoest
Senior Technology Architect, PCCW Global

Mike Preston
Staff Technical Marketing Manager, Rubrik








