> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seam.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating Ultraloq Access Codes

> Create and manage access codes for Ultraloq locks

This guide explains how to create and manage access codes for Ultraloq locks using the Seam API.

***

## Overview

Ultraloq locks support two types of access codes:

* **Permanent access codes** — Codes without start or end times that work indefinitely
* **Time-bound access codes** — Codes that automatically activate and deactivate at specific times

<Warning>
  **Important:** Time-bound access codes require [timezone
  configuration](./configuring-ultraloq-device-timezones). Permanent access
  codes work without timezone configuration.
</Warning>

***

## Before You Begin

To create access codes for an Ultraloq device:

1. Your Ultraloq device must be [connected to Seam](./ultraloq-setup-guide)
2. For **time-bound access codes only**: Device timezone must be [configured](./configuring-ultraloq-device-timezones)
3. Device must have `can_program_online_access_codes: true`

***

## Creating Permanent Access Codes

Permanent access codes work indefinitely until you delete them. They **do not require timezone configuration**.

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { Seam } from 'seam'

  const seam = new Seam()

  // Create permanent access code
  const accessCode = await seam.accessCodes.create({
    device_id: 'your-device-id',
    name: 'Maintenance Team',
    code: '1234', // Optional: auto-generated if omitted
  })

  console.log(`Access code created: ${accessCode.code}`)
  console.log(`Status: ${accessCode.status}`)
  ```

  ```bash cURL theme={null}
  curl -X 'POST' \
    'https://connect.getseam.com/access_codes/create' \
    -H 'accept: application/json' \
    -H "Authorization: Bearer ${SEAM_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d '{
      "device_id": "your-device-id",
      "name": "Maintenance Team",
      "code": "1234"
    }'
  ```

  ```python Python theme={null}
  from seam import Seam

  seam = Seam()

  # Create permanent access code
  access_code = seam.access_codes.create(
    device_id="your-device-id",
    name="Maintenance Team",
    code="1234"  # Optional: auto-generated if omitted
  )

  print(f"Access code created: {access_code.code}")
  print(f"Status: {access_code.status}")
  ```

  ```ruby Ruby theme={null}
  require "seam"

  seam = Seam.new()

  # Create permanent access code
  access_code = seam.access_codes.create(
    device_id: "your-device-id",
    name: "Maintenance Team",
    code: "1234"  # Optional: auto-generated if omitted
  )

  puts "Access code created: #{access_code.code}"
  puts "Status: #{access_code.status}"
  ```

  ```php PHP theme={null}
  <?php
  require 'vendor/autoload.php';

  use Seam\SeamClient;

  $seam = new SeamClient();

  // Create permanent access code
  $accessCode = $seam->access_codes->create(
    device_id: "your-device-id",
    name: "Maintenance Team",
    code: "1234"  // Optional: auto-generated if omitted
  );

  echo "Access code created: " . $accessCode->code . "\n";
  echo "Status: " . $accessCode->status . "\n";
  ```

  ```csharp C# theme={null}
  using Seam.Client;

  var seam = new SeamClient();

  // Create permanent access code
  var accessCode = seam.AccessCodes.Create(
    deviceId: "your-device-id",
    name: "Maintenance Team",
    code: "1234"  // Optional: auto-generated if omitted
  );

  Console.WriteLine($"Access code created: {accessCode.Code}");
  Console.WriteLine($"Status: {accessCode.Status}");
  ```

  ```java Java theme={null}
  import com.seam.api.Seam;
  import com.seam.api.types.AccessCode;

  Seam seam = Seam.builder().build();

  // Create permanent access code
  AccessCode accessCode = seam.accessCodes().create(
    AccessCodesCreateRequest.builder()
      .deviceId("your-device-id")
      .name("Maintenance Team")
      .code("1234")  // Optional: auto-generated if omitted
      .build()
  );

  System.out.println("Access code created: " + accessCode.getCode());
  System.out.println("Status: " + accessCode.getStatus());
  ```
</CodeGroup>

***

## Creating Time-Bound Access Codes

Time-bound access codes automatically activate and deactivate at specified times. They **require timezone configuration**.

<Warning>
  **Prerequisites:** 1. Device timezone must be configured (see [Configuring
  Ultraloq Device Timezones](./configuring-ultraloq-device-timezones)) 2.
  Device must not have the `ultraloq_time_zone_unknown` warning
</Warning>

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { Seam } from 'seam'

  const seam = new Seam()

  // Define time range in UTC
  const startsAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // Tomorrow
  const endsAt = new Date(startsAt.getTime() + 2 * 24 * 60 * 60 * 1000) // +2 days

  // Create time-bound access code
  const accessCode = await seam.accessCodes.create({
    device_id: 'your-device-id',
    name: 'Weekend Guest',
    code: '5678', // Optional: auto-generated if omitted
    starts_at: startsAt.toISOString(),
    ends_at: endsAt.toISOString(),
  })

  console.log(`Access code created: ${accessCode.code}`)
  console.log(`Active from ${accessCode.starts_at} to ${accessCode.ends_at}`)
  console.log(`Status: ${accessCode.status}`)
  ```

  ```bash cURL theme={null}
  # Calculate timestamps (requires date command)
  STARTS_AT=$(date -u -d '+1 day' '+%Y-%m-%dT%H:%M:%SZ')
  ENDS_AT=$(date -u -d '+3 days' '+%Y-%m-%dT%H:%M:%SZ')

  curl -X 'POST' \
    'https://connect.getseam.com/access_codes/create' \
    -H 'accept: application/json' \
    -H "Authorization: Bearer ${SEAM_API_KEY}" \
    -H 'Content-Type: application/json' \
    -d "{
      \"device_id\": \"your-device-id\",
      \"name\": \"Weekend Guest\",
      \"code\": \"5678\",
      \"starts_at\": \"${STARTS_AT}\",
      \"ends_at\": \"${ENDS_AT}\"
    }"
  ```

  ```python Python theme={null}
  from seam import Seam
  from datetime import datetime, timedelta

  seam = Seam()

  # Define time range in UTC
  starts_at = datetime.utcnow() + timedelta(days=1)
  ends_at = starts_at + timedelta(days=2)

  # Create time-bound access code
  access_code = seam.access_codes.create(
    device_id="your-device-id",
    name="Weekend Guest",
    code="5678",  # Optional: auto-generated if omitted
    starts_at=starts_at.isoformat() + "Z",
    ends_at=ends_at.isoformat() + "Z"
  )

  print(f"Access code created: {access_code.code}")
  print(f"Active from {access_code.starts_at} to {access_code.ends_at}")
  print(f"Status: {access_code.status}")
  ```

  ```ruby Ruby theme={null}
  require "seam"
  require "time"

  seam = Seam.new()

  # Define time range in UTC
  starts_at = Time.now.utc + (24 * 60 * 60)  # Tomorrow
  ends_at = starts_at + (2 * 24 * 60 * 60)   # +2 days

  # Create time-bound access code
  access_code = seam.access_codes.create(
    device_id: "your-device-id",
    name: "Weekend Guest",
    code: "5678",  # Optional: auto-generated if omitted
    starts_at: starts_at.iso8601,
    ends_at: ends_at.iso8601
  )

  puts "Access code created: #{access_code.code}"
  puts "Active from #{access_code.starts_at} to #{access_code.ends_at}"
  puts "Status: #{access_code.status}"
  ```

  ```php PHP theme={null}
  <?php
  require 'vendor/autoload.php';

  use Seam\SeamClient;

  $seam = new SeamClient();

  // Define time range in UTC
  $startsAt = new DateTime('+1 day', new DateTimeZone('UTC'));
  $endsAt = (clone $startsAt)->add(new DateInterval('P2D'));

  // Create time-bound access code
  $accessCode = $seam->access_codes->create(
    device_id: "your-device-id",
    name: "Weekend Guest",
    code: "5678",  // Optional: auto-generated if omitted
    starts_at: $startsAt->format(DateTime::ATOM),
    ends_at: $endsAt->format(DateTime::ATOM)
  );

  echo "Access code created: " . $accessCode->code . "\n";
  echo "Active from " . $accessCode->starts_at . " to " . $accessCode->ends_at . "\n";
  echo "Status: " . $accessCode->status . "\n";
  ```

  ```csharp C# theme={null}
  using Seam.Client;
  using System;

  var seam = new SeamClient();

  // Define time range in UTC
  var startsAt = DateTime.UtcNow.AddDays(1);
  var endsAt = startsAt.AddDays(2);

  // Create time-bound access code
  var accessCode = seam.AccessCodes.Create(
    deviceId: "your-device-id",
    name: "Weekend Guest",
    code: "5678",  // Optional: auto-generated if omitted
    startsAt: startsAt,
    endsAt: endsAt
  );

  Console.WriteLine($"Access code created: {accessCode.Code}");
  Console.WriteLine($"Active from {accessCode.StartsAt} to {accessCode.EndsAt}");
  Console.WriteLine($"Status: {accessCode.Status}");
  ```

  ```java Java theme={null}
  import com.seam.api.Seam;
  import com.seam.api.types.AccessCode;
  import java.time.Instant;
  import java.time.temporal.ChronoUnit;

  Seam seam = Seam.builder().build();

  // Define time range in UTC
  Instant startsAt = Instant.now().plus(1, ChronoUnit.DAYS);
  Instant endsAt = startsAt.plus(2, ChronoUnit.DAYS);

  // Create time-bound access code
  AccessCode accessCode = seam.accessCodes().create(
    AccessCodesCreateRequest.builder()
      .deviceId("your-device-id")
      .name("Weekend Guest")
      .code("5678")  // Optional: auto-generated if omitted
      .startsAt(startsAt.toString())
      .endsAt(endsAt.toString())
      .build()
  );

  System.out.println("Access code created: " + accessCode.getCode());
  System.out.println("Active from " + accessCode.getStartsAt() + " to " + accessCode.getEndsAt());
  System.out.println("Status: " + accessCode.getStatus());
  ```
