Submit feedback
One note in, a verdict out. A fast model reads what was written, calls it a bug or a suggestion, writes the title and the write-up, picks a priority, and drops it if it is spam. A note about something already on the list joins that signal instead of adding a row, so the same complaint from ten people is one signal with ten reporters.
This is the endpoint behind the feedback panel, and the one for notes collected anywhere else: an app store review, a survey, a message in a community. Use POST /v1/signals when you already know what the thing is and how it should read.
It waits for the reading, so expect a second or two. outcome is unread when the reader was unavailable or the organization has filing switched off; nothing is filed either way.
POST
/feedbackAuthorization
AuthorizationBearer token · headerrequired`MEDIAN_KEY` from Settings under API, or an MCP OAuth access token. A key acts as the organization; a token acts as the person who approved it.
Request body
requiredapplication/jsonsessionstringrequiredWho the note is from: any stable id for that person, the same one every time. It is what makes two notes from one person one reporter, and it is the browser's session token when the note comes from the widget. 8 to 128 characters.
bodystringrequiredWhat they actually wrote, in their words.
max length 2000
userobjectTheir name and email, if you know them.
Show propertiesHide properties
namestringemailstringavatarUrlstringpageobjectWhere they were. The query string is dropped before the URL is stored.
Show propertiesHide properties
urlstringtitlestringreferrerstringimagesstring | object[]Pictures of the thing, as http or https addresses. They show on the report and on any issue it is pushed to. A bare string works where you have nothing to call it.
max items 6
Show propertiesHide properties
Array of
string | objectOne of:
string
stringobject
urlstringrequirednamestringWhat it shows, in a few words. Read off the address when left out.
Responses
200Where the note ended up.
outcomestringAllowed:
bugsuggestionspamunreadsignalIdstring | nullThe signal it landed on, or null when nothing was filed.
400The request is malformed, and the message names the field.
errorobjectShow propertiesHide properties
codestringmessagestring401The bearer token is missing, revoked, or expired.
errorobjectShow propertiesHide properties
codestringmessagestring429Too many requests. Wait the seconds in `Retry-After`. Limits depend on the plan. See [rate limits](/api/errors-and-limits#rate-limits).
errorobjectShow propertiesHide properties
codestringmessagestringRequest
curl -X POST "https://api.median.sh/v1/feedback" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"session": "user_8812",
"body": "The export button on the billing page does nothing on my phone.",
"page": {
"url": "https://acme.com/settings/billing"
},
"images": [
{
"url": "https://files.acme.com/reports/8812-billing.png",
"name": "Billing page on iOS"
}
]
}'const response = await fetch("https://api.median.sh/v1/feedback", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json"
},
body: JSON.stringify({
"session": "user_8812",
"body": "The export button on the billing page does nothing on my phone.",
"page": {
"url": "https://acme.com/settings/billing"
},
"images": [
{
"url": "https://files.acme.com/reports/8812-billing.png",
"name": "Billing page on iOS"
}
]
})
});Response
{
"outcome": "bug",
"signalId": "string"
}{
"error": {
"code": "string",
"message": "string"
}
}{
"error": {
"code": "string",
"message": "string"
}
}{
"error": {
"code": "string",
"message": "string"
}
}