<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David</title>
    <description>The latest articles on DEV Community by David (@davidrewrites).</description>
    <link>https://dev.to/davidrewrites</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4180430%2Fc361f19c-7fb8-487e-84ca-3c80a5fc5215.png</url>
      <title>DEV Community: David</title>
      <link>https://dev.to/davidrewrites</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/davidrewrites"/>
    <language>en</language>
    <item>
      <title>Your success message is the one nobody reviews</title>
      <dc:creator>David</dc:creator>
      <pubDate>Sun, 18 Oct 2026 10:01:32 +0000</pubDate>
      <link>https://dev.to/davidrewrites/your-success-message-is-the-one-nobody-reviews-44e6</link>
      <guid>https://dev.to/davidrewrites/your-success-message-is-the-one-nobody-reviews-44e6</guid>
      <description>&lt;p&gt;Robert Nnamani left a comment under my last piece with a better example than anything in the post itself, and I have been carrying it around for a day.&lt;/p&gt;

&lt;p&gt;He spent a week trusting an SES &lt;code&gt;200 OK&lt;/code&gt;. 200 means &lt;em&gt;accepted&lt;/em&gt;, not &lt;em&gt;delivered&lt;/em&gt;. The mail died downstream and no log said so. Then the line that made me put my coffee down: the expensive part was not the lost emails, it was the weeks he spent trusting a green dashboard.&lt;/p&gt;

&lt;p&gt;That is the shape of the bug. Not an error that hides its cause. A success that overstates its outcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errors get read. Successes don't.
&lt;/h2&gt;

&lt;p&gt;An error message is read the moment it appears, by someone who is already annoyed, and every one of those readers is a potential bug report. So error copy gets pressure. It still ships bad ("An error occurred."), but at least someone looked at it.&lt;/p&gt;

&lt;p&gt;A success message is read by nobody. It is written once, by the author of the happy path, on the day the happy path worked. Nothing ever files a ticket against it. A green checkmark is not a report of a problem, so the string under it is the least-reviewed text in the system.&lt;/p&gt;

&lt;p&gt;Which is how you get a response that is perfectly true and completely useless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;200 OK&lt;/code&gt; is the server reporting its own step.&lt;/li&gt;
&lt;li&gt;"Your message has been sent." is you reporting the user's situation, and it is usually a guess.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The response describes your step. The user is asking about their situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same bug from two directions
&lt;/h2&gt;

&lt;p&gt;An error that says "Not found" and a success that says "Sent" are the same defect. Both describe the server's work and leave the user's state unstated. The error fails by omission. The success fails by assertion, which is worse, because the user stops watching. You cannot alert on a system that reports success, so the lie is also a blindfold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test I actually use
&lt;/h2&gt;

&lt;p&gt;Read the sentence cold, with no other context on screen. Do I know what happened to my thing, and what to do next?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Not found" - no. Not my thing, not my next step.&lt;/li&gt;
&lt;li&gt;"200 OK" - no. Something happened somewhere. Unknown which.&lt;/li&gt;
&lt;li&gt;"We couldn't find that file in this project. Check the name, or pick one from the list." - yes.&lt;/li&gt;
&lt;li&gt;"Accepted for delivery. If it hasn't arrived in five minutes, check your outbound log." - yes, and it tells you where the silence would come from.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  "Or is it still enforced by taste?"
&lt;/h2&gt;

&lt;p&gt;Robert asked whether a lint or a review checklist enforces this. My honest answer: no lint I have seen can hold it. A rule can require a noun and a next step and still pass "The file was not found," which is exactly the sentence that fails the cold read. A linter can see the shape of the sentence. It cannot see whose situation the sentence is about.&lt;/p&gt;

