---
llm-context: developer-guide
category: workflows
title: Contractor Temporary Access
difficulty: intermediate
time: 5 minutes
format: markdown
updated: 
---


# Contractor Temporary Access

Grant contractors access for weeks or months with automatic expiration. Similar to employees but with time limits.

## The Workflow

**Scenario:** Contractor needs access for 3 months to specific areas (loading dock + workshop).

**What we&#39;ll do:**
1. Create contractor record
2. Assign to &quot;Contractors&quot; group
3. Issue card or mobile credential
4. Create extended reservation (90 days)
5. Schedule automatic revocation when contract ends

**Time:** About 3-4 seconds per contractor

## When to Use

**Contractors (vs Visitors vs Employees):**
- Visitors: 1 day → Use PIN
- Contractors: 1 week to 6 months → Use card + reservation
- Employees: Permanent → Use card, no reservation

## Complete Implementation

```javascript
async function grantContractorAccess(
  firstName,
  lastName,
  email,
  cardNumber,
  channelIds,
  endDate
) {
  const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
  const baseURL = &#39;https://api.doorflow.com/api/3&#39;;

  try {
    console.log(`Granting contractor access: ${firstName} ${lastName}`);
    console.log(`Access until: ${endDate.toLocaleDateString()}`);

    // Step 1: Create contractor
    const personResponse = await fetch(`${baseURL}/people`, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Authorization&#39;: `Bearer ${accessToken}`,
        &#39;Content-Type&#39;: &#39;application/json&#39;,
      },
      body: JSON.stringify({
        first_name: firstName,
        last_name: lastName,
        email: email,
        enabled: true,
        group_ids: [3], // Contractors group (create if needed)
      }),
    });

    if (!personResponse.ok) {
      const error = await personResponse.json();
      throw new Error(`Failed to create contractor: ${error.error || error.message}`);
    }

    const person = await personResponse.json();
    console.log(`[OK] Contractor created (ID: ${person.id})`);

    // Step 2: Issue card credential
    const cardResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Authorization&#39;: `Bearer ${accessToken}`,
        &#39;Content-Type&#39;: &#39;application/json&#39;,
      },
      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 backup PIN
    const pinResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Authorization&#39;: `Bearer ${accessToken}`,
        &#39;Content-Type&#39;: &#39;application/json&#39;,
      },
      body: JSON.stringify({
        person_credential: {
          credential_type_id: 6, // PIN
          value: &#39;******&#39;, // Auto-generate
          enabled: true,
        },
      }),
    });

    const pin = await pinResponse.json();
    console.log(`[OK] PIN issued: ${pin.value}`);

    // Step 4: Create reservation until contract end date
    const reservationResponse = await fetch(`${baseURL}/reservations`, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Authorization&#39;: `Bearer ${accessToken}`,
        &#39;Content-Type&#39;: &#39;application/json&#39;,
      },
      body: JSON.stringify({
        person_id: person.id,
        channel_ids: channelIds,
        start_time: new Date().toISOString(),
        end_time: endDate.toISOString(),
      }),
    });

    if (!reservationResponse.ok) {
      throw new Error(`Failed to create reservation: ${await reservationResponse.text()}`);
    }

    const reservation = await reservationResponse.json();
    console.log(`[OK] Reservation created (expires: ${endDate.toLocaleDateString()})`);

    // Step 5: Schedule automatic offboarding
    await scheduleContractorOffboarding(person.id, endDate);

    console.log(&#39;[OK] Contractor access granted!&#39;);

    return {
      personId: person.id,
      firstName: firstName,
      lastName: lastName,
      email: email,
      cardNumber: cardNumber,
      pin: pin.value,
      contractEnds: endDate,
      reservationId: reservation.id,
    };

  } catch (error) {
    console.error(&#39;Contractor access failed:&#39;, error);
    throw error;
  }
}

async function scheduleContractorOffboarding(personId, endDate) {
  // Store in database for cron job to process
  await db.insert(&#39;scheduled_offboarding&#39;, {
    person_id: personId,
    offboard_date: endDate,
    status: &#39;pending&#39;,
    type: &#39;contractor&#39;,
    created_at: new Date(),
  });

  console.log(`[OK] Offboarding scheduled for ${endDate.toLocaleDateString()}`);
}

// Usage: 3-month contractor starting today
const contractEndDate = new Date();
contractEndDate.setMonth(contractEndDate.getMonth() + 3);

grantContractorAccess(
  &#39;Bob&#39;,
  &#39;Builder&#39;,
  &#39;bob@contractor.com&#39;,
  &#39;9876543210&#39;,
  [1340, 1342], // Loading dock + workshop only
  contractEndDate
).then(result =&gt; {
  console.log(&#39;\nContractor access granted:&#39;, result);
  console.log(`\nNext steps:`);
  console.log(`1. Give contractor card: ${result.cardNumber}`);
  console.log(`2. Send PIN backup: ${result.pin}`);
  console.log(`3. Access expires: ${result.contractEnds.toLocaleDateString()}`);
});
```

