> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go-mizu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Codes

> Complete guide to all Contract error codes and how they map to different protocols

# Error Codes Reference

Contract uses a standard set of error codes that work across all protocols. This page explains each code, when to use it, and how it maps to HTTP, JSON-RPC, and other protocols.

## Why Standard Error Codes?

Different protocols handle errors differently:

* **HTTP** uses status codes (404, 500, etc.)
* **JSON-RPC** uses negative error codes (-32600, -32601, etc.)
* **gRPC** uses its own codes (NOT\_FOUND, INTERNAL, etc.)

Contract uses protocol-agnostic codes that automatically translate to the right format:

```go theme={null}
// You write this once:
return nil, contract.ErrNotFound("user not found")

// And it becomes:
// - HTTP: 404 Not Found
// - JSON-RPC: {"error": {"code": -32601, "message": "user not found"}}
// - MCP: {"isError": true, "content": [{"type": "text", "text": "user not found"}]}
```

## Quick Reference Table

| Error Code            | HTTP | JSON-RPC | Use When                   |
| --------------------- | ---- | -------- | -------------------------- |
| `INVALID_ARGUMENT`    | 400  | -32602   | Bad input from user        |
| `NOT_FOUND`           | 404  | -32601   | Resource doesn't exist     |
| `ALREADY_EXISTS`      | 409  | -32003   | Trying to create duplicate |
| `PERMISSION_DENIED`   | 403  | -32004   | User can't do this         |
| `UNAUTHENTICATED`     | 401  | -32011   | User not logged in         |
| `RESOURCE_EXHAUSTED`  | 429  | -32005   | Rate limited               |
| `FAILED_PRECONDITION` | 412  | -32006   | System not ready           |
| `INTERNAL`            | 500  | -32603   | Server bug                 |
| `UNAVAILABLE`         | 503  | -32009   | Service down               |
| `UNIMPLEMENTED`       | 501  | -32601   | Feature not built          |

## Detailed Error Code Guide

### INVALID\_ARGUMENT

**Use when:** The user sent bad input.

```go theme={null}
func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
    if in.Title == "" {
        return nil, contract.ErrInvalidArgument("title is required")
    }
    if len(in.Title) > 500 {
        return nil, contract.ErrInvalidArgument("title must be 500 characters or less")
    }
    if !isValidEmail(in.Email) {
        return nil, contract.ErrInvalidArgument("email format is invalid")
    }
    // ...
}
```

**Common scenarios:**

* Missing required field
* Value too long or too short
* Invalid format (email, URL, phone)
* Number out of allowed range
* Wrong data type

**HTTP Status:** 400 Bad Request

***

### NOT\_FOUND

**Use when:** The requested item doesn't exist.

```go theme={null}
func (s *Service) Get(ctx context.Context, in *GetInput) (*Todo, error) {
    todo, ok := s.todos[in.ID]
    if !ok {
        return nil, contract.ErrNotFound("todo not found")
    }
    return todo, nil
}
```

**Common scenarios:**

* Getting item by ID that doesn't exist
* Referencing deleted resource
* Looking up user by email that's not registered

**With extra details:**

```go theme={null}
return nil, contract.ErrNotFound("todo not found").WithDetail("id", in.ID)
```

**HTTP Status:** 404 Not Found

***

### ALREADY\_EXISTS

**Use when:** Trying to create something that already exists.

```go theme={null}
func (s *Service) Create(ctx context.Context, in *CreateInput) (*User, error) {
    if s.emailExists(in.Email) {
        return nil, contract.ErrAlreadyExists("email already registered")
    }
    // ...
}
```

**Common scenarios:**

* Creating user with existing email
* Creating resource with duplicate unique ID
* Adding item that's already in a list

**HTTP Status:** 409 Conflict

***

### PERMISSION\_DENIED

**Use when:** User is logged in but not allowed to do this.

```go theme={null}
func (s *Service) Delete(ctx context.Context, in *DeleteInput) error {
    user := UserFromContext(ctx)
    todo := s.todos[in.ID]

    // Only owner or admin can delete
    if todo.OwnerID != user.ID && user.Role != "admin" {
        return contract.ErrPermissionDenied("you can only delete your own todos")
    }
    // ...
}
```

**Common scenarios:**

* User trying to access another user's data
* User without admin role trying admin action
* User trying to modify read-only resource

**HTTP Status:** 403 Forbidden

**Note:** Use `UNAUTHENTICATED` if user isn't logged in at all.

***

### UNAUTHENTICATED

**Use when:** User needs to log in first.

