Docs Sign in Get started

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.

Idempotency tip: Design your job handler to be safe to run more than once. Tickstem retries on network errors, so your handler may occasionally be called twice for the same scheduled time.

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

FieldTypeDescription
Name*stringHuman-readable label. Used as the upsert key — registering the same name updates the existing job.
Schedule*string5-field cron expression in UTC.
Endpoint*stringHTTPS URL to call on each execution.
DescriptionstringOptional note shown in the dashboard.
MethodstringHTTP method. Defaults to POST.
Headersmap[string]stringExtra headers sent with each request. Use for shared secrets.
TimeoutSecsintSeconds 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:

  • Statussuccess, failed, timeout, running, or pending
  • StatusCode — HTTP response code from your endpoint
  • DurationMs — wall-clock time of the HTTP call
  • Error — error message when status is failed or timeout
  • ScheduledAt, 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
}
Never hardcode the secret. Use environment variables. Rotate it by updating both your app's env and the job's headers in the dashboard or via the API.

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.

Jobs and history are in-memory — everything resets on restart. Use the real platform for production.

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)
│ │ │ │ │
* * * * *
ExpressionMeaning
* * * * *Every minute
*/15 * * * *Every 15 minutes
0 * * * *Every hour at :00
0 2 * * *Daily at 02:00 UTC
0 9 * * 1Every Monday at 09:00 UTC
0 0 1 * *First day of every month
0 0 * * 0Every Sunday at midnight

Use crontab.guru to build and validate expressions.