import requests
url = "https://platform.ankra.app/api/v1/org/decisions/{decision_id}/hold"
payload = { "note": "<string>" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({note: '<string>'})
};
fetch('https://platform.ankra.app/api/v1/org/decisions/{decision_id}/hold', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));curl --request POST \
--url https://platform.ankra.app/api/v1/org/decisions/{decision_id}/hold \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"note": "<string>"
}
'{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"area": "security",
"kind": "<string>",
"subject": {},
"summary": "<string>",
"plan": {},
"plan_hash": "<string>",
"evidence": {},
"status": "proposed",
"source": "<string>",
"created_by": "<string>",
"decided_by": "<string>",
"decided_at": "2023-11-07T05:31:56Z",
"approved_at": "2023-11-07T05:31:56Z",
"decision_note": "<string>",
"operation_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"receipt": {
"steps": [
{
"name": "<string>",
"outcome": "dispatched",
"detail": "<string>",
"evidence": {}
}
],
"claimed_at": "2023-11-07T05:31:56Z",
"executed_by": "<string>",
"dispatched_at": "2023-11-07T05:31:56Z",
"operation_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"completed_at": "2023-11-07T05:31:56Z",
"execution_status": "<string>",
"verification_hours_off": {}
},
"dedupe_key": "<string>",
"executable": true,
"expected_monthly_cents": 123,
"baseline_monthly_cents": 123,
"baseline_window": {
"from": "2023-11-07T05:31:56Z",
"to": "2023-11-07T05:31:56Z"
},
"measured_monthly_cents": 123,
"measured_at": "2023-11-07T05:31:56Z",
"measurement_status": "not_applicable",
"verify_until": "2023-11-07T05:31:56Z",
"subject_cluster_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"verification_status": "not_applicable",
"verification_days": [
{
"day": 123,
"from": "2023-11-07T05:31:56Z",
"to": "2023-11-07T05:31:56Z",
"state": "clear",
"cpu_p95_share": 123,
"memory_p95_share": 123,
"nodes": 123,
"reporting": 123,
"hottest_node": "<string>",
"reason": "<string>",
"hours_unknown": 123,
"hours_off": 123,
"tripped_hour": 123,
"signal": "cpu_saturation"
}
],
"rollback_of": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"parent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"run_after": "2023-11-07T05:31:56Z",
"approval_blocked": "<string>",
"can_decide": true,
"can_run": true
}{
"detail": "Cluster not found"
}{
"detail": "Cluster not found"
}{
"detail": "Cluster not found"
}{
"detail": "Cluster not found"
}{
"detail": "Cluster not found"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}{
"detail": "Cluster not found"
}Hold a decision
Holds an approved proposal before it ran, with the caller’s reason: a held proposal is run by nothing - neither the cost autopilot nor execute - until it is released or set aside. Allowed from approved. A cost approval older than seven days is refused with 409 (error_code approval_expired) and returned to proposed; a hold does not restart the seven days. The cost autopilot’s pre-notice links here (ankra-cozgu.2.2.1). Gated on the area’s permission: billing.manage for cost, security.manage for security. A cost proposal that changes one cluster (right_size, right_size_rollback, right_size_ladder, off_hours_schedule) may also be acted on by a caller who holds clusters.write on that cluster (at the organisation, in its project or on the cluster itself), the permission that resizing it or giving it a power schedule by hand takes; the event records the permission that authorised it as authorised_by. The token stands in for the browser route’s CSRF double-submit. Bearer-PAT twin of the browser route of the same name under /org; the console’s session cookie is not accepted here and the browser route answers a token with a login redirect.
import requests
url = "https://platform.ankra.app/api/v1/org/decisions/{decision_id}/hold"
payload = { "note": "<string>" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({note: '<string>'})
};
fetch('https://platform.ankra.app/api/v1/org/decisions/{decision_id}/hold', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));curl --request POST \
--url https://platform.ankra.app/api/v1/org/decisions/{decision_id}/hold \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"note": "<string>"
}
'{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"area": "security",
"kind": "<string>",
"subject": {},
"summary": "<string>",
"plan": {},
"plan_hash": "<string>",
"evidence": {},
"status": "proposed",
"source": "<string>",
"created_by": "<string>",
"decided_by": "<string>",
"decided_at": "2023-11-07T05:31:56Z",
"approved_at": "2023-11-07T05:31:56Z",
"decision_note": "<string>",
"operation_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"receipt": {
"steps": [
{
"name": "<string>",
"outcome": "dispatched",
"detail": "<string>",
"evidence": {}
}
],
"claimed_at": "2023-11-07T05:31:56Z",
"executed_by": "<string>",
"dispatched_at": "2023-11-07T05:31:56Z",
"operation_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"completed_at": "2023-11-07T05:31:56Z",
"execution_status": "<string>",
"verification_hours_off": {}
},
"dedupe_key": "<string>",
"executable": true,
"expected_monthly_cents": 123,
"baseline_monthly_cents": 123,
"baseline_window": {
"from": "2023-11-07T05:31:56Z",
"to": "2023-11-07T05:31:56Z"
},
"measured_monthly_cents": 123,
"measured_at": "2023-11-07T05:31:56Z",
"measurement_status": "not_applicable",
"verify_until": "2023-11-07T05:31:56Z",
"subject_cluster_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"verification_status": "not_applicable",
"verification_days": [
{
"day": 123,
"from": "2023-11-07T05:31:56Z",
"to": "2023-11-07T05:31:56Z",
"state": "clear",
"cpu_p95_share": 123,
"memory_p95_share": 123,
"nodes": 123,
"reporting": 123,
"hottest_node": "<string>",
"reason": "<string>",
"hours_unknown": 123,
"hours_off": 123,
"tripped_hour": 123,
"signal": "cpu_saturation"
}
],
"rollback_of": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"parent_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"run_after": "2023-11-07T05:31:56Z",
"approval_blocked": "<string>",
"can_decide": true,
"can_run": true
}{
"detail": "Cluster not found"
}{
"detail": "Cluster not found"
}{
"detail": "Cluster not found"
}{
"detail": "Cluster not found"
}{
"detail": "Cluster not found"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}{
"detail": "Cluster not found"
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
PAT organisation override.
Path Parameters
Body
Optional body of the set-aside, hold and release routes.
2000Response
Successful response
One proposal of the decision ledger: what it is about, what the surface computed, where it stands, who decided, and what running it did.
security or cost; decides which permission approving and running it takes.
security, cost What the proposal does, e.g. addon_upgrade, image_rebuild, right_size, off_hours_schedule, waste_cleanup. Only kinds with a platform operation are executable.
Identity of what the proposal is about (finding_id, cluster_id, addon_name, waste_finding_id ...), one document per kind.
The steps the surface computed. plan.parameters is the machine half the executable kinds are called with: addon_upgrade takes {cluster_id, addon_name, chart_version}; off_hours_schedule takes {cluster_id, timezone, schedules: [{action, stop_mode, cron_expression}]}; waste_cleanup takes {waste_finding_id}.
The identity of the plan this read serves: SHA-256, in lowercase hex, of the plan's canonical JSON (object keys sorted, no insignificant whitespace), so plans equal as JSON values hash alike. Approve echoes it as plan_hash to name the plan the person read.
^[0-9a-f]{64}$proposed, approved, held, set_aside, running, succeeded, failed, superseded Which surface computed it (default "surface").
Ankra user id of whoever posted it.
Ankra user id of the last approve or set-aside.
When the approval the proposal stands on was given. An approval writes it; a hold or a release keeps it while decided_at takes their own time, so a cost approval's seven-day expiry counts from here, or on a ladder between its waves from its previous wave finishing. Null on a proposal nobody approved, after an approval expired, and on a proposal approved before this field existed, whose decided_at is its approval time if it was never held or released.
The platform execution a run dispatched, to watch under /api/v1/org/executions.
The record of running a proposal: who ran it and when, the platform operation it became, the steps taken and how it ended.
Show child attributes
Show child attributes
Whether the platform has an operation for the kind, so a surface offers "run" only where running can work.
What the surface expected a cost proposal to save per month, in USD cents, recorded when it succeeded: plan.parameters.expected_monthly_cents, else evidence.expected_monthly_cents, each with an optional sibling expected_currency (ISO code, default usd) converted to USD. Zero, whatever it declared, for an off_hours_schedule whose every stop is a pause on a provider that bills a powered-off server at its full price (Hetzner, DigitalOcean, UpCloud): it saves no compute cost. Null when the proposal declared none, has not run, or is not a cost proposal; never zero for an unknown.
The subject cluster's run rate before the change: the mean monthly_cost_estimate_cents of its cost snapshots over baseline_window, in USD cents. Null until a measurement opens, or when no snapshot covered the window.
The 24 hours before the change was dispatched, [from, to); to is when the change ran.
Show child attributes
Show child attributes
What the change measurably saved per month, in USD cents: baseline_monthly_cents minus the cluster's mean run rate over the week after the change. A right-size is measured on the node groups it changed instead: the per-node cost it took off, over the nodes the groups ran on both sides, never more than their drop. Negative when the run rate rose. Null unless measurement_status is measured.
When the measurement loop wrote its verdict (measured, unmeasured or reverted).
Where the measured outcome stands: not_applicable (not a cost proposal, not run, or no cluster to measure), pending (inside the seven-day verification window), measured, unmeasured_coverage_moved (the cluster was not priced the same way on both sides), unmeasured_no_snapshots (nothing to read on one side, too few of a week's hours metered, or the cluster is no longer priced), unmeasured_confounded (another measured cost change ran on the same cluster inside the span, so the drop is not this change's alone), unmeasured_within_noise (the change was no larger than the run rate's own day-to-day swing, so it cannot be told apart from it, or the run rate grew from nothing on a day; the swing compares each day of the week after with the same weekday of the week before, as an amount and as a share of that day, and holds the change to the tighter of the two, so a weekly pattern such as a power schedule cancels, and so does a change that takes the same amount or the same share off every day), reverted (the change was undone inside the window; an off-hours change is settled the hour its schedules are gone and the cluster is back to size). The activity feed's measured / unmeasured / reverted event carries the reason.
not_applicable, pending, measured, unmeasured_coverage_moved, unmeasured_no_snapshots, reverted, unmeasured_confounded, unmeasured_within_noise When the verification window closes: seven days after the change ran. The measurement loop settles the row once it has passed.
The cluster whose cost snapshots measure the change: plan.parameters.cluster_id, else subject.cluster_id.
Where a right-size's seven-day verification stands (ankra-cozgu.1.2): verifying until every day is judged, passed when all seven were clear, failed on the first day any node of the group ran p95 CPU over 60% or p95 memory over 70% of capacity (a right_size_rollback proposal is filed), unverified_no_metrics when no day tripped but at least one could not be read. A day the cluster's power schedule had the group stopped throughout is off and left out: a window whose other days were clear passes, and a window that was off all seven days is unverified_no_metrics. not_applicable for every other proposal.
not_applicable, verifying, passed, failed, unverified_no_metrics The judged verification days in order, empty until the first 24h after the dispatch has been read.
Show child attributes
Show child attributes
On a right_size_rollback: the right-size it undoes, which reads measurement_status reverted once this rollback succeeds.
On a wave of a right-size ladder (a right_size proposal with source ladder): the right_size_ladder proposal it belongs to. Null for every other proposal.
The earliest time the cost autopilot may run a proposal it approved itself - the end of its pre-notice window, in which a person may hold it; null for a proposal a person approved, which a person runs.
Why approving this proposal would be refused because the platform could not carry it out: its kind has no operation, or its plan is one the operation refuses. It is the same sentence approve answers 422 with, so a surface shows it in place of an Approve action. Null when neither stands in the way; whether the status allows an approval is the status's own answer. A response without this field comes from a platform that did not say, and a client treats it as "may be approvable".
Whether the caller holds the authority to approve, set aside, hold and release this proposal: the organisation's permission for the area, or for a cost change of the one cluster the proposal acts on, the permission that governs that change held on that cluster. A cluster grant does not count on a proposal held or set aside on another authority. Read by the checks the decisions make, so a surface offers what they take; each decision checks again. It does not say whether the status allows a decision or whether the plan can run (executable, approval_blocked). Null: Ankra could not tell; do not offer the action, the platform decides when it is attempted. A response without the field comes from a platform that did not say, and a client treats it as null.
Whether the caller may also run this proposal: can_decide, and the further permission running it takes, if any. A right-size or a right-size ladder needs clusters.write where the authority that let the decision through was held; the cleanup of a released persistent volume needs clusters.write on the volume's cluster, and one whose finding names no cluster can be run by no one (false for every caller); no other kind needs a further permission. Read by the checks execute makes; execute checks again. Null: Ankra could not tell; do not offer the action, the platform decides when it is attempted.