```go theme={null}
func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
    user := UserFromContext(ctx)
    if user == nil {
        return nil, contract.ErrUnauthenticated("please log in first")
    }
    // ...
}
```

**Common scenarios:**

* No authentication token provided
* Token has expired
* Token is invalid or malformed

**HTTP Status:** 401 Unauthorized

**vs PERMISSION\_DENIED:**

* `UNAUTHENTICATED`: "Who are you?" (not logged in)
* `PERMISSION_DENIED`: "I know who you are, but you can't do this" (logged in but not allowed)

***

### RESOURCE\_EXHAUSTED

**Use when:** Some limit has been reached.

```go theme={null}
func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
    if s.rateLimiter.IsExceeded(ctx) {
        return nil, contract.ErrResourceExhausted("too many requests, try again later")
    }
    if s.storage.IsFull() {
        return nil, contract.ErrResourceExhausted("storage quota exceeded")
    }
    // ...
}
```

**Common scenarios:**

* Rate limit exceeded
* Storage quota full
* Too many items created
* Concurrent request limit

**HTTP Status:** 429 Too Many Requests

***

### FAILED\_PRECONDITION

**Use when:** System isn't in the right state for this operation.

```go theme={null}
func (s *Service) Publish(ctx context.Context, in *PublishInput) error {
    article := s.articles[in.ID]

    if article.Status != "draft" {
        return contract.ErrFailedPrecondition("can only publish draft articles")
    }
    if !article.HasContent {
        return contract.ErrFailedPrecondition("article has no content")
    }
    // ...
}
```

**Common scenarios:**

* Resource in wrong state for operation
* Required setup not complete
* Dependency not satisfied
* Order of operations violated

**HTTP Status:** 412 Precondition Failed

***

### INTERNAL

**Use when:** Something unexpected went wrong on the server.

```go theme={null}
func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
    err := s.db.Save(todo)
    if err != nil {
        // Log the real error for debugging
        log.Printf("database error: %v", err)

        // Return generic message to user (don't leak internals!)
        return nil, contract.ErrInternal("failed to save todo")
    }
    // ...
}
```

**With cause for logging:**

```go theme={null}
return nil, contract.ErrInternal("failed to save").WithCause(dbErr)
// The cause is logged but NOT exposed to the client
```

**Common scenarios:**

* Database errors
* Unexpected nil values
* Bug in your code
* External service returned unexpected response

**HTTP Status:** 500 Internal Server Error

**Important:** Never expose internal details to clients:

```go theme={null}
// Bad: Leaks internal info
contract.ErrInternal("postgres: connection refused to 10.0.0.1:5432")

// Good: Safe message
contract.ErrInternal("database temporarily unavailable")
```

***

### UNAVAILABLE

**Use when:** Service is temporarily unavailable.

```go theme={null}
func (s *Service) Create(ctx context.Context, in *CreateInput) (*Todo, error) {
    if s.isInMaintenanceMode() {
        return nil, contract.ErrUnavailable("service is under maintenance")
    }
    if !s.db.IsConnected() {
        return nil, contract.ErrUnavailable("service temporarily unavailable")
    }
    // ...
}
```

**Common scenarios:**

* Planned maintenance
* Database connection lost
* Dependent service is down
* Server overloaded

**HTTP Status:** 503 Service Unavailable

**vs INTERNAL:**

* `UNAVAILABLE`: Temporary problem, try again later
* `INTERNAL`: Something is broken, might need fixing

***

### UNIMPLEMENTED

**Use when:** Feature doesn't exist yet.

```go theme={null}
func (s *Service) Export(ctx context.Context, in *ExportInput) (*File, error) {
    if in.Format == "pdf" {
        return nil, contract.ErrUnimplemented("PDF export coming soon")
    }
    // ...
}
```

**Common scenarios:**

* Feature not built yet
* Method stub that's not implemented
* Format or option not supported

**HTTP Status:** 501 Not Implemented

***

### Other Error Codes

These are less commonly used but available:

| Code                | HTTP | Use When                           |
| ------------------- | ---- | ---------------------------------- |
| `CANCELED`          | 499  | Request was canceled by client     |
| `UNKNOWN`           | 500  | Error doesn't fit other categories |
| `DEADLINE_EXCEEDED` | 504  | Operation timed out                |
| `ABORTED`           | 409  | Concurrent modification conflict   |
| `OUT_OF_RANGE`      | 400  | Pagination or index out of bounds  |
| `DATA_LOSS`         | 500  | Unrecoverable data corruption      |

## Creating Errors

### Convenience Functions

The most common errors have shortcut functions:

