humanforai

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 16 Imported by: 0

README

humanforai-go

Go client and command-line tool for Human For AI — a human endpoint for AI agents. Let your agent, pipeline, or service hire a verified human operator for tasks that need physical presence, human perception, or human judgment:

  • Real-world verification — confirm a place, product, price, or claim exists, with photo/text evidence
  • Product or app testing — a real human installs, uses, and reports
  • Human judgment and feedback — tone, clarity, trustworthiness, design quality
  • AI output review — human review before your output reaches production
  • Data collection — gathering or labeling that needs human perception or local access
  • Local physical-world tasks — visit, photograph, check, measure, observe
  • Decision escalation — a human read on a judgment call before you act
  • …and anything else a human can legally and safely do (custom_human_in_the_loop)

No API key. Free during the proof-of-concept pilot. Every task is reviewed by the human before acceptance; illegal, harmful, deceptive, unsafe, or privacy-invasive tasks are rejected. First response within 12 hours, any day of the week — typically much faster.

Standard library only. Go 1.21+.

Install

Library:

go get github.com/humanforai/humanforai-go

Command-line tool:

go install github.com/humanforai/humanforai-go/cmd/humanforai@latest

Command line

humanforai services                       # the catalog with task_type identifiers
humanforai submit \
  -type real_world_verification \
  -description "Verify that [business] at [address] is open; photograph the storefront and posted hours." \
  -location "City, address or area" \
  -output-format text_report_with_photos \
  -email you@your-domain.com
humanforai status HFAI-2026-XXXXXXXXXXXXXXXX          # one look
humanforai status HFAI-2026-XXXXXXXXXXXXXXXX -watch   # poll until delivered or rejected
humanforai message -reply-to you@your-domain.com -message "Can you cover [city] next week?"
humanforai thread MSG-ID -token ACCESS_TOKEN
humanforai verify-receipt HFAI-2026-XXXXXXXXXXXXXXXX  # offline check of the signed receipt
humanforai health

Put -json before the command for raw output. Exit codes: 0 ok, 2 usage, 3 API error, 4 the task was rejected. HUMANFORAI_BASE_URL and HUMANFORAI_REQUESTER are honoured.

No mailbox? Autonomous agents can submit with -status-poll instead of -email: the deliverable arrives as text in operator_notes on the status endpoint (budget: one such task per client per day). Keep the task_id — it is your only key to the result.

Go

package main

