> ## Documentation Index
> Fetch the complete documentation index at: https://developer.utgl.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Requests & Responses

> Complete guide to API request formatting, response structures, status codes, error handling, and best practices

The Issuing API uses JSON for request and response bodies, and requires cryptographic signatures for authentication. This guide covers everything you need to know about making requests and handling responses.

***

## Quick Start

All API requests follow this pattern:

1. **Format** - JSON request body with `Content-Type: application/json`
2. **Authenticate** - Sign requests using RSA-SHA256 JWT tokens
3. **Handle** - Process responses and errors appropriately

***

## Request Format

### Content Type

All requests must use JSON encoding:

* **Content-Type:** `application/json`
* Request bodies must be valid, well-formed JSON
* All responses are returned in JSON format

### Required Headers

Every API request must include these headers:

| Header            | Required    | Description                          | Example                                          |
| ----------------- | ----------- | ------------------------------------ | ------------------------------------------------ |
| `Content-Type`    | Yes         | Must be `application/json`           | `application/json`                               |
| `Authorization`   | Yes         | Bearer token with signed JWT         | `Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...` |
| `Idempotency-Key` | Conditional | UUID for POST requests (recommended) | `550e8400-e29b-41d4-a716-446655440000`           |

<Tip>
  **Idempotency Key:** Always include an `Idempotency-Key` header for POST requests. This ensures safe retries and prevents duplicate operations. See [Idempotency](/issuing/getting-started/idempotency) for details.
</Tip>

### Authentication

All API requests require cryptographic signatures:

* **SHA-256** - For hashing request bodies
* **RSA** - For signing JWT tokens

See the [Authentication guide](/issuing/getting-started/request-signing) for detailed signing instructions.

### Example Request

```bash theme={null}
curl -X POST https://access.utgl.io/v1/accounts \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "name": "My Account",
    "currency": "USD"
  }'
```

***

## Response Format

### Response Headers

All API responses include standard HTTP headers plus:

| Header         | Description                                                                  | Example                                |
| -------------- | ---------------------------------------------------------------------------- | -------------------------------------- |
| `x-request-id` | Unique identifier for this request. **Always log this** for support purposes | `550e8400-e29b-41d4-a716-446655440000` |
| `Content-Type` | Response content type (always `application/json`)                            | `application/json`                     |
| `Retry-After`  | Present on 429 responses, indicates seconds to wait                          | `60`                                   |

### Success Responses

Successful requests return:

* **HTTP Status:** `200 OK` (or `201 Created` for resource creation)
* **Body:** JSON-encoded response data

**Example Success Response:**

```json theme={null}
{
  "id": "acc_1234567890",
  "name": "My Account",
  "currency": "USD",
  "balance": "1000.00",
  "status": "active",
  "createdAt": "2024-01-15T10:30:00Z"
}
```

### Response Structure

Response bodies follow consistent patterns:

* **Single Resource:** Object with resource properties
* **List Resources:** Object with `data` array and pagination metadata
* **Empty Response:** `{}` or `null` for DELETE operations

***

## HTTP Status Codes

The API uses standard HTTP status codes to communicate request outcomes:

| Status Code     | Category     | Meaning                              | Action Required                       |
| --------------- | ------------ | ------------------------------------ | ------------------------------------- |
| **200**         | Success      | Request completed successfully       | None - process response data          |
| **201**         | Success      | Resource created successfully        | None - process response data          |
| **400**         | Client Error | Invalid request format or parameters | Fix request before retrying           |
| **401**         | Client Error | Authentication failed                | Check credentials and signature       |
| **403**         | Client Error | Access denied                        | Verify permissions and account status |
| **404**         | Client Error | Resource not found                   | Verify resource ID exists             |
| **409**         | Client Error | Resource conflict (e.g., duplicate)  | Check for existing resources          |
| **429**         | Client Error | Rate limit exceeded                  | Wait and retry with backoff           |
| **500**         | Server Error | Internal server error                | Retry with exponential backoff        |
| **502/503/504** | Server Error | Service temporarily unavailable      | Retry after delay                     |

