Skip to main content
POST
https://api.fifteenth.com
/
v1beta
/
accounts
/
{account_id}
/
login-link
Generate Login Link
curl --request POST \
  --url https://api.fifteenth.com/v1beta/accounts/{account_id}/login-link \
  --header 'Authorization: <authorization>' \
  --header 'Content-Type: <content-type>' \
  --data '
{
  "user_email": "<string>",
  "expiry_hours": 123,
  "purpose": "<string>",
  "redirect_url": "<string>",
  "send_notification": true,
  "notification_settings": {
    "notification_settings.partner_branded": true,
    "notification_settings.custom_subject": "<string>",
    "notification_settings.custom_message": "<string>",
    "notification_settings.include_instructions": true
  }
}
'
{
  "login_link": "<string>",
  "user_email": "<string>",
  "expires_at": "<string>",
  "expires_in_hours": 123,
  "purpose": "<string>",
  "redirect_url": "<string>",
  "notification_sent": true,
  "previous_links_invalidated": 123,
  "created_at": "<string>",
  "account_id": 123,
  "404 Not Found": {},
  "400 Bad Request": {},
  "403 Forbidden": {},
  "429 Too Many Requests": {}
}

Overview

The Generate Login Link endpoint creates a new secure, time-limited login link for clients to access their Fifteenth account. This is useful when:
  • The original login link has expired (7-day expiry)
  • A client has lost their login link
  • You need to provide additional login links to multiple users
  • Security requires generating a fresh login link
Each login link is single-use for initial access but allows the client to set up permanent login credentials once used.

Request

Path Parameters

account_id
number
required
The unique Fifteenth account identifier to generate a login link for.Format: Numeric ID
Example: 12345

Headers

Authorization
string
required
Bearer token with your Partner API key
Content-Type
string
required
Must be application/json

Body Parameters

user_email
string
Email address of the specific user to generate a login link for. If not provided, generates a link for the primary account holder.Validation: Must be an existing user on the account
Example: [email protected]
expiry_hours
integer
Number of hours until the login link expires.Range: 1-168 hours (1 hour to 7 days)
Default: 168 hours (7 days)
Example: 48
purpose
string
Purpose for generating the login link (for audit logging).Options:
  • initial_access - First time account access
  • expired_link - Replacing an expired link
  • lost_credentials - Client lost their login information
  • additional_user - Link for additional account user
  • support_request - Generated for customer support
Default: expired_link
redirect_url
string
URL to redirect the client to after successful login. Must be a permitted domain for your partner account.Format: Valid HTTPS URL
Example: https://yourplatform.com/tax-dashboard
send_notification
boolean
Whether to send an email notification to the user with the new login link.Default: true
notification_settings
object
Email notification customization options.

Response

Success Response

The generated secure login link for the client.Format: https://app.fifteenth.com/auth/partner-login/{token}
Security: Token is cryptographically secure and single-use for initial access
user_email
string
Email address of the user this login link is for.
expires_at
string
ISO 8601 timestamp when the login link expires.
expires_in_hours
integer
Number of hours until the login link expires.
purpose
string
The purpose specified for generating this login link.
redirect_url
string
URL the client will be redirected to after login (if specified).
notification_sent
boolean
Whether an email notification was sent to the user.
Number of previous login links that were invalidated when generating this new one.
created_at
string
ISO 8601 timestamp when the login link was generated.
account_id
number
The account ID this login link provides access to.

Examples

import requests

account_id = 12345
url = f"https://api.fifteenth.com/v1beta/accounts/{account_id}/login-link"

headers = {
    "Authorization": "Bearer 15th_your_api_key_here",
    "Content-Type": "application/json"
}

# Generate login link with default settings
response = requests.post(url, headers=headers, json={})
link_data = response.json()

