Run CFP Blueprint from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page checks the timeline against the sourced floor, matches the formats to the structure, reads the form depth, picks the review posture, checks the criteria against the six community-call points and keeps only confirmed benefits - all free, in cfpkit.js. The run writes the brief or the call; the page then holds the reply to its own design (cfpreply.js).
Base URL and the response shape
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response has one shape:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "..." } }
The token is minted for this app (the guest call takes {"slug":"cfp-blueprint"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, event, design, facts) is missing. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. Retry with the same Idempotency-Key so a retry never double-bills. |
The task field comes first
Two lanes - brief (the CFP design brief for the organizers) and call (the public call for papers; not available when the timeline is below the floor). A missing or unknown task is answered as brief. Both lanes send the same four fields.
The input
| Field | Required | What |
|---|---|---|
task | yes | brief or call. If it is missing or neither, do brief and set "task": "brief". |
event | yes | the event, its date, the as-of date and program announcement, the team, the structure facts and last edition's numbers. |
design | yes | the planned dates, every format with structure and call flags, slots and form fields, the cap, the planned anonymization, the drafted criteria and the drafted benefits with their confirmation. |
facts | yes | the page's timeline, format, form-depth, anonymization, criteria, benefits and first-timer picks, the gates, the signals to record and every finding tagged [fix], [check] or [info]. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
- Timeline: open the call for at least a month, close it at least 6-8 weeks before the event, and leave at least two weeks to consider proposals - speakers need 4-6 weeks to arrange travel or time off. The standard window (about two months open, closing 8+ weeks out) is the default; floor-compressed is legitimate under a hard date; the long window (opening around seven months out) is promoted for a first edition, international speakers or mentorship, and costs promotion sustained for months. Below the floor the open call is deleted and the program goes invited. Slots held outside the call for late invitations are stated in the call with their count.
- The call offers exactly the session formats the event's structure chose, each with its own submission type. The standard form splits a short abstract (near 100 words) from a separate context ("why me") field, with format, audience level, bio and prior-talk links; minimal fits a meetup-scale call; extended (per-format fields, recording consent, an in-form grant request) is promoted by a grant program, formats with different requirements or a large committee. State a per-person cap.
- Anonymization: named review for a small first committee, honest only with published criteria; opt-in anonymity is a platform capability, not a fairness posture; mandatory blind is promoted by an equity goal and committee capacity, runs in two phases, states exactly when identity enters, and tells speakers to keep identifying details out of titles and free text.
- Publish the review criteria in the call. The community-pole points: no sponsor-bought speaker spots, ever; new content favored; local and out-of-town balance; new speakers favored; bold subjects encouraged; product talks routed to open space - or, with no open-space block, the lesson-transfer test: would the audience keep the lesson if they forgot the product name tomorrow? Publish only criteria that will actually be applied.
- Benefits: at minimum a free event ticket, stated explicitly; nothing the budget has not confirmed. Travel money and honoraria carry tax, visa and conflict-of-interest questions.
- First-time speakers: worked examples (past accepted proposals) by default, encouragement copy always, proposal mentorship when a mentor pool and a long enough window exist. Under-represented people are much less likely to answer an open call, so reach out actively where diversity is a goal. Every threshold here is self-set.
House rules
- Use only the input and the rules above. The page's names, dates, picks and counts are exact - copy them. Every number in a sentence must appear in the input.
- Label anything that is not one of the rules above, and is not in the input, as your own design.
- Never invent a date, a slot count, a benefit, a criterion, an amount or a person; where something must be filled in and is not, write a [bracketed placeholder]. Refer to people by name or as they - never infer anyone's gender from a name.
- Never use backticks, never refer to this input's fields by their key names in a sentence, and never copy this prompt's narrative wording into a reply.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
brief
summary- one sentence, at most 35 words: the timeline rung, the most important fix, and the first thing to settle.timeline-pickandplanned(long,standard,floororbelow: the page's pick and the rung the dates make),open,close,notify(the planned dates exactly, "" when not set),note(one sentence).formats- one object per format in the page's order:format(exactly),structureandcall(true or false, as the page lists them),note(one short sentence: keep, add to the call, or drop from it).form-pickandplanned(minimal,standardorextended: the page's pick and the depth the drafted fields make),cap(the planned cap as a number, or null),note(one sentence).review-pickandplanned(named,optinorblind: the page's pick and the posture as planned),note(one sentence: the mechanics to publish).criteria-missing(the community-pole points the page reports missing, by the page's names;[]when none),note(one sentence).benefits-state(the confirmed benefits, exactly as drafted),drop(the unconfirmed ones, exactly),note(one sentence).support-pick(examples,encouragementormentorship, the page's pick),note(one sentence).measurement- 2 or 3 objects:metric(one of the page's signals),revision(what it would change next edition - your design).caveats- at most 4 short strings:[fix]findings first (the most urgent four when there are more), then the most important[check]findings.[]when there are none of either.
{
"task": "brief",
"summary": "one sentence",
"timeline": { "pick": "standard", "planned": "standard", "open": "YYYY-MM-DD", "close": "YYYY-MM-DD", "notify": "YYYY-MM-DD", "note": "one sentence" },
"formats": [ { "format": "Talk (30 min)", "structure": true, "call": true, "note": "one short sentence" } ],
"form": { "pick": "standard", "planned": "standard", "cap": 3, "note": "one sentence" },
"review": { "pick": "named", "planned": "named", "note": "one sentence" },
"criteria": { "missing": [], "note": "one sentence" },
"benefits": { "state": [ "Free event ticket" ], "drop": [], "note": "one sentence" },
"support": { "pick": "examples", "note": "one sentence" },
"measurement": [ { "metric": "a signal", "revision": "what it changes" } ],
"caveats": [ "a caveat" ]
}
The page holds it to its own design: the timeline, form and review picks beside the planned ones, every format with its structure and call flags, the cap, the count of missing criteria, the confirmed and dropped benefits and the first-timer pick; no date or number may appear that the input does not hold.
call
title- the call's heading, naming the event.intro- two or three sentences: the event and who should submit; no figure the input does not hold.dates- exactly four objects in order:label("Call opens", "Call closes", "Speakers hear back", "Event"),date(the planned date exactly, or "[date to set]" when not set).formats- one object per format the structure has (the page's structure formats, in order - a format only in the call is left out):format(exactly),slots(the page's count as a number, or null),submit(one sentence: what to send, from that format's fields; "[fields to set]" when it has none).cap- one sentence with the per-person cap, or "[cap to set]".review- two or three sentences: how proposals are reviewed under the page's pick; for blind review, the two phases and when identity enters ("[when identity enters]" if not stated) and the instruction to keep identifying details out of titles and text.criteria- the drafted criteria, lightly edited, one string each in order; a criterion the page flags as routing to an open space that does not exist becomes the lesson-transfer question; with no drafted criteria,[].benefits- one string per confirmed benefit, exactly what it is; nothing unconfirmed.firsttimers- one or two sentences in line with the page's first-timer pick.reserved- one sentence stating the number of slots held outside the call when the page holds some, otherwise "".contact- "[contact address]" unless the input gives one.
{
"task": "call",
"title": "Call for papers - an event",
"intro": "two or three sentences",
"dates": [ { "label": "Call opens", "date": "YYYY-MM-DD" } ],
"formats": [ { "format": "Talk (30 min)", "slots": 18, "submit": "one sentence" } ],
"cap": "one sentence",
"review": "two or three sentences",
"criteria": [ "a criterion" ],
"benefits": [ "Free event ticket" ],
"firsttimers": "one or two sentences",
"reserved": "",
"contact": "[contact address]"
}
The page requires the four dates as planned, every structure format with its slots and no format outside the structure, the cap, every drafted criterion with no routing to a missing open space, only confirmed benefits, when identity enters under blind review, and the held-slot count when the call states it.
Worked examples - every lane, every sample
These are the page's three free samples (fictional events and people; every date is illustrative). Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped as the validating model wrote it; the last line is the page's own check. Every sample has both lanes.
Mirrowmere DevOps Conference (a first community call) - brief
Dates make a long window (the page's pick: long window); 3 formats in the call; form pick extended; review pick named; 2 of 4 gates.
Input:
{
"task": "brief",
"event": "Event: Mirrowmere DevOps Conference 2027; event date 2027-09-23; a community conference - the call fills the program; first CFP; a compounding annual asset\nAs of: 2026-11-02 (46.4 weeks before the event); program announcement 2027-06-30\nTeam: review committee 3; mentor pool no; international speakers yes; diversity a stated goal yes; outreach capacity no; past accepted proposals to publish no\nStructure: open-space block no; fast-moving field no; slots held outside the call 0\nLast edition: no data",
"design": "Timeline as planned: opens 2027-01-15, closes 2027-04-30, notifies 2027-05-28, event 2027-09-23 (open 15 weeks; closes 20.9 weeks out; review 4 weeks; speakers hear back 16.9 weeks out)\nFormats (structure / call / slots / form fields):\n Talk (30 min) | in structure yes | in call yes | slots 18 | fields: Title; Abstract (100 words); Why me; Audience level; Bio; Prior talk links\n Lightning talk (5 min) | in structure yes | in call yes | slots 6 | fields: Title; Abstract (100 words); Bio\n Workshop (2 h) | in structure yes | in call no | slots 2 | fields: none\n Panel | in structure no | in call yes | slots 1 | fields: Title; Panelists\nPer-person cap: 3\nAnonymization as planned: Named review\nCriteria as drafted:\n 1. No speaker spot can be bought by a sponsor - ever.\n 2. New content is favored over talks already given elsewhere.\n 3. We actively favor new speakers.\n 4. Product and tool talks go to the open space sessions.\nBenefits as drafted:\n Free event ticket | confirmed\n Travel reimbursement | NOT confirmed",
"facts": "Timeline: the dates make a long (Long window); the page's pick is long (a first edition reading CFP volume as demand, international speakers with visa lead times)\nFormats the call offers: Talk (30 min) (18 slots), Lightning talk (5 min) (6 slots), Panel (1 slots)\nForm depth: the fields make standard; the page's pick is extended\nAnonymization: planned named; the page's pick is named\nCriteria against the six community-pole points: No sponsor-bought speaker spots - present; New content favored - present; Local and out-of-town balance - MISSING; New speakers favored - present; Bold subjects encouraged - MISSING; Product talks routed to open space (or a lesson-transfer test) - present\nBenefits the call may state (confirmed): Free event ticket\nFirst-time-speaker support: encouragement (a first edition has no archive - the criteria and a split form do the calibrating)\nGates (2 of 4): The timeline sits at or above the sourced floor passed; The call offers exactly the structure's formats MISSING; Review criteria are published in the call passed; Benefits state the free-ticket floor and nothing unconfirmed MISSING\nSignals to record: Submissions-to-slot ratio at close (self-set threshold); First-time-submitter share (self-set threshold); Where in the window submissions arrive - a dead middle blames the promotion (self-set)\nFindings (10):\n[info] sustain (promotion): A long window is a promotion commitment, not just an early open date: plan posts across the whole window, or it sits silent in the middle.\n[fix] format_extra (formats): The call offers formats the structure does not have: Panel - submissions nobody can schedule.\n[fix] format_missing (formats): The structure has formats the call leaves out: Workshop (2 h).\n[check] depth (form): The fields make a standard form; the page's pick is extended (formats with different requirements).\n[check] crit_missing (criteria): Not in the published criteria: local and out-of-town balance; bold subjects encouraged.\n[fix] no_open_space (criteria): The criteria route product talks to an open space the structure does not have - publish the lesson-transfer test instead.\n[check] lesson_test (criteria): No open-space block: publish the lesson-transfer test so a founder can self-check - would the audience keep the lesson if they forgot the product name tomorrow?\n[fix] unconfirmed (benefits): Unconfirmed, so not in the call: Travel reimbursement - the call states exactly what speakers get and nothing the budget has not confirmed.\n[check] outreach (promotion): Diversity is a goal but nobody will reach out - under-represented people in tech are much less likely to respond to an open call.\n[check] gate (readiness): Before the call goes public: 2 of 4 - missing: the call offers exactly the structure's formats; benefits state the free-ticket floor and nothing unconfirmed (self-set)."
}
Reply:
{
"task": "brief",
"summary": "Long window for this first CFP; the most important fix is matching the call's formats to the structure (add Workshop, drop Panel); settle first whether travel reimbursement is actually funded.",
"timeline": {
"pick": "long",
"planned": "long",
"open": "2027-01-15",
"close": "2027-04-30",
"notify": "2027-05-28",
"note": "The long window gives international speakers time for visas and travel while a first edition reads CFP volume as demand."
},
"formats": [
{
"format": "Talk (30 min)",
"structure": true,
"call": true,
"note": "Keep this format as currently offered."
},
{
"format": "Lightning talk (5 min)",
"structure": true,
"call": true,
"note": "Keep this format as currently offered."
},
{
"format": "Workshop (2 h)",
"structure": true,
"call": false,
"note": "Add this format to the call since the structure already includes it."
},
{
"format": "Panel",
"structure": false,
"call": true,
"note": "Drop this format from the call since the structure does not include it."
}
],
"form": {
"pick": "extended",
"planned": "standard",
"cap": 3,
"note": "The drafted fields read as a standard-depth form, but the page recommends extended depth since the formats carry different requirements."
},
"review": {
"pick": "named",
"planned": "named",
"note": "With a 3-person committee reviewing by name, publishing full criteria is what keeps the review honest."
},
"criteria": {
"missing": [
"Local and out-of-town balance",
"Bold subjects encouraged"
],
"note": "Some community-pole points are missing from the draft, and it routes product talks to an open space the structure does not have."
},
"benefits": {
"state": [
"Free event ticket"
],
"drop": [
"Travel reimbursement"
],
"note": "Travel reimbursement is not yet confirmed by the budget, so it must stay out of the call until it is."
},
"support": {
"pick": "encouragement",
"note": "With no past proposals to draw worked examples from, encouragement copy together with the published criteria and split form fields does the calibrating this first time."
},
"measurement": [
{
"metric": "Submissions-to-slot ratio at close",
"revision": "If the ratio comes in high across the Talk and Lightning formats, that argues for adding review capacity rather than opening more slots next edition."
},
{
"metric": "First-time-submitter share",
"revision": "A low share here would prompt building a mentor pool so next edition can offer proposal mentorship instead of only encouragement."
},
{
"metric": "Where in the window submissions arrive",
"revision": "A dead middle would prompt scheduling promotion posts across the whole long window instead of only at the open and close dates."
}
],
"caveats": [
"The call offers Panel, a format the structure does not include, so submissions for it cannot be scheduled.",
"The structure includes Workshop (2 h), a format the call leaves out.",
"The criteria route product talks to an open space the structure does not have; publish the lesson-transfer test instead.",
"Travel reimbursement is not confirmed by the budget, so it should not appear in the call."
]
}
The page's check: kept - nothing to flag
Mirrowmere DevOps Conference (a first community call) - call
Dates make a long window (the page's pick: long window); 3 formats in the call; form pick extended; review pick named; 2 of 4 gates.
Input:
{
"task": "call",
"event": "Event: Mirrowmere DevOps Conference 2027; event date 2027-09-23; a community conference - the call fills the program; first CFP; a compounding annual asset\nAs of: 2026-11-02 (46.4 weeks before the event); program announcement 2027-06-30\nTeam: review committee 3; mentor pool no; international speakers yes; diversity a stated goal yes; outreach capacity no; past accepted proposals to publish no\nStructure: open-space block no; fast-moving field no; slots held outside the call 0\nLast edition: no data",
"design": "Timeline as planned: opens 2027-01-15, closes 2027-04-30, notifies 2027-05-28, event 2027-09-23 (open 15 weeks; closes 20.9 weeks out; review 4 weeks; speakers hear back 16.9 weeks out)\nFormats (structure / call / slots / form fields):\n Talk (30 min) | in structure yes | in call yes | slots 18 | fields: Title; Abstract (100 words); Why me; Audience level; Bio; Prior talk links\n Lightning talk (5 min) | in structure yes | in call yes | slots 6 | fields: Title; Abstract (100 words); Bio\n Workshop (2 h) | in structure yes | in call no | slots 2 | fields: none\n Panel | in structure no | in call yes | slots 1 | fields: Title; Panelists\nPer-person cap: 3\nAnonymization as planned: Named review\nCriteria as drafted:\n 1. No speaker spot can be bought by a sponsor - ever.\n 2. New content is favored over talks already given elsewhere.\n 3. We actively favor new speakers.\n 4. Product and tool talks go to the open space sessions.\nBenefits as drafted:\n Free event ticket | confirmed\n Travel reimbursement | NOT confirmed",
"facts": "Timeline: the dates make a long (Long window); the page's pick is long (a first edition reading CFP volume as demand, international speakers with visa lead times)\nFormats the call offers: Talk (30 min) (18 slots), Lightning talk (5 min) (6 slots), Panel (1 slots)\nForm depth: the fields make standard; the page's pick is extended\nAnonymization: planned named; the page's pick is named\nCriteria against the six community-pole points: No sponsor-bought speaker spots - present; New content favored - present; Local and out-of-town balance - MISSING; New speakers favored - present; Bold subjects encouraged - MISSING; Product talks routed to open space (or a lesson-transfer test) - present\nBenefits the call may state (confirmed): Free event ticket\nFirst-time-speaker support: encouragement (a first edition has no archive - the criteria and a split form do the calibrating)\nGates (2 of 4): The timeline sits at or above the sourced floor passed; The call offers exactly the structure's formats MISSING; Review criteria are published in the call passed; Benefits state the free-ticket floor and nothing unconfirmed MISSING\nSignals to record: Submissions-to-slot ratio at close (self-set threshold); First-time-submitter share (self-set threshold); Where in the window submissions arrive - a dead middle blames the promotion (self-set)\nFindings (10):\n[info] sustain (promotion): A long window is a promotion commitment, not just an early open date: plan posts across the whole window, or it sits silent in the middle.\n[fix] format_extra (formats): The call offers formats the structure does not have: Panel - submissions nobody can schedule.\n[fix] format_missing (formats): The structure has formats the call leaves out: Workshop (2 h).\n[check] depth (form): The fields make a standard form; the page's pick is extended (formats with different requirements).\n[check] crit_missing (criteria): Not in the published criteria: local and out-of-town balance; bold subjects encouraged.\n[fix] no_open_space (criteria): The criteria route product talks to an open space the structure does not have - publish the lesson-transfer test instead.\n[check] lesson_test (criteria): No open-space block: publish the lesson-transfer test so a founder can self-check - would the audience keep the lesson if they forgot the product name tomorrow?\n[fix] unconfirmed (benefits): Unconfirmed, so not in the call: Travel reimbursement - the call states exactly what speakers get and nothing the budget has not confirmed.\n[check] outreach (promotion): Diversity is a goal but nobody will reach out - under-represented people in tech are much less likely to respond to an open call.\n[check] gate (readiness): Before the call goes public: 2 of 4 - missing: the call offers exactly the structure's formats; benefits state the free-ticket floor and nothing unconfirmed (self-set)."
}
Reply:
{
"task": "call",
"title": "Call for papers - Mirrowmere DevOps Conference 2027",
"intro": "Mirrowmere DevOps Conference 2027 is a new community conference for DevOps practitioners, and this is our first call for papers. We are looking for speakers from anywhere, including first-time speakers, with ideas on DevOps practice, tooling and culture. If you have a talk, lightning talk or workshop idea, we want to hear from you.",
"dates": [
{
"label": "Call opens",
"date": "2027-01-15"
},
{
"label": "Call closes",
"date": "2027-04-30"
},
{
"label": "Speakers hear back",
"date": "2027-05-28"
},
{
"label": "Event",
"date": "2027-09-23"
}
],
"formats": [
{
"format": "Talk (30 min)",
"slots": 18,
"submit": "Send a title, a roughly 100-word abstract, a short note on why you are the right person to give this talk, your audience level, a bio and links to any prior talks."
},
{
"format": "Lightning talk (5 min)",
"slots": 6,
"submit": "Send a title, a roughly 100-word abstract and a bio."
},
{
"format": "Workshop (2 h)",
"slots": 2,
"submit": "[fields to set]"
}
],
"cap": "Each person may submit up to 3 proposals across all formats.",
"review": "Proposals are reviewed by name, not anonymized, by our 3-person review committee. We publish the criteria we use below so you know what we are weighing when we select talks.",
"criteria": [
"No speaker spot can be bought by a sponsor - ever.",
"New content is favored over talks already given elsewhere.",
"We actively favor new speakers.",
"For product and tool talks: would the audience still keep the lesson if they forgot the product name tomorrow?"
],
"benefits": [
"Free event ticket"
],
"firsttimers": "This is our first call for papers, so we have no past accepted talks to show you yet - but we actively favor new speakers, and the submission form lets you make the case for yourself alongside your idea. If you have never spoken at a conference before, we still want to hear from you.",
"reserved": "",
"contact": "[contact address]"
}
The page's check: kept - nothing to flag
Quillhaven JS Day (a repeat call on a short runway) - brief
Dates make a floor-compressed (the page's pick: floor-compressed); 1 formats in the call; form pick standard; review pick blind; 4 of 4 gates.
Input:
{
"task": "brief",
"event": "Event: Quillhaven JS Day 2027; event date 2027-03-18; a community conference - the call fills the program; repeat CFP; a compounding annual asset\nAs of: 2026-12-01 (15.3 weeks before the event); program announcement 2027-02-15\nTeam: review committee 5; mentor pool no; international speakers no; diversity a stated goal yes; outreach capacity yes; past accepted proposals to publish yes\nStructure: open-space block yes; fast-moving field yes; slots held outside the call 2 (count stated in the call: yes)\nLast edition: 34 submissions for 8 slots",
"design": "Timeline as planned: opens 2026-12-07, closes 2027-01-28, notifies 2027-02-11, event 2027-03-18 (open 7.4 weeks; closes 7 weeks out; review 2 weeks; speakers hear back 5 weeks out)\nFormats (structure / call / slots / form fields):\n Talk (25 min) | in structure yes | in call yes | slots 8 | fields: Title; Abstract (250 words); Bio\nPer-person cap: 2\nAnonymization as planned: Mandatory blind; identity enters: NOT STATED; speakers told to self-police identifying details: no\nCriteria as drafted:\n 1. No sponsor can buy a speaking slot.\n 2. Unpublished material is favored.\n 3. We balance local and out-of-town speakers.\n 4. First-time speakers are actively favored.\n 5. Bold subjects are welcome.\n 6. Vendor and product talks are routed to the open space.\nBenefits as drafted:\n Free event ticket | confirmed\n Honorarium of 200 dollars | confirmed",
"facts": "Timeline: the dates make a floor (Floor-compressed); the page's pick is floor (under about 16 weeks of runway - only the compressed window fits)\nFormats the call offers: Talk (25 min) (8 slots)\nForm depth: the fields make minimal; the page's pick is standard\nAnonymization: planned blind; the page's pick is blind\nCriteria against the six community-pole points: No sponsor-bought speaker spots - present; New content favored - present; Local and out-of-town balance - present; New speakers favored - present; Bold subjects encouraged - present; Product talks routed to open space (or a lesson-transfer test) - present\nBenefits the call may state (confirmed): Free event ticket; Honorarium of 200 dollars\nFirst-time-speaker support: examples (past accepted proposals to publish as calibration)\nGates (4 of 4): The timeline sits at or above the sourced floor passed; The call offers exactly the structure's formats passed; Review criteria are published in the call passed; Benefits state the free-ticket floor and nothing unconfirmed passed\nSignals to record: Submissions-to-slot ratio at close (self-set threshold); First-time-submitter share (self-set threshold); Where in the window submissions arrive - a dead middle blames the promotion (self-set)\nFindings (9):\n[fix] late_notify (timeline): Speakers hear back 5 weeks before the event - inside the 4-6 weeks they need for travel.\n[check] depth (form): The fields make a minimal form; the page's pick is standard.\n[check] one_box (form): An abstract with no separate context (\"why me\") field - one text box cannot be scored on topic and speaker fit at once.\n[check] long_abstract (form): The abstract allows 250 words - cap it near 100; the title carries twice the abstract's weight.\n[fix] theater (review): Mandatory blind review with no stated point where identity enters - publish the two-phase mechanics, or it is blind-review theater.\n[check] self_police (review): Tell speakers to keep identifying details out of titles and free text - the platform hides the author field, not what the author wrote.\n[info] tax (benefits): Honorarium of 200 dollars: direct payment can bring tax, visa and conflict-of-interest or employment-agreement issues - check before publishing.\n[info] ratio (measurement): Last edition drew 34 submissions for 8 slots (4.3 to 1). Oversubscription is the normal state of a healthy call - the fix for volume is review capacity.\n[info] gate (readiness): Before the call goes public: 4 of 4 (self-set)."
}
Reply:
{
"task": "brief",
"summary": "Floor-compressed timeline for this repeat CFP; fix the 5-week notify-to-event gap so speakers have travel time; settle when identity enters the mandatory blind review first.",
"timeline": {
"pick": "floor",
"planned": "floor",
"open": "2026-12-07",
"close": "2027-01-28",
"notify": "2027-02-11",
"note": "The compressed floor window is the only one that fits this runway, and notifying 5 weeks before the event leaves speakers less than the 4-6 weeks they need to arrange travel."
},
"formats": [
{
"format": "Talk (25 min)",
"structure": true,
"call": true,
"note": "Keep this format as currently offered."
}
],
"form": {
"pick": "standard",
"planned": "minimal",
"cap": 2,
"note": "The drafted fields make this a minimal form; the page recommends standard depth with a separate context field."
},
"review": {
"pick": "blind",
"planned": "blind",
"note": "Run the review in two stated phases, publish exactly when identity enters, and tell speakers to keep identifying details out of titles and free text."
},
"criteria": {
"missing": [],
"note": "All six community-pole points are already present in the draft, including routing vendor and product talks to the open space the structure has."
},
"benefits": {
"state": [
"Free event ticket",
"Honorarium of 200 dollars"
],
"drop": [],
"note": "A direct cash honorarium can raise tax, visa and conflict-of-interest questions worth checking before this goes out."
},
"support": {
"pick": "examples",
"note": "Publishing past accepted proposals as worked examples does the calibration for this repeat CFP."
},
"measurement": [
{
"metric": "Submissions-to-slot ratio at close",
"revision": "If the ratio comes in far above last edition's 34 submissions for 8 slots, that argues for adding review capacity rather than more slots next time."
},
{
"metric": "First-time-submitter share",
"revision": "A low first-time share despite having outreach capacity would prompt directing that capacity specifically at under-represented submitters next edition."
}
],
"caveats": [
"Speakers hear back only 5 weeks before the event, inside the 4-6 weeks they need for travel.",
"Mandatory blind review does not state when identity enters the process.",
"The abstract field allows 250 words instead of near 100, and the title already carries twice the abstract's weight.",
"The form has no separate context field, so one box cannot score topic and speaker fit at once."
]
}
The page's check: kept - nothing to flag
Quillhaven JS Day (a repeat call on a short runway) - call
Dates make a floor-compressed (the page's pick: floor-compressed); 1 formats in the call; form pick standard; review pick blind; 4 of 4 gates.
Input:
{
"task": "call",
"event": "Event: Quillhaven JS Day 2027; event date 2027-03-18; a community conference - the call fills the program; repeat CFP; a compounding annual asset\nAs of: 2026-12-01 (15.3 weeks before the event); program announcement 2027-02-15\nTeam: review committee 5; mentor pool no; international speakers no; diversity a stated goal yes; outreach capacity yes; past accepted proposals to publish yes\nStructure: open-space block yes; fast-moving field yes; slots held outside the call 2 (count stated in the call: yes)\nLast edition: 34 submissions for 8 slots",
"design": "Timeline as planned: opens 2026-12-07, closes 2027-01-28, notifies 2027-02-11, event 2027-03-18 (open 7.4 weeks; closes 7 weeks out; review 2 weeks; speakers hear back 5 weeks out)\nFormats (structure / call / slots / form fields):\n Talk (25 min) | in structure yes | in call yes | slots 8 | fields: Title; Abstract (250 words); Bio\nPer-person cap: 2\nAnonymization as planned: Mandatory blind; identity enters: NOT STATED; speakers told to self-police identifying details: no\nCriteria as drafted:\n 1. No sponsor can buy a speaking slot.\n 2. Unpublished material is favored.\n 3. We balance local and out-of-town speakers.\n 4. First-time speakers are actively favored.\n 5. Bold subjects are welcome.\n 6. Vendor and product talks are routed to the open space.\nBenefits as drafted:\n Free event ticket | confirmed\n Honorarium of 200 dollars | confirmed",
"facts": "Timeline: the dates make a floor (Floor-compressed); the page's pick is floor (under about 16 weeks of runway - only the compressed window fits)\nFormats the call offers: Talk (25 min) (8 slots)\nForm depth: the fields make minimal; the page's pick is standard\nAnonymization: planned blind; the page's pick is blind\nCriteria against the six community-pole points: No sponsor-bought speaker spots - present; New content favored - present; Local and out-of-town balance - present; New speakers favored - present; Bold subjects encouraged - present; Product talks routed to open space (or a lesson-transfer test) - present\nBenefits the call may state (confirmed): Free event ticket; Honorarium of 200 dollars\nFirst-time-speaker support: examples (past accepted proposals to publish as calibration)\nGates (4 of 4): The timeline sits at or above the sourced floor passed; The call offers exactly the structure's formats passed; Review criteria are published in the call passed; Benefits state the free-ticket floor and nothing unconfirmed passed\nSignals to record: Submissions-to-slot ratio at close (self-set threshold); First-time-submitter share (self-set threshold); Where in the window submissions arrive - a dead middle blames the promotion (self-set)\nFindings (9):\n[fix] late_notify (timeline): Speakers hear back 5 weeks before the event - inside the 4-6 weeks they need for travel.\n[check] depth (form): The fields make a minimal form; the page's pick is standard.\n[check] one_box (form): An abstract with no separate context (\"why me\") field - one text box cannot be scored on topic and speaker fit at once.\n[check] long_abstract (form): The abstract allows 250 words - cap it near 100; the title carries twice the abstract's weight.\n[fix] theater (review): Mandatory blind review with no stated point where identity enters - publish the two-phase mechanics, or it is blind-review theater.\n[check] self_police (review): Tell speakers to keep identifying details out of titles and free text - the platform hides the author field, not what the author wrote.\n[info] tax (benefits): Honorarium of 200 dollars: direct payment can bring tax, visa and conflict-of-interest or employment-agreement issues - check before publishing.\n[info] ratio (measurement): Last edition drew 34 submissions for 8 slots (4.3 to 1). Oversubscription is the normal state of a healthy call - the fix for volume is review capacity.\n[info] gate (readiness): Before the call goes public: 4 of 4 (self-set)."
}
Reply:
{
"task": "call",
"title": "Call for papers - Quillhaven JS Day 2027",
"intro": "Quillhaven JS Day 2027 is our community conference for the fast-moving JavaScript ecosystem, and we are opening our call for papers again this year. We want to hear from local and out-of-town developers with new, unpublished ideas, and we especially encourage first-time and under-represented speakers to submit. There is also an open space track for vendor and product talks that do not fit the main program.",
"dates": [
{
"label": "Call opens",
"date": "2026-12-07"
},
{
"label": "Call closes",
"date": "2027-01-28"
},
{
"label": "Speakers hear back",
"date": "2027-02-11"
},
{
"label": "Event",
"date": "2027-03-18"
}
],
"formats": [
{
"format": "Talk (25 min)",
"slots": 8,
"submit": "Send a title, a 250-word abstract and a bio."
}
],
"cap": "Each person may submit up to 2 proposals.",
"review": "Proposals are reviewed blind, in two phases: our 5-person committee first scores each proposal with the speaker's identity hidden, and [when identity enters]. Please keep your name, employer and other identifying details out of your title and the body of your proposal.",
"criteria": [
"No sponsor can buy a speaking slot.",
"Unpublished material is favored.",
"We balance local and out-of-town speakers.",
"First-time speakers are actively favored.",
"Bold subjects are welcome.",
"Vendor and product talks are routed to the open space."
],
"benefits": [
"Free event ticket",
"Honorarium of 200 dollars"
],
"firsttimers": "If you have not spoken before, we publish examples of past accepted proposals so you can see what a successful submission looks like. First-time speakers are actively favored in our review.",
"reserved": "2 additional slots are held outside this call for invited speakers.",
"contact": "[contact address]"
}
The page's check: kept - nothing to flag
Brindlewick User Summit (a vendor call, eight weeks out) - brief
Dates make a below the floor - open call deleted (the page's pick: below the floor - open call deleted); 1 formats in the call; form pick minimal; review pick named; 1 of 4 gates.
Input:
{
"task": "brief",
"event": "Event: Brindlewick User Summit; event date 2027-02-10; a vendor or user conference - the call fills a community slice; first CFP; a one-off\nAs of: 2026-12-10 (8.9 weeks before the event); program announcement 2027-01-20\nTeam: review committee 2; mentor pool no; international speakers no; diversity a stated goal no; outreach capacity no; past accepted proposals to publish no\nStructure: open-space block no; fast-moving field no; slots held outside the call 0\nLast edition: no data",
"design": "Timeline as planned: opens 2026-12-15, closes 2027-01-05, notifies 2027-01-12, event 2027-02-10 (open 3 weeks; closes 5.1 weeks out; review 1 weeks; speakers hear back 4.1 weeks out)\nFormats (structure / call / slots / form fields):\n Customer story (20 min) | in structure yes | in call yes | slots 4 | fields: Title; Abstract; Company\nPer-person cap: not set\nAnonymization as planned: Opt-in anonymous\nCriteria as drafted: none\nBenefits as drafted:\n Free event ticket | confirmed\n Hotel for two nights | NOT confirmed",
"facts": "Timeline: the dates make a below (Below the floor - open call deleted); the page's pick is below (the event is closer than about 10 weeks)\nFormats the call offers: Customer story (20 min) (4 slots)\nForm depth: the fields make minimal; the page's pick is minimal\nAnonymization: planned optin; the page's pick is named\nCriteria against the six community-pole points: No sponsor-bought speaker spots - MISSING; New content favored - MISSING; Local and out-of-town balance - MISSING; New speakers favored - MISSING; Bold subjects encouraged - MISSING; Product talks routed to open space (or a lesson-transfer test) - MISSING\nBenefits the call may state (confirmed): Free event ticket\nFirst-time-speaker support: encouragement (a first edition has no archive - the criteria and a split form do the calibrating)\nGates (1 of 4): The timeline sits at or above the sourced floor MISSING; The call offers exactly the structure's formats passed; Review criteria are published in the call MISSING; Benefits state the free-ticket floor and nothing unconfirmed MISSING\nSignals to record: Submissions-to-slot ratio at close (self-set threshold); First-time-submitter share (self-set threshold); Where in the window submissions arrive - a dead middle blames the promotion (self-set)\nFindings (13):\n[fix] short_open (timeline): Open for 3 weeks - keep the call open for at least a month.\n[fix] late_close (timeline): Closes 5.1 weeks before the event - close at least 6-8 weeks out; people need at least 4-6 weeks to arrange travel or time off.\n[fix] short_review (timeline): Only 1 week between close and notification - leave at least 2 weeks to consider proposals and fill gaps.\n[fix] deleted (timeline): The event is 8.9 weeks away - below the floor the open CFP is deleted, not compressed: go invited through speaker sourcing.\n[check] one_box (form): An abstract with no separate context (\"why me\") field - one text box cannot be scored on topic and speaker fit at once.\n[check] no_cap (form): No per-person submission cap - set one (the sourced example is three proposals per person).\n[info] vendor (formats): A vendor or user conference: the call fills a community slice of a curated program, and its timeline couples to the vendor's launch calendar.\n[check] optin (review): Opt-in anonymity is a platform capability, not a fairness posture - a pool mixing named and anonymous submissions is not reviewed on one footing.\n[info] anon_pick (review): The page's pick is named review (named, with published criteria doing the fairness work).\n[fix] no_criteria (criteria): No review criteria in the call - unstated criteria make rejections read as arbitrary.\n[fix] unconfirmed (benefits): Unconfirmed, so not in the call: Hotel for two nights - the call states exactly what speakers get and nothing the budget has not confirmed.\n[info] equity (promotion): Under-represented people in tech are much less likely to respond to an open call; the call alone will not reach them.\n[check] gate (readiness): Before the call goes public: 1 of 4 - missing: the timeline sits at or above the sourced floor; review criteria are published in the call; benefits state the free-ticket floor and nothing unconfirmed (self-set)."
}
Reply:
{
"task": "brief",
"summary": "Below the floor for this first CFP; the most important fix is deleting the open call and sourcing speakers by invitation instead; settle whether Hotel for two nights is funded before publishing anything else.",
"timeline": {
"pick": "below",
"planned": "below",
"open": "2026-12-15",
"close": "2027-01-05",
"notify": "2027-01-12",
"note": "At 8.9 weeks out this sits below the floor, so the open call should be deleted and the program filled through invited speaker sourcing instead."
},
"formats": [
{
"format": "Customer story (20 min)",
"structure": true,
"call": true,
"note": "Keep this format as currently offered."
}
],
"form": {
"pick": "minimal",
"planned": "minimal",
"cap": null,
"note": "No per-person cap is set yet; the sourced example suggests three proposals per person."
},
"review": {
"pick": "named",
"planned": "optin",
"note": "Opt-in anonymity is a platform toggle, not a fairness posture, so the page recommends named review backed by published criteria instead."
},
"criteria": {
"missing": [
"No sponsor-bought speaker spots",
"New content favored",
"Local and out-of-town balance",
"New speakers favored",
"Bold subjects encouraged",
"Product talks routed to open space (or a lesson-transfer test)"
],
"note": "None of the six community-pole points are drafted yet, so unstated criteria would make any rejection look arbitrary."
},
"benefits": {
"state": [
"Free event ticket"
],
"drop": [
"Hotel for two nights"
],
"note": "Hotel for two nights is not yet confirmed by the budget, so it must stay out of the call until it is."
},
"support": {
"pick": "encouragement",
"note": "With no past proposals and no mentor pool, encouragement copy together with published criteria would do the calibrating once criteria exist."
},
"measurement": [
{
"metric": "Submissions-to-slot ratio at close",
"revision": "A low ratio against the 4 slots offered would suggest promotion needs work if an open call is tried again."
},
{
"metric": "First-time-submitter share",
"revision": "A low first-time share would flag that active outreach is needed even though diversity is not currently a stated goal."
}
],
"caveats": [
"The call is open for only 3 weeks; keep it open at least a month.",
"The call closes 5.1 weeks before the event; close at least 6-8 weeks out so people have 4-6 weeks to arrange travel or time off.",
"Only 1 week sits between close and notification; leave at least 2 weeks to consider proposals and fill gaps.",
"The event is 8.9 weeks away, below the floor; delete the open call and go invited through speaker sourcing instead."
]
}
The page's check: kept - nothing to flag
The form-fields CSV for the first sample
format,in_structure,in_call,slots,field
Talk (30 min),yes,yes,18,Title
Talk (30 min),yes,yes,18,Abstract (100 words)
Talk (30 min),yes,yes,18,Why me
Talk (30 min),yes,yes,18,Audience level
Talk (30 min),yes,yes,18,Bio
Talk (30 min),yes,yes,18,Prior talk links
Lightning talk (5 min),yes,yes,6,Title
Lightning talk (5 min),yes,yes,6,Abstract (100 words)
Lightning talk (5 min),yes,yes,6,Bio
Workshop (2 h),yes,no,2,
Panel,no,yes,1,Title
Panel,no,yes,1,Panelists
Build the input yourself
Load the engine in node (global.window = global; require("./cfpkit.js"); require("./cfpreply.js")), then:
const K = CfpKit, R = CfpReply;
const f = K.form({ name: "My Conf", event: "2027-09-23", open: "2027-04-01", close: "2027-06-01", notify: "2027-06-20", pole: "community",
formats: fs.readFileSync("formats.csv", "utf8"), criteria: fs.readFileSync("criteria.txt", "utf8"), benefits: "benefit,confirmed\nFree event ticket,yes" });
// f.rung, f.rec, f.openWeeks, f.closeOut, f.review, f.offered, f.depth, f.recDepth, f.anon, f.recAnon, f.critHits
// f.published (confirmed benefits), f.support, f.gate, f.findings
K.formCSV(f) // format, in_structure, in_call, slots, field
const b = K.buildInput(f, "brief"); // or "call"
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "brief");
// R.check(reply, b) // {status: kept or flagged, notes}
Get a token for the steps below from /tokens.html.
Step 1 — a tiny client helper
One function that sends JSON and unwraps the response. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.
BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here" # from https://cfp-blueprint.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://cfp-blueprint.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// cfp-blueprint.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://cfp-blueprint.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "paste-your-token-here" // from https://cfp-blueprint.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// CfpBlueprint.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class CfpBlueprint {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://cfp-blueprint.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "paste-your-token-here" # from https://cfp-blueprint.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "paste-your-token-here"; // from https://cfp-blueprint.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "paste-your-token-here"; // from https://cfp-blueprint.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
Step 2 — a token
A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"cfp-blueprint"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "cfp-blueprint"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "cfp-blueprint" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "cfp-blueprint"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "cfp-blueprint"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "cfp-blueprint" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "cfp-blueprint"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "cfp-blueprint" }))!["token"]!.GetValue<string>();
Step 3 — GET /me
Who the token is (subject_type: user or guest) and the credit balance.
api GET /me | jq '.data | {subject_type, credits}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["credits"]}");
Step 4 — POST /estimate
Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.
# input.json: the run input object - {"task": "brief", "event": "Event: ...; event date 2027-09-23; a community conference ...", "design": "Timeline as planned: opens 2027-01-15, closes ...", "facts": "Timeline: the dates make a long ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "brief", "event": "Event: ...; event date 2027-09-23; a community conference ...", "design": "Timeline as planned: opens 2027-01-15, closes ...", "facts": "Timeline: the dates make a long ..."}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "brief" or "call", event, design, facts} (see the input table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
Step 5 — POST /run, then poll the job
Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses cfp-blueprint:<task>:<inputHash>:a<attempt> - the lane is part of the key - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A brief carries summary, timeline, formats, form, review, criteria, benefits, support, measurement and caveats; a call carries title, intro, dates, formats, cap, review, criteria, benefits, firsttimers, reserved and contact. The code below prints the headline parts of either (list items are plain strings).
KEY="cfp-blueprint:brief:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}'
jq -r '.summary // .format, .note // .intro, ((.signals // .caveats)[] | "\(.signal // .caveat): \(.text // .text)")' reply.json
jq -r '.review // .note // empty' reply.json # how proposals are reviewed (call)
import hashlib, time
key = "cfp-blueprint:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("summary"))
print(reply.get("note") or reply.get("intro", ""))
for s in reply.get("signals") or reply.get("caveats") or []:
print(s.get("signal", s.get("caveat")), "-", s.get("text", s.get("text")))
if reply.get("review"):
print("review:", reply["review"])
import { createHash } from "node:crypto";
const key = `cfp-blueprint:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.summary);
console.log(reply.note ?? reply.intro);
for (const s of reply.signals || reply.caveats || []) console.log(s.signal ?? s.caveat, "-", s.text ?? s.text);
if (reply.review) console.log("review:", reply.review);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "cfp-blueprint:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
items, _ := reply["signals"].([]any) // signals and caveats are plain strings
fmt.Println(j["charged_credits"], j["truncated"], reply["summary"])
fmt.Println(reply["note"], reply["intro"])
for _, x := range items {
c := x.(map[string]any)
fmt.Println(c["signal"], "-", c["status"])
}
if fix, ok := reply["review"].(string); ok && fix != "" {
fmt.Println("review:", fix)
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "cfp-blueprint:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply.get("summary"));
System.out.println(reply.path("note").asText(reply.path("intro").asText()));
for (JsonNode s : reply.has("signals") ? reply.path("signals") : reply.path("caveats")) System.out.println(s.path("signal").asText(s.path("caveat").asText()) + " - " + s.path("status").asText(s.path("text").asText()));
if (!reply.path("review").asText().isEmpty()) System.out.println("review: " + reply.get("review").asText());
require "digest"
key = "cfp-blueprint:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["summary"]].inspect
puts reply["note"] || reply["intro"]
(reply["signals"] || reply["caveats"] || []).each { |s| puts "#{s["signal"] || s["caveat"]} - #{s["text"] || s["text"]}" }
puts "review: #{reply["review"]}" unless reply["review"].to_s.empty?
$key = "cfp-blueprint:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", ($reply["summary"] ?? ""), "\n";
echo $reply["note"] ?? $reply["intro"] ?? "", "\n";
foreach ($reply["signals"] ?? $reply["caveats"] ?? [] as $s) echo $s["signal"] ?? $s["caveat"], " - ", $s["text"] ?? $s["text"], "\n";
if (!empty($reply["review"])) echo "review: ", $reply["review"], "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"cfp-blueprint:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>());
Console.WriteLine($"{j["charged_credits"]} {reply!["summary"]}");
Console.WriteLine($"{reply["note"] ?? reply["intro"]}");
foreach (var s in (reply["signals"] ?? reply["caveats"]) as JsonArray ?? new JsonArray()) Console.WriteLine($"{s!["signal"] ?? s["caveat"]} - {s["text"] ?? s["text"]}");
if (reply["review"] is JsonNode fix && fix.GetValue<string>() != "") Console.WriteLine("review: " + fix);
Step 6 — POST /run-stream
The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.
# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: cfp-blueprint:brief:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "brief", "event": "Event: ...; event date 2027-09-23; a community conference ...", "design": "Timeline as planned: opens 2027-01-15, closes ...", "facts": "Timeline: the dates make a long ..."}
# - see the input table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "cfp-blueprint:brief:" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `cfp-blueprint:brief:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "cfp-blueprint:brief:"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "cfp-blueprint:brief:" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "cfp-blueprint:brief:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: cfp-blueprint:brief:" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"cfp-blueprint:brief:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);