# Error Handling

The Dinie API uses the **RFC 9457 Problem Details** format for all error responses. Every error has a consistent structure, a machine-readable type URI, and a human-readable description.

## Error Response Format

All errors are returned with content type `application/problem+json`:


```json
{
  "type": "https://api.dinie.com.br/errors/invalid-request",
  "title": "Invalid Request",
  "status": 400,
  "detail": "The 'cpf' field is required.",
  "instance": "/v3/customers"
}
```

### Core Fields

| Field | Type | Description |
|  --- | --- | --- |
| `type` | string (URI) | URI that identifies the error type. Resolves to the documentation. |
| `title` | string | Short summary of the problem type. Consistent across all occurrences. |
| `status` | integer | HTTP status code for this occurrence. |
| `detail` | string | Human-readable explanation specific to this occurrence. |
| `instance` | string (URI) | URI that identifies this specific occurrence (usually the request path). |


### Extension Fields

| Field | Type | Description |
|  --- | --- | --- |
| `code` | string | Machine-readable sub-type (e.g., `missing_required_field`, `invalid_cpf`) |
| `param` | string | The request parameter that caused the error |
| `errors` | array | List of individual per-field errors (for validation failures) |


> **Tip:** Use the `code` field for programmatic error handling. The `type` identifies the broad category, while the `code` pinpoints the specific problem.


## Field Validation Errors

When a request has correct syntax but invalid field values, the response includes an `errors` array with details for each invalid field:


```json
{
  "type": "https://api.dinie.com.br/errors/validation-failed",
  "title": "Validation Failed",
  "status": 422,
  "detail": "One or more fields failed validation.",
  "errors": [
    {
      "param": "email",
      "detail": "Must be a valid email address.",
      "code": "invalid_format"
    },
    {
      "param": "cpf",
      "detail": "CPF is already registered.",
      "code": "already_exists"
    }
  ]
}
```

Each item in the `errors` array contains:

| Field | Description |
|  --- | --- |
| `param` | The field name that failed validation |
| `detail` | Human-readable explanation of the validation failure |
| `code` | Machine-readable validation error code |


## Error Type Reference

| Status | Type | Title | When |
|  --- | --- | --- | --- |
| 400 | [`invalid-request`](/errors/invalid-request) | Invalid Request | Malformed syntax, missing content type, or invalid parameters |
| 401 | [`authentication-failed`](/errors/authentication-failed) | Authentication Failed | Missing or invalid credentials or token |
| 403 | [`forbidden`](/errors/forbidden) | Forbidden | Valid token but insufficient permissions |
| 404 | [`not-found`](/errors/not-found) | Not Found | Resource does not exist or is not accessible for this partner |
| 409 | [`conflict`](/errors/conflict) | Conflict | Resource already exists or action conflicts with current state |
| 422 | [`validation-failed`](/errors/validation-failed) | Validation Failed | Request parsed successfully but field values are invalid |
| 429 | [`rate-limit-exceeded`](/errors/rate-limit-exceeded) | Rate Limit Exceeded | Too many requests. Check the `Retry-After` header. |
| 500 | [`internal`](/errors/internal) | Internal Error | Unexpected server error |


All type URIs are prefixed with `https://api.dinie.com.br`. Click the type to see common causes, examples, and resolution.

## SDK Error Handling

The SDKs throw typed exceptions for each error category:


```typescript Node.js
import Dinie, {
  AuthenticationError,
  NotFoundError,
  ValidationError,
  RateLimitError,
} from "dinie";

try {
  const customer = await dinie.customers.create({
    cpf: "invalid",
    name: "Joao Silva",
    email: "joao@example.com",
  });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error("Validation failed:", error.message);
    for (const fieldError of error.errors) {
      console.error(`  ${fieldError.param}: ${fieldError.detail}`);
    }
  } else if (error instanceof AuthenticationError) {
    console.error("Authentication failed -- check your credentials");
  } else if (error instanceof RateLimitError) {
    console.error(`Rate limit reached. Retry in ${error.retryAfter}s`);
  } else if (error instanceof NotFoundError) {
    console.error("Resource not found");
  }
}
```


```ruby Ruby
require "dinie"

begin
  customer = dinie.customers.create(
    cpf: "invalid",
    name: "Joao Silva",
    email: "joao@example.com"
  )
rescue Dinie::ValidationError => e
  puts "Validation failed: #{e.message}"
  e.errors.each do |field_error|
    puts "  #{field_error.param}: #{field_error.detail}"
  end
rescue Dinie::AuthenticationError
  puts "Authentication failed -- check your credentials"
rescue Dinie::RateLimitError => e
  puts "Rate limit reached. Retry in #{e.retry_after}s"
rescue Dinie::NotFoundError
  puts "Resource not found"
end
```


```python Python
from dinie import (
    AuthenticationError,
    NotFoundError,
    ValidationError,
    RateLimitError,
)

try:
    customer = dinie.customers.create(
        cpf="invalid",
        name="Joao Silva",
        email="joao@example.com",
    )
except ValidationError as e:
    print(f"Validation failed: {e.message}")
    for field_error in e.errors:
        print(f"  {field_error.param}: {field_error.detail}")
except AuthenticationError:
    print("Authentication failed -- check your credentials")
except RateLimitError as e:
    print(f"Rate limit reached. Retry in {e.retry_after}s")
except NotFoundError:
    print("Resource not found")
```

## Rate Limiting

All responses include rate limit headers:


```http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1709478060
```

When rate limited (429), the `Retry-After` header indicates how many seconds to wait:


```http
Retry-After: 30
```

## Retry Strategy

Not all errors should be retried. Use this table to decide:

| Status | Retry? | Strategy |
|  --- | --- | --- |
| 400 | No | Fix the request. The payload is malformed. |
| 401 | Once | Renew the access token and resend. |
| 403 | No | Check permissions with your account manager. |
| 404 | No | The resource does not exist. |
| 409 | No | Resolve the conflict (e.g., duplicate resource). |
| 422 | No | Fix the invalid field values. |
| 429 | Yes | Wait for the time indicated in `Retry-After` and resend. |
| 500 | Yes | Retry with exponential backoff. |


### Exponential Backoff

For retryable errors (429, 500), use exponential backoff with jitter:


```typescript
const MAX_RETRIES = 3;

async function requestWithRetry(fn: () => Promise<any>) {
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (!isRetryable(error) || attempt === MAX_RETRIES) throw error;

      const baseDelay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
      const jitter = Math.random() * 500;
      await sleep(baseDelay + jitter);
    }
  }
}
```

> **Info:** The SDKs implement automatic retries with exponential backoff for `429` and `500` responses. You can configure the maximum number of retries when initializing the client.