Security
Protecting Customer Data
Greyfinch Connect provides access to sensitive orthodontic practice data, including patient demographics, appointments, treatments, and clinical records. Whether you're a clinic staff member accessing your own practice data or a third-party developer building integrations, protecting this data is paramount.
Who This Guide Is For
This security guide serves two distinct audiences:
For Clinic Staff and Direct API Users: If you're accessing your own practice's data directly through the Greyfinch API, you'll use Personal Access Tokens. These tokens provide secure, scoped access to your organization's data for custom reporting, data analysis, or internal tooling.
→ Get started: API Getting Started Guide
For App Developers and Third-Party Integrators: If you're building a Greyfinch app or third-party integration that will be used by multiple clinics, you'll use the three-key authentication system (App Keys, Client Keys, and API Access Keys) to securely access customer data across multiple tenants.
→ Get started: Apps Security Guide
Core Security Principles
Regardless of which authentication method you use, these principles apply:
Data belongs to the clinics: Orthodontic practices trust you with sensitive patient information. Treat this data with the highest level of care and security.
Use appropriate credentials: Personal Access Tokens for your own practice, Client Keys for per-clinic integrations in apps.
Protect credentials like passwords: Never commit credentials to version control, expose them in logs, or share them insecurely.
Encrypt in transit and at rest: Always use HTTPS for API requests, and encrypt stored credentials.
Follow the principle of least privilege: Request only the access you need, and set appropriate expiration times for credentials.
Monitor and audit: Track credential usage and watch for unusual patterns that might indicate compromise.
This guide focuses on app developer authentication with the three-key system. If you're a clinic user seeking to access your own data via the API, please refer to the API Getting Started Guide for Personal Access Token setup.
App Authentication Overview
Authentication is the foundation of every Greyfinch app integration. Understanding how to properly authenticate your app—and generate the right credentials for accessing customer data—is critical for building secure, scalable integrations that provide seamless onboarding experiences for clinics.
Why Authentication Matters
Greyfinch apps operate in a multi-tenant environment where each clinic (customer) has their own isolated data. Your app needs to:
Identify itself to the Greyfinch platform (using App Keys)
Access each clinic's data securely (using Client Keys)
Monitor activity across all installations (using API Access Keys)
The three-key authentication system enables all of this while maintaining strong security boundaries and data isolation between tenants.
The Seamless Onboarding Vision
When a clinic installs your app, they should get instant access to your integration—no manual key exchanges, no configuration steps, no delays. The Greyfinch Connect SDK and app lifecycle hooks (onInstall, onConnect) make this possible by automatically generating client credentials when an app is installed.
This guide will show you how to leverage these tools to build integrations that "just work" from the moment of installation.
The Three Key Types
Greyfinch uses three distinct types of API keys, each with a specific role in your integration architecture.
App Keys
Purpose: Your app's identity credential—like a username and password for your application itself.
Generated By: Greyfinch provides these to you via a secure 1Password link when your app is approved.
Used For:
Generating Client Keys for newly installed clinics
Generating API Access Keys for monitoring
Updating your app's metadata
Querying your app's installation list
Key Format:
key: pk_app_WB+ALO********************UdDBU
secret: sk_app_nM3HQ********************b378dfLifespan: 2 years (Greyfinch provides updated credentials before expiration)
Security:
Store in backend secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
Never expose in frontend code or logs
Never use to access customer data directly (use Client Keys instead)
Client Keys
Purpose: Access a specific clinic's data—patients, appointments, treatments, etc.
Generated By: Your application, using your App Key, typically during the onInstall or onConnect lifecycle event.
Used For:
Querying patient demographics
Creating or updating appointments
Accessing treatment plans and clinical data
Managing external mappings for that clinic
Any operation scoped to a single customer
Key Format:
key: pk_client_pyrxB********************mkvmT
secret: sk_client_8dhhm********************1ncWNLifespan: You set the expiration when generating (1 month to 2 years)
Security:
Generate one key per clinic (never share across customers)
Store securely with customer identifier (XID)
Rotate regularly (recommended: every 90 days)
Revoke immediately if compromise suspected
API Access Keys
Purpose: Limited-scope credential for cross-tenant operations—specifically pulling audit logs across all your app installations.
Generated By: Your application, using your App Key.
Used For:
Monitoring app activity across all installations
Building analytics dashboards
Compliance reporting
Detecting usage patterns and anomalies
NOT Used For:
Accessing patient or clinical data (use Client Keys)
Clinic-specific operations (use Client Keys)
Updating app metadata (use App Keys)
Key Format:
key: pk_app_access_QmVPN********************sQQMX
secret: sk_app_access_MH9m9********************Xr1YYLifespan: You set the expiration when generating (default: 24 months)
Security:
Store in secrets manager like other credentials
Rotate regularly (recommended: every 90 days)
Limited scope reduces risk compared to App Keys
Comparison Table
Feature | App Keys | API Access Keys | Client Keys |
|---|---|---|---|
Key Prefix |
|
|
|
Secret Prefix |
|
|
|
Purpose | App identity, generate other keys | Cross-tenant audit logs | Single clinic data access |
Role |
|
|
|
Generated By | Greyfinch (via 1Password) | Your app (via App Key) | Your app (via App Key) |
Scope | App-wide operations | Audit logs across all tenants | Single clinic only |
Expiration | 2 years | Configurable (default 24 months) | Configurable (1-24 months) |
Quantity | One per app | One or more per app | One per clinic installation |
Authentication Flow
All three key types follow the same authentication pattern: exchange credentials for a short-lived access token.
The Two-Part Credential System
Every Greyfinch credential consists of two parts:
Key (
pk_*) - The public identifierSecret (
sk_*) - The private password
You must use both together to authenticate.
The Token Exchange Pattern
Credentials (key + secret) → apiLogin mutation → Access Token (24 hours)Access tokens:
Expire after 24 hours
Should be cached in-memory during their lifetime
Must be refreshed when expired by calling
apiLoginagain
The apiLogin Mutation
All three key types use the same GraphQL mutation to exchange credentials for access tokens:
mutation {
apiLogin(
key: "pk_app_..." # or pk_client_... or pk_app_access_...
secret: "sk_app_..." # or sk_client_... or sk_app_access_...
) {
accessToken # Use this in Authorization header
accessTokenExpiresIn # Seconds until token expires (86400 = 24 hours)
}
}Authentication: This mutation requires no Authorization header—the credentials themselves serve as authentication.
Response:
{
"data": {
"apiLogin": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"accessTokenExpiresIn": 86400
}
}
}Using the Access Token
Once you have an access token, include it in the Authorization header for all subsequent API requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Example query:
query {
patients(limit: 10) {
id
person {
firstName
lastName
email
}
}
}The access token is automatically scoped to the credential type you used:
Client Key token → Access to that clinic's data only
API Access Key token → Access to audit logs across all tenants
App Key token → Access to app-level operations
Seamless Onboarding: The SDK Way
The ideal app integration provides zero-friction onboarding: when a clinic installs your app, they immediately get access to your integration without any manual setup.
The Greyfinch Connect SDK makes this possible through automatic client key generation triggered by app lifecycle hooks.
Why This Matters
Without automation:
Clinic installs your app
Clinic admin must manually enter credentials
Developer must generate client key
Delay between installation and usability
With SDK automation:
Clinic installs your app
onInstallhook triggers your webhookSDK automatically generates client key and stores it securely
Clinic can use your app immediately
App Lifecycle Hooks
Greyfinch apps can define lifecycle hooks in their app.json configuration. These hooks notify your backend when important events occur.
The onInstall Hook
Triggered When: A clinic installs your app via Admin > Apps in Greyfinch
Payload Includes:
xid- The clinic's unique 5-character company identifierapp_id- Your app's UUIDinstallation_id- The installation record UUIDparent_company_id- The clinic's parent company UUID
Typical Action: Generate a client key for this clinic automatically
Configuration in app.json:
{
"onInstall": {
"requests": [
{
"request": {
"url": "https://your-backend.com/webhooks/greyfinch/install",
"method": "POST",
"headers": {
"Content-Type": "application/json"
}
}
}
]
}
} The onConnect Hook
Triggered When: A clinic completes the connection flow (OAuth authorization or API token setup for example)
Payload Includes:
xid- The clinic's unique identifierconnection_id- The connection record UUIDaccess_token- OAuth access token (if using OAuth)refresh_token- OAuth refresh token (if using OAuth)
Typical Action: Store OAuth tokens and generate client key if not already generated
Configuration in app.json:
{
"connections": [
{
"id": "oauth_connection",
"type": "OAUTH2",
"onConnect": {
"requests": [
{
"request": {
"url": "https://your-backend.com/webhooks/greyfinch/connect",
"method": "POST"
}
}
]
}
}
]
}The Greyfinch Connect SDK
The SDK is a Node.js library that handles the complex parts of credential management, token caching, and secure storage.
What It Provides:
Automatic credential management
Secure storage integration (AWS Secrets Manager, and other providers)
Access token caching and automatic refresh
Lifecycle hook handlers
Client key generation utilities
Installation:
The SDK is distributed via a private NPM registry. Greyfinch provides access credentials via a secure 1Password link when your app is approved.
# Details provided by Greyfinch team via 1Password
npm install @greyfinch/connect-sdkBasic Setup Example (Pseudocode):
import { GreyfinchSDK } from '@greyfinch/connect-sdk'
// Initialize SDK with your App Key
const sdk = new GreyfinchSDK({
appKey: process.env.GREYFINCH_APP_KEY,
appSecret: process.env.GREYFINCH_APP_SECRET,
secretsManager: awsSecretsManager, // or your preferred secrets store
})
// Handle onInstall event
sdk.on('install', async event => {
const { xid, app_id, installation_id } = event
// SDK automatically generates and stores client key
const clientKey = await sdk.generateClientKey(xid)
console.log(`Client key generated for clinic ${xid}`)
// Clinic can now access their data immediately!
})
// Handle onConnect event (for OAuth apps)
sdk.on('connect', async event => {
const { xid, connection_id, access_token, refresh_token } = event
// Store OAuth tokens
await sdk.storeOAuthTokens(xid, {
access_token,
refresh_token,
})
console.log(`OAuth connected for clinic ${xid}`)
})Implementation Pattern: Webhook Handler
Here's a practical Express.js example showing how to handle the onInstall webhook:
import express from 'express'
import { GreyfinchSDK } from '@greyfinch/connect-sdk'
const app = express()
const sdk = new GreyfinchSDK({
appKey: process.env.GREYFINCH_APP_KEY,
appSecret: process.env.GREYFINCH_APP_SECRET,
secretsManager: awsSecretsManager,
})
// Webhook endpoint for app installation
app.post('/webhooks/greyfinch/install', async (req, res) => {
const { xid, app_id, installation_id } = req.body
try {
// Generate client key for this clinic using App Key
const result = await sdk.generateClientKey(xid, {
expiresInMonths: 12, // 1 year expiration
})
// SDK automatically stores credentials securely
console.log(`✅ Client key generated for ${xid}`)
// Return success
res.status(200).json({
success: true,
message: 'Installation complete',
})
} catch (error) {
console.error(`❌ Failed to generate client key for ${xid}:`, error)
res.status(500).json({
success: false,
error: 'Key generation failed',
})
}
})
app.listen(3000)Sequence Diagram: Client Authentication Flow
This diagram shows how your app middleware handles user authentication and generates client keys on-demand when they don't exist:
Sequence Diagram: Client Key Manager
This diagram shows the background process that ensures all clinics have valid, non-expired client keys:
API Access Keys in Detail
API Access Keys are a specialized credential type introduced for cross-tenant monitoring and analytics. They provide read-only access to audit logs across all your app installations.
Use Cases
When to use API Access Keys:
Analytics Dashboards: Track installation counts, usage patterns, feature adoption
Compliance Reporting: Generate reports on app activity for security audits
Monitoring: Detect anomalies, track errors, monitor performance
Usage Metrics: Understand which clinics are actively using your app
Example scenarios:
Building a dashboard showing "Installations per month"
Generating compliance reports: "All data access events in Q1 2024"
Monitoring: "Which clinics had authentication failures today?"
Analytics: "Which features are most commonly used?"
Rule of thumb: If it's not an audit log query, don't use an API Access Key.
Generating API Access Keys
Use your App Key to generate an API Access Key via the apiGenerateAppAccessKey mutation:
mutation {
apiGenerateAppAccessKey(
description: "Analytics Dashboard Access"
expiresInMonths: 24
) {
key # pk_app_access_...
secret # sk_app_access_... (only returned once!)
status # GRANTED or INACTIVE
expiresAt # ISO 8601 timestamp
}
}Authentication: This mutation requires your App Key in the Authorization header:
Authorization: Bearer <access_token_from_app_key_login>Response:
{
"data": {
"apiGenerateAppAccessKey": {
"key": "pk_app_access_QmVPN********************sQQMX",
"secret": "sk_app_access_MH9m9********************Xr1YY",
"status": "GRANTED",
"expiresAt": "2026-01-15T10:30:00Z"
}
}
}Important: The secret is only returned once. Store it securely immediately—you cannot retrieve it again.
Using API Access Keys
Step 1: Exchange for Access Token
Like all credentials, you must exchange your API Access Key for an access token first:
mutation {
apiLogin(
key: "pk_app_access_QmVPN********************sQQMX"
secret: "sk_app_access_MH9m9********************Xr1YY"
) {
accessToken
accessTokenExpiresIn
}
}Step 2: Query Audit Logs
Use the access token to query the auditLogs field:
query {
auditLogs(
where: {
appId: { _eq: "your-app-uuid" }
type: { _in: ["INSTALL", "CONNECT", "GENERATE_API_KEY_APP_ACCESS"] }
createdAt: { _gte: "2024-01-01T00:00:00Z" }
}
order_by: { createdAt: desc }
limit: 100
) {
id
type
xid # Clinic identifier
connectionId
metadata # JSONB field with additional context
createdAt
}
}Authentication header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Audit Log Types
The type field indicates what action was logged. Common types include:
INSTALL- App was installed by a clinicCONNECT- Clinic completed OAuth or token connectionGENERATE_API_KEY_APP_ACCESS- API Access Key was generatedGENERATE_CLIENT_KEY- Client Key was generated
See the Audit Log Types reference for the complete list.
Example: Installation Analytics
query InstallationsByMonth {
auditLogs(
where: {
appId: { _eq: $appId }
type: { _eq: "INSTALL" }
createdAt: { _gte: "2024-01-01T00:00:00Z" }
}
) {
xid
createdAt
metadata
}
}Process this data to generate "Installations per month" analytics.
Manual Client Key Generation
If you're not using the SDK or need to generate client keys outside the automatic onInstall flow, you can generate them manually via the Greyfinch API.
Step 1: Obtain the Clinic's XID
The XID is a 5-character identifier unique to each Greyfinch company (e.g., "ABC12").
Ways to obtain XID:
Customer provides it: Ask the clinic admin to find their XID in Admin > Company Settings in Greyfinch
From webhook payload: The
onInstallandonConnectwebhooks include thexidfieldQuery via API: If you know the company UUID, you can query for the XID (requires appropriate permissions)
Step 2: Generate the Client Key
Use the apiGenerateClientKey mutation with your App Key:
mutation {
apiGenerateClientKey(xid: "ABC12") {
key # pk_client_...
secret # sk_client_... (only returned once!)
}
}Authentication: This mutation requires your App Key in the Authorization header:
Authorization: Bearer <access_token_from_app_key_login>Example using curl:
# Step 1: Login with App Key to get access token
curl -X POST https://api.greyfinch.com/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation { apiLogin(key: \"pk_app_...\", secret: \"sk_app_...\") { accessToken } }"
}'
# Step 2: Generate Client Key using access token
curl -X POST https://api.greyfinch.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <accessToken>" \
-d '{
"query": "mutation { apiGenerateClientKey(xid: \"ABC12\") { key secret } }"
}'Response:
{
"data": {
"apiGenerateClientKey": {
"key": "pk_client_pyrxB********************mkvmT",
"secret": "sk_client_8dhhm********************1ncWN"
}
}
}Important: The secret is only returned once. Store it securely immediately—you cannot retrieve it again.
Step 3: Store Credentials Securely
Client credentials must be stored with strong security measures:
Recommended storage solutions:
AWS Secrets Manager (recommended)
HashiCorp Vault
Azure Key Vault
Google Cloud Secret Manager
Encrypted database with proper key management
Storage structure (example):
{
"xid": "ABC12",
"client_key": "pk_client_pyrxB********************mkvmT",
"client_secret": "sk_client_8dhhm********************1ncWN",
"created_at": "2024-01-15T10:30:00Z",
"expires_at": "2025-01-15T10:30:00Z",
"last_used": "2024-01-15T14:22:00Z"
}Step 4: Test the Connection
Verify the new client key works by making a test API call:
# First: Login with client key to get access token
mutation {
apiLogin(
key: "pk_client_pyrxB********************mkvmT"
secret: "sk_client_8dhhm********************1ncWN"
) {
accessToken
accessTokenExpiresIn
}
}
# Then: Test with a simple query
query {
patients(limit: 1) {
id
person {
firstName
}
}
}If you receive patient data, the client key is working correctly!
Security Best Practices
API credentials grant access to sensitive customer data. Treat them with the same care as passwords or private keys.
For All Key Types
If you suspect credentials have been compromised, immediately revoke compromised credientals. Contact Greyfinch support for emergency key revocation.
Key Storage
✅ DO:
Use a dedicated secrets manager with encryption at rest (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, Google Cloud Secret Manager)
Implement access controls following the principle of least privilege
Audit all access to credentials (who accessed what and when)
Rotate credentials regularly according to your security policy
Use different credentials for sandbox and production environments
Encrypt secrets before storing in databases
Always use HTTPS with TLS 1.2 or higher for all API requests
Cache access tokens to minimize credential transmission
Implement automatic token refresh logic
Monitor for unusual access patterns or anomalies
Implement rate limiting on your backend
❌ DON'T:
Store credentials in plain text files
Commit secrets to version control (Git, SVN, etc.)
Store secrets in environment variables accessible to multiple services
Log secrets in application logs or error messages
Hardcode secrets directly in application code
Share secrets between development, staging, and production environments
Send credentials over unencrypted HTTP connections
Include credentials in URL parameters (they appear in server logs)
Share access tokens between different customers
Store access tokens long-term (they expire after 24 hours)
Ignore
401 Unauthorizedresponses (indicates potential security issue)
Reference Implementations
Example Apps in This Repository
Explore these working examples to see authentication patterns in action:
Hello World
The simplest possible app—demonstrates basic app structure without authentication. Start here to understand app.json basics.
What it shows:
Minimal
app.jsonstructureStatic URL launcher (no auth required)
Basic metadata fields
Simple API Integration
Adds API token authentication and demonstrates dynamic data with merge fields.
What it shows:
API_TOKEN connection type
Using merge fields to pass patient data
External mappings
Basic launcher patterns
Comprehensive Integration
Full-featured production app demonstrating OAuth, automations, webhooks, and bidirectional sync.
What it shows:
OAuth 2.0 authentication flow
Client key usage for accessing patient data
Automations (automatic patient creation)
Webhook signature verification
Bidirectional data sync
External mappings with metadata
Advanced launcher patterns
Greyfinch Connect SDK
The official SDK simplifies credential management and token caching.
GitHub Repository: [Contact Greyfinch for access]
NPM Package: Distributed via private registry (credentials provided via 1Password)
Key Features:
Automatic credential management
Secure storage integration (AWS Secrets Manager, others)
Access token caching and refresh
Lifecycle hook handlers (
onInstall,onConnect)Client key generation utilities
Getting Access: Contact the Greyfinch team at [email protected] to request SDK access and credentials.
Next Steps
Now that you understand the authentication system, you're ready to build your integration.
1. Build Your First App
Start with the simplest possible integration:
Follow the Hello World Tutorial
Understand basic
app.jsonstructureTest with a static URL launcher
2. Add Authentication
Upgrade to a more realistic integration:
Follow the API Token Integration Tutorial
Add API_TOKEN connection
Use merge fields for dynamic data
Create external mappings
3. Implement OAuth + Client Keys
Build a production-grade integration:
Study the Comprehensive Integration Example
Implement OAuth 2.0 flow
Set up
onInstallwebhook handlerUse SDK for automatic client key generation
4. Set Up the SDK
Get access to the official SDK:
Contact [email protected]
Request SDK access and private registry credentials
Integrate SDK into your backend
Test automatic credential management
5. Deploy and Monitor
Launch your app to production:
Submit app for Greyfinch review
Deploy to production environment
Set up monitoring with API Access Keys
Build analytics dashboard for installations
Support
Have questions about authentication, the SDK, or app development?
Email: [email protected]
Documentation:
Getting Started - Overview of the Apps Framework
Core Concepts - Deep dive into app.json, merge fields, and external mappings
Community:
Share your integration challenges
Learn from other developers
Get support from the Greyfinch team
Remember: Security is not optional. Always store credentials securely, rotate keys regularly, and follow the best practices outlined in this guide. Your users trust you with their sensitive patient data—treat that trust with the care it deserves.