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

# Best practices

> Guidelines and recommendations for building robust integrations with the FirstQuadrant API

Following these best practices will help you build efficient, reliable, and maintainable integrations with the FirstQuadrant API.

## Authentication & security

### Secure credential storage

Never hardcode API keys or tokens in your source code:

```javascript theme={null}
// ❌ Bad: Hardcoded credentials
const apiKey = "fqa_abc123def456";

// ✅ Good: Environment variables
const apiKey = process.env.FIRSTQUADRANT_API_KEY;

// ✅ Good: Secure key management service
const apiKey = await keyVault.getSecret("firstquadrant-api-key");
```

### API key rotation

Implement a rotation strategy for API keys:

1. Generate new API keys periodically
2. Update your applications with the new key
3. Revoke old keys after confirming the new key works
4. Monitor for unauthorized usage

### Minimize scope

Request only the permissions your integration needs:

```javascript theme={null}
// ❌ Bad: Requesting all permissions
const scopes = ["urn:firstquadrant:*:*:write"];

// ✅ Good: Specific permissions only
const scopes = ["urn:firstquadrant:contact:*:read", "urn:firstquadrant:campaign:*:write"];
```

## Request optimization

### Use field selection

Only request the fields you need to reduce payload size and improve performance:

```javascript theme={null}
// ❌ Bad: Fetching all fields when you only need a few
const response = await fetch("/v5/contacts?limit=100");

// ✅ Good: Select specific fields
const response = await fetch("/v5/contacts?select[]=id&select[]=email&select[]=firstName&limit=100");
```

### Batch operations

When possible, group operations to reduce API calls:

```javascript theme={null}
// ❌ Bad: Individual requests for each contact
for (const email of emails) {
  await createContact({ email });
}

// ✅ Good: Process in batches
async function processBatch(contacts) {
  // Use bulk endpoints when available
  // Or process with controlled concurrency
  const batchSize = 50;
  for (let i = 0; i < contacts.length; i += batchSize) {
    const batch = contacts.slice(i, i + batchSize);
    await Promise.all(batch.map((contact) => createContact(contact)));
  }
}
```

### Implement caching

Cache frequently accessed, rarely changing data:

```javascript theme={null}
class CachedAPI {
  constructor(ttl = 300000) {
    // 5 minutes
    this.cache = new Map();
    this.ttl = ttl;
  }

  async getOrganization(id) {
    const cacheKey = `org:${id}`;
    const cached = this.cache.get(cacheKey);

    if (cached && Date.now() < cached.expiry) {
      return cached.data;
    }

    const data = await fetchOrganization(id);
    this.cache.set(cacheKey, {
      data,
      expiry: Date.now() + this.ttl,
    });

    return data;
  }
}
```

## Error handling

### Implement comprehensive error handling

Handle all possible error scenarios:

```javascript theme={null}
async function apiRequest(endpoint, options = {}) {
  try {
    const response = await fetch(`${API_BASE}${endpoint}`, {
      ...options,
      headers: {
        Authorization: `Bearer ${API_KEY}`,
        "Content-Type": "application/json",
        ...options.headers,
      },
    });

    // Handle different error types
    if (!response.ok) {
      const error = await response.json();

      switch (response.status) {
        case 400:
          throw new ValidationError(error);
        case 401:
          await refreshToken();
          return apiRequest(endpoint, options); // Retry
        case 403:
          throw new PermissionError(error);
        case 404:
          throw new NotFoundError(error);
        case 429:
          await handleRateLimit(response);
          return apiRequest(endpoint, options); // Retry
        case 500:
        case 502:
        case 503:
        case 504:
          throw new ServerError(error);
        default:
          throw new APIError(error);
      }
    }

    return response.json();
  } catch (error) {
    if (error.name === "AbortError") {
      throw new TimeoutError("Request timeout");
    }
    if (error.name === "TypeError") {
      throw new NetworkError("Network error");
    }
    throw error;
  }
}
```

### Log errors with context

Include request details for debugging:

