Skip to main content

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:
# Example JWT token format
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

2. Basic Request Structure

All API requests follow this pattern:
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:
// 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

{
  "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)

{
  "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

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

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

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

CodeStatusWhen UsedAction Required
200OKData retrieved successfullyProcess the returned data
201CreatedResource created successfullyStore the new resource ID
202AcceptedRequest accepted (conflicts found)Handle the existing resource
204No ContentResource deleted successfullyUpdate local state

Error Codes

CodeStatusCommon CausesRecommended Action
400Bad RequestInvalid request formatCheck request structure
401UnauthorizedInvalid/missing tokenRe-authenticate
403ForbiddenInsufficient permissionsCheck partner scopes
404Not FoundResource doesn’t existVerify resource ID
409ConflictDuplicate identifierUse unique identifier
422Unprocessable EntityValidation failedFix validation errors
429Too Many RequestsRate limit exceededImplement backoff
500Internal Server ErrorServer errorRetry with backoff
503Service UnavailableService downRetry later

Best Practices

1. Request ID Logging

Always log the requestId for debugging:
// 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

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

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

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

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

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:
{
  "statusCode": 200,
  "message": "Success",
  "data": { ... }
}
New Format:
{
  "success": true,
  "statusCode": 200,
  "message": "Success",
  "data": { ... },
  "timestamp": "2024-01-15T10:30:00.000Z",
  "requestId": "req_1705312200000_abc123"
}

Code Migration Example

Before:
if (response.statusCode === 200) {
  // Handle success
  console.log(response.data);
} else {
  // Handle error
  console.error(response.message);
}
After:
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.