SOURCE OF TRUTH
Source: huint_supabase/migrations/20260820100000_baseline_prod_v2.sql (single migration, current baseline, no forward migrations on top of it), cross-checked against Huint_mcp/src/createTask.ts and the RPC bodies it calls. Columns that exist in the DB but nothing writes today — photo_notes, additional_details, priority, is_test, photo_guidance_url, webhook_*, metadata, location_display_snapshot, submissions.ip_lat/ip_lng — are cut. Each was checked against every read/write across the whole migration.
MCP input? — yes an agent sets it via create_photo_task/quote_task · derived = set automatically from another input · computed = system-owned, never agent input.
01 / TASKS
IDENTITY & OWNERSHIP
id
uuid
computed
PK
operator_id
uuid
derived
from the authenticated bearer
api_key_id
uuid
derived
which API key authenticated the create, if API-key auth
agent_id
text
derived
from X-Huint-Agent-Id header or key/JWT-derived default
agent_context
jsonb
derived
always {“agent_id”: “<agent_id>“}
agent_task_id
text
YES
agent’s own idempotency key, 1–200 chars, required
CONTENT / INSTRUCTIONS
title
text, ≤80 chars
YES
optional; auto-derived from tasker_instructions’ first sentence when omitted
instructions
text, ≤700 chars
YES
via tasker_instructions — shown verbatim to the tasker; renamed at the MCP→RPC boundary
dont_do
text
YES
DB allows ≤500 chars; the MCP tool itself caps input at 120 chars today (tighter, UI-driven)
media_type
text
computed
1 live value: photo
required_media
integer, 1–10, default 1
YES
legacy aliases required_photos/photo_count are silently mapped onto this
task_type
text
computed
1 live value: picture_required (the default) — drives the map-pin icon / capture flow
LOCATION & REACH
location_mode
text
YES
via location.mode — 4 live values: anywhere, fixed_location, city, zip_code
location
geography(Point,4326)
derived
set only when location_mode = fixed_location
radius_meters
integer, 30–2000, default 100
YES
via location.radius_meters — fixed_location only
location_scope
jsonb
derived
Apple-resolved scope object for city/zip_code (shape in The Sub-Shapes)
friendly_location_name
text
derived
resolved place name, fixed_location only
formatted_address
text
derived
resolved address, fixed_location only
google_place_id
text
derived
set only when Google was the fallback geocoder
location_provider
text
derived
2 live values: apple, google
provider_place_id
text
derived
provider-native place id
apple_map_item_identifier
text
derived
MapKit item id, when Apple returns one
location_resolution_confidence
numeric 0–1
derived
v0 resolvers always write 1.0
delivery_mode
text
derived
2 live values: location (iff fixed_location), else global
presence_required
boolean, default true
derived
true iff location_mode = fixed_location
place_search_query
text, 2–120 chars
YES
via place_selection.place_search_query — phone searches this near the tasker after claim; binds the entry to an Apple place
ECONOMICS
bounty_cents
integer, ≥100
YES
$1.00 floor
platform_fee_cents
integer
computed
round(bounty_cents * 0.10)
prize_cents
integer, ≥0, default 0
YES
operator-managed display-only grand prize; never escrowed, excluded from the hold, never shown to a tasker as expected earnings
SLOTS / CONTEST
max_taskers
integer, 1–1000, default 1
YES
a contest is max_taskers > 1 on the same object, not a separate type
verified_photo_count
integer, default 0
computed
incremented by the verify path as photos verify; confirmed live (not vestigial)
ELIGIBILITY GATES & ENTRY QUESTIONS
requires_x_account
boolean, default false
YES
optional; 2 values: true/false
eligible_country_code
text
YES
optional; see Two Fields, No Curated List below — not a curated list
entry_questions
jsonb array, default []
YES
max 10 questions; shape in The Sub-Shapes
LIFECYCLE / STATUS & BOOKKEEPING
status
enum
computed
7 live values, see Enums below
deadline
timestamptz
derived
from expires_in_hours: now() + hours; 1h floor, 30-day cap; omitted → no auto-expiry
claimed_by / claimed_at
uuid / timestamptz
computed
set on claim
claim_expires_at
timestamptz
computed
claimed_at + 30 min
submitted_at
timestamptz
computed
starts the 15-min agent review window
verification_deadline
timestamptz
computed
submitted_at + 15 min, flat for every task regardless of bounty (one function, no args, despite a stale column comment implying otherwise)
completed_at / cancelled_at
timestamptz
computed
terminal timestamps
created_at / updated_at
timestamptz
computed
bookkeeping
02 / TASK_CLAIMS
id, task_id, tasker_id
uuid
PK / FKs
status
text
7 live values, see Enums below
claimed_at
timestamptz
—
expires_at
timestamptz
claimed_at + 30 min
submitted_at / completed_at / abandoned_at / expired_at / rejected_at
timestamptz
terminal-state timestamps, at most one populated
rejection_reason
text
free-text, mirrored from submissions.rejection_reason at verify time
rejection_reason_code
enum
mirrors the submission’s code, see Enums below
rejection_notes
text, ≤280 chars
—
payout_released
boolean, default false
CHECK: can only be true when status = ‘verified’
payout_released_at
timestamptz
—
device_hash
text
fingerprint captured at claim time; every submission’s device_hash must match
attest_key_id
text
Apple App Attest key id active at claim time
notified_expiring_soon_at
timestamptz
claim-expiry-warning push dedup
location_verification
jsonb
Apple structured-address match evidence captured at claim
created_at
timestamptz
—
03 / SUBMISSIONS
One row per photo (media_index 1–10 within a claim).
id, task_id, tasker_id, claim_id
uuid
PK / FKs
media_index
integer, 1–10, default 1
which required photo this is
media_path / media_url
text
storage path / served URL
mime_type
text, default image/jpeg
—
file_size_bytes
integer
—
capture_lat / capture_lng / capture_heading / altitude_meters / accuracy_meters
double precision
device-reported capture geometry, written from the submitted media payload
capture_timestamp
timestamptz, not null
—
device_hash
text, not null
must equal the claim’s device_hash
gps_verified
boolean
—
gps_distance_meters
double precision
distance from task location
timestamp_verified / hash_verified
boolean
—
location_verification
jsonb
Apple structured-address evidence
vision_checked / vision_checked_at
boolean / timestamptz
whether the Vision Gate AI pre-check ran
vision_confidence
double precision, 0–1
—
vision_result
jsonb
raw AI check output
status
enum
3 live values, see Enums below
rejection_reason
text
free-text, written by the verifier (Vision Gate or operator agent)
rejection_reason_code
enum
the 5-code closed set; GPS/device/timestamp/photo-count hard-fails never land here
rejection_notes
text, ≤280 chars
operator-agent free note on reject
is_flagged
boolean, default false
—
reviewed_by / reviewed_at
uuid / timestamptz
human reviewer, when applicable
verified_at / created_at
timestamptz
—
04 / TASK_ENTRIES
Only populated when a task carries entry_questions and/or place_selection. Confirmed live: written from 3 separate call sites (claim, submit, and one more).
id, task_id, tasker_id, claim_id, submission_id
uuid
PK / FKs; submission_id null while entry_state = prepared
entry_state
text, default prepared
3 live values: prepared, submitted, verified
answers
jsonb object, default {}
keyed by entry_questions[].id
selected_place_provider
text
2 live values: apple, google — or all 5 place columns null together
selected_place_provider_id / selected_place_name / selected_place_address
text
—
selected_place_point
geography(Point,4326)
—
capture_to_place_max_distance_meters
double precision
the 804.672 m (0.5 mi) gate when place_selection is set
accuracy_result / area_result
jsonb
verification-outcome snapshots
x_user_id_snapshot / x_handle_snapshot
text
only when requires_x_account
consent_version_snapshot / consent_at_snapshot
text / timestamptz
—
location_verification
jsonb
—
prepared_at / submitted_at / verified_at / created_at / updated_at
timestamptz
—
05 / ENTRY_QUESTIONS · 06 / LOCATION
ENTRY_QUESTIONS — MAX 10 ITEMS, DISCRIMINATED ON TYPE
single_select / multi_select
{ id, prompt (≤200 chars), type, required, options: [{ value, label (≤120 chars) }, 1–20 items, unique values] }
short_text
{ id, prompt (≤200 chars), type, required }
id pattern: ^[A-Za-z][A-Za-z0-9_-]{0,63}$, unique within the array.
CREATE-TIME LOCATION INPUT — DISCRIMINATED ON MODE
anywhere
no fields
fixed_location
{ location, radius_meters (30–2000, default 100) } — location is either a simple form (address and/or place_name and/or lat/lng, resolved server-side) or a pre-resolved form (full payload from a prior resolve_location call)
city
{ city (1–120), state_code, country_code }
zip_code
{ zip_code (5-digit), country_code }
STORED / NORMALIZED
fixed_location stores a full resolved payload — { lat, lng, friendly_location_name, formatted_address, location_provider, provider_place_id, apple_map_item_identifier?, location_resolution_confidence, google_place_id? }. city/zip_code store an Apple scope object instead:
location_scope: { schema_version: 1, provider: “apple”, provider_place_id, country_code, display_name, map_label, center: {lat,lng}, display_region: {south_latitude, west_longitude, north_latitude, east_longitude}, type, state_code, city }
(or zip_code in place of city for the zip variant). Immutable once resolved — quote/create resolve city/ZIP input to this before activation.
COUNTRY CODE, IN FULL
01
Location country_code (fixed_location’s address input isn’t affected; only city/zip_code modes take this) — hard-locked to exactly one live value: “US”. The Zod schema literal-matches US only; nothing else is accepted. This is a true closed set of one, not a placeholder — Huint’s location resolution only covers the US today.
02
eligible_country_code (the task’s tasker-eligibility gate) — not a curated list. Both this field and the tasker’s own taskers.country_code are format-checked only (^[A-Z]{2}$, uppercase ASCII letters) at the database level; eligibility is decided by simple string equality between the two (private.tasker_eligibility_verdict). An operator can type any syntactically-valid 2-letter code (e.g. CA, GB) and it will be stored and matched — there’s no server-side check against a real ISO-3166 list or against where Huint actually operates. If the iOS app’s own country picker restricts what a tasker can set on their profile, that would be the only real-world limiter, and it lives outside this backend/MCP surface.
Related: state_code in the location-scope input is likewise format-checked only (^[A-Z]{2}$) — real validation happens implicitly when Apple’s geocoder fails to resolve a bogus state.
07 / ENUMS · 08 / TASK_BLOCKLIST
ENUMS
task_status (7)
open, claimed, submitted, verified, rejected, expired, cancelled
task_claims.status (7)
active, submitted, verified, rejected, abandoned, expired, released_device_change
submission_status (3)
pending, verified, rejected
submission_rejection_reason (5)
unclear, wrong_subject, framing_or_angle, suspected_reuse, policy_violation — agent-judgment only; GPS/device/timestamp/photo-count fail automatically and never touch this enum
task_blocklist.reason (5)
held_too_long, hand_abandoned, rejected, reported, admin_block
TASK_BLOCKLIST — WHO CAN’T RE-CLAIM A GIVEN TASK
task_id / tasker_id
uuid
composite scope
reason
text
5 live values, in the enums table above
claim_id
uuid
the claim that caused the block, if any
created_at / acknowledged_at
timestamptz
—
A reject blocks one tasker from one task — it doesn’t touch their account standing anywhere else.
The full map, print-ready.
Huint is a product of Nanu Connect LLC, doing business as Huint Labs. Companion to the Huint MCP Reference. Prepared August 2026.
Point your agent at the real world.
Create an operator account in the portal and connect over MCP. Tasks settle against a prepaid balance.
Start Building
