Skip to content

Error Reference

This page covers every error type that contract.WriteError can produce. For each entry you get the ErrorType constant, the HTTP status code it maps to, when it triggers, what the JSON envelope looks like, and how to resolve it.

For the builder API, see contract API Reference. For error handling patterns, see Handle Errors guide.

ConstantHTTPCategoryUse when
TypeRequired400validationRequired field or param is absent
TypeValidation400validationField present but fails a rule
TypeInvalidFormat400validationWrong format (UUID, email, date)
TypeOutOfRange400validationNumeric or date value out of range
TypeDuplicate400validationValue must be unique, already exists
TypeUnauthorized401authMissing or malformed credentials
TypeInvalidToken401authToken present but invalid signature
TypeExpiredToken401authToken exp claim is in the past
TypeForbidden403authAuthenticated but lacks permission
TypeNotFound404resourceResource does not exist
TypeConflict409resourceState conflict (e.g. concurrent update)
TypeAlreadyExists409resourceUnique constraint violation
TypeGone410resourceResource permanently deleted
TypeRateLimited429rate_limitRequest rate limit exceeded
TypeInternal500serverServer-side failure
TypeUnavailable503serverDownstream dependency unavailable
TypeTimeout504serverUpstream call timed out

Every error response uses the same envelope regardless of the error type:

{
"error": {
"type": "not_found",
"code": "NOT_FOUND",
"message": "user not found",
"category": "resource_error",
"severity": "error",
"details": { "id": "user_42" }
},
"request_id": "req-abc-123"
}
FieldSource
typeLowercase string from the ErrorType constant
codeUppercase string, derived automatically from ErrorType
messageDefault per type; override with .Message("...")
categoryDerived from ErrorType — see table below
severityAlways "error" for standard types
detailsKey-value pairs added with .Detail("key", "value")
request_idInjected by middleware/requestid; absent when middleware did not run

contract.TypeRequired
FieldValue
HTTP status400
typerequired_field_missing
codeREQUIRED
categoryvalidation_error

Triggers when: A required query parameter, path parameter, or request body field is absent.

Example:

if r.URL.Query().Get("name") == "" {
contract.WriteError(w, r, contract.NewErrorBuilder().
Type(contract.TypeRequired).
Detail("field", "name").
Message("name is required").
Build())
return
}

Client remediation: Include the missing field in the request.


contract.TypeValidation
FieldValue
HTTP status400
typevalidation_error
codeVALIDATION
categoryvalidation_error

Triggers when: A field is present but fails a business or format rule not covered by a more specific type. Use specific types (TypeInvalidFormat, TypeOutOfRange, TypeDuplicate) when they fit.

Example:

if len(req.Password) < 8 {
contract.WriteError(w, r, contract.NewErrorBuilder().
Type(contract.TypeValidation).
Detail("field", "password").
Message("password must be at least 8 characters").
Build())
return
}

contract.TypeInvalidFormat
FieldValue
HTTP status400
typeinvalid_format
codeINVALID_FORMAT
categoryvalidation_error

Triggers when: A field is present but has the wrong format — UUID, email, ISO 8601 date, phone number.

Example:

if _, err := uuid.Parse(id); err != nil {
contract.WriteError(w, r, contract.NewErrorBuilder().
Type(contract.TypeInvalidFormat).
Detail("field", "id").
Message("id must be a valid UUID").
Build())
return
}

contract.TypeInvalidFormat
FieldValue
HTTP status400
typeinvalid_format
codeINVALID_FORMAT
categoryvalidation_error

Triggers when: The request body cannot be parsed as JSON, or an input value does not match the expected format. To surface the more specific INVALID_JSON code, pass .Code(contract.CodeInvalidJSON) to the builder.

Example:

if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
contract.WriteError(w, r, contract.NewErrorBuilder().
Type(contract.TypeInvalidFormat).
Code(contract.CodeInvalidJSON).
Message("request body must be valid JSON").
Build())
return
}

contract.TypeOutOfRange
FieldValue
HTTP status400
typeout_of_range
codeOUT_OF_RANGE
categoryvalidation_error

Triggers when: A numeric or date value is outside the declared allowed range.

Example:

if req.PageSize < 1 || req.PageSize > 100 {
contract.WriteError(w, r, contract.NewErrorBuilder().
Type(contract.TypeOutOfRange).
Detail("field", "page_size").
Message("page_size must be between 1 and 100").
Build())
return
}

contract.TypeDuplicate
FieldValue
HTTP status400
typeduplicate_value
codeDUPLICATE
categoryvalidation_error

Triggers when: The client submits a value that must be unique but already exists in the system.


contract.TypeUnauthorized
FieldValue
HTTP status401
typeunauthorized
codeUNAUTHORIZED
categoryauth_error

