Quickstart

Go from zero to a verified human judgment in under 5 minutes. This guide walks you through creating your first HumanRail task.

1

Get an API key

Sign up at humanrail.dev and create an organization. Navigate to Settings → API Keys and generate a new key.

Note

API keys are prefixed with ek_live_ for production and ek_test_ for sandbox. Use the sandbox key during development -- it simulates worker responses without hitting the real worker pool.

Set the key as an environment variable:

bash
export HUMANRAIL_API_KEY="ek_test_your_key_here"
2

Install an SDK

Choose your language and install the HumanRail client library.

bash
npm install @humanrail/sdk
bash
pip install humanrail
bash
go get github.com/humanrail/humanrail-go
3

Create your first task

A task is a unit of work that gets routed to a vetted human worker. You define the input payload, the expected output schema, and a payout amount. HumanRail handles routing, verification, and payment.

TypeScript
import { HumanRail } from "@humanrail/sdk";

const client = new HumanRail({
  apiKey: process.env.HUMANRAIL_API_KEY,
});

const task = await client.tasks.create({
  idempotencyKey: "order-12345-refund-check",
  taskType: "refund_eligibility",
  riskTier: "medium",
  slaSeconds: 300,
  payload: {
    orderId: "order-12345",
    orderTotal: 89.99,
    reason: "Item arrived damaged",
  },
  outputSchema: {
    type: "object",
    required: ["eligible", "reason_code"],
    properties: {
      eligible: { type: "boolean" },
      reason_code: {
        type: "string",
        enum: ["approved", "denied_policy", "denied_timeframe"],
      },
      notes: { type: "string" },
    },
  },
  payout: { currency: "USD", maxAmount: 0.50 },
  callbackUrl: "https://myapp.com/webhooks/humanrail",
});

console.log(task.id, task.status);
// "01abc123-..." "posted"
Python
import os
from humanrail import HumanRail

client = HumanRail(api_key=os.environ["HUMANRAIL_API_KEY"])

task = client.tasks.create(
    idempotency_key="order-12345-refund-check",
    task_type="refund_eligibility",
    risk_tier="medium",
    sla_seconds=300,
    payload={
        "orderId": "order-12345",
        "orderTotal": 89.99,
        "reason": "Item arrived damaged",
    },
    output_schema={
        "type": "object",
        "required": ["eligible", "reason_code"],
        "properties": {
            "eligible": {"type": "boolean"},
            "reason_code": {
                "type": "string",
                "enum": ["approved", "denied_policy", "denied_timeframe"],
            },
            "notes": {"type": "string"},
        },
    },
    payout={"currency": "USD", "maxAmount": 0.50},
    callback_url="https://myapp.com/webhooks/humanrail",
)

print(task.id, task.status)
# "01abc123-..." "posted"
Go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/humanrail/humanrail-go"
)

func main() {
    client := humanrail.NewClient(os.Getenv("HUMANRAIL_API_KEY"))

    task, err := client.Tasks.Create(context.Background(), &humanrail.TaskCreateParams{
        IdempotencyKey: "order-12345-refund-check",
        TaskType:       "refund_eligibility",
        RiskTier:       humanrail.RiskTierMedium,
        SLASeconds:     300,
        Payload: map[string]any{
            "orderId":    "order-12345",
            "orderTotal": 89.99,
            "reason":     "Item arrived damaged",
        },
        OutputSchema: map[string]any{
            "type":     "object",
            "required": []string{"eligible", "reason_code"},
            "properties": map[string]any{
                "eligible":    map[string]any{"type": "boolean"},
                "reason_code": map[string]any{
                    "type": "string",
                    "enum": []string{"approved", "denied_policy", "denied_timeframe"},
                },
                "notes": map[string]any{"type": "string"},
            },
        },
        Payout:      &humanrail.Payout{Currency: "USD", MaxAmount: 0.50},
        CallbackURL: "https://myapp.com/webhooks/humanrail",
    })
    if err != nil {
        panic(err)
    }

    fmt.Println(task.ID, task.Status)
    // "01abc123-..." "posted"
}
4