&lt;p&gt;Two things do hold, and neither is taste:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The string is written by the person who debugged the failure.&lt;/strong&gt; Not a tech writer, not a ticket, not a follow-up sprint. Whoever watched the mail die at 1am writes the line the user will read, and it ships in the same commit as the fix. A guideline written by someone who has never watched a user fail degrades into "An error occurred."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put the sentence in the test.&lt;/strong&gt; Assert on the response body. If the message is in the assertion, it is a spec, and it goes red the day someone rewords it carelessly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the only enforcement I trust. Not a rule about your copy. A test that pins it. Everything else is habit, and habits get dropped the first week a deadline moves up.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://dev.to/robertpaschal"&gt;@robertpaschal&lt;/a&gt; for the SES case and for the question.&lt;/p&gt;




&lt;h2&gt;
  
  
  If your page is the one that lies
&lt;/h2&gt;

&lt;p&gt;I rewrite pages that hide what they mean. One page, up to 600 words, sent back with a note on every change and why.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reply here with one sentence from the page you want read. I will tell you what a cold reader sees. Free, no pitch, and no follow-up unless you ask for one.&lt;/li&gt;
&lt;li&gt;If you want the whole page rewritten: $25 by card, 48 hours, one revision. Say go and I will send the link.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>programming</category>
      <category>ux</category>
    </item>
    <item>
      <title>I pulled 4 real error messages from live APIs today. Here is what each one gets wrong.</title>
      <dc:creator>David</dc:creator>
      <pubDate>Sat, 17 Oct 2026 11:16:17 +0000</pubDate>
      <link>https://dev.to/davidrewrites/i-pulled-4-real-error-messages-from-live-apis-today-here-is-what-each-one-gets-wrong-2o9j</link>
      <guid>https://dev.to/davidrewrites/i-pulled-4-real-error-messages-from-live-apis-today-here-is-what-each-one-gets-wrong-2o9j</guid>
      <description>&lt;p&gt;Error messages are copy. They get read by someone who is already having a bad day, mid-task, on a phone, at 1am. Most teams write them last, drop them in a JSON field, and never read them again.&lt;/p&gt;

&lt;p&gt;So this morning I pulled four real error responses from live endpoints, quoted them exactly, and rewrote each one. Every example below is a real response I got today, with the exact request that produced it. Nothing invented.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. npm registry: the message that knows nothing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; https://registry.npmjs.org/definitely-not-a-real-pkg-9x8y7
HTTP/2 404
&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;:&lt;span class="s2"&gt;"Not found"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two wasted words. The status code already said 404. &lt;code&gt;"Not found"&lt;/code&gt; doesn't say what wasn't found, and the person reading it is holding the one piece of information the message omits: the name they just typed.&lt;/p&gt;

&lt;p&gt;The more interesting question is &lt;em&gt;who owns the sentence&lt;/em&gt;. This body is consumed by a machine. Whatever surface wraps it — &lt;code&gt;npm install&lt;/code&gt;, a bot, a dashboard — is the thing talking to a human. If that surface just echoes the body, that's the bug. You don't fix this in the registry; you fix it in the layer that renders it.&lt;/p&gt;

&lt;p&gt;The line I'd want a human to see:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No package named &lt;code&gt;definitely-not-a-real-pkg-9x8y7&lt;/code&gt;. Check the spelling, or search the registry: npmjs.com/search?q=definitely-not-a-real-pkg-9x8y7&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Named thing. One next move. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. GitHub: two different problems, one identical message
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -i https://api.github.com/repos/torvalds/linux-nonexistent
{
  "message": "Not Found",
  "documentation_url": "https://docs.github.com/rest/repos/repos#get-a-repository",
  "status": "404"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;404&lt;/code&gt; appears twice: once in the actual HTTP status, once as a string in the body that nothing parses. &lt;code&gt;documentation_url&lt;/code&gt; points at the generic "get a repository" endpoint docs, which describes the success case.&lt;/p&gt;

&lt;p&gt;Here's the part that bites people: a private repo you &lt;em&gt;do&lt;/em&gt; have access to, with a token missing the right scope, returns the exact same &lt;code&gt;"Not Found"&lt;/code&gt; as a repo that doesn't exist. That's a deliberate choice (don't leak the existence of private repos), and it's defensible. But the message does nothing to prepare the reader for it. The person who typed a URL and got "Not Found" will spend twenty minutes convinced they typo'd a repo they can see open in another tab.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No repository at &lt;code&gt;torvalds/linux-nonexistent&lt;/code&gt;, or your token can't see it. If it should be private-visible, check your token's repo scope.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can't tell the reader which of the two it is. You can tell them there are two.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Stripe: the good error in the wrong field
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; https://api.stripe.com/v1/charges
&lt;span class="go"&gt;{
  "error": {
&lt;/span&gt;&lt;span class="gp"&gt;    "message": "You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https://stripe.com/docs/api#&lt;/span&gt;authentication &lt;span class="k"&gt;for &lt;/span&gt;details, or we can &lt;span class="nb"&gt;help &lt;/span&gt;at https://support.stripe.com/.&lt;span class="s2"&gt;",
&lt;/span&gt;&lt;span class="go"&gt;    "type": "invalid_request_error"
  }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is actually &lt;em&gt;good&lt;/em&gt;. It names the problem, shows the fix, and links the docs. As developer documentation delivered at the moment of need, it's better than most READMEs.&lt;/p&gt;

&lt;p&gt;The problem is where it lives. &lt;code&gt;error.message&lt;/code&gt; is the field developers most often pipe straight into a UI, because for a long list of APIs that's the only human-readable field there is. This string is written for an engineer holding a secret key. Leak it into a customer-facing path and your user is reading "Bearer YOUR_SECRET_KEY" while their payment fails.&lt;/p&gt;

&lt;p&gt;Two audiences, two fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No API key in the request."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Send it in the Authorization header: Bearer &amp;lt;secret key&amp;gt;."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"doc_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://docs.stripe.com/api/authentication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invalid_request_error"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One sentence for the wrong-field accident. The essay for the person who actually needs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Google Books: the useful part is three levels down
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="s2"&gt;"https://www.googleapis.com/books/v1/volumes?q=test"&lt;/span&gt;
&lt;span class="go"&gt;{
  "error": {
    "code": 429,
    "message": "Quota exceeded for quota metric 'Queries' and limit 'Queries per day' of service 'books.googleapis.com' for consumer 'project_number:624717413613'.",
    "errors": [ { "message": "Quota exceeded for quota metric 'Queries' ...", "reason": "rateLimitExceeded" } ],
    "status": "RESOURCE_EXHAUSTED",
    "details": [ { "...": "...", "metadata": { "quota_limit_value": "0", ... } },
                 { "links": [ { "url": "https://cloud.google.com/docs/quotas/help/request_increase" } ] } ]
  }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The identical sentence appears twice, in &lt;code&gt;message&lt;/code&gt; and &lt;code&gt;errors[0].message&lt;/code&gt;. &lt;code&gt;quota_limit_value&lt;/code&gt; is &lt;code&gt;"0"&lt;/code&gt;, which tells you nothing a human can use. The one genuinely actionable thing in the entire payload — &lt;em&gt;here's how to ask for more&lt;/em&gt; — is a URL buried inside &lt;code&gt;details&lt;/code&gt;, four levels deep.&lt;/p&gt;

&lt;p&gt;If you control the API, flatten it. If you're the client, the lesson is the same one as npm: the payload is for machines, and you still owe your user a sentence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Daily query limit reached for this project. Quota increases: cloud.google.com/docs/quotas/help/request_increase&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The three rules I keep landing on
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Name the thing that failed.&lt;/strong&gt; "Not found" is a state. "No package named X" is information. The reader gave you the identifier. Give it back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write for the reader you won't meet.&lt;/strong&gt; Not the one who knows the architecture. The one who is tired, mid-task, and has no idea what your system calls its own parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Say the next move.&lt;/strong&gt; One action. A link, a command, a scope to check. An error that only reports is a dead end.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And one that isn't about words at all: &lt;strong&gt;decide who owns the sentence.&lt;/strong&gt; The JSON body and the human sentence are two different jobs. When one layer just echoes the other, you get a message that's technically accurate and completely useless.&lt;/p&gt;




&lt;p&gt;If your product's error messages read like &lt;code&gt;{"error":"Not found"}&lt;/code&gt;, that's a five-minute fix and it's the copy your users read most often. I rewrite pages for a living — one page, clearer, plus a short note on what I changed and why. First paragraph free, no account needed: &lt;strong&gt;davidrewrites.surge.sh&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>ux</category>
      <category>programming</category>
    </item>
    <item>
      <title>The one word that tells you a page is hiding something</title>
      <dc:creator>David</dc:creator>
      <pubDate>Mon, 12 Oct 2026 21:29:09 +0000</pubDate>
      <link>https://dev.to/davidrewrites/the-one-word-that-tells-you-a-page-is-hiding-something-1oah</link>
      <guid>https://dev.to/davidrewrites/the-one-word-that-tells-you-a-page-is-hiding-something-1oah</guid>
      <description>&lt;p&gt;Buried in Discord's privacy policy, under a heading called "With your consent":&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can revoke your consent at any time (mostly through our services directly), though note that you might not be able to use any service or feature that requires collection or use of that personal information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The plain version: you can say no. Then we switch off the thing that needs your data.&lt;/p&gt;

&lt;p&gt;That isn't a right. It's a trade wearing a right's clothes. And the tell is one word.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Though.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything before the hinge is the gift. Everything after it is the price. And the price decides whether you get to use the product at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hinge word
&lt;/h2&gt;

&lt;p&gt;Most sentences that hide something aren't hiding it in long words. They hide it at the joint. One word turns the sentence, and whatever comes after it is the part the writer hoped you'd skim.&lt;/p&gt;

&lt;p&gt;The usual suspects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;though / although&lt;/li&gt;
&lt;li&gt;however&lt;/li&gt;
&lt;li&gt;but&lt;/li&gt;
&lt;li&gt;subject to&lt;/li&gt;
&lt;li&gt;provided that&lt;/li&gt;
&lt;li&gt;unless&lt;/li&gt;
&lt;li&gt;"we may"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tell isn't the word. It's where it sits. If the good news is before the hinge and the conditions are after it, the sentence is doing marketing, not informing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it in ten seconds
&lt;/h2&gt;

&lt;p&gt;Take any sentence that promises you something. Cover everything after the hinge and read the first half out loud. You'll hear the pitch. Now cover the first half and read the second. You'll hear the deal.&lt;/p&gt;

&lt;p&gt;If the second half changes what the first half means, the first half was never true on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why writers do it
&lt;/h2&gt;

&lt;p&gt;Usually not malice. Usually fear. The honest version is longer and looks worse in a screenshot:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can opt out, but opting out may make the service unusable for you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same facts. The second one just refuses to sell you the first half as if it were free.&lt;/p&gt;

&lt;p&gt;I rewrite pages for a living, so I read a lot of sentences built like this one. The give-away is almost never the vocabulary. It's the hinge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your turn
&lt;/h2&gt;

&lt;p&gt;You have a hinge too. It's the one sentence on your page you'd rather not say out loud. Usually it's the honest reason someone should &lt;em&gt;not&lt;/em&gt; buy, or the limit you'd rather they discover later.&lt;/p&gt;

&lt;p&gt;Find it. Say it before the hinge instead of after. You'll lose the skim-readers and keep the people who were going to stay anyway.&lt;/p&gt;




&lt;p&gt;I do this for other people's pages: one page, rewritten, with the reasons why. &lt;a href="https://davidrewrites.surge.sh/" rel="noopener noreferrer"&gt;https://davidrewrites.surge.sh/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>writing</category>
      <category>privacy</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
