Olve Heitmann
10/22/2025, 6:53 PMMarvin
10/22/2025, 6:53 PMMarvin
10/22/2025, 6:59 PMschedules list in a PATCH to a deployment, each schedule in that list must include a unique slug. The slug is how the API matches your incoming schedules to existing ones for safe updates.
Why you see 422
- On PATCH /deployments/{id}, if schedules is present, each schedule must have a slug. If slugs are missing, you’ll get:
"Please provide a slug for each schedule in your request to ensure schedules are updated correctly."
How to fix it (pick one)
1) Include slugs on update
- Give every schedule a stable slug and include it on your update payload. Example PATCH payload:
PATCH /api/deployments/<deployment_id>
Content-Type: application/json
{
"schedules": [
{
"slug": "hourly",
"schedule": {
"interval": {"seconds": 3600},
"timezone": "UTC"
},
"active": true,
"parameters": {}
},
{
"slug": "weekday-9am",
"schedule": {
"cron": "0 9 * * 1-5",
"timezone": "Europe/Oslo"
},
"active": true
}
]
}
Notes:
- Keep the same slug when you intend to update an existing schedule.
- Use a new slug to create an additional schedule.
- Remove a schedule either by not including it and using an API that supports replacement semantics (varies), or explicitly delete it first (see option 2).
2) Replace schedules by delete + create (no slugs required on create)
- If you want to blow away what’s there and replace it:
- List schedules, capture their ids/slugs:
GET <http://127.0.0.1:4200/api/deployments/<deployment_id>/schedules>
- Delete each schedule:
DELETE <http://127.0.0.1:4200/api/deployments/<deployment_id>/schedules/<schedule_id>>
- Create your new schedules:
POST <http://127.0.0.1:4200/api/deployments/<deployment_id>/schedules>
Content-Type: application/json
{
"schedules": [
{
"schedule": {"interval": {"seconds": 3600}, "timezone": "UTC"},
"active": true,
"parameters": {},
"slug": "hourly" // optional on create, but recommended
}
]
}
How to retrieve existing schedule slugs
- Use the schedules read endpoint:
GET <http://127.0.0.1:4200/api/deployments/<deployment_id>/schedules>
The response includes each schedule’s slug. Use those slugs in your subsequent PATCH.
Using the Python SDK (async) to update
- If you’re calling the REST API via the client, you can send a raw request:
from prefect.client.orchestration import get_client
async def set_schedules(deployment_id: str):
payload = {
"schedules": [
{
"slug": "hourly",
"schedule": {"interval": {"seconds": 3600}, "timezone": "UTC"},
"active": True,
}
]
}
async with get_client() as client:
await client.request(
"PATCH",
f"/deployments/{deployment_id}",
json=payload,
)
Using flow.from_source(...).deploy(...) or prefect deploy
- On initial creation, schedules can be declared without slugs, but for updates they must include slugs if you pass a schedules list again.
- Recommended: set explicit slugs in your code so subsequent deploys can match and update them:
from prefect import flow, Schedule
from datetime import timedelta
@flow
def my_flow(): ...
my_flow.from_source(
"<https://github.com/your-org/your-repo>",
entrypoint="flows.py:my_flow",
).deploy(
name="prod",
schedules=[
Schedule(interval=timedelta(hours=1), timezone="UTC", slug="hourly"),
Schedule(cron="0 9 * * 1-5", timezone="Europe/Oslo", slug="weekday-9am"),
],
)
- If you run prefect deploy again and it updates the deployment with a schedules list, these slugs ensure the update succeeds.
Important notesMarvin
10/22/2025, 6:59 PMschedules from your PATCH payload entirely.
References
- Schedules (concepts)
- PATCH /deployments/{id}
- GET /deployments/{id}/schedules
- POST /deployments/{id}/schedules
Can you share how you’re updating the deployment (SDK code, prefect deploy, or a raw REST call), and whether you want to update specific schedules or replace them all? I can tailor an exact payload or snippet for your case.Olve Heitmann
10/22/2025, 7:10 PMMarvin
10/22/2025, 7:10 PMMarvin
10/22/2025, 7:12 PMschedules without slugs. For many deployments this “worked” if there were no existing schedules to match, but when a deployment already has schedules, Prefect 3.x requires slugs on PATCH so it can match/update them safely.
Quick fix
- Instead of passing a bare CronSchedule(...), pass a Schedule with a stable `slug`:
from prefect import Schedule
schedules = [
Schedule(
cron="22 22 * * 1-5",
timezone="Europe/Stockholm",
slug="weekdays-22-22" # pick any lowercase letters/numbers/dashes
)
]
Then use this list in your deploy call (e.g., flow.from_source(...).deploy(..., schedules=schedules)).
If you prefer to keep using CronSchedule
- You’ll need to attach the slug at the deployment-schedule level (not just inside the cron). Easiest is to use Schedule as above, since it lets you include slug directly.
If this deployment already has a schedule
- If you want to update that schedule in-place, you must reuse its slug. Retrieve existing slugs, then include them in your update:
- GET schedules:
GET <http://127.0.0.1:4200/api/deployments/<deployment_id>/schedules>
Use the slug from the response in your next deploy/update.
- If you want to replace schedules entirely, delete + recreate:
- GET the schedules (as above), then:
DELETE <http://127.0.0.1:4200/api/deployments/<deployment_id>/schedules/<schedule_id>>
- Create your new one(s) with POST (slug optional on create, but I recommend adding one now to make future updates painless):
POST <http://127.0.0.1:4200/api/deployments/<deployment_id>/schedules>
{
"schedules": [
{
"schedule": {"cron": "22 22 * * 1-5", "timezone": "Europe/Stockholm"},
"active": true,
"slug": "weekdays-22-22"
}
]
}
Docs for reference:
- Schedules concept: Concepts: Schedules
- GET schedules: GET /deployments/{id}/schedules
- PATCH deployment (requires slugs when schedules included): PATCH /deployments/{id}
- Create schedules: POST /deployments/{id}/schedules
If you can share the exact deploy(...) call you’re using (how you’re constructing schedules and calling deploy), I can suggest a minimal edit tailored to your code path.Olve Heitmann
10/22/2025, 7:25 PMMarvin
10/22/2025, 7:25 PM