Complete code to revoke employee access when they leave. Fast, secure, and maintains full audit trail.
The Workflow
Scenario: Employee's last day is Friday. Revoke all building access immediately.
What we'll do
Time: About 1 second per employee
Alternative: Disable credentials individually (slower, more granular)
Why Disable (Not Delete)?
Disabling
Deleting
Recommended: Always disable, never delete.
Quick Offboarding (Disable Person)
Fastest method - disables person and all their credentials:
Bash Script
#!/bin/bash
# Employee Offboarding Script
# Usage: ./offboard_employee.sh 12345
PERSON_ID=$1
ACCESS_TOKEN="your_access_token_here"
BASE_URL="https://api.doorflow.com/api/3"
echo "Offboarding person ID: $PERSON_ID..."
# Disable person (disables all credentials automatically)
curl -s -X PUT "$BASE_URL/people/$PERSON_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": false
}' | jq '.'
echo "[OK] Person disabled"
echo "[OK] All access revoked"
echo ""
echo "Person record retained for audit trail."
echo "Physical card should be collected during exit interview."
Save as: offboard_employee.sh
Run:
chmod +x offboard_employee.sh
./offboard_employee.sh 12345
JavaScript Version
async function offboardEmployee(personId) {
const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
const baseURL = 'https://api.doorflow.com/api/3';
try {
console.log(`Offboarding person ID: ${personId}...`);
// Disable person
const response = await fetch(`${baseURL}/people/${personId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: false,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Failed to offboard: ${error.error || error.message}`);
}
const person = await response.json();
console.log(`[OK] Person disabled: ${person.first_name} ${person.last_name}`);
console.log('[OK] All access revoked');
return {
personId: person.id,
firstName: person.first_name,
lastName: person.last_name,
offboardedAt: new Date(),
};
} catch (error) {
console.error('Offboarding failed:', error);
throw error;
}
}
// Usage
offboardEmployee(12345)
.then(result => {
console.log('Offboarding complete:', result);
console.log('\nNext steps:');
console.log('1. Collect physical access card');
console.log('2. Update HR system');
console.log('3. Verify access revoked at a door');
})
.catch(error => console.error('Error:', error.message));
Alternative: Disable Credentials Individually
More granular approach - disable each credential separately:
async function offboardEmployeeDetailed(personId) {
const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
const baseURL = 'https://api.doorflow.com/api/3';
try {
// Step 1: Get all credentials
console.log('Retrieving credentials...');
const credResponse = await fetch(`${baseURL}/people/${personId}/credentials`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (!credResponse.ok) {
throw new Error(`Failed to get credentials: ${await credResponse.text()}`);
}
const { credentials } = await credResponse.json();
console.log(`Found ${credentials.length} credentials`);
// Step 2: Disable each credential
for (const credential of credentials) {
await fetch(`${baseURL}/people/${personId}/credentials/${credential.id}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
person_credential: {
enabled: false,
},
}),
});
console.log(`[OK] Disabled ${credential.credential_type_name} (${credential.value})`);
}
// Step 3: Remove from all groups (optional)
await fetch(`${baseURL}/people/${personId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
group_ids: [], // Remove from all groups
}),
});
console.log('[OK] Removed from all groups');
console.log('[OK] Offboarding complete');
return {
personId,
credentialsDisabled: credentials.length,
offboardedAt: new Date(),
};
} catch (error) {
console.error('Offboarding failed:', error);
throw error;
}
}
// Usage
offboardEmployeeDetailed(12345)
.then(result => console.log('Detailed offboarding complete:', result));
When to use detailed method
Bulk Offboarding
Offboard multiple employees at once:
async function bulkOffboardEmployees(personIds) {
console.log(`Offboarding ${personIds.length} employees...`);
const results = [];
for (const personId of personIds) {
try {
const result = await offboardEmployee(personId);
results.push({ success: true, ...result });
// Small delay to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
console.error(`Failed to offboard person ${personId}:`, error.message);
results.push({
success: false,
personId: personId,
error: error.message,
});
}
}
// Summary
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
console.log(`\n[OK] Bulk offboarding complete`);
console.log(`Successful: ${successful.length}`);
console.log(`Failed: ${failed.length}`);
return results;
}
// Usage
const employeesToOffboard = [12345, 12346, 12347];
bulkOffboardEmployees(employeesToOffboard)
.then(results => {
// Log results
console.log('Results:', results);
});
Scheduled Offboarding
Automatically offboard employee on their last day:
async function scheduleOffboarding(personId, lastDay) {
// Store in database for cron job to process
await db.insert('scheduled_offboarding', {
person_id: personId,
offboard_date: lastDay,
status: 'pending',
});
console.log(`Offboarding scheduled for person ${personId} on ${lastDay}`);
}
// Cron job runs daily at 5 PM
async function processScheduledOffboarding() {
const today = new Date().toISOString().split('T')[0];
// Get employees scheduled for offboarding today
const scheduled = await db.query(
'SELECT person_id FROM scheduled_offboarding WHERE offboard_date = ? AND status = ?',
[today, 'pending']
);
console.log(`Processing ${scheduled.length} scheduled offboardings...`);
for (const row of scheduled) {
try {
await offboardEmployee(row.person_id);
// Mark as complete
await db.update('scheduled_offboarding', {
status: 'completed',
completed_at: new Date(),
}, { person_id: row.person_id });
} catch (error) {
console.error(`Failed to offboard ${row.person_id}:`, error.message);
// Mark as failed
await db.update('scheduled_offboarding', {
status: 'failed',
error: error.message,
}, { person_id: row.person_id });
}
}
}
// Usage: Schedule offboarding
const lastDay = new Date('2024-06-30');
scheduleOffboarding(12345, lastDay);
// Usage: Process scheduled (run via cron daily)
// 0 17 * * * node process_offboarding.js
processScheduledOffboarding();
Verify Access Revoked
Always verify offboarding succeeded:
async function verifyOffboarding(personId) {
const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
const baseURL = 'https://api.doorflow.com/api/3';
// Check person is disabled
const personResponse = await fetch(`${baseURL}/people/${personId}`, {
headers: { 'Authorization': `Bearer ${accessToken}` },
});
const person = await personResponse.json();
if (person.enabled === false) {
console.log(`[OK] Person is disabled`);
} else {
console.log(`[WARNING] Person is still enabled!`);
return false;
}
// Check credentials are disabled
const credResponse = await fetch(`${baseURL}/people/${personId}/credentials`, {
headers: { 'Authorization': `Bearer ${accessToken}` },
});
const { credentials } = await credResponse.json();
const enabledCredentials = credentials.filter(c => c.enabled);
if (enabledCredentials.length === 0) {
console.log(`[OK] All credentials disabled`);
} else {
console.log(`[WARNING] ${enabledCredentials.length} credentials still enabled!`);
return false;
}
return true;
}
// Usage
offboardEmployee(12345)
.then(() => verifyOffboarding(12345))
.then(verified => {
if (verified) {
console.log('[OK] Offboarding verified');
} else {
console.log('[ERROR] Offboarding verification failed');
}
});
Integration with HR System
Automatically offboard when HR system updates:
// When employee marked as terminated in HR system
hrSystem.on('employee.terminated', async (employee) => {
try {
// Find person in DoorFlow by email
const searchResponse = await fetch(
`${baseURL}/people?email=${employee.workEmail}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` },
}
);
const { people } = await searchResponse.json();
if (people.length === 0) {
console.log(`No DoorFlow person found for ${employee.workEmail}`);
return;
}
const person = people[0];
// Offboard in DoorFlow
await offboardEmployee(person.id);
// Log in HR system
await hrSystem.addNote(
employee.id,
`Building access revoked in DoorFlow on ${new Date().toISOString()}`
);
// Notify facilities to collect card
await emailService.send({
to: 'facilities@company.com',
subject: `Collect access card from ${employee.firstName} ${employee.lastName}`,
body: `Employee's last day is ${employee.lastDay}. Please collect building access card.`,
});
console.log(`[OK] ${employee.firstName} ${employee.lastName} offboarded`);
} catch (error) {
// Notify HR of failure
await hrSystem.addNote(
employee.id,
`DoorFlow offboarding failed: ${error.message}. Manual action required.`
);
}
});
Best Practices
1. Offboard immediately on last day
2. Collect physical cards
3. Maintain audit trail
4. Verify offboarding:
async function offboardWithVerification(personId) {
await offboardEmployee(personId);
const verified = await verifyOffboarding(personId);
if (!verified) {
throw new Error('Offboarding verification failed - manual review required');
}
return true;
}
5. Handle errors gracefully:
try {
await offboardEmployee(personId);
} catch (error) {
// Log error
console.error('Offboarding error:', error);
// Notify security team
await notifySecurityTeam(`Manual offboarding required for person ${personId}: ${error.message}`);
// Create manual task
await createManualTask({
type: 'offboarding',
personId: personId,
priority: 'high',
reason: error.message,
});
}
Common Errors
"Person not found"
- Person ID doesn't exist
- Double-check person ID
- Search by email:
GET /api/3/people?email=user@example.com
"Insufficient permissions"
- OAuth scope
account.personrequired - Check your access token scopes
"Person already disabled"
- Person was already offboarded
- Verify in DoorFlow UI or via API
Offboarding Checklist
Before offboarding
During offboarding
After offboarding
Required OAuth Scopes
-
account.person- Update person record to disable -
account.person(optional) - If disabling credentials individually