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

# Partner Integration Guide

> Complete guide for integrating with Parchment Health API

## Overview

This guide provides comprehensive information for partners integrating with the Parchment Health API. Our API follows REST principles and RFC 7807 standards to ensure consistent, predictable responses across all endpoints.

## Quick Start

### 1. Authentication Setup

First, obtain your partner credentials:

```bash theme={null}
# Example JWT token format
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
```

### 2. Basic Request Structure

All API requests follow this pattern:

```http theme={null}
POST /v1/organizations/{organization_id}/users/{user_id}/patients
Authorization: Bearer your_jwt_token
Content-Type: application/json

{
  "partner_id": "CLINIKO",
  "partner_patient_id": "12345",
  "family_name": "Smith",
  "given_name": "John"
}
```

### 3. Response Handling

All responses follow a standardized format:

```javascript theme={null}
// Check success field first
if (response.success) {
  // Handle success
  console.log('Success:', response.data);
} else {
  // Handle error
  console.error('Error:', response.error.detail);
  console.log('Request ID:', response.requestId); // For debugging
}
```

## Response Standards

### Success Response Structure

```json theme={null}
{
  "success": true,
  "statusCode": 201,
  "message": "Resource created successfully",
  "data": {
    // Response-specific data
  },
  "timestamp": "2024-01-15T10:30:00.000Z",
  "requestId": "req_1705312200000_abc123"
}
```

### Error Response Structure (RFC 7807)

```json theme={null}
{
  "success": false,
  "statusCode": 422,
  "error": {
    "type": "https://parchment.health/errors/validation-error",
    "title": "Validation failed",
    "detail": "There were some problems with your input.",
    "validation": [
      {
        "field": "family_name",
        "message": "Family name is required",
        "code": "VALIDATION_ERROR"
      }
    ]
  },
  "timestamp": "2024-01-15T10:30:00.000Z",
  "requestId": "req_1705312200000_def456"
}
```

## Common Integration Patterns

### 1. Patient Creation with Error Handling

```javascript theme={null}
async function createPatient(patientData) {
  try {
    const response = await apiClient.post('/patients', patientData);
    
    if (response.success) {
      // Success - patient created or found
      if (response.statusCode === 201) {
        console.log('New patient created:', response.data.parchment_patient_id);
      } else if (response.statusCode === 202) {
        console.log('Existing patient found:', response.data.parchment_patient_id);
      }
      
      return {
        id: response.data.parchment_patient_id,
        url: response.data.url
      };
    }
    
    // Handle errors
    throw new IntegrationError(response.error.detail, response.statusCode);
    
  } catch (error) {
    if (error.statusCode === 409) {
      throw new Error('Patient ID already exists - use unique identifier');
    } else if (error.statusCode === 422) {
      throw new ValidationError('Invalid patient data', error.validation);
    }
    throw error;
  }
}
```

### 2. Robust Error Handling Class

```javascript theme={null}
class ParchmentAPIClient {
  constructor(baseURL, token) {
    this.baseURL = baseURL;
    this.token = token;
  }
  
  async request(method, endpoint, data = null) {
    const response = await fetch(`${this.baseURL}${endpoint}`, {
      method,
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json'
      },
      body: data ? JSON.stringify(data) : null
    });
    
    const result = await response.json();
    
    // Always log request ID for debugging
    console.log(`Request ID: ${result.requestId}`);
    
    if (!result.success) {
      this.handleError(result);
    }
    
    return result;
  }
  
  handleError(errorResponse) {
    const { statusCode, error, requestId } = errorResponse;
    
    // Create detailed error with context
    const errorDetails = {
      statusCode,
      type: error.type,
      title: error.title,
      detail: error.detail,
      requestId,
      validation: error.validation || []
    };
    
    switch (statusCode) {
      case 400:
        throw new BadRequestError(errorDetails);
      case 401:
        throw new UnauthorizedError(errorDetails);
      case 403:
        throw new ForbiddenError(errorDetails);
      case 404:
        throw new NotFoundError(errorDetails);
      case 409:
        throw new ConflictError(errorDetails);
      case 422:
        throw new ValidationError(errorDetails);
      case 429:
        throw new RateLimitError(errorDetails);
      case 500:
        throw new InternalServerError(errorDetails);
      default:
        throw new APIError(errorDetails);
    }
  }
}
```

### 3. Validation Error Display

```javascript theme={null}
function displayValidationErrors(validationArray) {
  const errorsByField = {};
  
  validationArray.forEach(error => {
    if (!errorsByField[error.field]) {
      errorsByField[error.field] = [];
    }
    errorsByField[error.field].push(error.message);
  });
  
  // Display in UI
  Object.entries(errorsByField).forEach(([field, messages]) => {
    const fieldElement = document.getElementById(field);
    if (fieldElement) {
      fieldElement.classList.add('error');
      fieldElement.setAttribute('aria-invalid', 'true');
      
      // Show error messages
      const errorDiv = document.createElement('div');
      errorDiv.className = 'field-errors';
      errorDiv.innerHTML = messages.map(msg => `<p>${msg}</p>`).join('');
      fieldElement.parentNode.appendChild(errorDiv);
    }
  });
}
```

