Skip to main content
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
Important: Time-bound access codes require timezone configuration. Permanent access codes work without timezone configuration.

Before You Begin

To create access codes for an Ultraloq device:
  1. Your Ultraloq device must be connected to Seam
  2. For time-bound access codes only: Device timezone must be configured
  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.
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}`)

Creating Time-Bound Access Codes

Time-bound access codes automatically activate and deactivate at specified times. They require timezone configuration.
Prerequisites: 1. Device timezone must be configured (see Configuring Ultraloq Device Timezones) 2. Device must not have the ultraloq_time_zone_unknown warning
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}`)

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"
Auto-generated codes: If you omit the code parameter, Seam automatically generates a random 4-8 digit numeric code.

Valid Examples

# Valid codes
"1234"      # 4 digits
"56789"     # 5 digits
"123456"    # 6 digits
"1234567"   # 7 digits
"12345678"  # 8 digits

Invalid Examples

# 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:
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')
}

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

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')
}
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.

Validation and Error Handling

Time-Bound Code Without Timezone

If you attempt to create a time-bound code without configuring the device’s timezone:
{
  "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.

Invalid Code Format

If you provide a code that doesn’t meet the 4-8 digit numeric requirement:
{
  "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:
{
  "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:
{
  "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:
# 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:
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:
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):
# 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:

Next Steps