</CodeGroup>

### How Time Zone Conversion Works

When you create a time-bound access code:

1. **You provide:** UTC timestamps (`starts_at` and `ends_at`)
2. **Seam converts:** UTC → Device local time using the configured timezone
3. **Ultraloq schedules:** Code activation/deactivation in device's local time
4. **Seam stores:** UTC timestamps for consistent time representation

**Example:**

```
User creates code with:
  starts_at: "2024-01-20T19:00:00Z"  (7:00 PM UTC)
  ends_at:   "2024-01-22T02:00:00Z"  (2:00 AM UTC)

Device timezone: "America/New_York" (UTC-5)

Seam converts to local time:
  Starts: 2024-01-20 14:00 (2:00 PM EST)
  Ends:   2024-01-21 21:00 (9:00 PM EST)

Ultraloq device activates code from 2:00 PM to 9:00 PM EST.
```

***

## Access Code Requirements

### Code Format

Ultraloq access codes must be:

* **Numeric only** — Only digits 0-9
* **4-8 characters long** — Examples: `"1234"`, `"567890"`, `"12345678"`

<Info>
  **Auto-generated codes:** If you omit the `code` parameter, Seam automatically
  generates a random 4-8 digit numeric code.
</Info>

### Valid Examples

```python theme={null}
# Valid codes
"1234"      # 4 digits
"56789"     # 5 digits
"123456"    # 6 digits
"1234567"   # 7 digits
"12345678"  # 8 digits
```

