Scheduler
The Scheduler lets you program deferred HTTP callbacks: "in N seconds (or at a given date/time) call this URL with this payload". It supports one-shot and recurring jobs (by interval or by calendar), with automatic retries, dead-letter and a dashboard to inspect the queue and history.
How it works
Job state lives in a dedicated container: the store is the queue. An internal poller (every ~1 minute) picks up due jobs, runs their HTTP callback and updates the outcome. As a result:
- Firing precision equals the poll interval (~1 minute): it is not meant for second-level callbacks.
- The model is at-least-once: receivers must tolerate duplicate calls (use the idempotency key, see below).
- All instants are stored in UTC; calendar scheduling uses Italian wall-clock time (see Recurrence).
Endpoints
| Method | Endpoint | Description | Scope |
|---|---|---|---|
POST |
/scheduled-jobs |
Create a deferred job | write |
GET |
/scheduled-jobs |
Paginated list (filters: status, from, to) |
read |
GET |
/scheduled-jobs/{id} |
Job detail | read |
DELETE |
/scheduled-jobs/{id} |
Cancel a job (only while Pending) |
write |
Creating a job
curl -X POST "https://api.contit.cloud/scheduled-jobs" \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Order reminder",
"delaySeconds": 3600,
"callbackUrl": "https://example.com/hooks/reminder",
"method": "POST",
"headers": { "X-Source": "contit" },
"payload": { "orderId": "abc123" },
"maxRetries": 5
}'
Response: the created job, with id and status: "Pending".
Request fields
| Field | Type | Description |
|---|---|---|
name |
string | Friendly name (optional). |
delaySeconds |
number | Seconds to wait from now. Alternative to fireAtUtc. |
fireAtUtc |
datetime | Absolute fire time (UTC). Takes precedence over delaySeconds. |
callbackUrl |
string | http(s) URL to call. Required. |
method |
string | HTTP method (default POST). |
headers |
object | Custom HTTP headers sent with the callback. |
payload |
any (JSON) | Body sent to the callback (ignored for GET/HEAD). |
maxRetries |
number | Max attempts before dead-letter (default 5). |
idempotencyKey |
string | Dedup key propagated to the receiver (generated if absent). |
correlationId |
string | Correlation id for end-to-end tracing. |
recurrenceSeconds |
number | Interval recurrence (see below). |
scheduleTimes |
string[] | Calendar recurrence: "HH:mm" times (see below). |
scheduleDaysOfWeek |
number[] | Allowed days (0=Sunday .. 6=Saturday). |
endAtUtc |
datetime | Recurrence stop: no occurrence past this date. |
maxOccurrences |
number | Recurrence stop: maximum number of occurrences. |
Recurrence
A job can repeat automatically: when an occurrence completes (on success or dead-letter — the series continues even after a failure) the next one is created. All occurrences share a seriesId and have an increasing occurrenceNumber.
By interval
recurrenceSeconds (minimum 60): the next occurrence fires at a fixed cadence relative to the planned one.
{
"name": "Hourly ping",
"callbackUrl": "https://example.com/ping",
"recurrenceSeconds": 3600
}
By calendar (daily / weekly)
scheduleTimes (one or more "HH:mm" times) and optionally scheduleDaysOfWeek. Times are Italian wall-clock time (Europe/Rome) with automatic DST handling; the first run is the next matching occurrence.
// Every day at 10:00 and 15:00
{
"name": "Daily sync",
"callbackUrl": "https://example.com/sync",
"scheduleTimes": ["10:00", "15:00"]
}
// Monday and Tuesday at 11:00, max 10 occurrences
{
"callbackUrl": "https://example.com/report",
"scheduleTimes": ["11:00"],
"scheduleDaysOfWeek": [1, 2],
"maxOccurrences": 10
}
The cadence is fixed: even if a run is delayed by retries, the next occurrence is anchored to the planned time (no drift). If the poller was down, it resumes from the next future occurrence without replaying missed ones.
Retries and dead-letter
On failure (HTTP error or exception) the attempt is counted and the job rescheduled with exponential backoff (2^(attempts-1) minutes, capped at 1 hour). Once maxRetries is exceeded the job moves to DeadLetter and is logged as an error (useful for alerting).
Statuses
| Status | Meaning |
|---|---|
Pending |
Waiting to fire. |
Firing |
Claimed by the poller, delivery in progress. |
Done |
Callback delivered successfully. |
Cancelled |
Cancelled via DELETE (was Pending). |
DeadLetter |
All attempts failed. |
Jobs in a terminal state (Done, Cancelled, DeadLetter) are cleaned up automatically after 30 days.
Callback security
Each callback includes extra headers:
Contit-Idempotency-Key— the occurrence's idempotency key.Contit-Correlation-Id— if set on the job.Contit-Signature— HMAC-SHA256 signature of the body in the formt={timestamp},s={signature}, present only if a signing secret is configured. Verify it on the receiver by recomputingHMAC-SHA256(secret, "{timestamp}.{body}").
SDK
var client = new ContitClient(apiKey);
// One-shot in 1 hour
var job = await client.ScheduledJob.Create(new CreateScheduledJobRequest
{
Name = "Order reminder",
DelaySeconds = 3600,
CallbackUrl = "https://example.com/hooks/reminder",
Payload = JToken.Parse("""{ "orderId": "abc123" }""")
});
// Recurring: every day at 10:00 and 15:00
await client.ScheduledJob.Create(new CreateScheduledJobRequest
{
Name = "Daily sync",
CallbackUrl = "https://example.com/sync",
ScheduleTimes = new List<string> { "10:00", "15:00" }
});
// Inspect and cancel
var pending = await client.ScheduledJob.Get(status: ScheduledJobStatus.Pending);
await client.ScheduledJob.Cancel(job.Id);
Dashboard
In the panel, under Settings → Developer → Scheduler, you'll find the list of jobs (filterable by status), the detail view with payload/headers/outcome, and a New job button to create them manually — including the visual builder for daily/weekly scheduling at one or more times.