I am experiencing a critical platform-level issue that is blocking all app development for my account
Every client.request.invoke
call from any custom app fails with the error: {message: 'Invalid feature or feature not enabled.'}
.
We have definitively proven that this is a platform issue and not a code or configuration error.
- Direct API Calls Work: I can successfully make API calls to my Freshdesk instance using
cURL
andPostman
with my Admin API key and standardBasic
authentication. My key is valid and the API is responsive. - App Fails with an Identical Request: We built a minimal test app that does only one thing: makes the exact same
add note
API call as my workingcURL
command, using the exact sameBasic
authentication method. This app still fails with the “Invalid feature” error. - This happens on all app types: We have tried
platform-version: 3.0
apps,platform-version: 2.3
apps, plain JavaScript apps, and React apps. We have triedBasic
andBearer
authentication methods. Every single attempt to useclient.request.invoke
results in the same error.
Conclusion: The Freshworks app platform is blocking all server-side requests originating from my custom apps. The “feature” that is “not enabled” appears to be the Request Method (proxy) service itself for my account.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ticket Action</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@freshworks/crayons@v4/css/crayons.css">
<script async src="{{{appclient}}}"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@freshworks/crayons@v4/dist/crayons/crayons.esm.js"></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/@freshworks/crayons@v4/dist/crayons/crayons.js"></script>
<style>
body {
font-family: 'Inter', sans-serif;
margin: 0;
}
</style>
</head>
<body>
<div class="fw-flex fw-flex-column fw-p-16">
<fw-select
id="status-select"
label="Ticket Status"
value="0"
placeholder="Your choice"
disabled
>
<fw-select-option value="0" disabled>Please select a status</fw-select-option>
<fw-select-option value="1">Pending Input</fw-select-option>
<fw-select-option value="2">Resolved</fw-select-option>
</fw-select>
<fw-button color="primary" id="action-btn" disabled>Action Handled</fw-button>
</div>
<script src="https://static.freshdev.io/fdk/2.0/assets/fresh_client.js"></script>
<script src="scripts/tekno.js"></script>
</body>
</html>
[Read me.pdf|attachment](upload://tA1QajFdMset54zO7mMxkDJCjZU.pdf) (83.4 KB)
{
"getTicketDetails": {
"schema": {
"protocol": "https",
"method": "GET",
"host": "<%= iparam.freshdesk_domain %>",
"path": "/api/v2/tickets/<%= context.ticketId %>",
"headers": {
"Authorization": "Basic <%= encode(iparam.freshdesk_key) %>",
"Content-Type": "application/json"
}
}
},
"updateTicket": {
"schema": {
"protocol": "https",
"method": "PUT",
"host": "<%= iparam.freshdesk_domain %>",
"path": "/api/v2/tickets/<%= context.ticketId %>",
"headers": {
"Authorization": "Basic <%= encode(iparam.freshdesk_key) %>",
"Content-Type": "application/json"
}
}
},
"getConversations": {
"schema": {
"protocol": "https",
"method": "GET",
"host": "<%= iparam.freshdesk_domain %>",
"path": "/api/v2/tickets/<%= context.ticketId %>/conversations",
"headers": {
"Authorization": "Basic <%= encode(iparam.freshdesk_key) %>",
"Content-Type": "application/json"
}
}
},
"addNote": {
"schema": {
"protocol": "https",
"method": "POST",
"host": "<%= iparam.freshdesk_domain %>",
"path": "/api/v2/tickets/<%= context.ticketId %>/notes",
"headers": {
"Authorization": "Basic <%= encode(iparam.freshdesk_key) %>",
"Content-Type": "application/json"
}
}
}
}
// scripts/app.js
document.addEventListener("DOMContentLoaded", function() {
// Initialize the Freshworks client
app.initialized().then(function(_client) {
window.client = _client;
client.events.on("app.activated", getTicketDetails);
}).catch(function(error) {
console.error("Failed to initialize the app: ", error);
client.interface.trigger("showNotify", {
type: "danger",
message: "Error initializing app."
});
});
});
/**
let client;
(async function init() {
client = await app.initialized();
client.events.on('app.activated', getTicketDetails);
})();
* Fetches ticket details, checks if it's a child ticket, and updates the UI accordingly.
*/
async function getTicketDetails() {
const statusSelect = document.getElementById('status-select');
const actionButton = document.getElementById('action-btn');
try {
const data = await client.data.get("ticket");
const ticket = data.ticket;
// Save the ticketId to window for later use
window.currentTicketId = ticket.id;
// Based on the sample object, 'association_type' is a direct property of the ticket.
if (ticket.association_type === 2) {
// Enable UI elements
statusSelect.disabled = false;
actionButton.disabled = false;
client.interface.trigger("showNotify", {
type: "success",
message: "This is a child ticket. Please select an action."
});
} else {
// Keep UI disabled
statusSelect.disabled = true;
actionButton.disabled = true;
client.interface.trigger("showNotify", {
type: "info",
message: "This app can only be used on child tickets."
});
}
} catch (error) {
client.interface.trigger("showNotify", {
type: "danger",
message: "Could not load ticket details."
});
// Ensure UI remains disabled on error
if(statusSelect) statusSelect.disabled = true;
if(actionButton) actionButton.disabled = true;
}
}
// Add event listener for the button
document.getElementById('action-btn').addEventListener('click', async function() {
const statusSelect = document.getElementById('status-select');
const selectedValue = statusSelect.value;
if (selectedValue === "0") {
client.interface.trigger("showNotify", {
type: "warning",
message: "Please select a status from the dropdown before proceeding."
});
return;
}
if (selectedValue === "1") { // Pending Input
client.interface.trigger("showNotify", {
type: "info",
message: "Team will check your last note and provide you with the input soon."
});
setTimeout(() => {
client.interface.trigger("showNotify", {
type: "info",
message: "Ticket will be pending till we provide the input."
});
}, 2000);
await handleAction("pending");
}
if (selectedValue === "2") { // Resolved
client.interface.trigger("showNotify", {
type: "info",
message: "Issue will be closed and we will confirm with the customer."
});
setTimeout(() => {
client.interface.trigger("showNotify", {
type: "info",
message: "Ticket is closed for now."
});
}, 2000);
await handleAction("closed");
}
});
/**
* Main handler for the action. Will:
* 1. Get ticket details (for parent ID)
* 2. Get ticket conversations (for last note)
* 3. Update status
* 4. Add note to the parent
*/
async function handleAction(targetStatus) {
try {
// 1. Get child ticket details
const ticketDetails = await client.request.invoke("getTicketDetails", {
context: { ticketId: window.currentTicketId }
});
const ticketData = JSON.parse(ticketDetails.response);
// Find parent ID from associated_tickets_list
const parentIds = ticketData.associated_tickets_list || [];
const parentId = parentIds.length > 0 ? parentIds[0] : null;
if (!parentId) {
client.interface.trigger("showNotify", {
type: "danger",
message: "No parent ticket found for this child ticket."
});
return;
}
// 2. Get conversations/notes for this ticket
const convosResp = await client.request.invoke("getConversations", {
context: { ticketId: window.currentTicketId }
});
const conversations = JSON.parse(convosResp.response);
// Find the latest private note (if any)
let latestNote = null;
if (Array.isArray(conversations) && conversations.length > 0) {
// Sort by created_at descending, just in case
conversations.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
latestNote = conversations[0];
}
if (!latestNote) {
client.interface.trigger("showNotify", {
type: "danger",
message: "No notes found on this ticket to copy to parent."
});
return;
}
// 3. Update the ticket status
let statusCode = 2; // Default "Open"
if (targetStatus === "pending") statusCode = 3;
if (targetStatus === "closed") statusCode = 5;
await client.request.invoke("updateTicket", {
context: { ticketId: window.currentTicketId },
body: JSON.stringify({ status: statusCode })
});
// 4. Add the latest note to the parent
await client.request.invoke("addNote", {
context: { ticketId: parentId },
body: JSON.stringify({
body: latestNote.body_text || latestNote.body,
private: true
})
});
client.interface.trigger("showNotify", {
type: "success",
message: "Ticket status updated and note added to parent successfully."
});
} catch (err) {
console.error("Error in handleAction:", err);
client.interface.trigger("showNotify", {
type: "danger",
message: "An error occurred while processing the action. Please try again."
});
}
}
This is a hard blocker for any app development.
Thank you.