### Invalid Examples

```python theme={null}
# Invalid codes
"123"       # Too short (< 4 digits)
"123456789" # Too long (> 8 digits)
"abcd"      # Non-numeric characters
"12-34"     # Special characters not allowed
```

***

## Checking Device Readiness

Before creating time-bound access codes, verify that the device's timezone is configured:

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { Seam } from 'seam'

  const seam = new Seam()

  async function canCreateTimeBoundCodes(deviceId) {
    const device = await seam.devices.get({ device_id: deviceId })

    // Check for timezone warning
    const hasTimezoneWarning = device.warnings.some(
      (w) => w.warning_code === 'ultraloq_time_zone_unknown',
    )

    return !hasTimezoneWarning
  }

  // Check before creating
  if (await canCreateTimeBoundCodes('your-device-id')) {
    // Safe to create time-bound codes
    const accessCode = await seam.accessCodes.create({
      device_id: 'your-device-id',
      starts_at: '...',
      ends_at: '...',
    })
  } else {
    console.log('Configure device timezone first')
    console.log('See: Configuring Ultraloq Device Timezones')
  }
  ```

  ```python Python theme={null}
  from seam import Seam

  seam = Seam()

  def can_create_time_bound_codes(device_id):
    device = seam.devices.get(device_id=device_id)

    # Check for timezone warning
    has_timezone_warning = any(
      w.warning_code == "ultraloq_time_zone_unknown"
      for w in device.warnings
    )

    return not has_timezone_warning

  # Check before creating
  if can_create_time_bound_codes("your-device-id"):
    # Safe to create time-bound codes
    access_code = seam.access_codes.create(
      device_id="your-device-id",
      starts_at="...",
      ends_at="..."
    )
  else:
    print("Configure device timezone first")
    print("See: Configuring Ultraloq Device Timezones")
  ```

  ```ruby Ruby theme={null}
  require "seam"

  seam = Seam.new()

  def can_create_time_bound_codes?(seam, device_id)
    device = seam.devices.get(device_id: device_id)

    # Check for timezone warning
    has_timezone_warning = device.warnings.any? do |w|
      w.warning_code == "ultraloq_time_zone_unknown"
    end

    !has_timezone_warning
  end

  # Check before creating
  if can_create_time_bound_codes?(seam, "your-device-id")
    # Safe to create time-bound codes
    access_code = seam.access_codes.create(
      device_id: "your-device-id",
      starts_at: "...",
      ends_at: "..."
    )
  else
    puts "Configure device timezone first"
    puts "See: Configuring Ultraloq Device Timezones"
  end
  ```

  ```php PHP theme={null}
  <?php
  require 'vendor/autoload.php';

  use Seam\SeamClient;

  $seam = new SeamClient();

  function canCreateTimeBoundCodes($seam, $deviceId) {
    $device = $seam->devices->get(device_id: $deviceId);

    // Check for timezone warning
    foreach ($device->warnings as $warning) {
      if ($warning->warning_code === "ultraloq_time_zone_unknown") {
        return false;
      }
    }

    return true;
  }

  // Check before creating
  if (canCreateTimeBoundCodes($seam, "your-device-id")) {
    // Safe to create time-bound codes
    $accessCode = $seam->access_codes->create(
      device_id: "your-device-id",
      starts_at: "...",
      ends_at: "..."
    );
  } else {
    echo "Configure device timezone first\n";
    echo "See: Configuring Ultraloq Device Timezones\n";
  }
  ```
</CodeGroup>

***

## Disabled Access Codes

Users can disable access codes through the Ultraloq mobile app. When this happens, Seam detects the change and adds a warning to the access code.

### Detecting Disabled Codes

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { Seam } from 'seam'

  const seam = new Seam()

  const accessCode = await seam.accessCodes.get({
    access_code_id: 'your-access-code-id',
  })

  // Check for disabled warning
  const isDisabled = accessCode.warnings.some(
    (w) => w.warning_code === 'ultraloq_access_code_disabled',
  )

  if (isDisabled) {
    console.log('⚠️ Code is disabled on Ultraloq device')
    console.log('User must re-enable it in the Ultraloq mobile app')
  }
  ```

  ```python Python theme={null}
  from seam import Seam

  seam = Seam()

  access_code = seam.access_codes.get(
    access_code_id="your-access-code-id"
  )

  # Check for disabled warning
  is_disabled = any(
    w.warning_code == "ultraloq_access_code_disabled"
    for w in access_code.warnings
  )

  if is_disabled:
    print("⚠️ Code is disabled on Ultraloq device")
    print("User must re-enable it in the Ultraloq mobile app")
  ```

  ```ruby Ruby theme={null}
  require "seam"

  seam = Seam.new()

  access_code = seam.access_codes.get(
    access_code_id: "your-access-code-id"
  )

  # Check for disabled warning
  is_disabled = access_code.warnings.any? do |w|
    w.warning_code == "ultraloq_access_code_disabled"
  end

  if is_disabled
    puts "⚠️ Code is disabled on Ultraloq device"
    puts "User must re-enable it in the Ultraloq mobile app"
  end
  ```

  ```php PHP theme={null}
  <?php
  require 'vendor/autoload.php';

  use Seam\SeamClient;

  $seam = new SeamClient();

  $accessCode = $seam->access_codes->get(
    access_code_id: "your-access-code-id"
  );

  // Check for disabled warning
  $isDisabled = false;
  foreach ($accessCode->warnings as $warning) {
    if ($warning->warning_code === "ultraloq_access_code_disabled") {
      $isDisabled = true;
      break;
    }
  }

  if ($isDisabled) {
    echo "⚠️ Code is disabled on Ultraloq device\n";
    echo "User must re-enable it in the Ultraloq mobile app\n";
  }
  ```
