packages/server/api/src/assets/prompts/guides/http_fallback.md
Load this whenever the work needs a web API and either no piece exists for that service at all, or a piece exists but its connection is unavailable and the user can't/won't create one. Use the HTTP piece (@activepieces/piece-http, action send_request) to call the API directly and carry the task to completion — fetch the data, use it, finish the job. If the user declines the HTTP fallback too, report the limitation and stop.
ap_fetch_url is NOT how you call an API. ap_fetch_url reads a web page as text for your own reading; it is not the way to hit a JSON API and act on the result. To call an API (public or authed) and use its response in the task or an automation, ALWAYS use the HTTP piece send_request below — never stop at ap_fetch_url and hand back. A public API with no auth is the easiest case, not a reason to fall back to page-reading.
First, prefer a native piece. Before falling back to HTTP, confirm there isn't a native action that does this with no connection — e.g. Discord's send_message_webhook (just webhook_url + content), Slack incoming webhooks, etc. A native action has simple, validated fields and is far less error-prone than a raw HTTP request. Only use HTTP when no native action fits.
gmail → Gmail API, slack → Slack API).authType: "NONE".ap_execute_action using the EXACT send_request contract below — do not invent field names.send_request input contract (copy this shape exactly){
"method": "POST", // GET | POST | PUT | PATCH | DELETE | HEAD
"url": "https://api.example.com/things",
"headers": { "Content-Type": "application/json" }, // REQUIRED — pass {} if none
"queryParams": {}, // REQUIRED — pass {} if none
"authType": "NONE", // NONE | BASIC | BEARER_TOKEN
// authFields depends on authType — OMIT entirely when authType is "NONE":
// BASIC → "authFields": { "username": "...", "password": "..." }
// BEARER_TOKEN → "authFields": { "token": "..." }
"body_type": "json", // none | json | raw | form_data — MUST set when sending a body
"body": { "data": { "content": "hello" } } // see body shapes below
}
body shape depends on body_type (this is the #1 thing models get wrong):
body_type: "json" → "body": { "data": { ...your JSON object... } } — the JSON object is nested under a data key.body_type: "raw" → "body": { "data": "<raw string>" }.body_type: "form_data" → "body": { ...flat key/value pairs... }.body_type: "none" → omit body.headers, queryParams, and authType are all required — always include them (use {} / "NONE" when empty). Never use a key called authentication, type, or json_raw; they don't exist.
{
"method": "POST",
"url": "https://discord.com/api/webhooks/XXX/YYY",
"headers": { "Content-Type": "application/json" },
"queryParams": {},
"authType": "NONE",
"body_type": "json",
"body": { "data": { "content": "🎉 Your message text here" } }
}
{
"method": "GET",
"url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
"headers": {},
"queryParams": {},
"authType": "NONE",
"body_type": "none"
}
Then read the response and use it to finish the task (state the value, write it where it belongs, etc.) — the GET is the start, not the end.
If anything still fails after one corrected attempt, call ap_get_piece_props('@activepieces/piece-http', 'send_request') (passing your current body_type) to resolve the dynamic body sub-fields, fix once, and report if it still won't go through — do not re-send the same request repeatedly.
Always explain plainly: "Since we don't have a [App] connection set up, I'll call the [Service] API directly."