```go theme={null}
contract.ErrNotFound("user not found")
contract.ErrInvalidArgument("title required")
contract.ErrPermissionDenied("admin only")
contract.ErrUnauthenticated("please log in")
contract.ErrInternal("something went wrong")
contract.ErrAlreadyExists("email taken")
contract.ErrResourceExhausted("rate limit exceeded")
contract.ErrUnimplemented("coming soon")
contract.ErrUnavailable("maintenance mode")
```

### General Constructor

For other codes, use `NewError`:

```go theme={null}
contract.NewError(contract.ErrCodeCanceled, "request was canceled")
contract.NewError(contract.ErrCodeDeadlineExceeded, "operation timed out")
contract.NewError(contract.ErrCodeAborted, "concurrent modification")
```

### With Format String

```go theme={null}
contract.Errorf(contract.ErrCodeNotFound, "user %s not found", userID)
```

## Adding Context to Errors

### Add Details

Include structured data for debugging:

```go theme={null}
err := contract.ErrNotFound("todo not found").
    WithDetail("id", todoID).
    WithDetail("searched_at", time.Now())
```

### Add Multiple Details

```go theme={null}
err := contract.ErrInvalidArgument("validation failed").
    WithDetails(map[string]any{
        "field": "email",
        "value": email,
        "reason": "invalid format",
    })
```

### Add Cause

Wrap the underlying error (useful for logging):

```go theme={null}
result, err := s.db.Query(...)
if err != nil {
    return nil, contract.ErrInternal("database query failed").WithCause(err)
    // The cause is available for logging but NOT sent to the client
}
```

## Checking Error Types

### In Your Code

```go theme={null}
import "errors"

_, err := svc.Get(ctx, &GetInput{ID: "123"})
if err != nil {
    var contractErr *contract.Error
    if errors.As(err, &contractErr) {
        switch contractErr.Code {
        case contract.ErrCodeNotFound:
            // Handle not found
        case contract.ErrCodePermissionDenied:
            // Handle forbidden
        default:
            // Handle other errors
        }
    }
}
```

### Get HTTP Status

```go theme={null}
err := contract.ErrNotFound("not found")
status := err.HTTPStatus()  // Returns 404
```

### Convert HTTP Status to Error Code

```go theme={null}
code := contract.HTTPStatusToErrorCode(404)  // Returns ErrCodeNotFound
```

## How Errors Look in Different Protocols

### REST

```
HTTP/1.1 404 Not Found
Content-Type: text/plain

user not found
```

### JSON-RPC

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32601,
    "message": "user not found"
  }
}
```

### MCP

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {"type": "text", "text": "user not found"}
    ],
    "isError": true
  }
}
```

## Decision Guide

Use this flowchart to pick the right error code:

```
Is the user logged in?
├─ No → UNAUTHENTICATED
└─ Yes (or not required)
   │
   Is the input valid?
   ├─ No → INVALID_ARGUMENT
   └─ Yes
      │
      Does the resource exist?
      ├─ No → NOT_FOUND
      └─ Yes
         │
         Can this user access it?
         ├─ No → PERMISSION_DENIED
         └─ Yes
            │
            Is the system ready?
            ├─ No, maintenance → UNAVAILABLE
            ├─ No, rate limit → RESOURCE_EXHAUSTED
            ├─ No, wrong state → FAILED_PRECONDITION
            └─ Yes
               │
               Did something break?
               ├─ Yes → INTERNAL
               └─ No → (success!)
```

## Best Practices

### 1. Be Specific But Safe

```go theme={null}
// Good: Specific but safe
contract.ErrNotFound("user not found")

// Bad: Leaks information
contract.ErrNotFound("user with email admin@company.com not found")
```

### 2. Use the Right Code

```go theme={null}
// Good: UNAUTHENTICATED for "not logged in"
if user == nil {
    return nil, contract.ErrUnauthenticated("please log in")
}

// Bad: Using INTERNAL for auth failures
if user == nil {
    return nil, contract.ErrInternal("no user")  // Wrong code!
}
```

### 3. Log Internal Errors

```go theme={null}
if err := s.db.Save(item); err != nil {
    // Log the real error
    log.Printf("database error: %v", err)

    // Return safe error to user
    return nil, contract.ErrInternal("failed to save")
}
```

### 4. Add Context When Helpful

```go theme={null}
// Helpful for debugging
contract.ErrNotFound("todo not found").WithDetail("id", todoID)

// Not helpful (obvious from context)
contract.ErrNotFound("not found").WithDetail("type", "todo")
```

## See Also

* [Error Handling](/contract/errors) - Error patterns and examples
* [Transports Overview](/contract/transports-overview) - How errors travel
* [API Reference](/contract/api-reference) - Complete error API
