Skip to main content

Best Practices

Follow these recommendations to build a robust, secure, and performant integration with DocBit AI.

Security

Protect Your API Key

Never expose your API key in client-side code, mobile apps, or public repositories.
Do:
  • Store in environment variables
  • Use a secrets manager (AWS Secrets Manager, HashiCorp Vault)
  • Proxy requests through your backend
Don’t:
  • Hardcode in source code
  • Include in frontend JavaScript
  • Commit to version control
  • Log in plain text
// Good: Environment variable
const apiKey = process.env.DOCBIT_API_KEY;

// Bad: Hardcoded
const apiKey = 'sk_yourpartner_abc123...'; // Never do this!

Server-Side Role Assignment

Always determine roles server-side based on your authenticated user:
// Secure: Derive roles from authenticated user
app.post('/api/ask', authMiddleware, async (req, res) => {
  const roles = getRolesFromUser(req.user); // Server determines roles
  
  const response = await DocBit AI.chat({
    headers: {
      'X-External-Roles': JSON.stringify(roles)
    }
  });
});

// Insecure: Never trust client-provided roles
app.post('/api/ask', async (req, res) => {
  const roles = req.body.roles; // DANGEROUS!
});

Validate Organization Access

Ensure users can only access their authorized organizations:
app.post('/api/ask', authMiddleware, async (req, res) => {
  const user = req.user;
  const requestedOrgId = req.body.orgId;
  
  // Verify user belongs to this org
  if (!user.organizations.includes(requestedOrgId)) {
    return res.status(403).json({ error: 'Access denied' });
  }
  
  // Proceed with request
});

Error Handling

Always Handle Errors

Never assume API calls succeed:
async function askQuestion(question) {
  try {
    const response = await DocBit AI.chat({ message: question });
    return response;
  } catch (error) {
    if (error.response) {
      // API returned an error
      handleApiError(error.response.status, error.response.data);
    } else if (error.request) {
      // Network error
      handleNetworkError(error);
    } else {
      // Other error
      throw error;
    }
  }
}

Implement Retry Logic

Retry transient failures with exponential backoff:
async function withRetry(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      const status = error.response?.status;
      
      // Retry on rate limit or server errors
      if (status === 429 || status >= 500) {
        const waitTime = Math.pow(2, i) * 1000;
        await sleep(waitTime);
        continue;
      }
      
      // Don't retry client errors
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}

User-Friendly Error Messages

Don’t expose raw API errors to end users:
function getUserMessage(error) {
  switch (error.response?.status) {
    case 400:
      return 'Please check your question and try again.';
    case 401:
      return 'Session expired. Please log in again.';
    case 429:
      return 'Too many requests. Please wait a moment.';
    case 500:
      return 'Service temporarily unavailable. Please try again.';
    default:
      return 'Something went wrong. Please try again.';
  }
}

Performance

Use Streaming for Chat

Streaming provides better user experience for long responses:
// Better UX: Stream tokens as they arrive
async function streamResponse(question, onToken) {
  const response = await fetch('/api/ai/chat/stream', {
    method: 'POST',
    body: JSON.stringify({ message: question })
  });
  
  const reader = response.body.getReader();
  // Process tokens as they arrive...
}

Cache Conversation Context

Store conversation IDs to continue conversations efficiently:
class ConversationManager {
  constructor() {
    this.conversationId = null;
  }
  
  async ask(question) {
    const response = await DocBit AI.chat({
      message: question,
      conversationId: this.conversationId
    });
    
    // Store for follow-ups
    this.conversationId = response.conversationId;
    return response;
  }
}

Batch Document Uploads

When uploading multiple documents, pace them to avoid rate limits:
async function uploadBatch(files) {
  const results = [];
  
  for (const file of files) {
    const result = await uploadDocument(file);
    results.push(result);
    
    // Pace uploads
    await sleep(1000);
  }
  
  return results;
}

User Experience

Show Confidence Indicators

Display the confidence level to set user expectations:
function renderResponse(response) {
  return (
    <div>
      <p>{response.content}</p>
      <ConfidenceBadge level={response.confidence} />
      {response.confidence === 'Low' && (
        <Warning>This answer may be incomplete. Check the sources.</Warning>
      )}
    </div>
  );
}

Display Citations

Help users verify answers by showing sources:
function renderCitations(citations) {
  return (
    <div className="citations">
      <h4>Sources</h4>
      {citations.map(citation => (
        <Citation key={citation.documentId}>
          <strong>{citation.documentTitle}</strong>
          {citation.page && <span> - Page {citation.page}</span>}
          <blockquote>{citation.excerpt}</blockquote>
        </Citation>
      ))}
    </div>
  );
}

Handle Loading States

Show progress while waiting for responses:
function ChatInterface() {
  const [loading, setLoading] = useState(false);
  const [streaming, setStreaming] = useState(false);
  
  return (
    <div>
      {loading && !streaming && <LoadingSpinner />}
      {streaming && <TypingIndicator />}
      <MessageList messages={messages} />
    </div>
  );
}

Testing

Use a Sandbox Organization

Create a test organization for development:
const config = {
  development: {
    orgId: 'test-sandbox',
    apiUrl: 'https://api.docbit.ai'
  },
  production: {
    orgId: process.env.ORG_ID,
    apiUrl: 'https://api.docbit.ai'
  }
};

Test Role Combinations

Verify access control with different roles:
describe('Access Control', () => {
  it('HR user sees HR documents', async () => {
    const response = await askWithRoles(['hr'], 'What are the HR policies?');
    expect(response.citations).toContain(hrDocument);
  });
  
  it('Finance user does not see HR documents', async () => {
    const response = await askWithRoles(['finance'], 'What are the HR policies?');
    expect(response.citations).not.toContain(hrDocument);
  });
});

Test Error Scenarios

Verify error handling works correctly:
describe('Error Handling', () => {
  it('handles rate limits gracefully', async () => {
    // Simulate 429 response
    const response = await askWithRetry(question);
    expect(response).toBeDefined();
  });
  
  it('shows user-friendly error messages', async () => {
    const errorMessage = getUserMessage({ response: { status: 429 } });
    expect(errorMessage).not.toContain('429');
  });
});

Logging and Monitoring

Log API Interactions

Log requests for debugging (but not sensitive data):
function logRequest(method, endpoint, orgId, userId) {
  console.log({
    timestamp: new Date().toISOString(),
    method,
    endpoint,
    orgId,
    userId: hashUserId(userId), // Don't log raw user IDs
    // Don't log: API key, message content, roles
  });
}

Track Key Metrics

Monitor important metrics:
  • Response times
  • Error rates
  • Rate limit hits
  • Confidence distribution
  • Citation counts
class MetricsTracker {
  track(response, duration) {
    metrics.histogram('api.response_time', duration);
    metrics.increment('api.requests');
    metrics.increment(`api.confidence.${response.confidence.toLowerCase()}`);
  }
}

Checklist

Before going to production:
  • API key stored securely (not in code)
  • Roles determined server-side
  • Error handling implemented
  • Retry logic with backoff
  • Rate limiting respected
  • User-friendly error messages
  • Loading states shown
  • Confidence indicators displayed
  • Citations shown to users
  • Logging in place (without sensitive data)
  • Tested with different role combinations