Can You Just
API v1 · Documentation navigation

DEVELOPER DOCUMENTATION / API v1

Worker claims

Acquire, renew and release a 15-minute task lease, check replayed expiry and reconcile uncertain execution safely.

A claim coordinates execution

A task claim is a temporary 15-minute UTC lease used to coordinate active work. Claiming neither starts the task nor completes work, changes permanent assignment or guarantees exactly-once external execution. Workers sharing a client should claim before executing.

Acquire a runnable task #

POST /api/v1/tasks/42/claim requires tasks:update, the current task If-Match and an Idempotency-Key. Acquisition accepts no body or {}. A successful claim returns 200 with a token and UTC expiry. Required source freshness is checked before acquisition; failure creates no claim.

Acquire the lease · curl request
curl -i -X POST 'https://cyj.example/api/v1/tasks/42/claim' \
  -H "Authorization: Bearer $CYJ_API_TOKEN" \
  -H 'If-Match: "task-42-plan-7"' \
  -H 'Idempotency-Key: example-acquire-001' \
  -H 'Content-Type: application/json' \
  --data-raw '{}'
Claim acquired or renewed · JSON response
{
  "token": "22222222-2222-4222-8222-222222222222",
  "expiresUtc": "2026-10-19T09:15:00Z"
}
Retry · Check replayed expiry

Replaying a successful acquire or renew can return its original expiry, which may already have passed. A 200 response alone is not evidence of a usable current lease. Compare expiresUtc against the current UTC time before executing, allowing a sensible safety margin.

Renew or release

POST /api/v1/tasks/42/claim/renew renews a matching unexpired lease for 15 minutes. POST /api/v1/tasks/42/claim/release terminates it. Both need current task If-Match, a new logical retry key and a nonzero UUID token in the body. Missing, null, empty/zero or malformed tokens are 400; unavailable/expired/wrong leases are 409.

Renew before expiry · curl request
curl -i -X POST 'https://cyj.example/api/v1/tasks/42/claim/renew' \
  -H "Authorization: Bearer $CYJ_API_TOKEN" \
  -H 'If-Match: "task-42-plan-7"' \
  -H 'Idempotency-Key: example-renew-001' \
  -H 'Content-Type: application/json' \
  --data-raw '{"token":"22222222-2222-4222-8222-222222222222"}'
Release when stopping · curl request
curl -i -X POST 'https://cyj.example/api/v1/tasks/42/claim/release' \
  -H "Authorization: Bearer $CYJ_API_TOKEN" \
  -H 'If-Match: "task-42-plan-7"' \
  -H 'Idempotency-Key: example-release-request-001' \
  -H 'Content-Type: application/json' \
  --data-raw '{"token":"22222222-2222-4222-8222-222222222222"}'
Lease released · JSON response
{
  "released": true,
  "releasedUtc": "2026-10-19T09:10:00Z"
}

releasedUtc is termination time, not a future expiry. Release returns no token. Completing a task with its live token also releases the lease in the same successful write.

Safe worker loop

  1. Read the owned task and its current ETag.
  2. Claim with If-Match and a persisted unique Idempotency-Key.
  3. Confirm the returned UTC expiry is still usable.
  4. Explicitly PATCH status to InProgress when appropriate; acquisition did not do this.
  5. Perform work, renewing before expiry as needed.
  6. Re-read the task after successful writes. Send its current If-Match and X-Task-Claim with protected task writes.
  7. Report notes/factual effort, then complete or release appropriately.
  8. If a request outcome or external action is uncertain, recover the receipt and reconcile recorded state before repeating effects.

C# · acquire and append a note

This HTTP example shows the boundaries to retain in a real worker. Use durable request keys and operation-specific error handling; do not wrap the whole sequence in a blind retry loop.

using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient { BaseAddress = new Uri("https://cyj.example/") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer",
    Environment.GetEnvironmentVariable("CYJ_API_TOKEN")
    ?? throw new InvalidOperationException("Load the credential from your secret store."));

using var detail = await http.GetAsync("api/v1/tasks/42");
detail.EnsureSuccessStatusCode();
var etag = detail.Headers.ETag?.ToString()
    ?? throw new InvalidOperationException("Task response has no ETag.");
// Persist this key before sending; reuse it if this logical acquire has an uncertain outcome.
var acquireKey = Guid.NewGuid().ToString("N");
using var acquire = new HttpRequestMessage(HttpMethod.Post, "api/v1/tasks/42/claim");
acquire.Headers.TryAddWithoutValidation("If-Match", etag);
acquire.Headers.Add("Idempotency-Key", acquireKey);
using var acquired = await http.SendAsync(acquire);
acquired.EnsureSuccessStatusCode();
using var lease = JsonDocument.Parse(await acquired.Content.ReadAsStringAsync());
var token = lease.RootElement.GetProperty("token").GetGuid();
var expiry = lease.RootElement.GetProperty("expiresUtc").GetDateTimeOffset();
if (expiry <= DateTimeOffset.UtcNow.AddSeconds(30))
    throw new InvalidOperationException("Reconcile and obtain a usable lease before work.");

// Claiming did not start work. Re-read after each successful write to obtain the current ETag.
using var fresh = await http.GetAsync("api/v1/tasks/42");
fresh.EnsureSuccessStatusCode();
var noteKey = Guid.NewGuid().ToString("N"); // Persist alongside this exact note body.
using var note = new HttpRequestMessage(HttpMethod.Post, "api/v1/tasks/42/notes")
{ Content = JsonContent.Create(new { text = "Draft prepared. Waiting for approval." }) };
note.Headers.TryAddWithoutValidation("If-Match", fresh.Headers.ETag!.ToString());
note.Headers.Add("Idempotency-Key", noteKey);
note.Headers.Add("X-Task-Claim", token.ToString());
using var saved = await http.SendAsync(note);
saved.EnsureSuccessStatusCode();
using var latest = await http.GetAsync("api/v1/tasks/42");
latest.EnsureSuccessStatusCode();
var releaseKey = Guid.NewGuid().ToString("N"); // Persist before sending this logical release.
using var release = new HttpRequestMessage(HttpMethod.Post, "api/v1/tasks/42/claim/release")
{ Content = JsonContent.Create(new { token }) };
release.Headers.TryAddWithoutValidation("If-Match", latest.Headers.ETag!.ToString());
release.Headers.Add("Idempotency-Key", releaseKey);
using var released = await http.SendAsync(release);
released.EnsureSuccessStatusCode();
// Add explicit PATCH/start, renewal and reconciliation handling for your worker lifecycle.
// Keep external side effects outside blind retry loops.

An expired worker token must not be reused. Read and reconcile current work, then obtain a usable lease. See concurrency, safe retries and claim reference.