Complete, production-ready code to onboard new employees. Creates person record, assigns to group, issues card and PIN backup.
The Workflow
Scenario: New employee starts Monday. They need permanent building access.
What we'll do
Time: About 2-3 seconds per employee
Prerequisites
You need
account.person and account.person scopes
1)
5, PIN typically 6)
Check your credential types:
curl -X GET "https://api.doorflow.com/api/3/credential_types" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Bash Script
Complete onboarding script you can run from command line:
#!/bin/bash
# Employee Onboarding Script
# Usage: ./onboard_employee.sh "John" "Doe" "john.doe@company.com" "1234567890"
FIRST_NAME=$1
LAST_NAME=$2
EMAIL=$3
CARD_NUMBER=$4
ACCESS_TOKEN="your_access_token_here"
BASE_URL="https://api.doorflow.com/api/3"
echo "Onboarding: $FIRST_NAME $LAST_NAME..."
# Step 1: Create person and assign to Employees group (ID 1)
PERSON_RESPONSE=$(curl -s -X POST "$BASE_URL/people" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"first_name\": \"$FIRST_NAME\",
\"last_name\": \"$LAST_NAME\",
\"email\": \"$EMAIL\",
\"enabled\": true,
\"group_ids\": [1]
}")
PERSON_ID=$(echo $PERSON_RESPONSE | jq -r '.id')
if [ "$PERSON_ID" == "null" ]; then
echo "Error creating person:"
echo $PERSON_RESPONSE | jq '.'
exit 1
fi
echo "[OK] Person created (ID: $PERSON_ID)"
# Step 2: Issue card credential (type ID 5)
CARD_RESPONSE=$(curl -s -X POST "$BASE_URL/people/$PERSON_ID/credentials" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"person_credential\": {
\"credential_type_id\": 5,
\"value\": \"$CARD_NUMBER\",
\"enabled\": true
}
}")
CARD_ID=$(echo $CARD_RESPONSE | jq -r '.id')
echo "[OK] Card issued (ID: $CARD_ID, Number: $CARD_NUMBER)"
# Step 3: Issue auto-generated PIN (backup)
PIN_RESPONSE=$(curl -s -X POST "$BASE_URL/people/$PERSON_ID/credentials" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"person_credential": {
"credential_type_id": 6,
"value": "******",
"enabled": true
}
}')
PIN_VALUE=$(echo $PIN_RESPONSE | jq -r '.value')
PIN_ID=$(echo $PIN_RESPONSE | jq -r '.id')
echo "[OK] PIN issued (ID: $PIN_ID, PIN: $PIN_VALUE)"
# Step 4: Verify
VERIFY_RESPONSE=$(curl -s -X GET "$BASE_URL/people/$PERSON_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN")
echo ""
echo "[OK] Onboarding complete!"
echo ""
echo "Summary:"
echo " Name: $FIRST_NAME $LAST_NAME"
echo " Email: $EMAIL"
echo " Person ID: $PERSON_ID"
echo " Card Number: $CARD_NUMBER"
echo " PIN: $PIN_VALUE"
echo ""
echo "Next steps:"
echo " 1. Give employee their physical card (number: $CARD_NUMBER)"
echo " 2. Send PIN via secure channel (SMS/email): $PIN_VALUE"
echo " 3. Test access at a door"
Save as: onboard_employee.sh
Run:
chmod +x onboard_employee.sh
./onboard_employee.sh "Jane" "Smith" "jane@company.com" "9876543210"
JavaScript/Node.js Version
Async function you can integrate into your application:
async function onboardEmployee(firstName, lastName, email, cardNumber) {
const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
const baseURL = 'https://api.doorflow.com/api/3';
try {
// Step 1: Create person
console.log(`Creating person: ${firstName} ${lastName}...`);
const personResponse = await fetch(`${baseURL}/people`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
first_name: firstName,
last_name: lastName,
email: email,
enabled: true,
group_ids: [1], // Employees group
}),
});
if (!personResponse.ok) {
const error = await personResponse.json();
throw new Error(`Failed to create person: ${error.error || error.message}`);
}
const person = await personResponse.json();
console.log(`[OK] Person created (ID: ${person.id})`);
// Step 2: Issue card
console.log('Issuing card credential...');
const cardResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
person_credential: {
credential_type_id: 5, // Card
value: cardNumber,
enabled: true,
},
}),
});
if (!cardResponse.ok) {
throw new Error(`Failed to issue card: ${await cardResponse.text()}`);
}
const card = await cardResponse.json();
console.log(`[OK] Card issued (Number: ${cardNumber})`);
// Step 3: Issue auto-generated PIN
console.log('Issuing backup PIN...');
const pinResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
person_credential: {
credential_type_id: 6, // PIN
value: '******', // Auto-generate secure PIN
enabled: true,
},
}),
});
if (!pinResponse.ok) {
throw new Error(`Failed to issue PIN: ${await pinResponse.text()}`);
}
const pin = await pinResponse.json();
console.log(`[OK] PIN issued: ${pin.value}`);
console.log('[OK] Onboarding complete!');
return {
personId: person.id,
firstName: firstName,
lastName: lastName,
email: email,
cardNumber: cardNumber,
pin: pin.value,
};
} catch (error) {
console.error('Onboarding failed:', error);
throw error;
}
}
// Usage
onboardEmployee('Jane', 'Smith', 'jane@company.com', '9876543210')
.then(result => {
console.log('\nOnboarding complete:', result);
console.log('\nNext steps:');
console.log('1. Give employee card:', result.cardNumber);
console.log('2. Send PIN securely:', result.pin);
// Send welcome email with PIN
// sendWelcomeEmail(result.email, result.pin);
})
.catch(error => {
console.error('Error:', error.message);
});
Bulk Onboarding
Onboard multiple employees from CSV:
const fs = require('fs');
const csv = require('csv-parser');
async function bulkOnboardEmployees(csvFilePath) {
const employees = [];
// Read CSV
await new Promise((resolve, reject) => {
fs.createReadStream(csvFilePath)
.pipe(csv())
.on('data', (row) => {
employees.push({
firstName: row.first_name,
lastName: row.last_name,
email: row.email,
cardNumber: row.card_number,
});
})
.on('end', resolve)
.on('error', reject);
});
console.log(`Onboarding ${employees.length} employees...`);
// Onboard each employee with delay to respect rate limits
const results = [];
for (const employee of employees) {
try {
const result = await onboardEmployee(
employee.firstName,
employee.lastName,
employee.email,
employee.cardNumber
);
results.push({ success: true, ...result });
// Delay to avoid rate limits (120 req/minute = ~0.5 seconds per request)
await new Promise(resolve => setTimeout(resolve, 4000));
} catch (error) {
console.error(`Failed to onboard ${employee.firstName} ${employee.lastName}:`, error.message);
results.push({
success: false,
firstName: employee.firstName,
lastName: employee.lastName,
error: error.message,
});
}
}
// Generate report
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
console.log(`\n[OK] Bulk onboarding complete`);
console.log(`Successful: ${successful.length}`);
console.log(`Failed: ${failed.length}`);
return results;
}
// Usage
bulkOnboardEmployees('./new_employees.csv')
.then(results => {
// Save results to file
fs.writeFileSync(
'./onboarding_results.json',
JSON.stringify(results, null, 2)
);
console.log('Results saved to onboarding_results.json');
});
CSV format (new_employees.csv):
first_name,last_name,email,card_number
Jane,Smith,jane@company.com,9876543210
Bob,Jones,bob@company.com,1234567890
Error Handling
Always handle errors gracefully:
async function onboardEmployeeSafely(firstName, lastName, email, cardNumber) {
try {
return await onboardEmployee(firstName, lastName, email, cardNumber);
} catch (error) {
// Log error
console.error('Onboarding error:', {
employee: `${firstName} ${lastName}`,
email: email,
error: error.message,
timestamp: new Date(),
});
// Notify admin
// await notifyAdmin(`Onboarding failed for ${email}: ${error.message}`);
// Return error result instead of throwing
return {
success: false,
firstName,
lastName,
email,
error: error.message,
};
}
}
Common Errors
"Group not found"
- Group ID doesn't exist
- Check available groups:
GET /api/3/groups
"Duplicate credential value"
- Card number already in use
- Verify card number is correct and not already assigned
"Invalid credential_type_id"
- Credential type not available in your account
- Check credential types:
GET /api/3/credential_types
"Person already exists"
- Email address already in system
- Check existing people:
GET /api/3/people?email=user@example.com
Best Practices
1. Validate inputs before calling API:
function validateEmployee(firstName, lastName, email, cardNumber) {
if (!firstName || !lastName) {
throw new Error('First and last name required');
}
if (!email || !email.includes('@')) {
throw new Error('Valid email required');
}
if (!cardNumber || !/^\d+$/.test(cardNumber)) {
throw new Error('Valid card number required (numeric only)');
}
}
2. Always issue backup credentials
3. Store results for audit trail:
await db.insert('onboarding_log', {
person_id: result.personId,
card_number: result.cardNumber,
pin: encrypt(result.pin), // Encrypt sensitive data
onboarded_by: currentUser.id,
onboarded_at: new Date(),
});
4. Send credentials securely
5. Test before giving to employee:
// Verify person can be retrieved
const person = await fetch(`${baseURL}/people/${personId}`, {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
// Verify credentials exist
const credentials = await fetch(`${baseURL}/people/${personId}/credentials`, {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
console.log(`Person has ${credentials.length} credentials`);
Integration Examples
Integrate with HR system
// When employee added in HR system
hrSystem.on('employee.created', async (employee) => {
try {
// Get next available card from inventory
const cardNumber = await cardInventory.getNextAvailableCard();
// Onboard in DoorFlow
const result = await onboardEmployee(
employee.firstName,
employee.lastName,
employee.workEmail,
cardNumber
);
// Mark card as issued in inventory
await cardInventory.markCardIssued(cardNumber, result.personId);
// Send welcome email with PIN
await emailService.sendWelcomeEmail(employee.workEmail, {
cardNumber: cardNumber,
pin: result.pin,
});
console.log(`[OK] ${employee.firstName} ${employee.lastName} onboarded`);
} catch (error) {
// Notify HR team of failure
await hrSystem.addNote(employee.id, `DoorFlow onboarding failed: ${error.message}`);
}
});
Integrate with Slack
// Slack command: /onboard Jane Smith jane@company.com 1234567890
slackApp.command('/onboard', async ({ command, ack, respond }) => {
await ack();
const [firstName, lastName, email, cardNumber] = command.text.split(' ');
try {
const result = await onboardEmployee(firstName, lastName, email, cardNumber);
await respond({
text: `${firstName} ${lastName} onboarded successfully!`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Onboarding Complete*\n\nName: ${firstName} ${lastName}\nEmail: ${email}\nCard: ${cardNumber}\nPIN: ||${result.pin}|| (click to reveal)`,
},
},
],
});
} catch (error) {
await respond({
text: `Onboarding failed: ${error.message}`,
});
}
});
Required OAuth Scopes
-
account.person- Create and manage people -
account.person- Issue credentials -
account.group.readonly- Verify groups exist