</CodeGroup>

<Info>
  **Resolution:** The user must re-enable the code in the Ultraloq mobile app.
  Seam cannot programmatically re-enable disabled codes. Once re-enabled in the
  app, Seam will automatically detect the change and clear the warning.
</Info>

***

## Validation and Error Handling

### Time-Bound Code Without Timezone

If you attempt to create a time-bound code without configuring the device's timezone:

```json theme={null}
{
  "error": {
    "type": "invalid_input",
    "message": "Time zone required for time-bound access codes on Ultraloq devices"
  }
}
```

**Solution:** Configure the device's timezone first using `/devices/report_provider_metadata`. See [Configuring Ultraloq Device Timezones](./configuring-ultraloq-device-timezones).

### Invalid Code Format

If you provide a code that doesn't meet the 4-8 digit numeric requirement:

```json theme={null}
{
  "error": {
    "type": "invalid_input",
    "message": "Access code must be 4-8 digit numeric PIN for Ultraloq devices"
  }
}
```

**Solution:** Use only numeric digits (0-9) and ensure the code is 4-8 characters long.

### Missing Time Bounds

If you provide only `starts_at` or only `ends_at`:

```json theme={null}
{
  "error": {
    "type": "invalid_input",
    "message": "Both starts_at and ends_at must be provided together"
  }
}
```