## Bash Script Version

Simple command-line script:

```bash
#!/bin/bash
# Grant Contractor Access
# Usage: ./grant_contractor.sh &quot;Bob&quot; &quot;Builder&quot; &quot;bob@contractor.com&quot; &quot;9876543210&quot; &quot;1340,1342&quot; &quot;90&quot;

FIRST_NAME=$1
LAST_NAME=$2
EMAIL=$3
CARD_NUMBER=$4
CHANNEL_IDS=$5  # Comma-separated
DAYS=$6         # How many days from now

ACCESS_TOKEN=&quot;your_access_token_here&quot;
BASE_URL=&quot;https://api.doorflow.com/api/3&quot;

# Calculate end date
if [[ &quot;$OSTYPE&quot; == &quot;darwin&quot;* ]]; then
  # macOS
  END_TIME=$(date -u -v+${DAYS}d &#39;+%Y-%m-%dT%H:%M:%SZ&#39;)
else
  # Linux
  END_TIME=$(date -u -d &quot;+${DAYS} days&quot; &#39;+%Y-%m-%dT%H:%M:%SZ&#39;)
fi

START_TIME=$(date -u &#39;+%Y-%m-%dT%H:%M:%SZ&#39;)

echo &quot;Granting contractor access: $FIRST_NAME $LAST_NAME&quot;
echo &quot;Contract ends: $END_TIME&quot;

# Create contractor
PERSON_RESPONSE=$(curl -s -X POST &quot;$BASE_URL/people&quot; \
  -H &quot;Authorization: Bearer $ACCESS_TOKEN&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d &quot;{
    \&quot;first_name\&quot;: \&quot;$FIRST_NAME\&quot;,
    \&quot;last_name\&quot;: \&quot;$LAST_NAME\&quot;,
    \&quot;email\&quot;: \&quot;$EMAIL\&quot;,
    \&quot;enabled\&quot;: true,
    \&quot;group_ids\&quot;: [3]
  }&quot;)

PERSON_ID=$(echo $PERSON_RESPONSE | jq -r &#39;.id&#39;)
echo &quot;[OK] Contractor created (ID: $PERSON_ID)&quot;

# Issue card
curl -s -X POST &quot;$BASE_URL/people/$PERSON_ID/credentials&quot; \
  -H &quot;Authorization: Bearer $ACCESS_TOKEN&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d &quot;{
    \&quot;person_credential\&quot;: {
      \&quot;credential_type_id\&quot;: 5,
      \&quot;value\&quot;: \&quot;$CARD_NUMBER\&quot;,
      \&quot;enabled\&quot;: true
    }
  }&quot; &gt; /dev/null

echo &quot;[OK] Card issued: $CARD_NUMBER&quot;

# Issue PIN
PIN_RESPONSE=$(curl -s -X POST &quot;$BASE_URL/people/$PERSON_ID/credentials&quot; \
  -H &quot;Authorization: Bearer $ACCESS_TOKEN&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d &#39;{
    &quot;person_credential&quot;: {
      &quot;credential_type_id&quot;: 6,
      &quot;value&quot;: &quot;******&quot;,
      &quot;enabled&quot;: true
    }
  }&#39;)

PIN_VALUE=$(echo $PIN_RESPONSE | jq -r &#39;.value&#39;)
echo &quot;[OK] PIN issued: $PIN_VALUE&quot;

# Create reservation
curl -s -X POST &quot;$BASE_URL/reservations&quot; \
  -H &quot;Authorization: Bearer $ACCESS_TOKEN&quot; \
  -H &quot;Content-Type: application/json&quot; \
  -d &quot;{
    \&quot;person_id\&quot;: $PERSON_ID,
    \&quot;channel_ids\&quot;: [$CHANNEL_IDS],
    \&quot;start_time\&quot;: \&quot;$START_TIME\&quot;,
    \&quot;end_time\&quot;: \&quot;$END_TIME\&quot;
  }&quot; &gt; /dev/null

echo &quot;[OK] Reservation created&quot;
echo &quot;&quot;
echo &quot;Summary:&quot;
echo &quot;  Contractor: $FIRST_NAME $LAST_NAME&quot;
echo &quot;  Card: $CARD_NUMBER&quot;
echo &quot;  PIN: $PIN_VALUE&quot;
echo &quot;  Access expires: $END_TIME&quot;
```

