Frank Public API — Developer Guide
Create products and researches, publish them for interviews, and pull transcripts — programmatically. All requests and responses are camelCase JSON; all timestamps are ISO 8601 UTC strings.
Authentication
Send your secret key as a bearer token on every request. Keys are created under API keys and shown only once.
Base URL https://api.hifrank.ai
Prefix /v1
Header Authorization: Bearer frank_sk_live_...
FIRST REQUEST
curl https://api.hifrank.ai/v1/products \
-H "Authorization: Bearer frank_sk_live_..."
Typical flow
POST /v1/products— describe what you're researching.POST /v1/researches— goals + topics/questions. Comes back as adraft.PUT /v1/researches/{id}/product— attach the product (optional).PUT /v1/researches/{id}/publish— pick interviewer + language; becomesactiveand can take interviews.GET /v1/researches/{id}/interviewsthenGET /v1/interviews/{id}— read transcripts.
Products
POST/v1/products
Create a product
REQUEST BODY
Field Type Notes
name string Max 60 characters. Must be unique across your account — a duplicate returns 409.
required
description string Max 1500 characters. What the product is.
required
category enum One of the 16 values below.
required
brief string Context the interviewer uses. Defaults to description.
optional
urls string [] Up to 10 http(s) URLs.
optional
CATEGORY VALUES
saas_software mobile_app apparel_accessories beauty_personal_care home_living electronics_consumer_tech sports_outdoor toys_hobbies food_beverages health_wellness pet_products automotive_accessories office_stationery online_services offline_services other
REQUEST
curl -X POST https://api.hifrank.ai/v1/products \
-H "Authorization: Bearer frank_sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Notes",
"description": "A note-taking app for teams",
"category": "saas_software",
"urls": ["https://acme.example"]
}'
RESPONSE · 201 CREATED
Field Type Notes
productId string (uuid) Use this to link the product to a research.
always present
name string As sent.
always present
description string | null As sent.
always present
category string One of the category values.
always present
brief string Sent value, or the description.
always present
urls string[] Empty array when none were sent.
always present
createdAt string (ISO 8601) UTC, e.g. 2026-01-01T10:00:00.000Z.
always present
RESPONSE
{
"productId": "d91c8f22-3b7e-4c21-9f10-6a2b8c4d5e70",
"name": "Acme Notes",
"description": "A note-taking app for teams",
"category": "saas_software",
"brief": "A note-taking app for teams",
"urls": ["https://acme.example"],
"createdAt": "2026-01-01T10:00:00.000Z"
}
GET/v1/products
List products
Returns an array of the product object above, newest first. No parameters.
GET/v1/products/{id}
Get a product
Returns one product object. id must be a uuid — anything else is a 400; a product that isn't yours is a 404.
PUT/v1/products/{id}
Update a product
Send only the fields you want to change — omitted fields keep their current value. Same field rules and types as create; all are optional here. Returns the updated product object.
Researches
POST/v1/researches
Create a research
Creates the research immediately as a draft. It cannot take interviews until you publish it.
Field Type Notes
name string Non-empty; whitespace is trimmed.
required
description string What you want to learn.
required
goals string [] At least one non-empty goal.
required
topics object [] At least one topic — see the topic fields below.
required
researchType enum discovery, product_experience or retention_growth.
optional
botTypes enum [] Any of audio, video, chat. Publishing sets this to ["audio"].
optional
greetingMessage object Opening line per channel: chat, audio, video — all optional strings.
optional
prompts object Overrides the generated prompts: voice, video, chat, tts_style — all optional strings. Sending these stops Frank from auto-updating them later.
optional
object Overrides the generated prompts: voice, video, chat, tts_style — all optional strings. Sending these stops Frank from auto-updating them later.
TOPICS[] — EACH TOPIC
Field Type Notes
name string Topic heading.
required
message string Intro the interviewer reads before the topic.
optional
questions object [] At least one question.
required
TOPICS[].QUESTIONS[] — EACH QUESTION
Field Type Notes
text string The question itself.
required
followUpDepth string How hard to probe, e.g. shallow, medium, deep. Free text — it is guidance for the interviewer, not a fixed set.
optional
probing string What to dig into, e.g. ask for specific examples.
optional
tags string Your own labels for grouping, e.g. onboarding,pricing.
optional
REQUEST
curl -X POST https://api.hifrank.ai/v1/researches \
-H "Authorization: Bearer frank_sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Trial churn discovery",
"description": "Why trial users do not upgrade",
"researchType": "discovery",
"goals": ["Understand upgrade blockers"],
"topics": [
{
"name": "Onboarding",
"message": "Let us start with your first few days.",
"questions": [
{
"text": "How was your first week?",
"followUpDepth": "deep",
"probing": "Ask for specific moments of friction",
"tags": "onboarding"
},
{ "text": "What confused you early on?" }
]
}
]
}'
RESPONSE · 201 CREATED
Field Type Notes
researchId string (uuid) Use it for link, publish and interview calls.
always present
name string | null As sent.
always present
status string draft on create, active after publish, stopped when halted.
always present
type string | null The researchType you sent, else null.
always present
goals string [] As sent.
always present
productId string (uuid) | null null until you link a product.
always present
createdAt string (ISO 8601) UTC.
always present
RESPONSE
{
"researchId": "6b2f8c10-1a4d-4e88-b0c3-2f7a9d1e4b56",
"name": "Trial churn discovery",
"status": "draft",
"type": "discovery",
"goals": ["Understand upgrade blockers"],
"productId": null,
"createdAt": "2026-01-01T10:00:00.000Z"
}
PUT/v1/researches/{id}/product
Link a product
REQUEST BODY
Field Type Notes
productId string (uuid) A product you own.
required
A product can back many researches; a research holds at most one. Sending the product that's already linked is a no-op (200). Linking a research that already has a different product returns 409 — there is no re-link. Returns the research object.
REQUEST
curl -X PUT https://api.hifrank.ai/v1/researches/6b2f8c10-.../product \
-H "Authorization: Bearer frank_sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "productId": "d91c8f22-..." }'
PUT/v1/researches/{id}/publish
Publish (activate)
Sets the interviewer and flips the research to active so it can take voice interviews.
REQUEST BODY
Field Type Notes
language enum ISO 639-1 code from the list below. Case-insensitive.
required
persona enum olivia (default) or frank — the interviewer's voice and name.
optional
duration integer Target interview length in minutes. Minimum 1.
optional
prompts object Same shape as on create; overrides the generated prompts.
optional
LANGUAGE VALUES
zh Chinese
en English
fr French
de German
it Italian
ja Japanese
ko Korean
pt Portuguese
es Spanish
multi Multi language — follows the participant
REQUEST
curl -X PUT https://api.hifrank.ai/v1/researches/6b2f8c10-.../publish \
-H "Authorization: Bearer frank_sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "persona": "olivia", "language": "en", "duration": 15 }'
Returns the research object with status set to active.
GET/v1/researches
List researches
Returns an array of the research object above, newest first. No parameters.
Interviews & transcripts
GET/v1/researches/{researchId}/interviews
List a research's interviews
Summaries only, newest first — use it to discover interview ids.
RESPONSE· ARRAY OF
Field Type Notes
interviewId string (uuid) Fetch the transcript with it.
always present
researchId string (uuid) The parent research.
always present
status string | null scheduled, in_progress, completed, incomplete or dropped.
always present
type string audio, video or chat.
always present
participant object name and email, each a string or null when the participant stayed anonymous.
always present
startedAt string (ISO 8601) | null null if it never started.
always present
endedAt string (ISO 8601) | null null while in progress.
always present
duration integer | null Length in seconds.
always present
RESPONSE
[
{
"interviewId": "2b7f8c10-7c1d-4a90-9e63-1d5c8b3a2f41",
"researchId": "6b2f8c10-1a4d-4e88-b0c3-2f7a9d1e4b56",
"status": "completed",
"type": "audio",
"participant": { "name": "Jane", "email": "jane@acme.com" },
"startedAt": "2026-01-01T10:00:00.000Z",
"endedAt": "2026-01-01T10:20:00.000Z",
"duration": 1200
}
]
GET/v1/interviews/{id}
Get an interview + transcript
Every field from the summary above, plus the full transcript in order.
TRANSCRIPT[] — EACH TURN
Field Type Notes
sequence integer 1-based turn order.
always present
role string assistant (the interviewer) or user (the participant).
always present
text string What was said.
always present
startTimestamp string (ISO 8601) When the turn started.
always present
endTimestamp string (ISO 8601) When the turn ended.
always present
RESPONSE
{
"interviewId": "2b7f8c10-...",
"researchId": "6b2f8c10-...",
"status": "completed",
"type": "audio",
"participant": { "name": "Jane", "email": "jane@acme.com" },
"startedAt": "2026-01-01T10:00:00.000Z",
"endedAt": "2026-01-01T10:20:00.000Z",
"duration": 1200,
"transcript": [
{
"sequence": 1,
"role": "assistant",
"text": "Thanks for joining — how was your first week?",
"startTimestamp": "2026-01-01T10:00:04.000Z",
"endTimestamp": "2026-01-01T10:00:08.000Z"
},
{
"sequence": 2,
"role": "user",
"text": "Honestly, setup took longer than I expected.",
"startTimestamp": "2026-01-01T10:00:09.000Z",
"endTimestamp": "2026-01-01T10:00:14.000Z"
}
]
}
Errors & rate limits
Every error uses the same envelope. field is present on validation errors and names the offending property. Every response carries an x-request-id header — include it when reporting an issue.
{
"error": {
"code": "validation_error",
"message": "language must be one of: zh, en, fr, de, it, ja, ko, pt, es, multi",
"field": "language",
"requestId": "req_1a2b3c"
}
}
Status Code When
400 validation_error Malformed id, or the body failed validation
401 invalid_api_key Missing, invalid, or revoked key
404 not_found Not yours, or does not exist
409 conflict Product name taken, or research already has a product
429 rate_limited Rate limit hit — wait Retry-After seconds
Requests are rate limited per key. On 429 the Retry-After header tells you how many seconds to wait. Anything that isn't yours returns 404 rather than 403, so ids can't be probed.