Guides
Pages
Hunchwire hosts the site and your agent writes its pages. You can add your own domain.
Use your agent. Paste this into it:
Read hunchwire.com/docs/pages and write a landing page for my idea.
Pages
A page has id, path, version, media_type, updated_at and content.
version: the key of the page's version. Only an HTML page of a project testing versions has one.content: only when you read one page withGET. Adding or changing a page returns it withoutcontent, since you just sent it. Images havebyte_sizeinstead ofcontent.
The path sets how a page is served:
-
A path ending in
.css,.js,.svg,.txtor.mdis served as that type..txtistext/plainand.mdistext/markdown. -
An image path is served as its image type.
-
Any other path is an HTML page.
-
Hunchwire itself uses
/embed.js,/e/…,/up,/robots.txtand/sitemap.xml. -
/404is the page a missing path shows, with status 404. Without one, visitors get a plain "Page not found" that links to/.
POST
/projects/:id/pages
Add a page.
Details
- In a project testing versions, each HTML page belongs to one version. Send
"version": "B"inpage. - Without a version, the page is a 422. That way you never give one version a page the others lack by accident.
- Files (
.css,.js, images…) are shared by every version and take noversion. - Versions never share HTML. To change a page in every version, change each one.
curl -X POST https://app.hunchwire.com/projects/:id/pages \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"page": {"path": "/pricing", "content": "<!doctype html>…"}}'
import os, requests
r = requests.post(
"https://app.hunchwire.com/projects/:id/pages",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
json={"page": {"path": "/pricing", "content": "<!doctype html>…"}},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/pages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"page": {"path": "/pricing", "content": "<!doctype html>…"}}),
})
const data = await res.json()
POST
/projects/:id/pages
with an image: upload it as a multipart file, not JSON.
Details
- PNG, JPG, WebP, GIF or AVIF, up to 5 MB. The file's contents must match its extension.
- Leave out
page[path]to use/images/<file name>. - Show it on a page with
<img src="/images/hero.png">. - To replace an image,
PATCHthe page with a newpage[file].
curl -X POST https://app.hunchwire.com/projects/:id/pages \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.post(
"https://app.hunchwire.com/projects/:id/pages",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/pages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
curl -X POST https://app.hunchwire.com/projects/:id/pages \
-H "Authorization: Bearer hw_…" \
-F "page[path]=/images/hero.png" -F "page[file]=@hero.png"
GET
/projects/:id/pages/:page_id
Get one page, with its content. Images return byte_size instead of content.
curl https://app.hunchwire.com/projects/:id/pages/:page_id \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.get(
"https://app.hunchwire.com/projects/:id/pages/:page_id",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/pages/:page_id", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
PATCH
/projects/:id/pages/:page_id
Change a page's path or content.
curl -X PATCH https://app.hunchwire.com/projects/:id/pages/:page_id \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.patch(
"https://app.hunchwire.com/projects/:id/pages/:page_id",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/pages/:page_id", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
DELETE
/projects/:id/pages/:page_id
Remove a page.
Details
- Returns
204. If live campaigns' ads lead to it, Hunchwire pauses them on Meta first and returns200withpaused_campaigns. A path another version keeps doesn't count.
curl -X DELETE https://app.hunchwire.com/projects/:id/pages/:page_id \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.delete(
"https://app.hunchwire.com/projects/:id/pages/:page_id",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/pages/:page_id", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
Write pages
/is the home page. Hunchwire adds its script to every HTML page.- Put a form where you want it with
<div data-hunchwire="waitlist"></div>or<div data-hunchwire="login"></div>. See The embed for what each does and its options. You don't add the script tag: hosted pages load it themselves. - Count clicks on any link or button with
data-hunchwire-track="plan-team". - Count something that isn't a click from your page's own script:
window.hunchwire?.track("name-typed"). Use it for typing in a field or a swipe. It's recorded as a tracked click with that name, up to 100 characters. - Hunchwire doesn't count visits, engagement or clicks from bots, automated browsers, the workspace's ignored IP addresses, or browsers opted out with
?hunchwire=ignore. See Internal traffic. - Every page also reports how long it was visible and how far it was scrolled. Hunchwire records each visit's device, browser and country. There's nothing to set up.
- To share one stylesheet, add a
/styles.csspage and link to it. Scripts work the same way, with/app.js. - Real product photos or renders sell a product better than drawings. Upload them as images.
- A site that looks like a real product, with home, pricing, sign-up and log-in pages, tests demand far better than a lone form.
- Search engines: nothing to set up.
- Each site serves
/robots.txtand/sitemap.xml. - The sitemap lists every HTML page except
/404and pages with<meta name="robots" content="noindex">. Add that tag to a page that shouldn't be found, like a fake login. - Every page with a
<head>gets a<link rel="canonical">to the site's main address, unless it has its own. That address is your connected domain when you have one, else<slug>.hunchwire.site. For Hunchwire's own site, it'shunchwire.com. /Pricingand/pricing/redirect (301) to/pricing, keeping the query string. Link pages by their lowercase path, with no trailing slash.- Hunchwire caches
.css,.js,.svg,.txt,.mdand image files, and Markdown copies, for 5 minutes. An edit can take that long to show.
- Each site serves
- AI assistants and agents: nothing to set up.
- Every page in the sitemap also has a Markdown copy of its text at its path plus
.md:/pricing.md, and/index.mdfor/. The copy names the page as canonical. - To write the copy yourself, upload a page at that path.
- To give assistants a summary of the site, add an
/llms.txtpage in the llms.txt format: a# Name, a> one-line summary, then links to the pages, ideally their.mdcopies.
- Every page in the sitemap also has a Markdown copy of its text at its path plus
Be honest with visitors. Forms must reveal that the product isn't available yet. Pages must not invent customers, testimonials or statistics.
Versions
Test several pitches at once. Each visitor gets one version of the hosted site, picked at random by weight, and keeps it for 90 days through a cookie on the site. Search engines and bots always see the first live version. Versions work only with hosted_pages.
A version has:
-
key:A,B… in the order added. Keys are never reused. -
name: its name. -
weight: its share of new visitors, relative to the other live versions. -
status:live,paused(keeps its visitors but gets no new ones) orended. -
pages: how many HTML pages it has. -
preview_url: a link that shows this version as visitors see it.nullfor an ended version.
GET
/projects/:id/versions
List the versions.
Details
- Returns
versions, oldest first, andsplit_runs: true while more than one version is live or paused. - An empty list means one version: everyone sees the same pages.
curl https://app.hunchwire.com/projects/:id/versions \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.get(
"https://app.hunchwire.com/projects/:id/versions",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/versions", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
POST
/projects/:id/versions
Add a version.
Details
- It copies the first live version's HTML pages once. The first time, the project's pages become
A"Original" and the copy becomesB. From then on, the versions are independent. - Then change only what differs:
PATCH /projects/:id/pages/:page_idon the new version's pages, whichGET /projects/:id/setuplists. - At most 4 versions can be live or paused at once. Adding one sets the split to even.
curl -X POST https://app.hunchwire.com/projects/:id/versions \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"version": {"name": "Founder story"}}'
import os, requests
r = requests.post(
"https://app.hunchwire.com/projects/:id/versions",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
json={"version": {"name": "Founder story"}},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/versions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"version": {"name": "Founder story"}}),
})
const data = await res.json()
PATCH
/projects/:id/versions/:version_id
Change a version.
Details
version_idis the version'skey, likeB.- Rename it:
{"version": {"name": "…"}}. - Pause or resume it:
"status": "paused"or"live". - Set the split:
{"split": "even"}, or{"weights": {"A": 70, "B": 30}}for the live versions. Weights add up to 100.
curl -X PATCH https://app.hunchwire.com/projects/:id/versions/:version_id \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.patch(
"https://app.hunchwire.com/projects/:id/versions/:version_id",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/versions/:version_id", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
DELETE
/projects/:id/versions/:version_id
End a version and delete its pages.
Details
- Its results stay in Analytics. You can't delete the last version.
- Live campaigns whose ads lead to a page only this version has are paused on Meta first. Returns
versionsandpaused_campaigns.
curl -X DELETE https://app.hunchwire.com/projects/:id/versions/:version_id \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.delete(
"https://app.hunchwire.com/projects/:id/versions/:version_id",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/versions/:version_id", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
POST
/projects/:id/versions/:version_id/keep
End the test and keep this version.
Details
- Everyone sees this version from now on. The others end, and their pages are kept.
- Only do this when the founder decides. Hunchwire never picks a winner.
curl -X POST https://app.hunchwire.com/projects/:id/versions/:version_id/keep \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.post(
"https://app.hunchwire.com/projects/:id/versions/:version_id/keep",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/versions/:version_id/keep", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
Preview a version
Open a version's preview_url to see it as visitors do, for example https://loop.hunchwire.site/?version=C.
- Add
?version=with a live or paused version's key to any page of the site. - Hunchwire records nothing: no visit, no clicks and no sign-up. The form shows, but takes no sign-up.
- The preview never sets or changes the version you get on the site. Search engines don't index it.
- Links between the site's pages keep
?version=, so you can click through the version. - An unknown or ended key shows the site as usual.
Send the founder the link when you change a version's pages, so they can check it.
See results by version in Analytics with filter[version][]=B, or use by=version to see the funnels side by side.
Your domain
Use the founder's own domain for two things: to show a hosted project's pages there, with a certificate (hosted_pages only), and to email the project's people from it (any project; see Email). A project on its own site (own_site_forms) adds its domain for email only.
Adding a domain works on any plan. Connecting it for pages, and sending email, need a paid plan.
Each project has one domain. For pages, a bare domain (tryloop.app) or its www adds both, and another subdomain (go.tryloop.app) covers only itself. For email, Hunchwire uses the domain as typed.
A site has one address: its primary host. That's the bare domain once it serves, else its www, else the free address. Every other address of the site answers a GET with a 301 to the same path and query there: the other half of the domain, and <slug>.hunchwire.site. A POST stays where it lands. Ask the founder to add the records for both halves, so both open.
GET
/projects/:id/domain
Get the domain and the DNS records it needs.
Show 12 details
domain: the domain added, ornull.primary_host: the site's one address. Every other address of the site redirects there.on: false until Hunchwire turns on custom domains.connected: whether the domain is connected for pages.hostnames: each hashostname,status,connected,serving,checked_at,errorand its ownrecords.statusispending,active(ownership verified and certificate issued) orfailed.servingmeans active and connected: pages show there.redirects_to: the host a hostname redirects to, ornullwhen it's the primary host or doesn't serve yet.records: every DNS record the founder adds where they bought the domain, for pages and email together. Each hastype(ALIAS,CNAME,TXTorMX),name(relative to the domain;@is the bare domain),host,value,priority,hint,for(pages,ownershiporemail) andstatus(foundorwaiting).priority: the MX priority, as its own number, since DNS forms ask for it apart from the mail server.nullfor other types.valueis the mail server alone.hint: a line for the founder when their provider may need another type, ornull. The bare domain'sALIASsays: on Cloudflare, add a CNAME on@, DNS only (not proxied).- The email records are TXT
hw1._domainkey(the signing key), MX and TXT onbounce(where bounces come back), and TXT_dmarc. A DMARC record the domain already has counts. - Many providers call the bare domain's
ALIASan ANAME or "CNAME flattening". If the founder's provider has neither, they forward the bare domain towwwthere. email: the domain's email setup.on: false until Hunchwire turns on email.status:pending,verified,failedorpaused, ornullbefore the domain is added.paused: too many bounces or complaints. Sending stops until Hunchwire resumes it.ready: whether the project can email its people now.blocker: why not (see Email).setup: the first email setup step still missing, ornullonce it's done. It'sno_domain,unverified,failed,paused,no_mail_fromorno_sender. Unlikeblocker, it ignores the plan.by_hand: whether the founder sends by hand instead (see Send by hand). Always false once email is set up.checked_atanderror.
curl https://app.hunchwire.com/projects/:id/domain \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.get(
"https://app.hunchwire.com/projects/:id/domain",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/domain", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
PATCH
/projects/:id/domain
Add the domain.
Details
PUTworks too.- The records come back at once. Give them to the founder, or add them yourself if you can reach their DNS.
- A domain added before email was on gets its email records from this call, sent with no
domain.
curl -X PATCH https://app.hunchwire.com/projects/:id/domain \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domain": "tryloop.app"}'
import os, requests
r = requests.patch(
"https://app.hunchwire.com/projects/:id/domain",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
json={"domain": "tryloop.app"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/domain", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"domain": "tryloop.app"}),
})
const data = await res.json()
POST
/projects/:id/domain/check
Look up the records now.
Details
- Hunchwire also checks every 5 minutes for a week. After that, it checks one half of the domain once a day while the other half serves.
curl -X POST https://app.hunchwire.com/projects/:id/domain/check \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.post(
"https://app.hunchwire.com/projects/:id/domain/check",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/domain/check", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
POST
/projects/:id/domain/connect
Connect the domain for pages, once the founder agrees.
Details
- It's a release gate. Without a plan that has the domain feature, Hunchwire answers
402plan_required(see Plans and gates). It then connects the domain by itself once the founder upgrades.
curl -X POST https://app.hunchwire.com/projects/:id/domain/connect \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.post(
"https://app.hunchwire.com/projects/:id/domain/connect",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/domain/connect", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
DELETE
/projects/:id/domain
Remove the domain.
Details
- Pages stop showing there, and email stops coming from it, at once. A held connect is dropped.
- Live campaigns whose ads lead to it are paused on Meta first. The response then adds
paused_campaigns.
curl -X DELETE https://app.hunchwire.com/projects/:id/domain \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.delete(
"https://app.hunchwire.com/projects/:id/domain",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/domain", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
Forward paths to your app
Send some paths on the connected domain to the founder's own app. Visitors stay on the founder's domain: tryloop.app/ shows the hosted pages, and tryloop.app/join comes from the app.
Forwarding works only on the connected domain of a project with hosted pages. On the primary host, every method forwards. On the other half of the domain, a GET redirects to the primary host first, and other methods forward. The free address never forwards: a GET there redirects to the primary host too.
| Path | Matches |
|---|---|
/privacy |
/privacy only |
/join* |
/join, /join/step-2 and /joined |
/hb-assets/* |
everything under /hb-assets/ |
GET
/projects/:id/forwarding
Get what the project forwards.
Details
to: the app's address, ornullwhen nothing is forwarded.paths: the paths forwarded.forwarding_secret_hint: the last 4 characters of the forwarding secret, ornull.hidden_pages: hosted pages a path hides. A forwarded path wins over a page.
curl https://app.hunchwire.com/projects/:id/forwarding \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.get(
"https://app.hunchwire.com/projects/:id/forwarding",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/forwarding", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
{"to": "https://app.tryloop.app", "paths": ["/join*", "/privacy"], "forwarding_secret_hint": "x3Fq", "hidden_pages": ["/privacy"]}
PATCH
/projects/:id/forwarding
Set the app's address and the paths.
Show 5 details
tois https and names a host, with nothing after it. It can't be a Hunchwire address, an IP address or a private network.- You can forward up to 20 paths. Each starts with
/. End one with*to include everything after it. - Hunchwire's own paths can't be forwarded:
/,/embed.js,/e/*,/robots.txt,/sitemap.xml,/share/*,/invite/*,/confirm/*,/unsubscribe/*,/upand/lab_mail/*. - The first save makes the forwarding secret. It comes back once, as
forwarding_secret. Give it to the founder for the app's environment. - A
422lists what's wrong inerrors.
curl -X PATCH https://app.hunchwire.com/projects/:id/forwarding \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"forwarding": {"to": "https://app.tryloop.app", "paths": ["/join*", "/privacy"]}}'
import os, requests
r = requests.patch(
"https://app.hunchwire.com/projects/:id/forwarding",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
json={"forwarding": {"to": "https://app.tryloop.app", "paths": ["/join*", "/privacy"]}},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/forwarding", {
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"forwarding": {"to": "https://app.tryloop.app", "paths": ["/join*", "/privacy"]}}),
})
const data = await res.json()
POST
/projects/:id/forwarding/secret
Make a new forwarding secret. Only do this when the founder asks.
Details
- It comes back once, as
forwarding_secret. The old one stops working at once.
curl -X POST https://app.hunchwire.com/projects/:id/forwarding/secret \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.post(
"https://app.hunchwire.com/projects/:id/forwarding/secret",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/forwarding/secret", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
DELETE
/projects/:id/forwarding
Stop forwarding.
Details
- The paths show the hosted pages again, and the forwarding secret is gone.
curl -X DELETE https://app.hunchwire.com/projects/:id/forwarding \
-H "Authorization: Bearer $HUNCHWIRE_TOKEN"
import os, requests
r = requests.delete(
"https://app.hunchwire.com/projects/:id/forwarding",
headers={"Authorization": f"Bearer {os.environ['HUNCHWIRE_TOKEN']}"},
)
r.json()
const res = await fetch("https://app.hunchwire.com/projects/:id/forwarding", {
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.HUNCHWIRE_TOKEN}`,
},
})
const data = await res.json()
Hunchwire sends the visitor's method, path, query, body and cookies on to the app. It returns the app's answer as it is, redirects and cookies included. It never follows a redirect.
Hunchwire adds these headers:
| Header | Value |
|---|---|
X-Forwarded-Host |
The domain the visitor used, such as tryloop.app. |
X-Forwarded-Proto |
https. |
X-Hunchwire-Client-Ip |
The visitor's IP address. |
X-Hunchwire-Time-Zone |
The visitor's time zone, such as America/Toronto. Left out when it isn't known. |
X-Hunchwire-Forwarding-Secret |
The forwarding secret. Trust the two headers above only when it matches. |
Limits: the app has 5 seconds to connect and 15 to answer. A request body can be up to 1 MB, and an answer up to 10 MB.
| Status | Meaning |
|---|---|
502 |
The app didn't answer in time, or answered with more than 10 MB. |
413 |
The request body is over 1 MB. |
503 |
Too many forwards at once. Retry after the seconds in Retry-After. |
Forwarded pages count in Analytics only with the embed snippet (see The embed). On the same domain, a visitor who moves from a hosted page to a forwarded one is one visit.
Made from skill.md, the file agents read. Add .md to any page's address for its Markdown.