print(f"Login Link: {link_data['login_link']}")
print(f"Expires: {link_data['expires_at']}")
print(f"Notification Sent: {link_data['notification_sent']}")
data = {
    "user_email": "[email protected]",
    "expiry_hours": 48,  # 2 days
    "purpose": "support_request",
    "redirect_url": "https://yourplatform.com/tax-dashboard",
    "send_notification": True,
    "notification_settings": {
        "partner_branded": True,
        "custom_subject": "New Access Link for Your Tax Account",
        "custom_message": "We've generated a new login link at your request. This link expires in 48 hours for security.",
        "include_instructions": True
    }
}

response = requests.post(url, headers=headers, json=data)
link_data = response.json()

print(f"Custom Login Link Generated:")
print(f"- Link: {link_data['login_link']}")
print(f"- Expires in: {link_data['expires_in_hours']} hours")
print(f"- Redirect: {link_data['redirect_url']}")
print(f"- Purpose: {link_data['purpose']}")

Generate for Specific User

# Generate login link for a specific user (like an accountant)
data = {
    "user_email": "[email protected]",
    "expiry_hours": 24,  # 1 day for security
    "purpose": "additional_user",
    "send_notification": True,
    "notification_settings": {
        "custom_message": "Your client has granted you access to their Fifteenth account for the 2024 tax preparation."
    }
}

response = requests.post(url, headers=headers, json=data)
link_data = response.json()

print(f"Generated link for: {link_data['user_email']}")
print(f"Link expires: {link_data['expires_at']}")
# Generate link without sending email (for manual delivery)
data = {
    "expiry_hours": 72,  # 3 days
    "purpose": "manual_delivery",
    "send_notification": False
}

response = requests.post(url, headers=headers, json=data)
link_data = response.json()

print(f"Login Link (for manual delivery): {link_data['login_link']}")
print(f"No notification sent: {not link_data['notification_sent']}")

# You would now deliver this link through your secure channel
secure_delivery_system.send_link(
    recipient=client_email,
    link=link_data['login_link'],
    expires_at=link_data['expires_at']
)

Response Examples

Response
{
  "login_link": "https://app.fifteenth.com/auth/partner-login/tk_A7xM9pQ2vK8xLqR4S1E6B3F",
  "user_email": "[email protected]",
  "expires_at": "2024-01-22T10:30:00Z",
  "expires_in_hours": 168,
  "purpose": "expired_link",
  "redirect_url": null,
  "notification_sent": true,
  "previous_links_invalidated": 1,
  "created_at": "2024-01-15T10:30:00Z",
  "account_id": "12345"
}
Response (Custom Settings)
{
  "login_link": "https://app.fifteenth.com/auth/partner-login/tk_M4pQ7vN9xR2mK5wL8S1E6B",
  "user_email": "[email protected]",
  "expires_at": "2024-01-17T10:30:00Z",
  "expires_in_hours": 48,
  "purpose": "support_request",
  "redirect_url": "https://yourplatform.com/tax-dashboard",
  "notification_sent": true,
  "previous_links_invalidated": 2,
  "created_at": "2024-01-15T10:30:00Z",
  "account_id": 12345
}

Error Responses

404 Not Found
object
Account not found or user email not associated with account.
{
  "error": {
    "code": "ACCOUNT_NOT_FOUND",
    "message": "Account not found",
    "details": {
      "account_id": "12345"
    }
  }
}
{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "User email not found on this account",
    "details": {
      "user_email": "[email protected]",
      "account_id": "12345"
    }
  }
}
400 Bad Request
object
Invalid request parameters.
{
  "error": {
    "code": "INVALID_EXPIRY",
    "message": "Expiry hours must be between 1 and 168",
    "details": {
      "provided_hours": 200,
      "min_hours": 1,
      "max_hours": 168
    }
  }
}
{
  "error": {
    "code": "INVALID_REDIRECT_URL",
    "message": "Redirect URL is not permitted for your partner account",
    "details": {
      "provided_url": "https://untrusted.com/dashboard",
      "permitted_domains": ["yourplatform.com", "yourapp.net"]
    }
  }
}
403 Forbidden
object
Account access denied or suspended.
{
  "error": {
    "code": "ACCOUNT_SUSPENDED",
    "message": "Cannot generate login links for suspended accounts",
    "details": {
      "account_id": "12345",
      "status": "suspended",
      "contact_support": true
    }
  }
}
429 Too Many Requests
object
Too many login link generation requests.
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many login link generation requests",
    "details": {
      "limit": 10,
      "window_minutes": 60,
      "reset_at": "2024-01-15T11:30:00Z"
    }
  }
}