## Automatic Offboarding

Cron job to automatically offboard expired contractors:

```javascript
// Run daily at 5 PM to offboard contractors whose contracts ended
async function processExpiredContractors() {
  const today = new Date();
  today.setHours(0, 0, 0, 0);

  try {
    // Get contractors scheduled for offboarding today or earlier
    const expired = await db.query(
      &#39;SELECT person_id, offboard_date FROM scheduled_offboarding WHERE offboard_date &lt;= ? AND status = ? AND type = ?&#39;,
      [today, &#39;pending&#39;, &#39;contractor&#39;]
    );

    console.log(`Processing ${expired.length} expired contractors...`);

    for (const row of expired) {
      try {
        // Offboard contractor
        await offboardEmployee(row.person_id);

        // Mark as completed
        await db.update(&#39;scheduled_offboarding&#39;, {
          status: &#39;completed&#39;,
          completed_at: new Date(),
        }, { person_id: row.person_id });

        console.log(`[OK] Contractor ${row.person_id} offboarded`);

      } catch (error) {
        console.error(`Failed to offboard contractor ${row.person_id}:`, error.message);

        // Mark as failed
        await db.update(&#39;scheduled_offboarding&#39;, {
          status: &#39;failed&#39;,
          error: error.message,
        }, { person_id: row.person_id });

        // Notify admin
        await notifyAdmin(`Failed to offboard contractor ${row.person_id}: ${error.message}`);
      }
    }

    console.log(&#39;[OK] Expired contractor processing complete&#39;);

  } catch (error) {
    console.error(&#39;Expired contractor processing failed:&#39;, error);
  }
}

// Cron schedule: 0 17 * * * (every day at 5 PM)
// Add to your cron: node process_expired_contractors.js
processExpiredContractors();
```

## Contract Extension

Extend contractor&#39;s access if contract is extended:

```javascript
async function extendContractorAccess(personId, newEndDate) {
  const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
  const baseURL = &#39;https://api.doorflow.com/api/3&#39;;

  try {
    // Get existing reservations
    const resResponse = await fetch(`${baseURL}/reservations?person_id=${personId}`, {
      headers: { &#39;Authorization&#39;: `Bearer ${accessToken}` },
    });

    const { reservations } = await resResponse.json();

    // Update each reservation end time
    for (const reservation of reservations) {
      await fetch(`${baseURL}/reservations/${reservation.id}`, {
        method: &#39;PUT&#39;,
        headers: {
          &#39;Authorization&#39;: `Bearer ${accessToken}`,
          &#39;Content-Type&#39;: &#39;application/json&#39;,
        },
        body: JSON.stringify({
          end_time: newEndDate.toISOString(),
        }),
      });

      console.log(`[OK] Reservation ${reservation.id} extended to ${newEndDate.toLocaleDateString()}`);
    }

    // Update scheduled offboarding
    await db.update(&#39;scheduled_offboarding&#39;, {
      offboard_date: newEndDate,
      status: &#39;pending&#39;,
    }, { person_id: personId });

    console.log(&#39;[OK] Contract extended!&#39;);

    return {
      personId,
      newEndDate,
      reservationsUpdated: reservations.length,
    };

  } catch (error) {
    console.error(&#39;Contract extension failed:&#39;, error);
    throw error;
  }
}

// Usage: Extend for another 2 months
const newEndDate = new Date();
newEndDate.setMonth(newEndDate.getMonth() + 2);

extendContractorAccess(12345, newEndDate)
  .then(result =&gt; console.log(&#39;Contract extended:&#39;, result));
```