### Status Code Details

#### 200 OK

The request succeeded. Response body contains the requested data.

#### 201 Created

Resource was successfully created. Response body contains the new resource.

#### 400 Bad Request

The request is malformed or contains invalid parameters. Check the error response for specific issues.

**Common causes:**

* Missing required fields
* Invalid field formats
* Malformed JSON
* Invalid enum values

#### 401 Unauthorized

Authentication failed. The access key or JWT signature is invalid.

**Common causes:**

* Invalid or missing access key
* Incorrect JWT signature
* Missing Authorization header
* Signature verification failure
* JWT token expired (must be less than 30 seconds old)
* URI or method claims don't match the request

#### 403 Forbidden

Access denied. Your credentials are valid but you don't have permission.

**Common causes:**

* Account is inactive
* Subscription doesn't include this feature
* IP address not whitelisted
* Invalid account number or unique\_id
* Insufficient permissions

#### 404 Not Found

The requested resource doesn't exist or isn't accessible.

**Common causes:**

* Resource ID is incorrect
* Resource belongs to another account
* Resource has been deleted

#### 409 Conflict

The request conflicts with the current state of the resource.

**Common causes:**

* Duplicate resource (e.g., card with same reference)
* Resource already exists
* Concurrent modification conflict

#### 429 Too Many Requests

Rate limit exceeded. Too many requests in a given time period.

**Response includes:**

* `Retry-After` header indicating seconds to wait
* Error details in response body

See [Rate Limiting](/issuing/getting-started/rate-limits) for details.

#### 500 Internal Server Error

A server-side error occurred. This is typically temporary.

**Action:** Retry the request with exponential backoff.

#### 502/503/504 Service Unavailable

The service is temporarily unavailable or undergoing maintenance.

**Action:** Retry after a delay. Check status page for maintenance windows.

***

## Error Responses

When an error occurs, the API returns an appropriate HTTP status code with a structured error response.

### Error Response Format

All error responses follow this structure:

```json theme={null}
{
  "code": "INSUFFICIENT_BALANCE",
  "message": "The funding account has insufficient balance to complete the transfer. Create a Transfer to fund the source account."
}
```

Some errors may include additional context:

```json theme={null}
{
  "code": "INVALID_PARAMETERS",
  "message": "Validation failed",
  "details": {
    "field": "amount",
    "reason": "must_be_positive"
  }
}
```

### Error Response Fields

| Field     | Type   | Description                                           | Example                                             |
| --------- | ------ | ----------------------------------------------------- | --------------------------------------------------- |
| `code`    | string | Machine-readable error code for programmatic handling | `INSUFFICIENT_BALANCE`                              |
| `message` | string | Human-readable error description with guidance        | `The source account has insufficient balance...`    |
| `details` | object | Additional error context (optional, varies by error)  | `{"field": "amount", "reason": "must_be_positive"}` |

<Warning>
  **Important:** Always use the `code` field for programmatic error handling. The `message` field is for human readability and may change over time.
</Warning>

### Common Error Codes by Status

#### Authentication Errors (401)

```json theme={null}
{
  "code": "INVALID_SIGNATURE",
  "message": "JWT signature verification failed"
}
```

**Solutions:**

* Verify your access key is correct
* Check the `Authorization` header is properly formatted
* Verify your JWT signature is correct
* Check that the `uri` and `method` claims match the request exactly
* Ensure the JWT token hasn't expired (must be less than 30 seconds old)

#### Validation Errors (400)

```json theme={null}
{
  "code": "INVALID_PARAMETERS",
  "message": "Validation failed",
  "details": {
    "field": "amount",
    "reason": "must_be_positive"
  }
}
```

**Solutions:**

* Check all required fields are present
* Verify field formats match the API specification
* Ensure numeric values are within valid ranges
* Review validation rules in the [API Reference](/issuing/api-reference/openapi)

#### Insufficient Balance (400)