Handle the result

You have two options for receiving the verified result: webhooks (recommended) or polling.

Option A: Webhooks (recommended)

Set a callbackUrl when creating the task. HumanRail will send a task.verified event when the worker's response passes verification. See the Webhooks guide for signature verification and retry details.

TypeScript (Express)
import express from "express";
import { verifyWebhookSignature } from "@humanrail/sdk";

const app = express();

app.post("/webhooks/humanrail", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-escalation-signature"];
  const isValid = verifyWebhookSignature({
    payload: req.body,
    signature,
    secret: process.env.HUMANRAIL_WEBHOOK_SECRET,
  });

  if (!isValid) return res.status(401).send("Invalid signature");

  const event = JSON.parse(req.body);
  if (event.type === "task.verified") {
    console.log("Verified output:", event.data.output);
    // { eligible: true, reason_code: "approved", notes: "..." }
  }

  res.sendStatus(200);
});
Python (FastAPI)
from fastapi import FastAPI, Request, HTTPException
from humanrail import verify_webhook_signature

app = FastAPI()

@app.post("/webhooks/humanrail")
async def handle_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get("x-escalation-signature")

    if not verify_webhook_signature(
        payload=body,
        signature=signature,
        secret=os.environ["HUMANRAIL_WEBHOOK_SECRET"],
    ):
        raise HTTPException(status_code=401, detail="Invalid signature")

    event = request.json()
    if event["type"] == "task.verified":
        print("Verified output:", event["data"]["output"])
        # {"eligible": True, "reason_code": "approved", "notes": "..."}

    return {"ok": True}
Go
func webhookHandler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    signature := r.Header.Get("X-Escalation-Signature")

    if !humanrail.VerifyWebhookSignature(body, signature, webhookSecret) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }

    var event humanrail.WebhookEvent
    json.Unmarshal(body, &event)

    if event.Type == "task.verified" {
        fmt.Println("Verified output:", event.Data.Output)
    }

    w.WriteHeader(http.StatusOK)
}

Option B: Polling

If you prefer not to set up webhooks, you can poll the task status until it reaches a terminal state (verified, failed, expired, or cancelled).

TypeScript
const waitForResult = async (taskId: string) => {
  while (true) {
    const task = await client.tasks.get(taskId);

    if (task.status === "verified") {
      return task.output;
    }
    if (["failed", "expired", "cancelled"].includes(task.status)) {
      throw new Error(`Task ended with status: ${task.status}`);
    }

    await new Promise((r) => setTimeout(r, 5000)); // poll every 5s
  }
};

const output = await waitForResult(task.id);
console.log(output);
// { eligible: true, reason_code: "approved", notes: "..." }
Tip

Webhooks are strongly recommended for production. Polling works for development and simple scripts, but it wastes resources and adds latency. Use the task.verified webhook event for real-time results.

5

Go to production checklist

Before going live, make sure you have covered the following:

  • Switch from ek_test_ to ek_live_ API key
  • Set up webhook signature verification (see Webhooks)
  • Use idempotency keys on all POST /tasks calls to prevent duplicates
  • Handle all terminal states: verified, failed, expired, cancelled
  • Configure appropriate SLA timeouts for each task type
  • Set up monitoring for task.failed and task.expired events
  • Register your task types and output schemas in the dashboard
  • Test with the POST /webhooks/test endpoint to verify your handler
  • Review rate limits and implement retry logic with exponential backoff
  • Store the X-Request-Id header from responses for debugging
Important

Never trigger business-critical actions based solely on task.submitted. Always wait for task.verified -- this is the event that confirms the worker's output has passed HumanRail's verification pipeline.

Next steps

📖

API Reference

Full endpoint documentation with request/response schemas.

🔌

Webhooks

Event types, signature verification, and retry policies.

📦

SDKs

TypeScript, Python, and Go client libraries.