> For the complete documentation index, see [llms.txt](https://api-docs.helloaviary.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://api-docs.helloaviary.ai/schedulingrules.md).

# SchedulingRules

Cron++ scheduling rules — every "campaign window" is a row in `scheduling_rules`. External callers can manage rules for use cases their API key owns (DELETE soft-deletes via `enabled = false`); internal callers have unrestricted CRUD including hard delete.

## List scheduling rules for a use case (external)

> Returns the rules attached to \`use\_case\_id\`. The use case must\
> belong to the caller's client (resolved from API key) — 403\
> otherwise. Soft-deleted rules (\`enabled = false\`) are excluded.<br>

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"SchedulingRuleListResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SchedulingRule"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"SchedulingRule":{"type":"object","required":["id","use_case_id","timezone","holiday_handling","pattern","priority","enabled","number_concurrent","created_at","updated_at"],"description":"A Cron++ scheduling rule. The `pattern` field is a JSON document\nfollowing the cronpp shape — see operator/cronpp/types.go. Soft\ndelete flips `enabled` to `false`; the engine treats disabled\nrules as if they didn't exist.\n","properties":{"id":{"type":"integer","format":"int64"},"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","description":"One of \"exclude\", \"include_only\", \"ignore\"."},"pattern":{"description":"cronpp-shape pattern — see operator/cronpp/types.go."},"priority":{"type":"integer"},"enabled":{"type":"boolean"},"number_concurrent":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Meta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","format":"uuid","description":"Unique ID for this request. Echoed in the `X-Request-ID` response\nheader. Send this back to support to debug a specific call.\n"}}},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/v1/scheduling-rules":{"get":{"summary":"List scheduling rules for a use case (external)","description":"Returns the rules attached to `use_case_id`. The use case must\nbelong to the caller's client (resolved from API key) — 403\notherwise. Soft-deleted rules (`enabled = false`) are excluded.\n","operationId":"listSchedulingRulesExternalV1","tags":["SchedulingRules"],"parameters":[{"name":"use_case_id","in":"query","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleListResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"use_case_id not owned by caller's client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## Create a scheduling rule (external)

> Creates a rule on a use case the caller owns. The cronpp parser\
> validates \`pattern\` server-side; a bad pattern returns 400 with\
> the validator's error message.<br>

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"SchedulingRuleCreateRequest":{"type":"object","required":["use_case_id","pattern"],"description":"Body for POST /v1/scheduling-rules and POST\n/internal/v1/scheduling-rules. External callers must own the\ntarget use case (resolved from API key); internal callers are\ntrusted to pass any use_case_id.\n","properties":{"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string","default":"UTC"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","default":"exclude"},"pattern":{"description":"cronpp-shape pattern. Validated server-side; bad shapes return 400."},"priority":{"type":"integer"},"number_concurrent":{"type":"integer","default":1}}},"SchedulingRuleResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/SchedulingRule"},"meta":{"$ref":"#/components/schemas/Meta"}}},"SchedulingRule":{"type":"object","required":["id","use_case_id","timezone","holiday_handling","pattern","priority","enabled","number_concurrent","created_at","updated_at"],"description":"A Cron++ scheduling rule. The `pattern` field is a JSON document\nfollowing the cronpp shape — see operator/cronpp/types.go. Soft\ndelete flips `enabled` to `false`; the engine treats disabled\nrules as if they didn't exist.\n","properties":{"id":{"type":"integer","format":"int64"},"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","description":"One of \"exclude\", \"include_only\", \"ignore\"."},"pattern":{"description":"cronpp-shape pattern — see operator/cronpp/types.go."},"priority":{"type":"integer"},"enabled":{"type":"boolean"},"number_concurrent":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Meta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","format":"uuid","description":"Unique ID for this request. Echoed in the `X-Request-ID` response\nheader. Send this back to support to debug a specific call.\n"}}},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/v1/scheduling-rules":{"post":{"summary":"Create a scheduling rule (external)","description":"Creates a rule on a use case the caller owns. The cronpp parser\nvalidates `pattern` server-side; a bad pattern returns 400 with\nthe validator's error message.\n","operationId":"postSchedulingRuleExternalV1","tags":["SchedulingRules"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleCreateRequest"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleResponse"}}}},"400":{"description":"Bad body or invalid pattern","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"use_case_id not owned by caller's client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## Get a scheduling rule by id (external)

> Returns 404 (not 403) when the rule belongs to a different\
> client — this avoids leaking the existence of out-of-tenant\
> rules.<br>

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"SchedulingRuleResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/SchedulingRule"},"meta":{"$ref":"#/components/schemas/Meta"}}},"SchedulingRule":{"type":"object","required":["id","use_case_id","timezone","holiday_handling","pattern","priority","enabled","number_concurrent","created_at","updated_at"],"description":"A Cron++ scheduling rule. The `pattern` field is a JSON document\nfollowing the cronpp shape — see operator/cronpp/types.go. Soft\ndelete flips `enabled` to `false`; the engine treats disabled\nrules as if they didn't exist.\n","properties":{"id":{"type":"integer","format":"int64"},"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","description":"One of \"exclude\", \"include_only\", \"ignore\"."},"pattern":{"description":"cronpp-shape pattern — see operator/cronpp/types.go."},"priority":{"type":"integer"},"enabled":{"type":"boolean"},"number_concurrent":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Meta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","format":"uuid","description":"Unique ID for this request. Echoed in the `X-Request-ID` response\nheader. Send this back to support to debug a specific call.\n"}}},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/v1/scheduling-rules/{id}":{"get":{"summary":"Get a scheduling rule by id (external)","description":"Returns 404 (not 403) when the rule belongs to a different\nclient — this avoids leaking the existence of out-of-tenant\nrules.\n","operationId":"getSchedulingRuleExternalV1","tags":["SchedulingRules"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleResponse"}}}},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or out-of-tenant)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## Delete a scheduling rule (external, soft delete)

> Soft delete via \`UPDATE … SET enabled = false\`. Idempotent — a\
> second DELETE on an already-disabled rule still returns 204.\
> Out-of-tenant ids return 404.<br>

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/v1/scheduling-rules/{id}":{"delete":{"summary":"Delete a scheduling rule (external, soft delete)","description":"Soft delete via `UPDATE … SET enabled = false`. Idempotent — a\nsecond DELETE on an already-disabled rule still returns 204.\nOut-of-tenant ids return 404.\n","operationId":"deleteSchedulingRuleExternalV1","tags":["SchedulingRules"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"204":{"description":"Disabled"},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or out-of-tenant)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## Update a scheduling rule (external)

> Partial update. Empty body or unknown top-level keys return\
> 400\. \`pattern\` is re-validated on every patch that touches it.\
> Out-of-tenant ids return 404.<br>

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"SchedulingRulePatchRequest":{"type":"object","description":"Body for PATCH /v1/scheduling-rules/:id. Empty body returns 400.\nUnknown top-level keys return 400 `field_not_editable`. The\nexternal variant rejects `enabled` (use DELETE for soft delete);\nthe internal variant accepts it so a soft-deleted rule can be\nreactivated.\n","properties":{"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string"},"pattern":{"description":"cronpp-shape pattern. Re-validated on every PATCH that touches this field."},"priority":{"type":"integer"},"number_concurrent":{"type":"integer"},"enabled":{"type":"boolean","description":"Internal-only. External callers should use DELETE to disable a rule."}}},"SchedulingRuleResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/SchedulingRule"},"meta":{"$ref":"#/components/schemas/Meta"}}},"SchedulingRule":{"type":"object","required":["id","use_case_id","timezone","holiday_handling","pattern","priority","enabled","number_concurrent","created_at","updated_at"],"description":"A Cron++ scheduling rule. The `pattern` field is a JSON document\nfollowing the cronpp shape — see operator/cronpp/types.go. Soft\ndelete flips `enabled` to `false`; the engine treats disabled\nrules as if they didn't exist.\n","properties":{"id":{"type":"integer","format":"int64"},"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","description":"One of \"exclude\", \"include_only\", \"ignore\"."},"pattern":{"description":"cronpp-shape pattern — see operator/cronpp/types.go."},"priority":{"type":"integer"},"enabled":{"type":"boolean"},"number_concurrent":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Meta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","format":"uuid","description":"Unique ID for this request. Echoed in the `X-Request-ID` response\nheader. Send this back to support to debug a specific call.\n"}}},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/v1/scheduling-rules/{id}":{"patch":{"summary":"Update a scheduling rule (external)","description":"Partial update. Empty body or unknown top-level keys return\n400. `pattern` is re-validated on every patch that touches it.\nOut-of-tenant ids return 404.\n","operationId":"patchSchedulingRuleExternalV1","tags":["SchedulingRules"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRulePatchRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleResponse"}}}},"400":{"description":"Bad body, unknown field, or invalid pattern","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found (or out-of-tenant)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## List scheduling rules (internal)

> \`use\_case\_id\` is required. Pass \`?include\_disabled=true\` to\
> include soft-deleted rows (those with \`enabled = false\`).<br>

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"},{"name":"Internal","description":"Internal-only endpoints. Forbidden for external client keys."}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"SchedulingRuleListResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SchedulingRule"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"SchedulingRule":{"type":"object","required":["id","use_case_id","timezone","holiday_handling","pattern","priority","enabled","number_concurrent","created_at","updated_at"],"description":"A Cron++ scheduling rule. The `pattern` field is a JSON document\nfollowing the cronpp shape — see operator/cronpp/types.go. Soft\ndelete flips `enabled` to `false`; the engine treats disabled\nrules as if they didn't exist.\n","properties":{"id":{"type":"integer","format":"int64"},"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","description":"One of \"exclude\", \"include_only\", \"ignore\"."},"pattern":{"description":"cronpp-shape pattern — see operator/cronpp/types.go."},"priority":{"type":"integer"},"enabled":{"type":"boolean"},"number_concurrent":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Meta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","format":"uuid","description":"Unique ID for this request. Echoed in the `X-Request-ID` response\nheader. Send this back to support to debug a specific call.\n"}}},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/internal/v1/scheduling-rules":{"get":{"summary":"List scheduling rules (internal)","description":"`use_case_id` is required. Pass `?include_disabled=true` to\ninclude soft-deleted rows (those with `enabled = false`).\n","operationId":"listSchedulingRulesInternalV1","tags":["SchedulingRules","Internal"],"parameters":[{"name":"use_case_id","in":"query","required":true,"schema":{"type":"integer","format":"int64"}},{"name":"include_disabled","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleListResponse"}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"External key cannot reach internal scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## Create a scheduling rule (internal)

> Same body as the external variant; skips the\
> use\_case→client\_id ownership check since internal callers are\
> trusted to pass any use\_case\_id.<br>

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"},{"name":"Internal","description":"Internal-only endpoints. Forbidden for external client keys."}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"SchedulingRuleCreateRequest":{"type":"object","required":["use_case_id","pattern"],"description":"Body for POST /v1/scheduling-rules and POST\n/internal/v1/scheduling-rules. External callers must own the\ntarget use case (resolved from API key); internal callers are\ntrusted to pass any use_case_id.\n","properties":{"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string","default":"UTC"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","default":"exclude"},"pattern":{"description":"cronpp-shape pattern. Validated server-side; bad shapes return 400."},"priority":{"type":"integer"},"number_concurrent":{"type":"integer","default":1}}},"SchedulingRuleResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/SchedulingRule"},"meta":{"$ref":"#/components/schemas/Meta"}}},"SchedulingRule":{"type":"object","required":["id","use_case_id","timezone","holiday_handling","pattern","priority","enabled","number_concurrent","created_at","updated_at"],"description":"A Cron++ scheduling rule. The `pattern` field is a JSON document\nfollowing the cronpp shape — see operator/cronpp/types.go. Soft\ndelete flips `enabled` to `false`; the engine treats disabled\nrules as if they didn't exist.\n","properties":{"id":{"type":"integer","format":"int64"},"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","description":"One of \"exclude\", \"include_only\", \"ignore\"."},"pattern":{"description":"cronpp-shape pattern — see operator/cronpp/types.go."},"priority":{"type":"integer"},"enabled":{"type":"boolean"},"number_concurrent":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Meta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","format":"uuid","description":"Unique ID for this request. Echoed in the `X-Request-ID` response\nheader. Send this back to support to debug a specific call.\n"}}},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/internal/v1/scheduling-rules":{"post":{"summary":"Create a scheduling rule (internal)","description":"Same body as the external variant; skips the\nuse_case→client_id ownership check since internal callers are\ntrusted to pass any use_case_id.\n","operationId":"postSchedulingRuleInternalV1","tags":["SchedulingRules","Internal"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleCreateRequest"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleResponse"}}}},"400":{"description":"Bad body or invalid pattern","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"External key cannot reach internal scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## GET /internal/v1/scheduling-rules/{id}

> Get a scheduling rule by id (internal)

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"},{"name":"Internal","description":"Internal-only endpoints. Forbidden for external client keys."}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"SchedulingRuleResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/SchedulingRule"},"meta":{"$ref":"#/components/schemas/Meta"}}},"SchedulingRule":{"type":"object","required":["id","use_case_id","timezone","holiday_handling","pattern","priority","enabled","number_concurrent","created_at","updated_at"],"description":"A Cron++ scheduling rule. The `pattern` field is a JSON document\nfollowing the cronpp shape — see operator/cronpp/types.go. Soft\ndelete flips `enabled` to `false`; the engine treats disabled\nrules as if they didn't exist.\n","properties":{"id":{"type":"integer","format":"int64"},"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","description":"One of \"exclude\", \"include_only\", \"ignore\"."},"pattern":{"description":"cronpp-shape pattern — see operator/cronpp/types.go."},"priority":{"type":"integer"},"enabled":{"type":"boolean"},"number_concurrent":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Meta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","format":"uuid","description":"Unique ID for this request. Echoed in the `X-Request-ID` response\nheader. Send this back to support to debug a specific call.\n"}}},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/internal/v1/scheduling-rules/{id}":{"get":{"summary":"Get a scheduling rule by id (internal)","operationId":"getSchedulingRuleInternalV1","tags":["SchedulingRules","Internal"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleResponse"}}}},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"External key cannot reach internal scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## Delete a scheduling rule (internal, HARD delete)

> HARD delete. The row is removed from the table. Internal\
> callers are the only path to truly purge a rule (compliance /\
> data correction). External callers are limited to soft delete.<br>

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"},{"name":"Internal","description":"Internal-only endpoints. Forbidden for external client keys."}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/internal/v1/scheduling-rules/{id}":{"delete":{"summary":"Delete a scheduling rule (internal, HARD delete)","description":"HARD delete. The row is removed from the table. Internal\ncallers are the only path to truly purge a rule (compliance /\ndata correction). External callers are limited to soft delete.\n","operationId":"deleteSchedulingRuleInternalV1","tags":["SchedulingRules","Internal"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"External key cannot reach internal scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```

## Update a scheduling rule (internal)

> Internal-only superset of the external PATCH — also accepts\
> \`enabled\` so a soft-deleted rule can be reactivated without a\
> DELETE/POST round-trip.<br>

```json
{"openapi":"3.0.3","info":{"title":"Aviary v1 API (merged — Osprey v1 + legacy clientApi)","version":"1.0.0"},"tags":[{"name":"SchedulingRules","description":"Cron++ scheduling rules — every \"campaign window\" is a row in\n`scheduling_rules`. External callers can manage rules for use\ncases their API key owns (DELETE soft-deletes via\n`enabled = false`); internal callers have unrestricted CRUD\nincluding hard delete.\n"},{"name":"Internal","description":"Internal-only endpoints. Forbidden for external client keys."}],"servers":[{"url":"https://osprey.helloaviary.com/api","description":"Production"},{"url":"https://osprey-dev.helloaviary.com/api","description":"Development"}],"security":[{"ApiKeyAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"API key for authentication"}},"schemas":{"SchedulingRulePatchRequest":{"type":"object","description":"Body for PATCH /v1/scheduling-rules/:id. Empty body returns 400.\nUnknown top-level keys return 400 `field_not_editable`. The\nexternal variant rejects `enabled` (use DELETE for soft delete);\nthe internal variant accepts it so a soft-deleted rule can be\nreactivated.\n","properties":{"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string"},"pattern":{"description":"cronpp-shape pattern. Re-validated on every PATCH that touches this field."},"priority":{"type":"integer"},"number_concurrent":{"type":"integer"},"enabled":{"type":"boolean","description":"Internal-only. External callers should use DELETE to disable a rule."}}},"SchedulingRuleResponse":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/SchedulingRule"},"meta":{"$ref":"#/components/schemas/Meta"}}},"SchedulingRule":{"type":"object","required":["id","use_case_id","timezone","holiday_handling","pattern","priority","enabled","number_concurrent","created_at","updated_at"],"description":"A Cron++ scheduling rule. The `pattern` field is a JSON document\nfollowing the cronpp shape — see operator/cronpp/types.go. Soft\ndelete flips `enabled` to `false`; the engine treats disabled\nrules as if they didn't exist.\n","properties":{"id":{"type":"integer","format":"int64"},"use_case_id":{"type":"integer","format":"int64"},"start_date":{"type":"string","format":"date-time","nullable":true},"end_date":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"holiday_calendar":{"type":"string","nullable":true},"holiday_handling":{"type":"string","description":"One of \"exclude\", \"include_only\", \"ignore\"."},"pattern":{"description":"cronpp-shape pattern — see operator/cronpp/types.go."},"priority":{"type":"integer"},"enabled":{"type":"boolean"},"number_concurrent":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Meta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string","format":"uuid","description":"Unique ID for this request. Echoed in the `X-Request-ID` response\nheader. Send this back to support to debug a specific call.\n"}}},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string"},"request_id":{"type":"string","format":"uuid"}}}}},"ErrorCode":{"type":"string","enum":["bad_request","unauthorized","forbidden","not_found","conflict","internal_error","field_not_editable","call_already_started"]}}},"paths":{"/internal/v1/scheduling-rules/{id}":{"patch":{"summary":"Update a scheduling rule (internal)","description":"Internal-only superset of the external PATCH — also accepts\n`enabled` so a soft-deleted rule can be reactivated without a\nDELETE/POST round-trip.\n","operationId":"patchSchedulingRuleInternalV1","tags":["SchedulingRules","Internal"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRulePatchRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulingRuleResponse"}}}},"400":{"description":"Bad body, unknown field, or invalid pattern","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid X-Api-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"External key cannot reach internal scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://api-docs.helloaviary.ai/schedulingrules.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
