{"openapi":"3.1.0","info":{"title":"Untimely API","version":"0.1.0","description":"Canonical API for dashboard users and API-key authenticated automation. API keys are created in the dashboard by signed-in users."},"servers":[{"url":"https://untimely.app","description":"Production"},{"url":"https://untimely-preview.bitspice.workers.dev","description":"Preview"}],"paths":{"/api/events":{"get":{"summary":"List events with schedule and run status","security":[{"ApiKey":[]}],"responses":{"200":{"description":"Events owned by the authenticated user","content":{"application/json":{"schema":{"type":"object","required":["events"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/EventStatus"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"summary":"Create an event","description":"Creates random or deterministic schedules. Use Idempotency-Key for safe retries.","security":[{"ApiKey":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":200},"description":"Use the same key with the same JSON body to retry safely."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventRequest"}}}},"responses":{"201":{"description":"Created event and generated schedule dates","headers":{"Idempotency-Replayed":{"schema":{"type":"string","enum":["true","false"]}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEventResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/api/events/{id}":{"get":{"summary":"Read schedule and run status for one event","security":[{"ApiKey":[]}],"parameters":[{"$ref":"#/components/parameters/EventId"}],"responses":{"200":{"description":"Event status","content":{"application/json":{"schema":{"type":"object","required":["event"],"properties":{"event":{"$ref":"#/components/schemas/EventStatus"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Update an event","security":[{"ApiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEventRequest"}}}},"responses":{"204":{"$ref":"#/components/responses/NoContent"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"summary":"Delete an event","security":[{"ApiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"$ref":"#/components/responses/NoContent"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/events/{id}/schedules/{scheduleId}":{"get":{"summary":"Read one schedule run and tool attempts","security":[{"ApiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"scheduleId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Schedule details with run and tool attempt history","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleDetailResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/events/{id}/test":{"post":{"summary":"Send a test delivery for an event immediately","description":"Executes the event's configured tools right now against their real destinations. Does not consume or create schedules and does not appear in run history. Webhook payloads carry a scheduleId prefixed with 'test_' so receivers can distinguish test deliveries. Rate limited per event and per user; a failed delivery still returns 200 with success=false and per-tool results.","security":[{"ApiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Test executed; inspect success and per-tool results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventTestSendResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"description":"Test send rate limit exceeded (per event or per user)"}}}},"/api/users/api-keys":{"get":{"summary":"List dashboard API key metadata","description":"Requires a browser session. Key material is never returned.","responses":{"200":{"description":"API key metadata","content":{"application/json":{"schema":{"type":"object","required":["keys"],"properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyMetadata"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}},"security":[{"CookieSession":[]}]},"post":{"summary":"Create a dashboard API key","description":"Requires a browser session. The raw API key is returned once.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","default":"API key"},"expiresInDays":{"type":["integer","null"],"minimum":1,"maximum":365,"description":"Days until the key expires. Omit or null for a non-expiring key."}}}}}},"responses":{"201":{"description":"New one-time API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}},"security":[{"CookieSession":[]}]}},"/api/users/api-keys/{id}":{"delete":{"summary":"Revoke a dashboard API key","description":"Requires a browser session.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"API key revoked"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[{"CookieSession":[]}]}}},"components":{"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","bearerFormat":"utly"},"CookieSession":{"type":"apiKey","in":"cookie","name":"better-auth.session_token","description":"Browser session cookie used by dashboard-only endpoints."}},"parameters":{"EventId":{"name":"id","in":"path","required":true,"schema":{"type":"string"}},"ScheduleId":{"name":"scheduleId","in":"path","required":true,"schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"Request body or parameters were invalid"},"Unauthorized":{"description":"Missing session or invalid API key"},"Forbidden":{"description":"Plan limit or permission boundary prevented the request"},"NotFound":{"description":"The requested resource was not found for the authenticated user"},"Conflict":{"description":"Idempotency key was already used with a different request body"},"NoContent":{"description":"The request completed successfully with no response body"}},"schemas":{"ApiKeyMetadata":{"type":"object","required":["id","name","keyPrefix","createdAt"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"keyPrefix":{"type":"string"},"expiresAt":{"type":["string","null"],"format":"date-time"},"lastUsedAt":{"type":["string","null"],"format":"date-time"},"revokedAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"ApiKeyCreateResponse":{"allOf":[{"$ref":"#/components/schemas/ApiKeyMetadata"},{"type":"object","required":["apiKey"],"properties":{"apiKey":{"type":"string","description":"Returned once. Store it securely; the server stores only its hash."}}}]},"CreateEventRequest":{"type":"object","allOf":[{"$ref":"#/components/schemas/EventWriteFields"},{"if":{"properties":{"scheduleType":{"const":"deterministic"}},"required":["scheduleType"]},"then":{"required":["fixedTime","timezone"]}},{"if":{"properties":{"actionType":{"const":"WEBHOOK"}},"required":["actionType"]},"then":{"required":["webhook"]}},{"if":{"properties":{"actionType":{"const":"SLACK"}},"required":["actionType"]},"then":{"required":["slackWebhookUrl"]}}],"properties":{"scheduleType":{"type":"string","enum":["random","deterministic"],"default":"random"},"fixedTime":{"type":"string","description":"Deterministic schedules only. HH:MM in the provided timezone."},"timezone":{"type":"string","description":"Required for deterministic schedules. IANA timezone, for example America/Toronto."},"betweenTimeStart":{"type":"string","description":"Random schedules only. HH:MM UTC."},"actionType":{"type":"string","enum":["EMAIL","WEBHOOK","SLACK"],"description":"Delivery action to run when a schedule triggers."},"webhook":{"$ref":"#/components/schemas/WebhookConfig"},"slackWebhookUrl":{"type":"string","description":"Required for SLACK actions. Must start with https://hooks.slack.com/. Requires a Pro plan."},"tools":{"prefixItems":[{"type":"object","required":["toolType","prompt"],"properties":{"toolType":{"const":"LLM"},"prompt":{"type":"string","minLength":1,"maxLength":4000},"responseFormat":{"type":"string","enum":["plaintext","markdown","html"]}}},{"type":"object","required":["toolType"],"properties":{"toolType":{"type":"string","enum":["EMAIL","WEBHOOK","SLACK"]},"webhook":{"$ref":"#/components/schemas/WebhookConfig"},"slackWebhookUrl":{"type":"string","description":"Required for SLACK delivery. Must start with https://hooks.slack.com/."}}}],"type":"array","minItems":2,"maxItems":2,"description":"Pro-only generate → deliver pipeline. On create, flat action fields are ignored when tools is present; on PATCH they are rejected. For WEBHOOK delivery, an explicit webhook.body overrides generated content."}},"unevaluatedProperties":false,"required":[]},"UpdateEventRequest":{"allOf":[{"$ref":"#/components/schemas/EventWriteFields"}],"unevaluatedProperties":false},"EventWriteFields":{"type":"object","properties":{"name":{"type":"string"},"enabled":{"type":"boolean"},"scheduleType":{"type":"string","enum":["random","deterministic"],"default":"random"},"interval":{"type":"integer","minimum":1,"maximum":365,"default":7},"frequency":{"type":"integer","minimum":1,"maximum":5,"default":1,"description":"Random schedules only."},"betweenTimeStart":{"type":"string","pattern":"^\\d{2}:\\d{2}$","default":"09:00","description":"Random schedules only. HH:MM UTC."},"betweenTimeEnd":{"type":"string","pattern":"^\\d{2}:\\d{2}$","default":"17:00","description":"Random schedules only. HH:MM UTC."},"fixedTime":{"type":"string","pattern":"^\\d{2}:\\d{2}$","description":"Deterministic schedules only. HH:MM in the provided timezone."},"timezone":{"type":"string","description":"Required for deterministic schedules. IANA timezone, for example America/Toronto."},"daysOfWeek":{"type":"array","minItems":1,"maxItems":7,"items":{"type":"integer","minimum":0,"maximum":6},"default":[0,1,2,3,4,5,6]},"actionType":{"type":"string","enum":["EMAIL","WEBHOOK","SLACK"],"default":"EMAIL"},"content":{"type":"string","description":"EMAIL action content."},"contentType":{"type":"string","enum":["static","gpt"],"default":"static","description":"OpenAI-generated content (gpt) requires a Pro plan."},"webhook":{"$ref":"#/components/schemas/WebhookConfig"},"slackWebhookUrl":{"type":"string","description":"Required for SLACK actions. Must start with https://hooks.slack.com/. Requires a Pro plan."},"tools":{"type":"array","minItems":2,"maxItems":2,"description":"Pro-only generate → deliver pipeline. On create, flat action fields are ignored when tools is present; on PATCH they are rejected. For WEBHOOK delivery, an explicit webhook.body overrides generated content.","prefixItems":[{"type":"object","required":["toolType","prompt"],"properties":{"toolType":{"const":"LLM"},"prompt":{"type":"string","minLength":1,"maxLength":4000},"responseFormat":{"type":"string","enum":["plaintext","markdown","html"]}}},{"type":"object","required":["toolType"],"properties":{"toolType":{"type":"string","enum":["EMAIL","WEBHOOK","SLACK"]},"webhook":{"$ref":"#/components/schemas/WebhookConfig"},"slackWebhookUrl":{"type":"string","description":"Required for SLACK delivery. Must start with https://hooks.slack.com/."}}}]}}},"WebhookConfig":{"type":"object","required":["method","url"],"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"]},"url":{"type":"string","format":"uri","description":"Public HTTP or HTTPS webhook URL."},"headers":{"type":"array","items":{"$ref":"#/components/schemas/WebhookHeader"},"default":[],"maxItems":25},"body":{"type":"string","description":"Optional request body. GET requests reject non-empty bodies.","maxLength":65536}}},"WebhookHeader":{"type":"object","required":["key","value"],"properties":{"key":{"type":"string","minLength":1,"maxLength":256},"value":{"type":"string","minLength":1,"maxLength":2048}}},"CreateEventResponse":{"type":"object","required":["id","dates"],"properties":{"id":{"type":"string"},"dates":{"type":"array","items":{"type":"string","format":"date-time"}}}},"EventStatus":{"type":"object","required":["id","name","schedules"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"rule":{"type":["object","null"]},"action":{"type":["object","null"]},"tools":{"type":"array","items":{"$ref":"#/components/schemas/EventTool"}},"schedules":{"type":"array","items":{"$ref":"#/components/schemas/EventSchedule"}}}},"EventTestSendResponse":{"type":"object","properties":{"test":{"type":"boolean","const":true},"success":{"type":"boolean","description":"True only if every tool delivered successfully"},"results":{"type":"array","items":{"type":"object","properties":{"position":{"type":"integer"},"toolType":{"type":"string","enum":["EMAIL","WEBHOOK","SLACK","LLM"]},"success":{"type":"boolean"},"statusCode":{"type":["integer","null"]},"message":{"type":["string","null"]}}}}}},"EventTool":{"type":"object","properties":{"id":{"type":"string"},"toolType":{"type":"string","enum":["EMAIL","WEBHOOK","SLACK"]},"enabled":{"type":"boolean"},"toolConfig":{"type":["object","null"],"additionalProperties":true}}},"EventSchedule":{"type":"object","properties":{"id":{"type":"string"},"triggersAt":{"type":"string","format":"date-time"},"triggeredAt":{"type":["string","null"],"format":"date-time"},"output":{"type":["object","null"],"additionalProperties":true}}},"ScheduleRun":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"startedAt":{"type":["string","null"],"format":"date-time"},"completedAt":{"type":["string","null"],"format":"date-time"}}},"ToolRunAttempt":{"type":"object","properties":{"id":{"type":"string"},"toolType":{"type":"string","enum":["EMAIL","WEBHOOK","SLACK"]},"status":{"type":"string"},"output":{"type":["object","null"],"additionalProperties":true,"description":"Stored outputs are redacted before persistence."}}},"ScheduleDetailResponse":{"type":"object","properties":{"schedule":{"allOf":[{"$ref":"#/components/schemas/EventSchedule"},{"type":"object","properties":{"run":{"anyOf":[{"$ref":"#/components/schemas/ScheduleRunWithAttempts"},{"type":"null"}]}}}]}}},"ScheduleRunWithAttempts":{"allOf":[{"$ref":"#/components/schemas/ScheduleRun"},{"type":"object","properties":{"attempts":{"type":"array","items":{"$ref":"#/components/schemas/ToolRunAttempt"}}}}]}}}}