Security Considerations

Login links provide full account access. Handle them with the same security as passwords:
  • Single-use for initial access: After first use, clients set up permanent credentials
  • Time-limited: Links automatically expire (default 7 days, max 7 days)
  • Cryptographically secure: Uses secure random token generation
  • Invalidation: Previous links are automatically invalidated when new ones are generated
  • Audit logging: All link generation and usage is logged for security

Best Practices

Choose appropriate expiry times based on urgency:
  • Immediate support: 1-4 hours
  • Normal client access: 24-48 hours
  • Account setup: 7 days (default)
  • High security situations: 1 hour minimum
When sending notifications:
  • Always use partner branding for legitimacy
  • Include expiry information in emails
  • Warn clients not to share links
  • Use custom messages to provide context
For sensitive situations:
  • Set send_notification: false
  • Deliver links through secure channels
  • Use shorter expiry times
  • Track delivery method for your records
Respect rate limits to prevent abuse:
  • Max 10 links per account per hour
  • Use purpose field for audit trails
  • Monitor link generation patterns
  • Implement client-side rate limiting

Monitoring and Alerts

Monitor login link usage for security:
def monitor_login_link_security(account_id, link_data):
    """Monitor login link generation for security patterns"""
    
    # Alert on rapid link generation
    if link_data['previous_links_invalidated'] > 3:
        security_alert(
            f"Multiple login links generated rapidly for account {account_id}",
            severity="medium"
        )
    
    # Alert on short expiry links (potential support issue)
    if link_data['expires_in_hours'] < 4:
        operational_alert(
            f"Short-expiry login link generated: {link_data['expires_in_hours']} hours",
            context=link_data['purpose']
        )
    
    # Log for audit trail
    audit_log({
        'event': 'login_link_generated',
        'account_id': account_id,
        'purpose': link_data['purpose'],
        'expiry': link_data['expires_at'],
        'notification_sent': link_data['notification_sent']
    })

Usage Patterns

def replace_expired_links():
    """Automatically replace expired links for active accounts"""
    
    expired_accounts = get_accounts_with_expired_links()
    
    for account in expired_accounts:
        try:
            # Generate new link
            new_link = generate_login_link(account['id'], {
                'purpose': 'automated_renewal',
                'expiry_hours': 168,  # 7 days
                'send_notification': True,
                'notification_settings': {
                    'custom_message': 'Your previous login link has expired. Here is your new access link.'
                }
            })
            
            print(f"Renewed link for account {account['id']}")
            
        except Exception as e:
            print(f"Failed to renew link for {account['id']}: {e}")
            
        # Respect rate limits
        time.sleep(1)

Support Ticket Integration

def generate_support_link(ticket_id, account_id, agent_name):
    """Generate login link for support ticket resolution"""
    
    link_data = generate_login_link(account_id, {
        'purpose': 'support_request',
        'expiry_hours': 4,  # Short expiry for security
        'send_notification': True,
        'notification_settings': {
            'custom_subject': f'Support Access Link - Ticket #{ticket_id}',
            'custom_message': f'Our support team has generated a secure access link to help resolve your inquiry. This link expires in 4 hours.',
            'partner_branded': True
        },
    })
    
    # Update support ticket with link info
    update_ticket(ticket_id, {
        'login_link_generated': True,
        'link_expires_at': link_data['expires_at'],
        'agent': agent_name
    })
    
    return link_data

Next Steps