**Solution:** Either provide both `starts_at` and `ends_at`, or omit both for a permanent code.

### Invalid Time Ordering

If `ends_at` is before `starts_at`:

```json theme={null}
{
  "error": {
    "type": "invalid_input",
    "message": "ends_at must be after starts_at"
  }
}
```

**Solution:** Ensure `starts_at` comes before `ends_at`.

***

## Best Practices

### 1. Use Auto-Generated Codes

For better security, let Seam generate random codes instead of using predictable patterns:

```python theme={null}
# Good: Auto-generated random code
access_code = seam.access_codes.create(
  device_id="your-device-id",
  name="Guest 123"
  # code parameter omitted - Seam generates random code
)

# Less secure: Predictable pattern
access_code = seam.access_codes.create(
  device_id="your-device-id",
  name="Guest 123",
  code="1234"  # Easy to guess
)
```

### 2. Check Timezone Before Creating Time-Bound Codes

Always verify timezone configuration before attempting to create time-bound codes:

```python theme={null}
device = seam.devices.get(device_id="your-device-id")

if not any(w.warning_code == "ultraloq_time_zone_unknown" for w in device.warnings):
  # Safe to create time-bound codes
  seam.access_codes.create(device_id=device.device_id, starts_at="...", ends_at="...")
else:
  # Configure timezone first
  print("Configure timezone before creating time-bound codes")
```

### 3. Monitor Access Code Warnings

Regularly check access code warnings to detect disabled codes:

```python theme={null}
access_codes = seam.access_codes.list(device_id="your-device-id")

disabled_codes = [
  code for code in access_codes
  if any(w.warning_code == "ultraloq_access_code_disabled" for w in code.warnings)
]

if disabled_codes:
  print(f"⚠️ {len(disabled_codes)} codes are disabled")
  # Notify user to re-enable in Ultraloq app
```

### 4. Use UTC Timestamps

Always provide timestamps in UTC (ISO 8601 format with 'Z' suffix):

```python theme={null}
# Good: UTC timestamp
starts_at = "2024-01-20T15:00:00Z"

# Bad: Local time without timezone info
starts_at = "2024-01-20T10:00:00"  # Ambiguous!
```

***

## Troubleshooting

### Code Not Appearing on Device

If an access code doesn't appear on the physical device:

1. Verify the code status is `set` (not `setting` or `unset`)
2. Check for warnings on the access code
3. Ensure the device is online and connected to Wi-Fi
4. Wait a few minutes for synchronization

### Time-Bound Code Activates at Wrong Time

If a time-bound code activates at an unexpected time:

1. Verify the device's timezone is correctly configured
2. Check `device.properties.ultraloq_metadata.time_zone`
3. Ensure you provided UTC timestamps (with 'Z' suffix)
4. Recalculate the local time conversion to verify correctness

### Code Validation Errors

If you receive validation errors when creating codes:

1. **Invalid format:** Ensure code is 4-8 numeric digits
2. **Timezone required:** Configure device timezone for time-bound codes
3. **Missing time bounds:** Provide both `starts_at` and `ends_at`, or neither
4. **Invalid ordering:** Ensure `starts_at` is before `ends_at`

***

## API Reference

For complete API documentation, see:

* [POST /access\_codes/create](/api/access_codes/create)
* [GET /access\_codes/get](/api/access_codes/get)
* [GET /access\_codes/list](/api/access_codes/list)
* [POST /access\_codes/delete](/api/access_codes/delete)

***

## Next Steps

* **Learn about timezone configuration:** See [Configuring Ultraloq Device Timezones](./configuring-ultraloq-device-timezones)
* **Understand code constraints:** See [Understanding Code Constraints](/low-level-apis/smart-locks/access-codes/creating-access-codes/understanding-code-constraints)
* **Review the setup guide:** See [Ultraloq Setup Guide](./ultraloq-setup-guide)
* **Explore access code webhooks:** See [Webhooks](/developer-tools/webhooks)

***