```json theme={null}
{
  "code": "INSUFFICIENT_BALANCE",
  "message": "The funding account has insufficient balance to complete the transfer. Create a Transfer to fund the source account."
}
```

**Solutions:**

* Check account balance before initiating transfers
* Fund the account using the appropriate endpoint
* Verify currency matches between accounts

#### Resource Not Found (404)

```json theme={null}
{
  "code": "INVALID_CARD_ACCOUNT",
  "message": "The card account is invalid or does not exist."
}
```

**Solutions:**

* Verify the resource ID is correct
* Ensure the resource belongs to your account
* Check if the resource has been deleted
* Verify you're using the correct endpoint

#### Rate Limit Errors (429)

```json theme={null}
{
  "code": "RATE_LIMIT_EXCEEDED",
  "message": "Too many requests. Please try again later."
}
```

<Note>
  The `Retry-After` header will be included in the HTTP response headers, not in the error body.
</Note>

**Solutions:**

* Implement exponential backoff
* Respect the `Retry-After` header
* Review your [rate limits](/issuing/getting-started/rate-limits)
* Consider batching requests when possible

#### Conflict Errors (409)

```json theme={null}
{
  "code": "INVALID_PARAMETERS",
  "message": "A card with this reference already exists"
}
```

**Solutions:**

* Check for existing resources before creating
* Use idempotency keys for safe retries
* Verify unique constraints in the API specification

### Common Error Codes

| Error Code             | HTTP Status | Description                             |
| ---------------------- | ----------- | --------------------------------------- |
| `INVALID_PARAMETERS`   | 400         | Invalid request parameters              |
| `INVALID_ACCOUNT`      | 400         | Invalid account identifier              |
| `INVALID_CARD_ACCOUNT` | 400         | Invalid card account identifier         |
| `INSUFFICIENT_BALANCE` | 400         | Account doesn't have sufficient balance |
| `INVALID_SIGNATURE`    | 401         | JWT signature verification failed       |
| `RATE_LIMIT_EXCEEDED`  | 429         | Too many requests                       |

<Note>
  For a complete list of error codes, refer to the [API Reference](/issuing/api-reference/openapi). Error codes are defined in the OpenAPI specification for each endpoint.
</Note>

***

## Error Handling Best Practices

### 1. Check HTTP Status Codes

Always check the HTTP status code first:

```javascript theme={null}
if (response.status >= 400) {
  const error = await response.json();
  console.error('API Error:', {
    code: error.code,
    message: error.message,
    status: response.status
  });
  // Handle error based on code
}
```

### 2. Implement Retry Logic

Retry only for transient failures (5xx, 429):

```javascript theme={null}
async function apiCall(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      
      // Don't retry client errors (4xx)
      if (response.status >= 400 && response.status < 500) {
        const error = await response.json();
        throw new APIError(error.code, error.message, response.status);
      }
      
      // Retry server errors (5xx)
      if (response.status >= 500 && i < maxRetries - 1) {
        await sleep(Math.pow(2, i) * 1000); // Exponential backoff
        continue;
      }
      
      return await response.json();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }
}
```

### 3. Log Request IDs

Always log the `x-request-id` from error responses:

```javascript theme={null}
try {
  const result = await createCard(data);
} catch (error) {
  const requestId = error.response?.headers?.get('x-request-id');
  console.error('Error creating card:', {
    requestId,
    code: error.code,
    message: error.message,
    status: error.status
  });
  
  // Report to your error tracking service
  reportError(error, requestId);
}
```

### 4. Handle Rate Limits Gracefully

Respect rate limits and retry after the specified time:

```javascript theme={null}
async function apiCallWithRateLimit(url, options) {
  const response = await fetch(url, options);
  
  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
    await sleep(retryAfter * 1000);
    return apiCallWithRateLimit(url, options); // Retry
  }
  
  return response.json();
}
```

### 5. Display User-Friendly Messages

Don't expose raw API errors to end users:

