Carbon Aware
Let a Mangrove run wait for the cleanest hour in a window you grant, and read what was decided.
A run that does not need its answer straight away can wait for cleaner
electricity. Send greenShiftWindowHours (a whole number from 1 to 24) with
the JSON body, and the run may start up to that many hours late. When it is
created, Mangrove looks up the carbon intensity forecast for the grid its
models run on and holds the run until the cleanest start in that window.
Send a window
curl -X POST https://api.greenpt.ai/v1/agents/runs \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"question": "Summarise the EU grid carbon intensity news from this week",
"greenShiftWindowHours": 8
}'const response = await fetch('https://api.greenpt.ai/v1/agents/runs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer YOUR_API_KEY',
},
body: JSON.stringify({
question: 'Summarise the EU grid carbon intensity news from this week',
greenShiftWindowHours: 8,
}),
});
const { agentId, greenShift } = await response.json();
if (greenShift.outcome === 'held') {
console.log(`${agentId} starts at ${greenShift.plannedStart}, ${greenShift.savingPct}% cleaner`);
} else {
console.log(`${agentId} started now (${greenShift.outcome})`);
}import requests
response = requests.post(
"https://api.greenpt.ai/v1/agents/runs",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"question": "Summarise the EU grid carbon intensity news from this week",
"greenShiftWindowHours": 8,
},
)
run = response.json()
shift = run["greenShift"]
if shift["outcome"] == "held":
print(f"{run['agentId']} starts at {shift['plannedStart']}, {shift['savingPct']}% cleaner")
else:
print(f"{run['agentId']} started now ({shift['outcome']})")The response for a run that was held:
{
"agentId": "7d41c2e9-0b3a-4f6e-a1d8-2c9e5b7f3a10",
"status": "queued",
"greenShift": {
"version": 1,
"outcome": "held",
"windowHours": 8,
"plannedStart": "2026-07-27T14:00:00.000Z",
"gco2Kwh": 41.2,
"ifRunNowGco2Kwh": 58.7,
"savingPct": 29.8,
"kind": "forecast"
}
}Outcomes
outcome says what was decided:
outcome | Meaning |
|---|---|
held | Waiting saves enough. The run starts at plannedStart. |
run_now | Starting now is cleanest, or waiting would save too little to be worth the delay. The run starts at once. |
unavailable | No forecast could be read. The run starts at once; a carbon check never delays or fails a run. |
Fields
| Field | Type | Description |
|---|---|---|
version | number | Shape of this object, 1. |
windowHours | number | The window you granted. |
plannedStart | string (ISO 8601) | When the run starts. Equal to the creation time unless outcome is held. |
gco2Kwh | number | Carbon intensity over the planned run, in gCO₂e/kWh. Absent when outcome is unavailable. |
ifRunNowGco2Kwh | number | Carbon intensity had the run started at once, in gCO₂e/kWh. Absent when the current hour has no figure. |
savingPct | number | How much cleaner the planned start is, in percent. Present when outcome is held. |
kind | string | How certain the least certain hour in the window is: measured, day_ahead or forecast. |
Good to know
- A held run reports
queueduntil it starts. Nothing changes beforeplannedStart, so check back after that time rather than polling it. - The same
greenShiftobject is on Get a run and on the run list, so you can show when a run will start. expiresAtcounts fromplannedStart, so a held run stays readable for 24 hours after it starts.- The window is a JSON field only; a
multipart/form-dataupload always starts at once.