@50heads/mcp 2026.925.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/cli.js +7447 -0
- package/dist/index.js +7123 -0
- package/dist/snippets.js +100 -0
- package/dist/types/account.d.ts +22 -0
- package/dist/types/actions.d.ts +53 -0
- package/dist/types/apps.d.ts +22 -0
- package/dist/types/cli/stdio.d.ts +1735 -0
- package/dist/types/constants.d.ts +36 -0
- package/dist/types/draft.d.ts +23 -0
- package/dist/types/errors.d.ts +58 -0
- package/dist/types/generated/skill.d.ts +6 -0
- package/dist/types/idempotency.d.ts +35 -0
- package/dist/types/language.d.ts +10 -0
- package/dist/types/manifest.d.ts +141 -0
- package/dist/types/pricing.d.ts +89 -0
- package/dist/types/public.d.ts +36 -0
- package/dist/types/question-types.d.ts +18 -0
- package/dist/types/results.d.ts +29 -0
- package/dist/types/schemas.d.ts +2015 -0
- package/dist/types/server.d.ts +71 -0
- package/dist/types/snippets.d.ts +31 -0
- package/dist/types/tasks.d.ts +87 -0
- package/package.json +74 -0
- package/server.json +84 -0
- package/skills/50heads/LICENSE +21 -0
- package/skills/50heads/SKILL.md +51 -0
- package/skills/50heads/references/pricing.md +27 -0
- package/skills/50heads/references/question-types.md +235 -0
- package/skills/50heads/references/tools.md +25 -0
- package/skills/50heads/scripts/estimate.py +90 -0
- package/skills/50heads.zip +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { McpServer, type ServerContext } from "@modelcontextprotocol/server";
|
|
2
|
+
import { type Client } from "@50heads/shared/client";
|
|
3
|
+
export { MCP_RESOURCE_URL, PROMPT_SCOPES, PUBLIC_RESOURCE_PREFIXES, SERVER_NAME, SERVER_VERSION, TOOL_NAMES, TOOL_SCOPES, resourceScope, type ToolName, } from "./constants.js";
|
|
4
|
+
export type CallLog = {
|
|
5
|
+
kind: "tool" | "resource" | "prompt";
|
|
6
|
+
name: string;
|
|
7
|
+
questionId?: string;
|
|
8
|
+
outcome: "ok" | "input_required" | string;
|
|
9
|
+
latencyMs: number;
|
|
10
|
+
};
|
|
11
|
+
export type Era = "modern" | "legacy";
|
|
12
|
+
export type ServerOptions = {
|
|
13
|
+
/** A typed API client for the calling principal, tagged with what it serves (for logs). */
|
|
14
|
+
api: (tool: string) => Client;
|
|
15
|
+
/** 2026-07-28 (per-request envelope) or 2025-11-25 (initialize handshake). */
|
|
16
|
+
era: Era;
|
|
17
|
+
/** Stable id of the caller for idempotency derivation (connection id or token hash). */
|
|
18
|
+
principal: string;
|
|
19
|
+
/** Granted scopes; null when unknown (the API enforces them). */
|
|
20
|
+
scopes: readonly string[] | null;
|
|
21
|
+
/** False when the request carried no token (only public tools and resources work). */
|
|
22
|
+
authenticated: boolean;
|
|
23
|
+
portalUrl: string;
|
|
24
|
+
webUrl: string;
|
|
25
|
+
/** Protected resource metadata URL, advertised in the com.50heads/auth extension. */
|
|
26
|
+
resourceMetadataUrl?: string;
|
|
27
|
+
traceId?: string;
|
|
28
|
+
/** Longest a single call may block (wait_for_results), in ms. Default 600 s. */
|
|
29
|
+
maxWaitMs?: number;
|
|
30
|
+
onCall?: (log: CallLog) => void;
|
|
31
|
+
/** Fetches public files from webUrl (the help index for search_help). Default: global fetch. */
|
|
32
|
+
fetchWeb?: typeof fetch;
|
|
33
|
+
/** Sent with feedback so support can tell hosts and versions apart, such as "mcp/2026.924.1". */
|
|
34
|
+
source?: string;
|
|
35
|
+
};
|
|
36
|
+
/** The extensions the server declares (server/discover and initialize). */
|
|
37
|
+
export declare function serverExtensions(opts: Pick<ServerOptions, "resourceMetadataUrl" | "webUrl">): {
|
|
38
|
+
"io.modelcontextprotocol/tasks": {
|
|
39
|
+
methods: string[];
|
|
40
|
+
tools: string[];
|
|
41
|
+
pollIntervalMs: number;
|
|
42
|
+
};
|
|
43
|
+
"io.modelcontextprotocol/apps": {
|
|
44
|
+
mimeTypes: string[];
|
|
45
|
+
views: string[];
|
|
46
|
+
};
|
|
47
|
+
"com.50heads/skills": {
|
|
48
|
+
skillVersion: string;
|
|
49
|
+
skills: {
|
|
50
|
+
name: string;
|
|
51
|
+
version: string;
|
|
52
|
+
uri: string;
|
|
53
|
+
files: string[];
|
|
54
|
+
download: string;
|
|
55
|
+
}[];
|
|
56
|
+
};
|
|
57
|
+
"com.50heads/auth": {
|
|
58
|
+
authorizationServer: string;
|
|
59
|
+
apiKeys: string;
|
|
60
|
+
resourceMetadata?: string | undefined;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export declare const INSTRUCTIONS: string;
|
|
64
|
+
export declare function clientName(ctx: ServerContext): string | undefined;
|
|
65
|
+
/** Creates the 50heads MCP server for one request (HTTP) or one connection (stdio). */
|
|
66
|
+
export declare function createServer(opts: ServerOptions): McpServer;
|
|
67
|
+
/**
|
|
68
|
+
* A first draft from a goal and assets: two image URLs make an A or B question; listed
|
|
69
|
+
* options make a single choice; otherwise a yes, mostly or no check. The model refines it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function draftFromGoal(goal: string, assets?: string): Record<string, unknown>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config snippets per host: the one source for install instructions. Printed by
|
|
3
|
+
* `npx @50heads/mcp config`, written into README.md by scripts/generate.ts, and rendered by the
|
|
4
|
+
* website's MCP docs page and For agents page (apps/web imports @50heads/mcp/snippets).
|
|
5
|
+
* Hosted URL plus OAuth for the hosts that support it; the npx line for the rest.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CLIENTS: readonly ["claude-desktop", "claude-code", "cursor", "chatgpt", "vscode", "windsurf", "generic"];
|
|
8
|
+
export type ClientId = (typeof CLIENTS)[number];
|
|
9
|
+
export type SnippetOptions = {
|
|
10
|
+
/** The hosted endpoint. */
|
|
11
|
+
url?: string;
|
|
12
|
+
/** An API key to show, or the placeholder. */
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
};
|
|
15
|
+
export type Snippet = {
|
|
16
|
+
client: ClientId;
|
|
17
|
+
title: string;
|
|
18
|
+
/** Where the snippet goes, in a sentence. */
|
|
19
|
+
where: string;
|
|
20
|
+
/** "oauth": the hosted URL with a browser sign-in. "key": the npx line with an API key. */
|
|
21
|
+
auth: "oauth" | "key";
|
|
22
|
+
format: "json" | "shell" | "text";
|
|
23
|
+
body: string;
|
|
24
|
+
/** A second way in, when the host offers one. */
|
|
25
|
+
alternative?: {
|
|
26
|
+
where: string;
|
|
27
|
+
format: "json" | "shell";
|
|
28
|
+
body: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare function configSnippets(options?: SnippetOptions): Snippet[];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { QuestionV2 } from "@50heads/shared";
|
|
2
|
+
import type { Client } from "@50heads/shared/client";
|
|
3
|
+
import type { Currency } from "./pricing.js";
|
|
4
|
+
/**
|
|
5
|
+
* Tasks (io.modelcontextprotocol/tasks): a fifty-answer question is a long-running operation.
|
|
6
|
+
* `ask` returns a task whose id is the question_id, so the task state lives in the API and a
|
|
7
|
+
* results object stays fetchable for ever after the task is gone. The server keeps nothing.
|
|
8
|
+
*
|
|
9
|
+
* Methods: tasks/get (progress, and the results once done), tasks/update (min_answers to
|
|
10
|
+
* complete early), tasks/cancel (maps to cancel), and tasks/result for 2025-11-25 clients.
|
|
11
|
+
* There is no tasks/list.
|
|
12
|
+
*/
|
|
13
|
+
export declare const TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
|
|
14
|
+
export declare const TASK_POLL_INTERVAL_MS = 5000;
|
|
15
|
+
export declare const TASK_METHODS: readonly ["tasks/get", "tasks/update", "tasks/cancel", "tasks/result", "tasks/list"];
|
|
16
|
+
export type TaskMethod = (typeof TASK_METHODS)[number];
|
|
17
|
+
export declare function isTaskMethod(method: unknown): method is TaskMethod;
|
|
18
|
+
/** The scope each task method needs (spec: questions:read for tasks/get, write for cancel). */
|
|
19
|
+
export declare const TASK_SCOPES: Record<TaskMethod, string | null>;
|
|
20
|
+
export type TaskStatus = "working" | "input_required" | "completed" | "failed" | "cancelled";
|
|
21
|
+
export type TaskObject = {
|
|
22
|
+
taskId: string;
|
|
23
|
+
/** The 2026-07-28 extension names it `id`; 2025-11-25 names it `taskId`. Same value. */
|
|
24
|
+
id: string;
|
|
25
|
+
status: TaskStatus;
|
|
26
|
+
statusMessage: string;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
lastUpdatedAt: string;
|
|
29
|
+
/** Results are kept for ever; the task handle never expires. */
|
|
30
|
+
ttl: null;
|
|
31
|
+
pollInterval: number;
|
|
32
|
+
};
|
|
33
|
+
export declare function isTerminal(status: TaskStatus): boolean;
|
|
34
|
+
/** "12 of 50 answered · about 6 minutes left" */
|
|
35
|
+
export declare function statusMessage(q: Pick<QuestionV2, "status" | "answered" | "n" | "etaMinutes" | "refusedCategory">): string;
|
|
36
|
+
export declare function toTask(q: QuestionV2, now?: string): TaskObject;
|
|
37
|
+
export type TaskDeps = {
|
|
38
|
+
/** A client for the calling principal, tagged with the task method. */
|
|
39
|
+
api: (method: string) => Client;
|
|
40
|
+
portalUrl: string;
|
|
41
|
+
/** Overrides the account's display currency; by default it is read from the balance. */
|
|
42
|
+
currency?: Currency;
|
|
43
|
+
traceId?: string;
|
|
44
|
+
/** Aborts the tasks/result long-poll when the caller goes away. */
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
/** Longest tasks/result waits, in ms (the HTTP host's request budget). */
|
|
47
|
+
maxWaitMs?: number;
|
|
48
|
+
};
|
|
49
|
+
type RpcRequest = {
|
|
50
|
+
jsonrpc: "2.0";
|
|
51
|
+
id: string | number;
|
|
52
|
+
method: string;
|
|
53
|
+
params?: Record<string, unknown>;
|
|
54
|
+
};
|
|
55
|
+
type RpcResponse = {
|
|
56
|
+
jsonrpc: "2.0";
|
|
57
|
+
id: string | number;
|
|
58
|
+
result: Record<string, unknown>;
|
|
59
|
+
} | {
|
|
60
|
+
jsonrpc: "2.0";
|
|
61
|
+
id: string | number;
|
|
62
|
+
error: {
|
|
63
|
+
code: number;
|
|
64
|
+
message: string;
|
|
65
|
+
data?: unknown;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
/** True when the request carries the 2026-07-28 per-request envelope. */
|
|
69
|
+
export declare function isModernMessage(msg: {
|
|
70
|
+
params?: Record<string, unknown>;
|
|
71
|
+
}): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Serves one tasks/* JSON-RPC request end to end (the SDK has no task runtime for the
|
|
74
|
+
* 2026-07-28 extension, so the HTTP edge and the stdio transport both route here).
|
|
75
|
+
*/
|
|
76
|
+
export declare function serveTaskMessage(msg: RpcRequest, deps: TaskDeps & {
|
|
77
|
+
serverInfo: {
|
|
78
|
+
name: string;
|
|
79
|
+
version: string;
|
|
80
|
+
};
|
|
81
|
+
}): Promise<RpcResponse>;
|
|
82
|
+
/**
|
|
83
|
+
* Serves one tasks/* request. Throws McpToolError (not_found → -32602, and so on); an unknown
|
|
84
|
+
* method or tasks/list throws a -32601.
|
|
85
|
+
*/
|
|
86
|
+
export declare function handleTaskRequest(method: string, rawParams: unknown, deps: TaskDeps): Promise<Record<string, unknown>>;
|
|
87
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@50heads/mcp",
|
|
3
|
+
"version": "2026.925.1",
|
|
4
|
+
"mcpName": "com.50heads/mcp",
|
|
5
|
+
"description": "50heads MCP: ask fifty verified real people from Claude, ChatGPT, Cursor or your own agent.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"50heads",
|
|
10
|
+
"human feedback",
|
|
11
|
+
"agent skill"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://50heads.com/docs/mcp",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/50heads/mcp.git",
|
|
17
|
+
"directory": "packages/mcp"
|
|
18
|
+
},
|
|
19
|
+
"bugs": "https://github.com/50heads/mcp/issues",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"50heads-mcp": "dist/cli.js"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/types/public.d.ts",
|
|
31
|
+
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./skills/*": "./skills/*",
|
|
34
|
+
"./server.json": "./server.json",
|
|
35
|
+
"./package.json": "./package.json",
|
|
36
|
+
"./snippets": {
|
|
37
|
+
"types": "./dist/types/snippets.d.ts",
|
|
38
|
+
"default": "./dist/snippets.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"skills",
|
|
44
|
+
"server.json",
|
|
45
|
+
"README.md",
|
|
46
|
+
"CHANGELOG.md",
|
|
47
|
+
"LICENSE"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
54
|
+
"zod": "^4.6.5"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@modelcontextprotocol/client": "2.0.0",
|
|
58
|
+
"@types/node": "^22.18.0",
|
|
59
|
+
"esbuild": "^0.28.2",
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"@50heads/brand": "0.0.0",
|
|
62
|
+
"@50heads/login": "0.0.0",
|
|
63
|
+
"@50heads/shared": "0.0.0",
|
|
64
|
+
"@50heads/tsconfig": "0.0.0"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"generate": "node scripts/generate.ts",
|
|
68
|
+
"build": "node scripts/build.ts",
|
|
69
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
70
|
+
"test": "node --test \"src/**/*.test.ts\" \"scripts/**/*.test.ts\"",
|
|
71
|
+
"release": "node scripts/release.ts"
|
|
72
|
+
},
|
|
73
|
+
"types": "./dist/types/public.d.ts"
|
|
74
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "com.50heads/mcp",
|
|
4
|
+
"title": "50heads",
|
|
5
|
+
"description": "Ask fifty verified real people a five-second question and get the split back.",
|
|
6
|
+
"version": "2026.925.1",
|
|
7
|
+
"websiteUrl": "https://50heads.com/agents",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/50heads/mcp",
|
|
10
|
+
"source": "github",
|
|
11
|
+
"subfolder": "packages/mcp"
|
|
12
|
+
},
|
|
13
|
+
"icons": [
|
|
14
|
+
{
|
|
15
|
+
"src": "https://50heads.com/icon-512.png",
|
|
16
|
+
"mimeType": "image/png",
|
|
17
|
+
"sizes": [
|
|
18
|
+
"512x512"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"src": "https://50heads.com/logo.svg",
|
|
23
|
+
"mimeType": "image/svg+xml",
|
|
24
|
+
"sizes": [
|
|
25
|
+
"any"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"remotes": [
|
|
30
|
+
{
|
|
31
|
+
"type": "streamable-http",
|
|
32
|
+
"url": "https://mcp.50heads.com/mcp",
|
|
33
|
+
"headers": [
|
|
34
|
+
{
|
|
35
|
+
"name": "Authorization",
|
|
36
|
+
"description": "Optional: Bearer fh_live_… for an API key. Without it the host signs in with OAuth.",
|
|
37
|
+
"isRequired": false,
|
|
38
|
+
"isSecret": true
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"packages": [
|
|
44
|
+
{
|
|
45
|
+
"registryType": "npm",
|
|
46
|
+
"registryBaseUrl": "https://registry.npmjs.org",
|
|
47
|
+
"identifier": "@50heads/mcp",
|
|
48
|
+
"version": "2026.925.1",
|
|
49
|
+
"runtimeHint": "npx",
|
|
50
|
+
"transport": {
|
|
51
|
+
"type": "stdio"
|
|
52
|
+
},
|
|
53
|
+
"environmentVariables": [
|
|
54
|
+
{
|
|
55
|
+
"name": "FIFTYHEADS_API_KEY",
|
|
56
|
+
"description": "A 50heads API key (fh_live_…) from the dashboard. Or run `npx @50heads/mcp login` once instead.",
|
|
57
|
+
"isRequired": false,
|
|
58
|
+
"isSecret": true,
|
|
59
|
+
"format": "string"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"_meta": {
|
|
65
|
+
"io.modelcontextprotocol.registry/publisher-provided": {
|
|
66
|
+
"categories": [
|
|
67
|
+
"research",
|
|
68
|
+
"productivity",
|
|
69
|
+
"human-feedback"
|
|
70
|
+
],
|
|
71
|
+
"authorization": {
|
|
72
|
+
"resourceMetadata": "https://mcp.50heads.com/.well-known/oauth-protected-resource",
|
|
73
|
+
"authorizationServer": "https://auth.50heads.com",
|
|
74
|
+
"scopes": [
|
|
75
|
+
"questions:read",
|
|
76
|
+
"questions:write",
|
|
77
|
+
"templates:read",
|
|
78
|
+
"account:read"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"skillVersion": "2026.925.1"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 50heads
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 50heads
|
|
3
|
+
description: Ask fifty verified real people a five-second question and get a distribution back; use when the work in hand needs a human preference, first impression or sanity check that a model cannot supply.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
version: "2026.925.1"
|
|
7
|
+
homepage: https://50heads.com/agents
|
|
8
|
+
allowed-tools: "50heads:*"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Asking 50heads
|
|
12
|
+
|
|
13
|
+
50heads puts one short question to verified people on their phones and returns how they split. Answers cost credits, priced in your currency; fifty take about ten minutes.
|
|
14
|
+
|
|
15
|
+
## When to ask
|
|
16
|
+
|
|
17
|
+
Ask when the answer is a human reaction: which name, headline, image or menu people prefer, whether copy is clear, what they notice first. Never ask for facts, predictions, advice or research. Before asking again, call `list_questions` and reuse a recent answer.
|
|
18
|
+
|
|
19
|
+
## Eight rules for a good question
|
|
20
|
+
|
|
21
|
+
1. One question a head can answer in five seconds.
|
|
22
|
+
2. Ask about a preference or first impression, never a fact.
|
|
23
|
+
3. Show the thing: images (`ab_image`, `upload_image`) for anything visual.
|
|
24
|
+
4. Keep options short, parallel and few: two to four is best. Add Neither when a forced pick would mislead.
|
|
25
|
+
5. Word it neutrally. No leading words, no hint of the answer you want.
|
|
26
|
+
6. Put background in `context`, one line, only when a head needs it.
|
|
27
|
+
7. Pick the type that fits: `single_choice` to pick, `pairwise` for two, `ranking` for order, `scale_1_5` for strength, `free_text` (Tier 2) for why.
|
|
28
|
+
8. Match heads and tier to the stakes: 50 at Tier 1 for a quick read, 100 or more or Tier 2 for a decision; target the countries and language of the real audience.
|
|
29
|
+
|
|
30
|
+
## How to ask
|
|
31
|
+
|
|
32
|
+
1. Call `estimate` and, when a person is present, show them the price and time together: "50 answers, about 10 minutes, $12.70."
|
|
33
|
+
2. Call `ask` with a fresh UUID as `idempotency_key`. Reuse that key if you retry.
|
|
34
|
+
3. Wait on the task, or call `wait_for_results` on older hosts. `get_results` shows it while live.
|
|
35
|
+
|
|
36
|
+
## Reading results
|
|
37
|
+
|
|
38
|
+
Lead with the winner, the margin in points and the confidence. With 50 heads a margin under 10 points is noise: say so and suggest more heads, not a verdict. Report underfilled as underfilled. Quote the split, not adjectives.
|
|
39
|
+
|
|
40
|
+
## Worked example
|
|
41
|
+
|
|
42
|
+
"Which menu would you order from?" with two menu photos, `ab_image`, 50 heads, Tier 1. Menu B 68%, Menu A 32%: "Menu B wins by 36 points. Confidence high." Follow up with 20 Tier 2 heads saying why.
|
|
43
|
+
|
|
44
|
+
## Avoid
|
|
45
|
+
|
|
46
|
+
- Double questions ("Is it clear and friendly?").
|
|
47
|
+
- Yes-or-no questions that lead.
|
|
48
|
+
- Asking for personal data or anything that identifies someone.
|
|
49
|
+
- Re-asking the same question to fish for a different answer.
|
|
50
|
+
|
|
51
|
+
More: `references/question-types.md`, `pricing.md`, `tools.md`.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Pricing
|
|
2
|
+
|
|
3
|
+
Prices are in credits. `estimate`, `balance` and 50heads://pricing show them in your account currency at the day's rate. You pay per accepted answer; unanswered heads are refunded.
|
|
4
|
+
|
|
5
|
+
Per answer = tier base × length × format, plus images and targeting traits, times 1.5 for rush. Total = per answer × heads.
|
|
6
|
+
|
|
7
|
+
| Tier | Heads | Credits an answer | Credits for 50 answers |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| 1 | phone-verified | 20 | 1000 |
|
|
10
|
+
| 2 | ID-verified | 42 | 2100 |
|
|
11
|
+
| 3 | trusted panel | 80 | 4000 |
|
|
12
|
+
|
|
13
|
+
Length (question, context and options together): up to 150 characters × 1.0, up to 300 × 1.1, up to 600 × 1.25.
|
|
14
|
+
|
|
15
|
+
Format: `single_choice` × 1.0, `multi_choice` × 1.1, `ab_image` × 1.0, `pairwise` × 1.1, `scale_1_5` × 1.0, `ranking` × 1.3, `yes_mostly_no` × 1.0, `free_text` × 2.0, `click_test` × 1.5, `reaction` × 1.0.
|
|
16
|
+
|
|
17
|
+
A reason with every answer (`reason`, not for free_text) adds to the format: optional + 0.3, required + 0.6. A required reason on a Tier 1 single choice is 20 × 1.6 = 32 credits an answer. Heads get the same share of the higher price.
|
|
18
|
+
|
|
19
|
+
Images: 7 credits an answer for each image (audio counts the same). Rush: × 1.5. Five-second test: × 1.3.
|
|
20
|
+
|
|
21
|
+
Targeting: countries are free. Age, gender and each tag group are one trait each, 3 credits an answer per trait, up to 4, at any tier (PickFu charges about $0.40 per trait). See 50heads://targeting.
|
|
22
|
+
|
|
23
|
+
Heads: 10 to 5000, default 50.
|
|
24
|
+
|
|
25
|
+
Every connection has a daily spend cap (default 5000 credits), set when you connect and editable in the dashboard. Call `balance` to see what is left.
|
|
26
|
+
|
|
27
|
+
Always call `estimate` first: it returns the exact price, the time and any fixes the question needs, and never spends.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Question types
|
|
2
|
+
|
|
3
|
+
Every question has `type`, `text` (8 to 600 characters), `language` (such as `en` or `pt-BR`), and optionally `context` (one line, 120 characters), `stimulus` (an image or a line of text shown above the question), `n` (heads, default 50), `tier` (1 to 3, default 1), `rush` and `targeting`.
|
|
4
|
+
|
|
5
|
+
The format multiplier is part of the price: per answer = tier base × length × format, plus 5 credits an image.
|
|
6
|
+
|
|
7
|
+
Five-second test: add `exposure_ms` (usually 5000) to an image stimulus on single_choice, multi_choice, yes_mostly_no, scale_1_5, reaction or free_text. The app shows the image for that long, hides it, then asks, so the answer is a first impression or what people remember. × 1.3.
|
|
8
|
+
Every type except `free_text` can ask for a short written reason with each answer: `reason: "optional"` (format + 0.3) or `"required"` (format + 0.6), 10 to 140 characters. Reasons come back on each answer, translated into your language when heads wrote in another, and `get_results` summarises them (themes with counts and quotes, a sentiment split and a takeaway). Use it instead of a follow-up when you want the which and the why from the same heads.
|
|
9
|
+
|
|
10
|
+
## Single choice (`single_choice`)
|
|
11
|
+
|
|
12
|
+
- Use: Pick one of several: names, headlines, taglines, colours.
|
|
13
|
+
- Options: 2 to 8 options, 40 characters each; images optional. Two to four reads best.
|
|
14
|
+
- Answer: One option per head; the distribution counts each option.
|
|
15
|
+
- Tier: any. Format × 1.0.
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"type": "single_choice",
|
|
20
|
+
"text": "Which name sounds most like a bakery?",
|
|
21
|
+
"language": "en",
|
|
22
|
+
"options": [
|
|
23
|
+
{
|
|
24
|
+
"label": "Crumb & Co"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"label": "Loafers"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"label": "Proof"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"n": 50
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Multiple choice (`multi_choice`)
|
|
38
|
+
|
|
39
|
+
- Use: Tick all that apply: which features matter, which words fit.
|
|
40
|
+
- Options: 2 to 8 options, 40 characters each. Shares add up to more than 100%.
|
|
41
|
+
- Answer: Any number of options per head; each option's share is of all heads.
|
|
42
|
+
- Tier: any. Format × 1.1.
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"type": "multi_choice",
|
|
47
|
+
"text": "Which of these would make you try a new coffee shop?",
|
|
48
|
+
"language": "en",
|
|
49
|
+
"options": [
|
|
50
|
+
{
|
|
51
|
+
"label": "Oat milk at no extra cost"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"label": "Quiet seating"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"label": "Loyalty card"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"n": 50
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## A or B image (`ab_image`)
|
|
65
|
+
|
|
66
|
+
- Use: Two images side by side: menus, logos, screenshots, packaging.
|
|
67
|
+
- Options: Exactly 2 options, each with an image_url (https, or a pre-signed upload). Labels optional.
|
|
68
|
+
- Answer: One image per head.
|
|
69
|
+
- Tier: any. Format × 1.0.
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"type": "ab_image",
|
|
74
|
+
"text": "Which menu would you order from?",
|
|
75
|
+
"language": "en",
|
|
76
|
+
"options": [
|
|
77
|
+
{
|
|
78
|
+
"label": "Menu A",
|
|
79
|
+
"image_url": "https://example.com/menu-a.png"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"label": "Menu B",
|
|
83
|
+
"image_url": "https://example.com/menu-b.png"
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"n": 50
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Pairwise (`pairwise`)
|
|
91
|
+
|
|
92
|
+
- Use: Compare options two at a time when there are too many to show at once.
|
|
93
|
+
- Options: 2 to 8 options; each head sees one pair, balanced so every pair is seen about equally.
|
|
94
|
+
- Answer: One pick per pair. With 3 or more options each row has count (wins), share (win share), appearances, a Bradley–Terry strength and a rank.
|
|
95
|
+
- Tier: any. Format × 1.1.
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"type": "pairwise",
|
|
100
|
+
"text": "Which subject line would you open first?",
|
|
101
|
+
"language": "en",
|
|
102
|
+
"options": [
|
|
103
|
+
{
|
|
104
|
+
"label": "Your order is on its way"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"label": "Good news: it has shipped"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"label": "Tracking number inside"
|
|
111
|
+
}
|
|
112
|
+
],
|
|
113
|
+
"n": 100
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Scale 1 to 5 (`scale_1_5`)
|
|
118
|
+
|
|
119
|
+
- Use: How strongly: clarity, appeal, trust. Report the mean and the split.
|
|
120
|
+
- Options: Fixed: 1, 2, 3, 4, 5. Do not send options.
|
|
121
|
+
- Answer: A number from 1 to 5; results include the mean.
|
|
122
|
+
- Tier: any. Format × 1.0.
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"type": "scale_1_5",
|
|
127
|
+
"text": "How clear is this sentence? 1 is not clear, 5 is very clear.",
|
|
128
|
+
"context": "Your parcel will be left in a safe place if you are out.",
|
|
129
|
+
"language": "en",
|
|
130
|
+
"n": 50
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Ranking (`ranking`)
|
|
135
|
+
|
|
136
|
+
- Use: Put options in order of preference.
|
|
137
|
+
- Options: 2 to 8 options; keep to five or fewer for a five-second answer.
|
|
138
|
+
- Answer: A full order per head; the distribution carries each option's average rank.
|
|
139
|
+
- Tier: any. Format × 1.3.
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"type": "ranking",
|
|
144
|
+
"text": "Rank these pizza toppings, favourite first.",
|
|
145
|
+
"language": "en",
|
|
146
|
+
"options": [
|
|
147
|
+
{
|
|
148
|
+
"label": "Mushroom"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"label": "Pepperoni"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"label": "Olives"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"label": "Pineapple"
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
"n": 50
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Yes, mostly or no (`yes_mostly_no`)
|
|
165
|
+
|
|
166
|
+
- Use: A quick check with room for a middle answer: does this make sense, would you trust it.
|
|
167
|
+
- Options: Fixed: Yes, Mostly, No. Do not send options.
|
|
168
|
+
- Answer: One of the three.
|
|
169
|
+
- Tier: any. Format × 1.0.
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"type": "yes_mostly_no",
|
|
174
|
+
"text": "Does this button label tell you what will happen?",
|
|
175
|
+
"context": "Button: Save and continue",
|
|
176
|
+
"language": "en",
|
|
177
|
+
"n": 50
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Free text (`free_text`)
|
|
182
|
+
|
|
183
|
+
- Use: Why, in the head's own words. Best as a follow-up to a choice.
|
|
184
|
+
- Options: No options. Needs Tier 2 or 3. Answers are up to 200 characters.
|
|
185
|
+
- Answer: Short text per head; results carry the texts and a summary.
|
|
186
|
+
- Tier: 2 or 3. Format × 2.0.
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"type": "free_text",
|
|
191
|
+
"text": "Why would you order from Menu B?",
|
|
192
|
+
"language": "en",
|
|
193
|
+
"n": 20,
|
|
194
|
+
"tier": 2
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Click test (`click_test`)
|
|
199
|
+
|
|
200
|
+
- Use: Where people look or tap first on a page, ad, pack or screen. Results are a heatmap.
|
|
201
|
+
- Options: No options. stimulus.image_url is required; max_taps 1 to 5 (default 1).
|
|
202
|
+
- Answer: One to max_taps points per head, 0–1 from the top left. Results carry every tap, the busiest areas with counts and a heatmap PNG.
|
|
203
|
+
- Tier: any. Format × 1.5.
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"type": "click_test",
|
|
208
|
+
"text": "Where would you tap to buy this?",
|
|
209
|
+
"language": "en",
|
|
210
|
+
"stimulus": {
|
|
211
|
+
"image_url": "https://example.com/product-page.png"
|
|
212
|
+
},
|
|
213
|
+
"max_taps": 1,
|
|
214
|
+
"n": 50
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Reaction (`reaction`)
|
|
219
|
+
|
|
220
|
+
- Use: A gut reaction to an image or line: a logo, a cover, an ad. Five faces drawn as glyphs.
|
|
221
|
+
- Options: Fixed: Love it, Like it, Not sure, Dislike it, Hate it. Do not send options.
|
|
222
|
+
- Answer: One of the five; results add a positive, neutral and negative split.
|
|
223
|
+
- Tier: any. Format × 1.0.
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"type": "reaction",
|
|
228
|
+
"text": "How does this cover make you feel?",
|
|
229
|
+
"language": "en",
|
|
230
|
+
"stimulus": {
|
|
231
|
+
"image_url": "https://example.com/cover.png"
|
|
232
|
+
},
|
|
233
|
+
"n": 50
|
|
234
|
+
}
|
|
235
|
+
```
|