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.
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 '{}'
{
"token": "22222222-2222-4222-8222-222222222222",
"expiresUtc": "2026-10-19T09:15:00Z"
}
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.
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"}'
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"}'
{
"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
- Read the owned task and its current ETag.
- Claim with If-Match and a persisted unique Idempotency-Key.
- Confirm the returned UTC expiry is still usable.
- Explicitly PATCH status to InProgress when appropriate; acquisition did not do this.
- Perform work, renewing before expiry as needed.
- Re-read the task after successful writes. Send its current If-Match and X-Task-Claim with protected task writes.
- Report notes/factual effort, then complete or release appropriately.
- 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.