Quickstart
Get your first cron job running in under five minutes.
1. Install the SDK
go get github.com/tickstem/cron
2. Get an API key
Sign in at app.tickstem.dev, go to API Keys, and create a key. Store it as an environment variable:
export TICKSTEM_API_KEY=tsk_live_...
3. Register your first job
package main
import (
"context"
"log"
"os"
"github.com/tickstem/cron"
)
func main() {
client := cron.New(os.Getenv("TICKSTEM_API_KEY"))
job, err := client.Register(context.Background(), cron.RegisterParams{
Name: "daily-cleanup",
Schedule: "0 2 * * *", // every day at 02:00 UTC
Endpoint: "https://yourapp.com/jobs/cleanup",
})
if err != nil {
log.Fatal(err)
}
log.Printf("registered: %s next run: %s", job.ID, job.NextRunAt)
}
Tickstem will call POST https://yourapp.com/jobs/cleanup every day at 02:00 UTC. Your endpoint just needs to return a 2xx status — no SDK required on the receiving side.
Core concepts
Jobs
A Job is a named schedule that calls an HTTP endpoint. Jobs are identified by a stable id (e.g. job_abc123). You can register the same job on every deploy — if a job with the same name already exists for your account, Register upserts it.
Executions
Each time a job fires, an Execution is created. It records the outcome (success, failed, timeout), HTTP status code, duration, and any error message. History is retained for 7 days on the Free plan, 30 days on Starter, and 90 days on Pro and Business.
Schedules
Schedules use standard 5-field cron syntax in UTC. See the cron reference for examples.
Installation
go get github.com/tickstem/cron
Requires Go 1.21 or later. The SDK has no third-party dependencies.
Client
// Minimal — points at https://api.tickstem.dev/v1
client := cron.New(os.Getenv("TICKSTEM_API_KEY"))
// With options
client := cron.New(apiKey,
cron.WithBaseURL("http://localhost:8090/v1"), // local dev
)
The client is safe for concurrent use and should be created once and reused.
Register a job
job, err := client.Register(ctx, cron.RegisterParams{
Name: "send-digest",
Description: "Weekly email digest to all users",
Schedule: "0 9 * * 1", // every Monday at 09:00 UTC
Endpoint: "https://yourapp.com/jobs/digest",
Method: "POST", // default
Headers: map[string]string{
"X-Tickstem-Secret": os.Getenv("CRON_SECRET"), // see Securing your endpoint
},
TimeoutSecs: 60, // default: 30
})
Parameters
| Field | Type | Description |
|---|---|---|
| Name* | string | Human-readable label. Used as the upsert key — registering the same name updates the existing job. |
| Schedule* | string | 5-field cron expression in UTC. |
| Endpoint* | string | HTTPS URL to call on each execution. |
| Description | string | Optional note shown in the dashboard. |
| Method | string | HTTP method. Defaults to POST. |
| Headers | map[string]string | Extra headers sent with each request. Use for shared secrets. |
| TimeoutSecs | int | Seconds before the execution is marked timeout. Defaults to 30. |
List & Get
// list all jobs
jobs, err := client.List(ctx)
for _, j := range jobs {
fmt.Printf("%s %s next: %s\n", j.ID, j.Schedule, j.NextRunAt)
}
// get a single job
job, err := client.Get(ctx, "job_abc123")
Pause & Resume
job, err := client.Pause(ctx, jobID)
job, err := client.Resume(ctx, jobID)
A paused job is not executed but retains its schedule. Resume it to re-enable execution from the next scheduled time.
Delete
err := client.Delete(ctx, jobID)
Permanently removes the job and all its execution history.
Execution history
executions, err := client.Executions(ctx, jobID)
for _, e := range executions {
fmt.Printf("%s status=%s duration=%dms\n",
e.ScheduledAt.Format(time.RFC3339),
e.Status,
*e.DurationMs,
)
}
Returns executions in reverse chronological order. Each Execution includes:
Status—success,failed,timeout,running, orpendingStatusCode— HTTP response code from your endpointDurationMs— wall-clock time of the HTTP callError— error message when status isfailedortimeoutScheduledAt,StartedAt,FinishedAt— timing breakdown
Error handling
job, err := client.Get(ctx, someID)
if err != nil {
if cron.IsNotFound(err) {
// job doesn't exist
}
if cron.IsUnauthorized(err) {
// invalid or revoked API key
}
if cron.IsQuotaExceeded(err) {
// monthly execution limit reached
// upgrade at app.tickstem.dev/dashboard/billing
}
// inspect the raw API error
var apiErr *cron.APIError
if errors.As(err, &apiErr) {
fmt.Println(apiErr.StatusCode, apiErr.Message)
}
}
Securing your endpoint
Tickstem calls your endpoint over the public internet. Any caller that knows your URL can trigger it. Add a shared secret to reject unauthorized requests.
1. Generate a secret
openssl rand -hex 32
Store the output as CRON_SECRET in both your app's environment and as a job header in Tickstem.
2. Register the job with the secret
job, err := client.Register(ctx, cron.RegisterParams{
Name: "daily-cleanup",
Schedule: "0 2 * * *",
Endpoint: "https://yourapp.com/jobs/cleanup",
Headers: map[string]string{
"X-Tickstem-Secret": os.Getenv("CRON_SECRET"),
},
})
3. Validate in your handler
func cronHandler(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Tickstem-Secret") != os.Getenv("CRON_SECRET") {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
// ... do work
}
Local dev with tsk-local
tsk-local runs the full Tickstem API contract in-memory on your machine so you can test job handlers without touching the live API or needing real credentials.
Install
go install github.com/tickstem/cron/cmd/tsk-local@latest
Run
tsk-local # starts on :8090
tsk-local --port 9000 # custom port
Point the SDK at it
client := cron.New("any-key",
cron.WithBaseURL("http://localhost:8090/v1"))
The local dashboard at http://localhost:8090 shows registered jobs, execution history, and a Run button for instant manual triggers.
Cron expression reference
Schedules use standard 5-field cron syntax. All times are UTC.
┌───────── minute (0–59)
│ ┌─────── hour (0–23)
│ │ ┌───── day of month (1–31)
│ │ │ ┌─── month (1–12)
│ │ │ │ ┌─ day of week (0–6, Sun=0)
│ │ │ │ │
* * * * *
| Expression | Meaning |
|---|---|
| * * * * * | Every minute |
| */15 * * * * | Every 15 minutes |
| 0 * * * * | Every hour at :00 |
| 0 2 * * * | Daily at 02:00 UTC |
| 0 9 * * 1 | Every Monday at 09:00 UTC |
| 0 0 1 * * | First day of every month |
| 0 0 * * 0 | Every Sunday at midnight |
Use crontab.guru to build and validate expressions.