## Status Code Reference

### Success Codes

| Code  | Status     | When Used                          | Action Required              |
| ----- | ---------- | ---------------------------------- | ---------------------------- |
| `200` | OK         | Data retrieved successfully        | Process the returned data    |
| `201` | Created    | Resource created successfully      | Store the new resource ID    |
| `202` | Accepted   | Request accepted (conflicts found) | Handle the existing resource |
| `204` | No Content | Resource deleted successfully      | Update local state           |

### Error Codes

| Code  | Status                | Common Causes            | Recommended Action      |
| ----- | --------------------- | ------------------------ | ----------------------- |
| `400` | Bad Request           | Invalid request format   | Check request structure |
| `401` | Unauthorized          | Invalid/missing token    | Re-authenticate         |
| `403` | Forbidden             | Insufficient permissions | Check partner scopes    |
| `404` | Not Found             | Resource doesn't exist   | Verify resource ID      |
| `409` | Conflict              | Duplicate identifier     | Use unique identifier   |
| `422` | Unprocessable Entity  | Validation failed        | Fix validation errors   |
| `429` | Too Many Requests     | Rate limit exceeded      | Implement backoff       |
| `500` | Internal Server Error | Server error             | Retry with backoff      |
| `503` | Service Unavailable   | Service down             | Retry later             |

## Best Practices

### 1. Request ID Logging

Always log the `requestId` for debugging:

```javascript theme={null}
// Log all requests with their IDs
const logRequest = (method, url, requestId, duration) => {
  console.log(`${method} ${url} - Request ID: ${requestId} (${duration}ms)`);
};

// Include in error reports
const reportError = (error, requestId) => {
  errorTracker.captureException(error, {
    tags: { requestId },
    extra: { timestamp: new Date().toISOString() }
  });
};
```

### 2. Retry Logic with Exponential Backoff

```javascript theme={null}
async function withRetry(fn, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      // Don't retry client errors (4xx)
      if (error.statusCode < 500) {
        throw error;
      }
      
      if (attempt === maxRetries) {
        throw error;
      }
      
      // Exponential backoff
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
```

### 3. Rate Limiting Handling

```javascript theme={null}
class RateLimitHandler {
  constructor() {
    this.requestQueue = [];
    this.processing = false;
  }
  
  async handleRequest(fn) {
    return new Promise((resolve, reject) => {
      this.requestQueue.push({ fn, resolve, reject });
      this.processQueue();
    });
  }
  
  async processQueue() {
    if (this.processing || this.requestQueue.length === 0) {
      return;
    }
    
    this.processing = true;
    
    while (this.requestQueue.length > 0) {
      const { fn, resolve, reject } = this.requestQueue.shift();
      
      try {
        const result = await fn();
        resolve(result);
      } catch (error) {
        if (error.statusCode === 429) {
          // Rate limited - wait and retry
          const retryAfter = error.headers?.['retry-after'] || 60;
          await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
          this.requestQueue.unshift({ fn, resolve, reject });
          continue;
        }
        reject(error);
      }
      
      // Small delay between requests
      await new Promise(resolve => setTimeout(resolve, 100));
    }
    
    this.processing = false;
  }
}
```

### 4. Response Caching

```javascript theme={null}
class ResponseCache {
  constructor(ttl = 300000) { // 5 minutes default
    this.cache = new Map();
    this.ttl = ttl;
  }
  
  get(key) {
    const entry = this.cache.get(key);
    if (!entry) return null;
    
    if (Date.now() - entry.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }
    
    return entry.data;
  }
  
  set(key, data) {
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
  }
  
  // Use with API calls
  async getCachedResponse(cacheKey, apiCall) {
    let response = this.get(cacheKey);
    
    if (!response) {
      response = await apiCall();
      if (response.success) {
        this.set(cacheKey, response);
      }
    }
    
    return response;
  }
}
```

## Testing Integration

### 1. Unit Tests

```javascript theme={null}
describe('Parchment API Client', () => {
  it('should handle successful patient creation', async () => {
    const mockResponse = {
      success: true,
      statusCode: 201,
      message: 'Patient created successfully',
      data: {
        external_patient_id: 'TEST#123',
        parchment_patient_id: 'pat_abc123',
        url: 'https://app.parchment.health/dashboard/patients/pat_abc123'
      },
      requestId: 'req_test_123'
    };
    
    fetchMock.mockResponseOnce(JSON.stringify(mockResponse));
    
    const client = new ParchmentAPIClient(baseURL, token);
    const result = await client.createPatient(patientData);
    
    expect(result.data.parchment_patient_id).toBe('pat_abc123');
  });
  
  it('should handle validation errors', async () => {
    const mockResponse = {
      success: false,
      statusCode: 422,
      error: {
        type: 'https://parchment.health/errors/validation-error',
        title: 'Validation failed',
        detail: 'There were some problems with your input.',
        validation: [
          {
            field: 'family_name',
            message: 'Family name is required',
            code: 'VALIDATION_ERROR'
          }
        ]
      },
      requestId: 'req_test_456'
    };
    
    fetchMock.mockResponseOnce(JSON.stringify(mockResponse));
    
    const client = new ParchmentAPIClient(baseURL, token);
    
    await expect(client.createPatient({}))
      .rejects.toThrow('Validation failed');
  });
});
```