```javascript theme={null}
function getUserFriendlyMessage(error) {
  const messages = {
    'INSUFFICIENT_BALANCE': 'You don't have enough balance for this transaction.',
    'INVALID_CARD_ACCOUNT': 'The requested card account was not found.',
    'INVALID_PARAMETERS': 'Please check your input and try again.',
    'RATE_LIMIT_EXCEEDED': 'We're experiencing high traffic. Please try again in a moment.',
    'INVALID_SIGNATURE': 'Authentication failed. Please check your credentials.'
  };
  
  return messages[error.code] || 'Something went wrong. Please try again.';
}
```

***

## Retrying Requests

Not all failed requests should be retried. Implement retry logic only for specific scenarios.

### When to Retry

**Retry these status codes:**

* **5xx** (Server errors) - Temporary server-side issues
* **429** (Rate Limits) - Rate limit exceeded, wait and retry

**Do NOT retry these status codes:**

* **400** (Bad Request) - Fix the request first
* **401** (Unauthorized) - Check authentication
* **403** (Forbidden) - Verify permissions
* **404** (Not Found) - Resource doesn't exist
* **409** (Conflict) - Resolve conflict first

### Retry Implementation

#### Exponential Backoff

Implement exponential backoff with jitter to prevent thundering herd problems:

```javascript theme={null}
async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      // Don't retry client errors (4xx)
      if (error.status >= 400 && error.status < 500) {
        throw error;
      }
      
      // Last attempt
      if (i === maxRetries - 1) {
        throw error;
      }
      
      // Exponential backoff with jitter
      const baseDelay = Math.pow(2, i) * 1000; // 1s, 2s, 4s...
      const jitter = Math.random() * 1000; // 0-1s random
      await sleep(baseDelay + jitter);
    }
  }
}
```

#### Respect Rate Limits

When receiving a 429 response, respect the `Retry-After` header:

```javascript theme={null}
async function handleRateLimit(response) {
  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
    await sleep(retryAfter * 1000);
    return true; // Retry
  }
  return false;
}
```

#### Idempotency Keys

Always use a **new** idempotency key for each retry attempt:

```javascript theme={null}
async function makeRequestWithRetry(data) {
  let attempt = 0;
  const maxRetries = 3;
  
  while (attempt < maxRetries) {
    const idempotencyKey = generateUUID(); // New key each time
    const headers = {
      'Idempotency-Key': idempotencyKey,
      // ... other headers
    };
    
    try {
      return await fetch(url, { headers, body: JSON.stringify(data) });
    } catch (error) {
      if (shouldRetry(error.status)) {
        attempt++;
        await exponentialBackoff(attempt);
        continue;
      }
      throw error;
    }
  }
}
```

<Warning>
  **Critical:** When retrying, always provide a **new idempotency key**. Requests with the same idempotency key return the same result, including errors. This ensures retries are treated as new attempts.
</Warning>

***

## Request ID Tracking

Every API response includes an `x-request-id` header. This unique identifier is essential for:

* **Debugging** - Track specific requests in your logs
* **Support** - Reference requests when contacting support
* **Troubleshooting** - Correlate requests across systems
* **Auditing** - Track API usage and issues

### Best Practices

**Always log the request ID:**

```javascript theme={null}
async function makeAPIRequest(url, options) {
  const response = await fetch(url, options);
  const requestId = response.headers.get('x-request-id');
  
  // Log for debugging
  console.log(`Request ID: ${requestId}`);
  
  if (!response.ok) {
    const error = await response.json();
    // Include request ID in error logs
    logger.error('API Error', {
      requestId,
      status: response.status,
      error: error.code,
      message: error.message
    });
  }
  
  return response;
}
```

**Include in support requests:**

When contacting support, always include:

* The `x-request-id` from the response
* Timestamp of the request
* Endpoint and method used
* Request payload (sanitized of sensitive data)

***

## Best Practices

### Request Formatting

1. **Validate JSON** - Ensure request bodies are valid JSON before sending
2. **Use consistent formatting** - Follow the API specification exactly
3. **Include all required fields** - Check endpoint documentation
4. **Use proper data types** - Numbers as numbers, not strings (unless specified)

