Complete code to check in visitors with temporary access. Auto-generated PINs sent via email/SMS with automatic expiration.
The Workflow
Scenario: Visitor arriving at 9 AM, meeting until 5 PM, needs lobby and conference room access.
What we'll do
Time: About 2-3 seconds per visitor
Prerequisites
You need
account.person, account.person, account.reservation
Get your channel IDs:
curl -X GET "https://api.doorflow.com/api/3/channels" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Quick Visitor Check-in
Bash Script
Complete script for command-line visitor check-in:
#!/bin/bash
# Visitor Check-in Script
# Usage: ./checkin_visitor.sh "Jane" "Smith" "jane@example.com" "1340,1341" "8"
FIRST_NAME=$1
LAST_NAME=$2
EMAIL=$3
CHANNEL_IDS=$4 # Comma-separated
HOURS=$5 # How many hours from now
ACCESS_TOKEN="your_access_token_here"
BASE_URL="https://api.doorflow.com/api/3"
# Calculate time window
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
START_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
END_TIME=$(date -u -v+${HOURS}H '+%Y-%m-%dT%H:%M:%SZ')
else
# Linux
START_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
END_TIME=$(date -u -d "+${HOURS} hours" '+%Y-%m-%dT%H:%M:%SZ')
fi
echo "Checking in visitor: $FIRST_NAME $LAST_NAME"
echo "Access until: $END_TIME"
# Step 1: Create visitor (assign to Visitors group ID 2)
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\": [2]
}")
PERSON_ID=$(echo $PERSON_RESPONSE | jq -r '.id')
if [ "$PERSON_ID" == "null" ]; then
echo "Error creating visitor:"
echo $PERSON_RESPONSE | jq '.'
exit 1
fi
echo "[OK] Visitor created (ID: $PERSON_ID)"
# Step 2: Issue auto-generated PIN
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')
echo "[OK] PIN issued: $PIN_VALUE"
# Step 3: Create reservation
RESERVATION_RESPONSE=$(curl -s -X POST "$BASE_URL/reservations" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"person_id\": $PERSON_ID,
\"channel_ids\": [$CHANNEL_IDS],
\"start_time\": \"$START_TIME\",
\"end_time\": \"$END_TIME\"
}")
RESERVATION_ID=$(echo $RESERVATION_RESPONSE | jq -r '.id')
echo "[OK] Reservation created (ID: $RESERVATION_ID)"
echo ""
echo "[OK] Visitor check-in complete!"
echo ""
echo "Visitor Information:"
echo " Name: $FIRST_NAME $LAST_NAME"
echo " Email: $EMAIL"
echo " PIN: $PIN_VALUE"
echo " Access Until: $END_TIME"
echo ""
echo "Next steps:"
echo " 1. Send PIN to visitor: $PIN_VALUE"
echo " 2. Provide door access instructions"
Save as: checkin_visitor.sh
Run:
chmod +x checkin_visitor.sh
./checkin_visitor.sh "Jane" "Smith" "jane@example.com" "1340,1341" "8"
JavaScript Version with Email
Complete implementation with automatic email notification:
async function checkInVisitor(firstName, lastName, email, channelIds, hours = 8) {
const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
const baseURL = 'https://api.doorflow.com/api/3';
// Calculate time window (now to now + hours)
const now = new Date();
const startTime = new Date(now);
const endTime = new Date(now.getTime() + (hours * 60 * 60 * 1000));
try {
console.log(`Checking in visitor: ${firstName} ${lastName}`);
console.log(`Access: ${startTime.toISOString()} to ${endTime.toISOString()}`);
// Step 1: Create visitor
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: [2], // Visitors group
}),
});
if (!personResponse.ok) {
const error = await personResponse.json();
throw new Error(`Failed to create visitor: ${error.error || error.message}`);
}
const person = await personResponse.json();
console.log(`[OK] Visitor created (ID: ${person.id})`);
// Step 2: Issue auto-generated 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
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}`);
// Step 3: Create time-limited reservation
const reservationResponse = await fetch(`${baseURL}/reservations`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
person_id: person.id,
channel_ids: channelIds,
start_time: startTime.toISOString(),
end_time: endTime.toISOString(),
}),
});
if (!reservationResponse.ok) {
throw new Error(`Failed to create reservation: ${await reservationResponse.text()}`);
}
const reservation = await reservationResponse.json();
console.log(`[OK] Reservation created (ID: ${reservation.id})`);
// Step 4: Send email to visitor
console.log('Sending access information to visitor...');
await sendVisitorEmail(email, {
firstName: firstName,
lastName: lastName,
pin: pin.value,
expiresAt: endTime,
});
console.log('[OK] Visitor checked in successfully!');
return {
personId: person.id,
firstName: firstName,
lastName: lastName,
email: email,
pin: pin.value,
reservationId: reservation.id,
expiresAt: endTime,
};
} catch (error) {
console.error('Visitor check-in failed:', error);
throw error;
}
}
async function sendVisitorEmail(email, details) {
// Integrate with your email service (SendGrid, AWS SES, etc.)
const emailBody = `
Dear ${details.firstName} ${details.lastName},
Welcome! Here is your temporary building access information:
Access PIN: ${details.pin}
Your access is valid until: ${details.expiresAt.toLocaleString()}
Instructions:
1. Enter the PIN on the keypad at the entrance
2. Press # after entering the PIN
3. Wait for the green light and door unlock
If you have any issues, please contact reception.
This PIN will automatically expire after your visit.
Thank you for visiting!
`;
console.log(`Sending PIN to ${email}: ${details.pin}`);
// Example: SendGrid
// await sendgrid.send({
// to: email,
// from: 'reception@yourcompany.com',
// subject: 'Your Building Access PIN',
// text: emailBody,
// });
// Example: AWS SES
// await ses.sendEmail({
// Destination: { ToAddresses: [email] },
// Message: {
// Subject: { Data: 'Your Building Access PIN' },
// Body: { Text: { Data: emailBody } },
// },
// Source: 'reception@yourcompany.com',
// }).promise();
}
// Usage
checkInVisitor('Jane', 'Smith', 'jane@example.com', [1340, 1341], 8)
.then(result => {
console.log('\nVisitor check-in complete:', result);
console.log(`\nPIN: ${result.pin} (valid until ${result.expiresAt.toLocaleString()})`);
})
.catch(error => console.error('Error:', error.message));
Scheduled Visitor Pre-registration
Register visitors in advance for future visits:
async function preRegisterVisitor(firstName, lastName, email, channelIds, visitDate, visitHours = 8) {
const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
const baseURL = 'https://api.doorflow.com/api/3';
// Visit date at 9 AM
const startTime = new Date(visitDate);
startTime.setHours(9, 0, 0, 0);
// End time = start + visitHours
const endTime = new Date(startTime.getTime() + (visitHours * 60 * 60 * 1000));
try {
// Create visitor (disabled until visit day)
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: false, // Disabled until visit day
group_ids: [2],
}),
});
const person = await personResponse.json();
console.log(`[OK] Visitor pre-registered (ID: ${person.id})`);
// Issue PIN (but person is disabled)
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,
value: '******',
enabled: true,
},
}),
});
const pin = await pinResponse.json();
console.log(`[OK] PIN issued: ${pin.value}`);
// Create future reservation
const reservationResponse = await fetch(`${baseURL}/reservations`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
person_id: person.id,
channel_ids: channelIds,
start_time: startTime.toISOString(),
end_time: endTime.toISOString(),
}),
});
const reservation = await reservationResponse.json();
// Enable person (now PIN will work during reservation window)
await fetch(`${baseURL}/people/${person.id}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
}),
});
console.log(`[OK] Visitor access scheduled for ${startTime.toLocaleDateString()}`);
// Send email with PIN
await sendVisitorEmail(email, {
firstName: firstName,
lastName: lastName,
pin: pin.value,
expiresAt: endTime,
});
return {
personId: person.id,
pin: pin.value,
visitDate: startTime,
expiresAt: endTime,
};
} catch (error) {
console.error('Visitor pre-registration failed:', error);
throw error;
}
}
// Usage: Register visitor for next Monday
const nextMonday = new Date('2024-06-10');
preRegisterVisitor('Bob', 'Johnson', 'bob@example.com', [1340, 1341], nextMonday, 8)
.then(result => console.log('Visitor pre-registered:', result));
Bulk Visitor Check-in
Check in multiple visitors at once:
async function bulkCheckInVisitors(visitors, channelIds, hours = 8) {
console.log(`Checking in ${visitors.length} visitors...`);
const results = [];
for (const visitor of visitors) {
try {
const result = await checkInVisitor(
visitor.firstName,
visitor.lastName,
visitor.email,
channelIds,
hours
);
results.push({ success: true, ...result });
// Delay to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
console.error(`Failed to check in ${visitor.firstName} ${visitor.lastName}:`, error.message);
results.push({
success: false,
firstName: visitor.firstName,
lastName: visitor.lastName,
email: visitor.email,
error: error.message,
});
}
}
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
console.log(`\n[OK] Bulk check-in complete`);
console.log(`Successful: ${successful.length}`);
console.log(`Failed: ${failed.length}`);
return results;
}
// Usage
const visitors = [
{ firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' },
{ firstName: 'Bob', lastName: 'Jones', email: 'bob@example.com' },
{ firstName: 'Alice', lastName: 'Brown', email: 'alice@example.com' },
];
bulkCheckInVisitors(visitors, [1340, 1341], 8)
.then(results => console.log('Results:', results));
Visitor Check-out
Optionally revoke access early when visitor leaves:
async function checkOutVisitor(personId) {
const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
const baseURL = 'https://api.doorflow.com/api/3';
try {
// Disable person (revokes access immediately)
await fetch(`${baseURL}/people/${personId}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: false,
}),
});
console.log(`[OK] Visitor checked out (person ${personId})`);
console.log('[OK] Access revoked');
return {
personId: personId,
checkedOutAt: new Date(),
};
} catch (error) {
console.error('Visitor check-out failed:', error);
throw error;
}
}
// Usage
checkOutVisitor(12345)
.then(result => console.log('Visitor checked out:', result));
Reception Desk Integration
Web interface for reception to check in visitors:
// Express.js API endpoint
app.post('/api/visitors/checkin', async (req, res) => {
const { firstName, lastName, email, channelIds, hours } = req.body;
try {
// Validate input
if (!firstName || !lastName || !email) {
return res.status(400).json({
error: 'First name, last name, and email required',
});
}
// Check in visitor
const result = await checkInVisitor(
firstName,
lastName,
email,
channelIds || [1340, 1341], // Default channels
hours || 8
);
// Log check-in
await db.insert('visitor_log', {
person_id: result.personId,
checked_in_at: new Date(),
checked_in_by: req.user.id,
pin: encrypt(result.pin), // Encrypt in logs
});
res.json({
success: true,
visitor: result,
message: `Visitor checked in. PIN sent to ${email}`,
});
} catch (error) {
console.error('Check-in error:', error);
res.status(500).json({
error: 'Check-in failed',
message: error.message,
});
}
});
Best Practices
1. Use reservations for automatic expiration
2. Auto-generate PINs:
// Always use "******" for random PIN
value: "******" // Good
// Never use predictable PINs
value: "1234" // Bad
3. Send PIN via multiple channels:
// Email AND SMS for reliability
await sendVisitorEmail(email, details);
await sendVisitorSMS(phone, details.pin);
4. Limit channel access:
// Only grant access to necessary doors
channelIds: [1340, 1341] // Lobby + Conference Room
// Don't grant access to all doors
// channelIds: allChannels // Bad - too much access
5. Clean up old visitors:
// Cron job to disable expired visitors (daily)
async function cleanupExpiredVisitors() {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
// Find visitors with expired reservations
const expiredReservations = await db.query(
'SELECT person_id FROM reservations WHERE end_time < ?',
[yesterday]
);
for (const row of expiredReservations) {
// Disable visitor
await offboardEmployee(row.person_id);
}
}
Required OAuth Scopes
-
account.person- Create visitor -
account.person- Issue PIN -
account.reservation- Create time-limited access