HEYHUMAN
Back to HeyHuman

Put human judgment in the loop with one request.

Create a tightly scoped creative review, poll its state, and receive a structured decision your workflow can act on.

JSON over HTTPS Bearer auth Polling now Webhooks next

Make your first review request.

Pilot keys are organization-scoped, environment-labelled, hashed at rest, and limited by API scope. The local dashboard uses a development-only key that is disabled in production.

Local development tokenhh_test_demo

Three operations. No vague prompts.

publish_readinessShould this ship to customers now?visible_problemsWhat concrete issue is visibly wrong?compareWhich of two options better serves the stated goal?
POST /api/v1/assets/import

Send visuals from any pipeline.

Upload PNG, JPEG, WebP, AVIF, or GIF files up to 25 MB with a private signed-upload session, or import a public or presigned HTTPS image URL. HeyHuman validates the real bytes and dimensions, blocks private-network destinations, preserves the source object, and creates a reviewer-safe WebP copy. Review requests use the returned heyhuman-asset:// reference.

REMOTE IMPORT
curl --request POST \
  --url https://heyhuman.vercel.app/api/v1/assets/import \
  --header 'Authorization: Bearer hh_live_••••••••' \
  --header 'Content-Type: application/json' \
  --data '{
    "url": "https://signed-cdn.example.com/build/creative.avif",
    "filename": "launch-creative.avif"
  }'

Webpage reviews currently accept uploaded or imported screenshots. Automatic URL-to-screenshot capture is provider-backed work still to come; the API does not claim to render arbitrary webpages yet.

POST /api/v1/reviews

Create a review.

A successful request is assigned an ID and placed in the managed pilot queue with a target of two independent reviewers. Available reviewers may receive a best-effort private alert; an operator remains responsible for coverage. Every write requires an Idempotency-Key; replaying the same body returns the original request.

REQUEST
curl --request POST \
  --url https://heyhuman.vercel.app/api/v1/reviews \
  --header 'Authorization: Bearer hh_test_••••••••' \
  --header 'Idempotency-Key: launch-creative-042' \
  --header 'Content-Type: application/json' \
  --data '{
    "review_type": "publish_readiness",
    "output_type": "image",
    "asset_url": "https://example.com/summer-ad.png",
    "asset_name": "Summer campaign — option A",
    "audience": "First-time US shoppers, ages 25–45",
    "market": "United States",
    "language": "en",
    "intent": "Is this clear, credible, and ready for paid social?",
    "requested_checks": ["readability", "credibility", "primary_action_clarity"],
    "customer_reference": "launch-creative-042",
    "notes": "Judge the creative, not the offer."
  }'
201 CREATED
{
  "data": {
    "id": "rev_01k2a7f4c9d8e6b5a3f1",
    "object": "review_request",
    "review_type": "publish_readiness",
    "status": "awaiting_coverage",
    "customer_reference": "launch-creative-042",
    "coverage": { "policy": "two_reviewers", "required": 2, "completed": 0 },
    "quote": { "amount": 1500, "currency": "USD" },
    "result": null
  },
  "meta": {
    "request_id": "req_8f7a...",
    "idempotent_replay": false
  }
}

Cancel and close the learning loop.

POST /api/v1/reviews/:id/cancel cancels a nonterminal request. POST /api/v1/reviews/:id/outcome records whether the customer published, revised, rejected, ignored, or avoided publishing because of the result. Both writes require an Idempotency-Key.

GET /api/v1/reviews/:id

Retrieve the current decision.

Poll by ID during the pilot. The response keeps the same shape as the create call and fills decision fields after two independent judgments agree or an operator resolves a disagreement.

COMPLETED RESULT
{
  "data": {
    "id": "rev_01k2a7f4c9d8e6b5a3f1",
    "status": "completed",
    "result": {
      "result_schema_version": "1.0",
      "decision": "needs_revision",
      "confidence": "high",
      "summary": "The primary CTA lacks contrast and is easy to miss.",
      "recommended_next_action": "revise",
      "agreement": { "level": "full", "percentage": 100 },
      "coverage": { "required": 2, "completed": 2 }
    }
  },
  "meta": { "request_id": "req_8f7a..." }
}

Task states.

  1. 01awaiting_coverage

    Request accepted; finding two eligible reviewers.

  2. 02in_review

    At least one reviewer has claimed the task.

  3. 03needs_adjudication

    Two valid independent judgments disagree; an operator must decide.

  4. 04completed

    A structured decision is ready for the customer.

  5. 05cancelled

    The customer cancelled before the request became terminal.

Errors stay machine-readable.

Every response carries an X-Request-Id header and meta.request_id. Every non-2xx response contains error.code and error.message; validation failures also include field-level details. Common codes include unauthorized, insufficient_scope, invalid_json, invalid_request, missing_idempotency_key, idempotency_conflict, and not_found.

Ready for the native reviewer app.

Invite-only reviewers authenticate with a Supabase access token. GET /api/v1/reviewer/tasks returns redacted offers; assets and customer context appear only after POST /api/v1/reviewer/tasks/:id/claim. Native clients submit structured judgments through POST /api/v1/reviewer/tasks/:id/submissions and manage availability and earnings through GET/PATCH /api/v1/reviewer/me.

See it work in the pilot dashboard.

Open dashboard