### Error Handling

1. **Check status codes first** - Handle different status codes appropriately
2. **Use error codes, not messages** - Parse the `code` field for programmatic handling
3. **Log request IDs** - Always capture `x-request-id` for debugging
4. **Implement retry logic** - For transient failures (5xx, 429)
5. **Don't retry client errors** - Fix 4xx errors before retrying

### Performance

1. **Use idempotency keys** - Prevents duplicate operations
2. **Implement connection pooling** - Reuse HTTP connections
3. **Batch operations** - When possible, use batch endpoints
4. **Respect rate limits** - Monitor your usage and implement backoff

### Security

1. **Never log sensitive data** - Don't log full request/response bodies
2. **Store credentials securely** - Use environment variables or secure vaults
3. **Rotate keys regularly** - Follow security best practices
4. **Validate responses** - Don't trust response data blindly

***

## Code Examples

### JavaScript/TypeScript

```typescript theme={null}
interface APIResponse<T> {
  data?: T;
  error?: {
    code: string;
    message: string;
    details?: any;
  };
}

async function apiRequest<T>(
  endpoint: string,
  options: RequestInit = {}
): Promise<T> {
  const url = `https://access.utgl.io/v1${endpoint}`;
  const requestId = generateUUID();
  
  const headers = {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${await generateJWT(endpoint, options.method || 'GET')}`,
    'Idempotency-Key': requestId,
    ...options.headers,
  };
  
  try {
    const response = await fetch(url, {
      ...options,
      headers,
    });
    
    const requestIdHeader = response.headers.get('x-request-id');
    logger.info('API Request', { endpoint, requestId: requestIdHeader });
    
    if (!response.ok) {
      const error = await response.json();
      throw new APIError(error.code, error.message, response.status, requestIdHeader);
    }
    
    return await response.json();
  } catch (error) {
    if (error instanceof APIError) {
      throw error;
    }
    throw new Error(`Request failed: ${error.message}`);
  }
}
```

### Python

```python theme={null}
import requests
import uuid
from typing import Optional, Dict, Any

def api_request(
    endpoint: str,
    method: str = 'GET',
    data: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
    url = f"https://access.utgl.io/v1{endpoint}"
    request_id = str(uuid.uuid4())
    
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {generate_jwt(endpoint, method, data)}',
        'Idempotency-Key': request_id,
    }
    
    try:
        response = requests.request(
            method=method,
            url=url,
            headers=headers,
            json=data,
            timeout=30
        )
        
        request_id_header = response.headers.get('x-request-id')
        logger.info(f'API Request: {endpoint}', extra={'request_id': request_id_header})
        
        response.raise_for_status()
        return response.json()
        
    except requests.exceptions.HTTPError as e:
        error_data = e.response.json() if e.response.content else {}
        raise APIError(
            code=error_data.get('code'),
            message=error_data.get('message'),
            status_code=e.response.status_code,
            request_id=request_id_header
        )
```

***

## Troubleshooting

### Common Issues

**Issue: "Invalid JSON" errors**

* **Solution:** Validate JSON before sending. Ensure proper escaping of special characters.

**Issue: "Signature verification failed"**

* **Solution:** Verify your private key is correct and the JWT is properly signed. Check the `uri` and `method` claims match exactly.

**Issue: "Rate limit exceeded"**

* **Solution:** Implement exponential backoff and respect `Retry-After` headers. Review your rate limits.

**Issue: "Request ID not found in logs"**

* **Solution:** Always capture and log the `x-request-id` header from responses.

**Issue: "Duplicate operations"**

* **Solution:** Use unique idempotency keys for each request. Don't reuse keys across different operations.

***

***

## Next Steps

* Review [Authentication](/issuing/getting-started/request-signing) for detailed signing instructions
* Learn about [Idempotency](/issuing/getting-started/idempotency) for safe retry handling
* Understand [Rate Limits](/issuing/getting-started/rate-limits) to optimize your integration
* Explore the [API Reference](/api-reference) for endpoint-specific details