Triggers when: The request does not include valid credentials (missing or malformed Authorization header).

Client remediation: Include a valid Authorization: Bearer <token> header.


contract.TypeInvalidToken
FieldValue
HTTP status401
typeinvalid_token
codeINVALID_TOKEN
categoryauth_error

Triggers when: The token is present but has an invalid signature, wrong issuer, or malformed structure.

Troubleshooting:

SymptomCheck
Valid token rejectedConfirm the middleware and issuer use the same signing key and JWTManager instance
Signature mismatchVerify the key store is not re-initialized between issue and verify calls
Wrong algorithmsecurity/jwt uses HS256 by default; confirm the token algorithm matches

contract.TypeExpiredToken
FieldValue
HTTP status401
typeexpired_token
codeEXPIRED_TOKEN
categoryauth_error

Triggers when: The token’s exp claim is in the past.

Client remediation: Use the refresh token to obtain a new access token, then retry.


contract.TypeForbidden
FieldValue
HTTP status403
typeforbidden
codeFORBIDDEN
categoryauth_error

Triggers when: The request is authenticated but the principal lacks the required permission to perform the action.

Troubleshooting: Check role or scope assignments. Do not reveal which permission is required in the message — that leaks authorization structure.


contract.TypeNotFound
FieldValue
HTTP status404
typenot_found
codeNOT_FOUND
categoryresource_error

Triggers when: The requested resource does not exist. Use Detail("id", id) to let clients identify which resource was missing.

if err == sql.ErrNoRows {
contract.WriteError(w, r, contract.NewErrorBuilder().
Type(contract.TypeNotFound).
Detail("id", id).
Message("user not found").
Build())
return
}

contract.TypeConflict
FieldValue
HTTP status409
typeconflict
codeCONFLICT
categoryresource_error

Triggers when: The request cannot be completed because of a state conflict — for example, a concurrent update changed the resource since the client last read it.


contract.TypeAlreadyExists
FieldValue
HTTP status409
typealready_exists
codeALREADY_EXISTS
categoryresource_error

Triggers when: The client attempts to create a resource that already exists (unique constraint violation).

Client remediation: Use PUT or PATCH to update the existing resource, or check for existence before creating.


contract.TypeGone
FieldValue
HTTP status410
typegone
codeGONE
categoryresource_error

Triggers when: The resource existed but has been permanently deleted. Unlike 404, 410 signals the client should not retry.


contract.TypeRateLimited
FieldValue
HTTP status429
typerate_limited
codeRATE_LIMITED
categoryrate_limit_error

Triggers when: The client has exceeded the request rate limit.

Troubleshooting:

SymptomCheck
All requests rate-limitedConfirm middleware/ratelimit is configured with the correct limit and window
Per-user limit not appliedVerify the key function extracts a user identifier, not a fixed string
Rate limit too low in testsUse a test-scoped limiter with a higher limit or mock it entirely

contract.TypeInternal
FieldValue
HTTP status500
typeinternal_error
codeINTERNAL
categoryserver_error

Triggers when: A server-side failure occurs that is not the client’s fault. Never expose internal error details (stack traces, SQL errors) in the message.

result, err := h.DB.QueryRowContext(r.Context(), query, id).Scan(&out)
if err != nil {
h.Logger.Error("query failed", "err", err)
contract.WriteError(w, r, contract.NewErrorBuilder().
Type(contract.TypeInternal).
Message("an unexpected error occurred").
Build())
return
}

Troubleshooting: Log the underlying error server-side; return only a generic message to the client.


contract.TypeUnavailable
FieldValue
HTTP status503
typeservice_unavailable
codeUNAVAILABLE
categoryserver_error

Triggers when: A downstream dependency (database, external API, cache) is not reachable or returns an error that prevents the request from completing.

Client remediation: Retry with exponential backoff. Respect any Retry-After response header.


contract.TypeTimeout
FieldValue
HTTP status504
typetimeout
codeTIMEOUT
categoryserver_error

Triggers when: An upstream call timed out before responding. Use this when the server’s own deadline was exceeded waiting for a dependency, not when the client’s request took too long (that is a 408, which Plumego does not map to a type).


SymptomMost likely causeFix
Handler returns HTTP 200 for an errorWriteResponse used instead of WriteErrorReplace with WriteError and the correct builder
request_id is missing from error envelopemiddleware/requestid not registeredAdd requestid.Middleware() via app.Use(...) before Prepare
Handler writes body twiceNo return after WriteErrorAdd return immediately after every WriteError call
Error type too broadAlways using TypeInternal or TypeValidationChoose the specific type from the catalog above
Error message leaks internal detailError from err.Error() used as messageLog internally; return a fixed human message with no stack or SQL content
details map not reaching the clientBuilder chain incompleteConfirm .Detail(k, v) is called before .Build()