import (
	"context"
	"fmt"
	"log"
	"time"

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

func main() {
	ctx := context.Background()
	client := humanforai.New(humanforai.WithRequester("my-agent/1.0"))

	sub, err := client.SubmitTask(ctx, humanforai.TaskRequest{
		TaskType: "ai_output_review",
		Description: "Review these 10 AI-written product descriptions for plausibility and tone; " +
			"verdict + one-line reason each: [content or link].",
		OutputFormat: "structured_json",
		ContactEmail: "you@your-domain.com",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(sub.TaskID, sub.StatusURL)

	// Human review is not instant. Poll every minute or slower.
	final, err := client.WaitForTask(ctx, sub.TaskID, 2*time.Minute, nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(final.Status, final.OperatorNotes)
}

Errors are structured:

_, err := client.GetTask(ctx, "HFAI-NOPE")
if apiErr, ok := humanforai.IsAPIError(err); ok {
	fmt.Println(apiErr.Status, apiErr.Code, apiErr.Message, apiErr.Details, apiErr.RateLimit)
}

Every response carries the standard RateLimit-* headers; the most recent set is client.LastRateLimit(). Retrying a submission with the same IdempotencyKey replays the original response instead of creating a duplicate.

Status transparency

GetTask returns progress, not just the final state: SeenByOperatorAt (the moment a human actually saw the task), ETA (set on acceptance), and StatusHistory (submitted → accepted → delivered, or rejected).

Signed receipts

Every delivered task carries Receipt, a compact JWS (Ed25519) binding the deliverable's SHA-256 to the task's lifecycle timestamps. Verify it offline against the public key at https://humanforai.dev/.well-known/jwks.json, with nothing but the standard library:

task, _ := client.GetTask(ctx, taskID)
jwks, _ := client.JWKS(ctx)
payload, err := humanforai.VerifyReceipt(task.Receipt, jwks, &humanforai.VerifyOptions{
	DeliverableText: &task.OperatorNotes,
})

A valid receipt proves the deliverable is byte-identical to what was delivered and that it was issued by humanforai.dev. It does not prove the timestamps were witnessed by anyone else — they are the service's own signed assertion. Details: https://humanforai.dev/trust#receipts.

Other ways in

Trust, in one paragraph

You never pay, never create an account, and are never asked for credentials — a request for payment or credentials is not from this service. You send a task description and an optional contact email; the worst case is a rejected task. Deliverables are one careful human's observation with stated confidence, not guaranteed truth. New here? Send a small, checkable test task first. Full analysis: https://humanforai.dev/trust.

License

MIT

Documentation

Overview

Package humanforai is a Go client for Human For AI (https://humanforai.dev), a human endpoint for AI agents: submit tasks that need physical presence, human perception, or human judgment, and a verified human operator performs them. No API key, free during the proof-of-concept pilot.

client := humanforai.New(humanforai.WithRequester("my-agent/1.0"))
sub, err := client.SubmitTask(ctx, humanforai.TaskRequest{
    TaskType:     "real_world_verification",
    Description:  "Check whether store X at address Y stocks product Z; photo proof.",
    ContactEmail: "you@your-domain.com",
})
task, err := client.GetTask(ctx, sub.TaskID)

Standard library only. API reference: https://humanforai.dev/api — OpenAPI: https://humanforai.dev/openapi.json

Index

Constants

View Source
const DefaultBaseURL = "https://humanforai.dev"

DefaultBaseURL is the production API origin.

View Source
const Version = "0.1.1"

Version of this client, sent in the User-Agent header.

Variables

View Source
var ErrReceipt = errors.New("humanforai: invalid receipt")

ErrReceipt is wrapped by every receipt verification failure.

View Source
var OutputFormats = []string{
	"text_report",
	"text_report_with_photos",
	"structured_json",
	"annotated_screenshots",
	"video",
}

OutputFormats lists the deliverable formats a task can request.

View Source
var TaskTypes = []string{
	"real_world_verification",
	"product_or_app_testing",
	"human_judgment_and_feedback",
	"data_collection",
	"local_physical_task",
	"ai_output_review",
	"prompt_and_workflow_testing",
	"simulation_and_automation_testing",
	"accessibility_and_usability_check",
	"decision_escalation",
	"custom_human_in_the_loop",
}

TaskTypes lists the task types in the service catalog. The catalog is examples, not limits: anything a human can legally and safely do is in scope; use "custom_human_in_the_loop" when nothing else fits. The live list is Client.AllServices.

Functions

func SHA256Hex

func SHA256Hex(text string) string

SHA256Hex returns the hash a receipt commits to: SHA-256 of the UTF-8 text.

Types

type APIError

type APIError struct {
	// Status is the HTTP status code.
	Status int
	// Code is the API's machine-readable error code, e.g. "validation_failed",
	// "task_not_found", "rate_limited", "duplicate".
	Code string
	// Message is the human-readable explanation.
	Message string
	// Details lists validation details when present.
	Details []string
	// RateLimit holds the RateLimit headers on the failing response.
	RateLimit RateLimit
	// Body is the full decoded JSON body, when there was one.
	Body map[string]any
}

APIError is an error response from the API (4xx/5xx).

func IsAPIError

func IsAPIError(err error) (*APIError, bool)

IsAPIError returns the *APIError wrapped in err, if any.

func (*APIError) Error

func (e *APIError) Error() string

type Client

type Client struct {
	// BaseURL is the API origin, without a trailing slash.
	BaseURL string
	// Requester is sent as "requester" on tasks and "from" on messages when the
	// request does not set its own (your agent or product name).
	Requester string
	// HTTPClient performs the requests. Defaults to a client with a 30s timeout.
	HTTPClient *http.Client
	// UserAgent overrides the default "humanforai-go/<version>".
	UserAgent string
	// contains filtered or unexported fields
}

Client talks to the Human For AI REST API.

func New

func New(opts ...Option) *Client

New returns a Client. HUMANFORAI_BASE_URL and HUMANFORAI_REQUESTER are honoured as defaults; options override them.

func (*Client) AgentManifest

func (c *Client) AgentManifest(ctx context.Context) (map[string]any, error)

AgentManifest fetches the machine-readable platform manifest (/agent.json).

func (*Client) AllServices

func (c *Client) AllServices(ctx context.Context) ([]Service, error)

AllServices follows the cursor until the whole catalog has been read.

func (*Client) Do

func (c *Client) Do(ctx context.Context, method, path string, query url.Values, body any, headers map[string]string, out any) error

Do performs one API call and decodes the JSON body into out (which may be nil). Non-2xx responses are returned as *APIError.

func (*Client) GetTask

func (c *Client) GetTask(ctx context.Context, taskID string) (*Task, error)

GetTask returns the current status of a task.

func (*Client) GetThread

func (c *Client) GetThread(ctx context.Context, messageID, accessToken string) (*Thread, error)

GetThread reads a thread using the access token from SendMessage.

func (*Client) Health

func (c *Client) Health(ctx context.Context) (*Health, error)

Health checks liveness and reports the API version.

func (*Client) JWKS

func (c *Client) JWKS(ctx context.Context) (*JWKS, error)

JWKS fetches the public keys that sign deliverable receipts.

func (*Client) LastRateLimit

func (c *Client) LastRateLimit() RateLimit

LastRateLimit returns the RateLimit headers from the most recent response.

func (*Client) ListServices

func (c *Client) ListServices(ctx context.Context, limit int, cursor string) (*ServicesPage, error)

ListServices returns one page of the service catalog. limit 0 means the API default; cursor "" means the first page.

func (*Client) ReplyInThread

func (c *Client) ReplyInThread(ctx context.Context, messageID, accessToken, message string) (*ReplyResult, error)

ReplyInThread follows up in an existing thread.

func (*Client) SendMessage

func (c *Client) SendMessage(ctx context.Context, req MessageRequest) (*MessageResponse, error)

SendMessage messages the operator.

func (*Client) SubmitTask

func (c *Client) SubmitTask(ctx context.Context, req TaskRequest) (*TaskSubmission, error)

SubmitTask submits a task. Keep the returned TaskID: it is the key to the result.

func (*Client) WaitForTask

func (c *Client) WaitForTask(ctx context.Context, taskID string, interval time.Duration, onUpdate func(*Task)) (*Task, error)

WaitForTask polls until the task is delivered or rejected, or ctx ends. Human review is not instant (first response within 12 hours, any day of the week, typically much faster): poll every minute or slower. Intervals under 10 seconds are raised to 10 seconds. onUpdate, when non-nil, is called after every poll.

type Health

type Health struct {
	Status     string `json:"status"`
	Service    string `json:"service"`
	APIVersion string `json:"api_version"`
	Time       string `json:"time"`
}

Health is the liveness response.

type JWK

type JWK struct {
	Kty string `json:"kty"`
	Crv string `json:"crv"`
	Kid string `json:"kid"`
	X   string `json:"x"`
	Use string `json:"use"`
	Alg string `json:"alg"`
}

JWK is one key of the receipt-signing key set.

type JWKS

type JWKS struct {
	Keys []JWK `json:"keys"`
}

JWKS is the published key set at /.well-known/jwks.json.

type MessageRequest

type MessageRequest struct {
	Message string `json:"message"`
	// ReplyTo must be a real mailbox: the operator's only way to answer.
	ReplyTo string `json:"reply_to"`
	Subject string `json:"subject,omitempty"`
	// From overrides Client.Requester for this message.
	From           string `json:"from,omitempty"`
	IdempotencyKey string `json:"-"`
}

MessageRequest opens a thread with the operator (scoping, questions, custom work).

type MessageResponse

type MessageResponse struct {
	MessageID string `json:"message_id"`
	CreatedAt string `json:"created_at"`
	ThreadURL string `json:"thread_url"`
	// AccessToken reads and replies in the thread. Keep it.
	AccessToken string `json:"access_token"`
	Message     string `json:"message"`
}

MessageResponse is the 201 Created body.

type Option

type Option func(*Client)

Option configures a Client.

func WithBaseURL

func WithBaseURL(u string) Option

WithBaseURL points the client at another origin (for example a local API).

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient replaces the underlying *http.Client.

func WithRequester

func WithRequester(r string) Option

WithRequester sets the identifier sent with tasks and messages.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent replaces the User-Agent header.

type RateLimit

type RateLimit struct {
	Limit        int
	Remaining    int
	ResetSeconds int
	Policy       string
	RetryAfter   int
}

RateLimit carries the standard RateLimit headers (draft-ietf-httpapi-ratelimit-headers) present on every API response. Zero values mean the header was absent.

type ReceiptPayload

type ReceiptPayload struct {
	Issuer            string         `json:"iss"`
	TaskID            string         `json:"task_id"`
	DeliverableSHA256 string         `json:"deliverable_sha256"`
	Timeline          map[string]any `json:"timeline"`
	// Raw holds every claim, including ones not modelled above.
	Raw map[string]any `json:"-"`
}

ReceiptPayload is the decoded, verified receipt.

func VerifyReceipt

func VerifyReceipt(receipt string, jwks *JWKS, opts *VerifyOptions) (*ReceiptPayload, error)

VerifyReceipt checks the signature of a compact JWS receipt against the published key set and returns its payload. Every failure wraps ErrReceipt.

type Reply

type Reply struct {
	Author    string `json:"author"`
	Message   string `json:"message"`
	CreatedAt string `json:"created_at"`
}

Reply is one entry of Thread.Replies.

type ReplyResult

type ReplyResult struct {
	MessageID  string `json:"message_id"`
	ReplyCount int    `json:"reply_count"`
	Message    string `json:"message"`
}

ReplyResult is the 201 body of a thread reply.

type Service

type Service struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description"`
}

Service is one entry of the catalog.

type ServicesPage

type ServicesPage struct {
	Items      []Service `json:"items"`
	Total      int       `json:"total"`
	NextCursor string    `json:"next_cursor"`
}

ServicesPage is one page of the cursor-paginated catalog.

type StatusEvent

type StatusEvent struct {
	Status string `json:"status"`
	At     string `json:"at"`
}

StatusEvent is one entry of Task.StatusHistory.

type Task

type Task struct {
	TaskID           string        `json:"task_id"`
	Status           string        `json:"status"`
	TaskType         string        `json:"task_type"`
	Description      string        `json:"description"`
	LocationRequired bool          `json:"location_required"`
	LocationDetail   string        `json:"location_detail"`
	Deadline         string        `json:"deadline"`
	OutputFormat     string        `json:"output_format"`
	Requester        string        `json:"requester"`
	Source           string        `json:"source"`
	CreatedAt        string        `json:"created_at"`
	UpdatedAt        string        `json:"updated_at"`
	SeenByOperatorAt string        `json:"seen_by_operator_at"`
	ETA              string        `json:"eta"`
	StatusHistory    []StatusEvent `json:"status_history"`
	RejectionReason  string        `json:"rejection_reason"`
	// OperatorNotes carries the deliverable text (always for status_poll
	// delivery, and whatever the operator chose to publish otherwise).
	OperatorNotes string `json:"operator_notes"`
	// Receipt, DeliverableSHA256 and ReceiptIssuedAt appear once delivered.
	Receipt           string `json:"receipt"`
	DeliverableSHA256 string `json:"deliverable_sha256"`
	ReceiptIssuedAt   string `json:"receipt_issued_at"`
}

Task is the public status of a task.

func (*Task) Terminal

func (t *Task) Terminal() bool

Terminal reports whether the task has reached a final state.

type TaskRequest

type TaskRequest struct {
	// TaskType is one of TaskTypes (or any id from the live catalog).
	TaskType string `json:"task_type"`
	// Description says what to do, where, and what success looks like. The
	// human cannot see your conversation context, so be self-contained.
	Description string `json:"description"`
	// ContactEmail receives the deliverable and clarifying questions. A real
	// mailbox: placeholder domains are rejected and the domain is MX-checked.
	// Required unless Delivery is "status_poll".
	ContactEmail string `json:"contact_email,omitempty"`
	// Delivery is "email" (default) or "status_poll": the no-mailbox path,
	// where the deliverable arrives as text in Task.OperatorNotes (one such
	// task per client per day).
	Delivery         string `json:"delivery,omitempty"`
	LocationRequired *bool  `json:"location_required,omitempty"`
	LocationDetail   string `json:"location_detail,omitempty"`
	// Deadline is an ISO 8601 timestamp.
	Deadline     string `json:"deadline,omitempty"`
	OutputFormat string `json:"output_format,omitempty"`
	// Requester overrides Client.Requester for this task.
	Requester string `json:"requester,omitempty"`
	// IdempotencyKey makes retries safe: the same key with the same payload
	// within 24h replays the original response. Sent as a header.
	IdempotencyKey string `json:"-"`
}

TaskRequest describes a task for the human operator.

type TaskSubmission

type TaskSubmission struct {
	TaskID     string `json:"task_id"`
	Status     string `json:"status"`
	CreatedAt  string `json:"created_at"`
	StatusURL  string `json:"status_url"`
	StatusPage string `json:"status_page"`
	Message    string `json:"message"`
}

TaskSubmission is the 202 Accepted body returned on submission.

type Thread

type Thread struct {
	MessageID   string  `json:"message_id"`
	Status      string  `json:"status"`
	CreatedAt   string  `json:"created_at"`
	From        string  `json:"from"`
	Subject     string  `json:"subject"`
	Message     string  `json:"message"`
	Replies     []Reply `json:"replies"`
	ReplyCount  int     `json:"reply_count"`
	LastReplyAt string  `json:"last_reply_at"`
	Note        string  `json:"note"`
}

Thread is a message thread with the operator.

type VerifyOptions

type VerifyOptions struct {
	// DeliverableText, when non-nil, must hash to the receipt's
	// deliverable_sha256 (pass the task's OperatorNotes).
	DeliverableText *string
	// Issuer is the expected "iss" claim. Empty means DefaultBaseURL.
	Issuer string
}

VerifyOptions tunes VerifyReceipt.

Directories

Path Synopsis
cmd
humanforai command
Command humanforai hires a verified human operator from the terminal.
Command humanforai hires a verified human operator from the terminal.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL