“Something went wrong” may be honest, but it is rarely enough to operate a system. The person using the software does not know whether to try again, correct an input, or call someone. The support person cannot tell which failure to investigate. The next automation in the chain receives words instead of a dependable signal.

The useful improvement is not a longer error message. It is an error contract: a small, consistent record describing a problem in fields that both people and software can understand. The Internet standard RFC 9457 defines a general “Problem Details” format for HTTP APIs. Its core ideas also make a sound design checklist for the internal automations and focused applications a small team relies on.

This is a design pattern, not a claim about a real Poygan client or implementation. The examples below are synthetic and intentionally small.

Before: one message is trying to do four jobs

Consider a hypothetical workflow that receives a service request and creates a work item in a CRM. The CRM rejects one submission, and the workflow records only: “Request failed.”

That sentence is being asked to serve the customer, the operator, the developer, and the retry logic at once. Those audiences need different information. A customer may need a calm next step. An operator needs to find the affected run. A developer needs the category and context of the failure. Retry logic needs to know whether another attempt is sensible.

The mistake is treating an error as leftover text. An error is part of the interface between components. If the successful path has named fields and expected values, the unsuccessful path deserves comparable care.

  • The user sees no useful next step.
  • The operator cannot identify the exact occurrence.
  • A downstream step cannot reliably distinguish bad input from temporary unavailability.
  • Developers may be tempted to expose raw internal details just to make diagnosis possible.

After: give every failure a stable shape

RFC 9457 defines a Problem Details object with standard members. The “type” identifies the kind of problem. “title” provides a short summary of that problem type. “status” carries the HTTP status code. “detail” explains this particular occurrence. “instance” identifies the particular occurrence. The standard also permits extension members when an application needs additional information.

A small business application does not need to use every member in every situation. It does need a predictable shape. A practical internal contract might contain a stable problem type, a safe summary, an occurrence identifier, a timestamp, the workflow step that stopped, and a disposition such as “needs correction,” “safe to retry,” or “needs review.” That disposition is a Poygan design recommendation, not a field required by RFC 9457.

Keep classification separate from explanation. Software can branch on a stable type such as “customer-record-not-found.” A person can read a detail such as “No customer matched reference C-1842.” If wording changes later, the automation does not have to break.

  • Type: a stable category that code may recognize.
  • Title: a short human-readable summary of that category.
  • Status: the relevant HTTP response status when this is an HTTP API.
  • Detail: safe context about this occurrence, not a database dump.
  • Instance or occurrence ID: a way to correlate the report with the relevant run.
  • Disposition: an optional local rule describing the allowed next action.

Route the error instead of merely storing it

A structured record becomes valuable when it changes the workflow. A validation problem can return to the person who supplied the information. A temporary dependency failure can enter a bounded retry path. An unknown or risky failure can stop in a visible review queue. The category should not silently authorize destructive changes, customer promises, or unlimited retries.

Here is a synthetic before-and-after example. Before, the workflow writes “CRM error” to a log and continues, so a confirmation message may imply that work was created when it was not. After, the CRM step returns a structured “customer-record-not-found” problem with occurrence ID “run-1048.” The workflow stops before confirmation and places the request in human review. No result is being claimed here; this example simply shows how an error contract can support a safer decision.

The customer-facing message can remain plain: “We could not finish submitting this request. Your reference is run-1048.” Internal diagnostic context can remain in protected logs. RFC 9457 specifically warns that problem details can expose implementation information and advises authors to review what they reveal. A useful error should improve diagnosis without publishing secrets, stack traces, tokens, personal data, or infrastructure details.

  • Correctable input: explain the field and let a person revise it.
  • Temporary dependency problem: retry only under a written limit and stopping rule.
  • Duplicate or conflict: compare the existing record before creating another.
  • Unknown failure: stop, preserve the occurrence ID, and assign review.
  • Sensitive internal failure: show a safe public explanation while protecting diagnostic details.

Build the contract from decisions backward

Start with the decisions the receiving system must make. Ask whether it should correct, retry, ignore, escalate, or stop. Then define the few problem types needed to support those decisions. This keeps the contract smaller than a catalog of every technical exception.

Write one test for each type. Confirm that the correct route is selected, the occurrence can be found, and the public detail contains no sensitive information. Also test an unrecognized type. The safe default is usually to stop for review rather than guess.

Finally, document ownership. Someone should know who may add a new problem type, who reviews public wording, and what downstream rules depend on it. A structured format without this ownership can slowly turn back into miscellaneous messages with nicer punctuation.

  • List the decisions that follow a failure.
  • Define stable types for meaningful categories, not every low-level exception.
  • Choose which details are safe for users and which belong only in protected diagnostics.
  • Attach a unique occurrence reference that connects the interface, workflow run, and logs.
  • Set explicit retry limits and a human-review fallback.
  • Test known types, unknown types, and accidental disclosure of sensitive data.

Useful takeaways

  • Treat failure output as an interface contract, not an improvised sentence.
  • Separate a stable machine-readable problem type from occurrence-specific human explanation.
  • Give each failure occurrence a reference that operators can trace.
  • Route correctable, retryable, conflicting, and unknown problems differently.
  • Keep secrets and unnecessary implementation details out of public error responses.
  • Design the safe fallback for an error type the receiving system does not recognize.

One next thought

If one of your workflows currently ends with “failed” or “unknown error,” choose a single recurring failure and give it a stable type, an occurrence reference, and one explicit next action.

ONE EMAIL · PERSONAL REPLY

Let’s find the right first step.

Leave your email. I’ll reply personally and help you figure out where to start.

Personal replyNo mailing listNo obligation