## Mobile Credentials for Contractors

For contractors without physical cards:

```javascript
async function grantContractorMobileAccess(
  firstName,
  lastName,
  email,
  channelIds,
  endDate
) {
  const accessToken = process.env.DOORFLOW_ACCESS_TOKEN;
  const baseURL = &#39;https://api.doorflow.com/api/3&#39;;

  try {
    // Create contractor
    const personResponse = await fetch(`${baseURL}/people`, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Authorization&#39;: `Bearer ${accessToken}`,
        &#39;Content-Type&#39;: &#39;application/json&#39;,
      },
      body: JSON.stringify({
        first_name: firstName,
        last_name: lastName,
        email: email,
        enabled: true,
        group_ids: [3],
      }),
    });

    const person = await personResponse.json();

    // Issue mobile credential (PassFlow)
    const mobileResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Authorization&#39;: `Bearer ${accessToken}`,
        &#39;Content-Type&#39;: &#39;application/json&#39;,
      },
      body: JSON.stringify({
        person_credential: {
          credential_type_id: 9, // PassFlow
          value: &#39;passflow-credential-id&#39;, // From PassFlow system
          enabled: true,
        },
      }),
    });

    const mobile = await mobileResponse.json();
    console.log(`[OK] Mobile credential issued`);

    // Issue backup PIN
    const pinResponse = await fetch(`${baseURL}/people/${person.id}/credentials`, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Authorization&#39;: `Bearer ${accessToken}`,
        &#39;Content-Type&#39;: &#39;application/json&#39;,
      },
      body: JSON.stringify({
        person_credential: {
          credential_type_id: 6,
          value: &#39;******&#39;,
          enabled: true,
        },
      }),
    });

    const pin = await pinResponse.json();
    console.log(`[OK] Backup PIN issued: ${pin.value}`);

    // Create reservation
    await fetch(`${baseURL}/reservations`, {
      method: &#39;POST&#39;,
      headers: {
        &#39;Authorization&#39;: `Bearer ${accessToken}`,
        &#39;Content-Type&#39;: &#39;application/json&#39;,
      },
      body: JSON.stringify({
        person_id: person.id,
        channel_ids: channelIds,
        start_time: new Date().toISOString(),
        end_time: endDate.toISOString(),
      }),
    });

    return {
      personId: person.id,
      email: email,
      pin: pin.value,
      contractEnds: endDate,
    };

  } catch (error) {
    console.error(&#39;Mobile credential failed:&#39;, error);
    throw error;
  }
}
```

## Best Practices

**1. Always set end date:**
- Never give contractors permanent access
- Use reservations to enforce automatic expiration
- Schedule offboarding reminder before contract ends

**2. Limit access to necessary doors:**
```javascript
// Good: Only areas contractor needs
channelIds: [1340, 1342]  // Loading dock + workshop

// Bad: All doors
// channelIds: allChannels
```

**3. Track contract dates:**
```javascript
// Store contract info for reporting
await db.insert(&#39;contractor_contracts&#39;, {
  person_id: personId,
  company_name: &#39;ABC Contracting&#39;,
  start_date: new Date(),
  end_date: endDate,
  contact_email: email,
  approved_by: currentUser.id,
});
```

**4. Issue both card and PIN:**
- Card for daily use (faster)
- PIN for backup (when card is forgotten)

**5. Collect card on contract end:**
- Include in offboarding workflow
- Track card collection
- Return card to inventory

## Required OAuth Scopes

- `account.person` - Create contractor
- `account.person` - Issue credentials
- `account.reservation` - Time-limited access

## Next Steps

**Related workflows:**
- [Employee Onboarding] - Permanent employee access
- [Visitor Management] - Short-term visitor access
- [Employee Offboarding] - Revoke access when contract ends

**Learn more:**
- [Card Credentials] - Physical access cards
- [Mobile Credentials] - Smartphone-based access
- [Credential Comparison] - Choose the right type