```javascript theme={null}
function logError(error, context) {
  console.error({
    timestamp: new Date().toISOString(),
    error: {
      message: error.message,
      code: error.code,
      status: error.status,
    },
    request: {
      method: context.method,
      endpoint: context.endpoint,
      requestId: context.headers?.["X-Request-Id"],
    },
    user: context.userId,
    organization: context.organizationId,
  });
}
```

## Performance

### Implement request timeouts

Prevent hanging requests:

```javascript theme={null}
async function fetchWithTimeout(url, options = {}, timeout = 30000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
    });
    return response;
  } finally {
    clearTimeout(timeoutId);
  }
}
```

### Use pagination efficiently

Process large datasets without overwhelming your system:

```javascript theme={null}
async function* getAllContacts(filters = {}) {
  let cursor = null;
  const pageSize = 100; // Maximum allowed

  while (true) {
    const params = new URLSearchParams({
      ...filters,
      limit: pageSize,
      ...(cursor && { startingAfter: cursor }),
    });

    const contacts = await fetch(`/v5/contacts?${params}`);

    if (contacts.length === 0) break;

    yield contacts;

    if (contacts.length < pageSize) break;
    cursor = contacts[contacts.length - 1].id;
  }
}

// Process in streams
for await (const batch of getAllContacts()) {
  await processBatch(batch);
}
```

### Implement connection pooling

Reuse connections for better performance:

```javascript theme={null}
import { Agent } from "https";

const httpsAgent = new Agent({
  keepAlive: true,
  keepAliveMsecs: 60000,
  maxSockets: 10,
});

const response = await fetch(url, {
  agent: httpsAgent,
  // ... other options
});
```

## ID management

### Use type-prefixed IDs

Always validate ID formats:

```javascript theme={null}
const ID_PATTERNS = {
  user: /^usr_[a-zA-Z0-9]+$/,
  organization: /^org_[a-zA-Z0-9]+$/,
  contact: /^con_[a-zA-Z0-9]+$/,
  campaign: /^cam_[a-zA-Z0-9]+$/,
  deal: /^del_[a-zA-Z0-9]+$/,
};

function validateId(id, type) {
  const pattern = ID_PATTERNS[type];
  if (!pattern || !pattern.test(id)) {
    throw new Error(`Invalid ${type} ID format: ${id}`);
  }
  return id;
}

// Usage
const contactId = validateId(inputId, "contact");
```

## Data consistency

### Handle concurrent updates

Implement optimistic locking when needed:

```javascript theme={null}
async function updateContact(id, updates) {
  // Fetch current version
  const current = await getContact(id);

  try {
    const updated = await apiRequest(`/contacts/${id}`, {
      method: "PATCH",
      body: JSON.stringify({
        ...updates,
        expectedVersion: current.version, // If API supports versioning
      }),
    });
    return updated;
  } catch (error) {
    if (error.code === "conflict") {
      // Handle concurrent modification
      console.warn("Contact was modified by another process");
      // Retry with fresh data or merge changes
    }
    throw error;
  }
}
```

### Validate data before sending

Validate on the client side to avoid unnecessary API calls:

```javascript theme={null}
import { z } from "zod";

const ContactSchema = z.object({
  email: z.string().email(),
  firstName: z.string().min(1).max(100),
  lastName: z.string().min(1).max(100),
  phone: z
    .string()
    .regex(/^\+[1-9]\d{1,14}$/)
    .optional(),
  customProperties: z.record(z.any()).optional(),
});

function createContact(data) {
  // Validate before API call
  const validated = ContactSchema.parse(data);

  return apiRequest("/contacts", {
    method: "POST",
    body: JSON.stringify(validated),
  });
}
```

## Monitoring & observability

### Track API usage

Monitor your integration's performance:

