curl --request GET \
--url https://app.kardinal.ai/api/v2/plans/{planId}/state \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.kardinal.ai/api/v2/plans/{planId}/state"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.kardinal.ai/api/v2/plans/{planId}/state', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.kardinal.ai/api/v2/plans/{planId}/state",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.kardinal.ai/api/v2/plans/{planId}/state"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.kardinal.ai/api/v2/plans/{planId}/state")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.kardinal.ai/api/v2/plans/{planId}/state")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"item": {
"planVersion": 1,
"timestamp": "2026-03-21T08:00:05Z",
"state": "optimized"
},
"agencyId": "BLD1234567_production",
"planId": "4cbd0ab8-282c-4b30-b981-29e1ed8a2016"
}Retrieve the latest state of a plan
Returns the latest state of a plan, as a single summary value (waiting, processing, preOptimizing, and so on — see PlanState). For the detailed per-stage breakdown (waiting room, creation, optimization, and, if predictive traffic is enabled, traffic fetching), see PlanStatus on the plan itself.
state describes the latest version of the plan that /state knows about, not an earlier version you may still be waiting on. Just after a PUT /plans/{planId}, /state may not know the new version yet and can still describe the previous one, even as optimized. So read the version returned by your PUT /plans/{planId} (item.version in its response), and treat optimized as covering your changes only once /state reports a planVersion at least equal to that version. The check still works if the update changed nothing and the version stays the same. A restart with PUT /plans/{planId}/running keeps the same version, so this check can’t tell the new run from the previous one. See Polling the state and the solution.
Modelling pitfall. The state outlives the plan for a while: after a
DELETE, this endpoint can keep answering200. The state showsdeletedonly if the plan had finished optimizing; otherwise it moves tostopped, theninterrupted. To know whether a plan still exists, callGET /plans/{planId}instead.
curl --request GET \
--url https://app.kardinal.ai/api/v2/plans/{planId}/state \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.kardinal.ai/api/v2/plans/{planId}/state"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.kardinal.ai/api/v2/plans/{planId}/state', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.kardinal.ai/api/v2/plans/{planId}/state",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.kardinal.ai/api/v2/plans/{planId}/state"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.kardinal.ai/api/v2/plans/{planId}/state")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.kardinal.ai/api/v2/plans/{planId}/state")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"item": {
"planVersion": 1,
"timestamp": "2026-03-21T08:00:05Z",
"state": "optimized"
},
"agencyId": "BLD1234567_production",
"planId": "4cbd0ab8-282c-4b30-b981-29e1ed8a2016"
}Authorizations
JWT bearer token, sent as Authorization: Bearer <token>. Two kinds of token are accepted:
- A session token (1 hour), obtained through the interactive Console login flow. Used to sign in to the Console; not meant to be used against this API.
- A long-term API token (366 days), obtained once via the Console and meant to be stored by your integration. It is the credential recommended for backend use, since it does not depend on repeating the interactive login flow.
Path Parameters
The plan UUID. Universally Unique Identifier.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"cd4ce4e3-0208-4b10-b346-25f235214e4f"
Response
Latest plan state.
A plan's latest state, wrapped with its identifying metadata.
The state itself.
Show child attributes
Show child attributes
Identifier of the Kardinal tenant/account the plan is scoped to, derived automatically from the access token used to make the request.
Modelling pitfall. Not a physical-depot or site partition key. A single fleet based at several physical depots is still one
Plan; do not split it into severalPlanobjects just because resources have differentdeparture/arrivalpositions.
^BLD[0-9]{7}_[a-zA-Z0-9-._~:@!$,]+$"BLD1234567_production"
The plan id.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"4cbd0ab8-282c-4b30-b981-29e1ed8a2016"
Was this page helpful?

