Getting Error 404/Route not allowed when calling external endpoint from custom app in dev mode

const options = {
headers: {
“Authorization”: "token " + “XXXX:XXXX”,
“Content-Type”: “application/json”,
“Accept”: “application/json”
},
body: JSON.stringify(payload),
isOAuth: false
};
console.log(“Making request to:”, url);
console.log(“With headers:”, options.headers);
const response = await clientInstance.request.post(url, options);
console.log(“Frappe API response:”, response);
// Parse response
let responseData = response;
if (typeof response === “string”) {
try {
responseData = JSON.parse(response);
} catch (e) {
console.warn(“Could not parse response as JSON”);
}
}
Console Output:
vendor-4676a9e788102…f473c64d61f0.js:468
POST http://localhost:10001/dprouter?product=freshchat 404 (Not Found)
send @ vendor-4676a9e788102…f473c64d61f0.js:468
ajax @ vendor-4676a9e788102…f473c64d61f0.js:444
(anonymous) @ fc_agent_app-c39a559…18bee5c7bc.js:13138
(anonymous) @ vendor-4676a9e788102…473c64d61f0.js:3690
x @ vendor-4676a9e788102…473c64d61f0.js:3690
ajax @ fc_agent_app-c39a559…18bee5c7bc.js:13138
value @ fresh_parent.js?=4066591385:21
value @ fresh_parent.js?
=4066591385:21
value @ fresh_parent.js?=4066591385:21
value @ fresh_parent.js?
=4066591385:11
channel.port1.onmessage @ fresh_parent.js?_=4066591385:11
app.js:2621 Ticket creation failed:
{message: ‘Route not allowed’, status: 404}
createFrappeTicket @ app.js:2621
await in createFrappeTicket
handleFormSubmit @ app.js:2516

Hey @Vedant_Khandelwal! :waving_hand:

I see you’re getting a 404/Route not allowed error when calling your Frappe API from the frontend. This happens because you’re using client.request.post() to make direct external API calls from the frontend, which isn’t supported in Platform 3.0.

The correct approach is to move your external API logic to the backend using Server Method Invocation (SMI) and request templates.

Here’s the fix:

1. Create a serverless function (server/server.js):

exports = {
  createFrappeTicketHandler: async function(args) {
    const response = await $request.invokeTemplate('frappeCreateTicket', {
      body: JSON.stringify(args.payload)
    });
    return JSON.parse(response);
  }
};

2. Add request template (config/requests.json):

{
  "frappeCreateTicket": {
    "schema": {
      "method": "POST",
      "host": "your-frappe-domain.com",
      "path": "/api/method/your.endpoint",
      "headers": {
        "Authorization": "token <%= iparam.api_key %>:<%= iparam.api_secret %>",
        "Content-Type": "application/json"
      }
    }
  }
}

3. Store credentials (config/iparams.json):

{
  "api_key": { "type": "text", "required": true, "secure": true },
  "api_secret": { "type": "text", "required": true, "secure": true }
}

4. Update frontend to call backend:

const response = await client.request.invoke('createFrappeTicketHandler', { payload });

5. Declare in manifest:

"modules": {
  "common": {
    "requests": { "frappeCreateTicket": {} },
    "functions": { "createFrappeTicketHandler": {} }
  }
}

This keeps your credentials secure and follows Platform 3.0 architecture. Let me know if you hit any issues!