```javascript theme={null}
class APIMetrics {
  constructor() {
    this.metrics = {
      requests: 0,
      errors: 0,
      latency: [],
    };
  }

  async track(fn) {
    const start = Date.now();
    this.metrics.requests++;

    try {
      const result = await fn();
      this.metrics.latency.push(Date.now() - start);
      return result;
    } catch (error) {
      this.metrics.errors++;
      throw error;
    }
  }

  getStats() {
    const latency = this.metrics.latency;
    return {
      totalRequests: this.metrics.requests,
      errorRate: this.metrics.errors / this.metrics.requests,
      avgLatency: latency.reduce((a, b) => a + b, 0) / latency.length,
      p95Latency: latency.sort()[Math.floor(latency.length * 0.95)],
    };
  }
}
```

### Include correlation IDs

Track requests across systems:

```javascript theme={null}
import { randomUUID } from "crypto";

function createRequestHeaders(correlationId = randomUUID()) {
  return {
    Authorization: `Bearer ${API_KEY}`,
    "FirstQuadrant-Organization-ID": ORGANIZATION_ID,
    "X-Correlation-ID": correlationId,
    "User-Agent": "MyApp/1.0.0",
  };
}
```

## Integration patterns

### Implement idempotency

Make operations safe to retry:

```javascript theme={null}
async function createCampaignIdempotent(campaign, idempotencyKey) {
  // Check if already processed
  const existing = await cache.get(`idempotent:${idempotencyKey}`);
  if (existing) return existing;

  const result = await apiRequest("/campaigns", {
    method: "POST",
    headers: {
      "Idempotency-Key": idempotencyKey,
    },
    body: JSON.stringify(campaign),
  });

  // Cache result
  await cache.set(`idempotent:${idempotencyKey}`, result, 86400000); // 24h

  return result;
}
```

### Handle webhooks securely

If implementing webhook endpoints:

```javascript theme={null}
function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac("sha256", secret);
  const digest = hmac.update(payload).digest("hex");

  // Use timing-safe comparison
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}

app.post("/webhook", (req, res) => {
  const signature = req.headers["x-webhook-signature"];

  if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }

  // Process webhook
  processWebhook(req.body);

  // Always respond quickly
  res.status(200).send("OK");
});
```

## Testing

### Mock API responses

Test without hitting the real API:

```javascript theme={null}
class MockAPI {
  constructor() {
    this.responses = new Map();
  }

  setResponse(method, path, response) {
    this.responses.set(`${method}:${path}`, response);
  }

  async fetch(path, options = {}) {
    const method = options.method || "GET";
    const key = `${method}:${path}`;

    const response = this.responses.get(key);
    if (!response) {
      throw new Error(`No mock response for ${key}`);
    }

    return {
      ok: response.status >= 200 && response.status < 300,
      status: response.status,
      json: async () => response.body,
    };
  }
}

// In tests
const mockAPI = new MockAPI();
mockAPI.setResponse("GET", "/v5/contacts/con_123", {
  status: 200,
  body: { id: "con_123", email: "test@example.com" },
});
```

## Documentation

### Document your integration

Maintain clear documentation:

```javascript theme={null}
/**
 * FirstQuadrant API Client
 *
 * @example
 * const client = new FirstQuadrantClient({
 *   apiKey: process.env.FQ_API_KEY,
 *   organizationId: process.env.FQ_ORG_ID
 * });
 *
 * const contacts = await client.contacts.list({
 *   filter: { tags: { has: 'customer' } },
 *   limit: 50
 * });
 */
class FirstQuadrantClient {
  // Implementation
}
```

## Summary

Key takeaways for building robust FirstQuadrant API integrations:

1. **Security First**: Protect credentials, validate inputs, use minimum required permissions
2. **Handle Errors Gracefully**: Implement comprehensive error handling and retry logic
3. **Optimize Performance**: Use field selection, pagination, and caching
4. **Monitor Everything**: Track metrics, log errors with context, use correlation IDs
5. **Test Thoroughly**: Mock API responses, test error scenarios, validate edge cases
6. **Document Well**: Maintain clear documentation for your integration

Following these practices will help ensure your integration is reliable, performant, and maintainable.