### 2. Integration Tests

```javascript theme={null}
describe('Integration Tests', () => {
  let testPatientId;
  
  afterEach(async () => {
    // Cleanup test data
    if (testPatientId) {
      await client.deletePatient(testPatientId);
    }
  });
  
  it('should create and retrieve patient', async () => {
    // Create patient
    const createResponse = await client.createPatient({
      partner_id: 'TEST',
      partner_patient_id: 'integration_test_123',
      family_name: 'TestFamily',
      given_name: 'TestGiven',
      date_of_birth: '1990-01-01',
      sex: 'M'
    });
    
    expect(createResponse.success).toBe(true);
    testPatientId = createResponse.data.parchment_patient_id;
    
    // Retrieve patient
    const getResponse = await client.getPatient(testPatientId);
    expect(getResponse.success).toBe(true);
    expect(getResponse.data.family_name).toBe('TestFamily');
  });
});
```

## Troubleshooting

### Common Issues

#### 1. Authentication Failures (401)

**Problem**: `"Valid authentication token is required"`

**Solutions**:

* Verify JWT token is valid and not expired
* Check token includes required scopes
* Ensure `Bearer ` prefix in Authorization header

#### 2. Partner Patient ID Conflicts (409)

**Problem**: `"Partner patient ID already in use"`

**Solutions**:

* Use globally unique identifiers (include timestamp/UUID)
* Check existing patients before creation
* Implement ID generation strategy

#### 3. Validation Errors (422)

**Problem**: Field validation failures

**Solutions**:

* Review required fields in API documentation
* Validate data format (dates, phone numbers)
* Check field length limits

#### 4. Rate Limiting (429)

**Problem**: `"Rate limit exceeded"`

**Solutions**:

* Implement request queuing
* Add delays between requests
* Use retry-after header values

### Debugging Checklist

1. **Log Request IDs**: Always capture and log `requestId` from responses
2. **Check API Version**: Verify `meta.apiVersion` compatibility
3. **Monitor Response Times**: Use `meta.responseTime` for performance monitoring
4. **Validate Request Format**: Ensure JSON structure matches API requirements
5. **Test Authentication**: Verify JWT token and scopes are correct

### Support Information

When contacting support, always include:

* **Request ID**: From the `requestId` field
* **Timestamp**: When the error occurred
* **API Version**: From `meta.apiVersion`
* **Error Details**: Complete error response
* **Request Data**: Sanitized request payload (remove sensitive data)

Example support request:

```
Subject: API Error - Patient Creation Failed

Request ID: req_1705312200000_abc123
Timestamp: 2024-01-15T10:30:00.000Z
API Version: 1.0
Endpoint: POST /v1/organizations/org123/users/user456/patients

Error Response:
{
  "success": false,
  "statusCode": 409,
  "error": {
    "type": "https://parchment.health/errors/partner-patient-id-conflict",
    "title": "Partner patient ID conflict",
    "detail": "Partner patient ID 'CLINIKO#12345' is already in use"
  }
}

Expected Behavior: Should create new patient or return existing patient
Actual Behavior: Returns 409 conflict error
```

## Migration from Legacy API

If you're migrating from the legacy API format:

### Response Format Changes

**Legacy Format**:

```json theme={null}
{
  "statusCode": 200,
  "message": "Success",
  "data": { ... }
}
```

**New Format**:

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": { ... },
  "timestamp": "2024-01-15T10:30:00.000Z",
  "requestId": "req_1705312200000_abc123"
}
```

### Code Migration Example

**Before**:

```javascript theme={null}
if (response.statusCode === 200) {
  // Handle success
  console.log(response.data);
} else {
  // Handle error
  console.error(response.message);
}
```

**After**:

```javascript theme={null}
if (response.success) {
  // Handle success
  console.log(response.data);
  console.log('Request ID:', response.requestId); // New field
} else {
  // Handle error with detailed error object
  console.error(response.error.detail);
  console.log('Request ID:', response.requestId); // For debugging
  
  // Handle validation errors
  if (response.error.validation) {
    response.error.validation.forEach(err => {
      console.error(`${err.field}: ${err.message}`);
    });
  }
}
```

This guide provides everything needed to successfully integrate with the Parchment Health API using the new standardized response format.
