---
title: "How to use the Linkbreakers API"
description: "Complete guide to integrating with the Linkbreakers API - create QR codes, manage links, customize designs, track analytics, and automate workflows programmatically."
canonical: "https://linkbreakers.com/help/article/how-to-use-the-linkbreakers-api"
last-updated: 2026-10-21
---

# How to use the Linkbreakers API

> Complete guide to integrating with the Linkbreakers API - create QR codes, manage links, customize designs, track analytics, and automate workflows programmatically.

## Short answer

This article explains how to use the linkbreakers api with practical guidance, limits, and implementation details so you can apply it consistently.

Building QR code functionality shouldn't mean starting from scratch. The Linkbreakers API transforms complex QR code operations into simple HTTP requests that integrate seamlessly with any technology stack.

Whether you're automating marketing campaigns, building customer-facing tools, or integrating QR codes into existing products, our REST API provides the building blocks you need. Think of it as having Linkbreakers' full feature set available as programmable components.

Every API endpoint returns consistent JSON responses with comprehensive error handling. Authentication uses standard bearer tokens. Rate limits are generous enough for production workloads while preventing abuse.

> Most constructions of Linkbreakers can be built via the Linkbreakers API. Full documentation is available at https://linkbreakers.com/help/api

## Authentication and workspace tokens

Before making API calls, you'll need a workspace token that identifies your account and provides access to your resources. These tokens act like API keys but with better security and audit capabilities.

### Creating your first token

Navigate to [Workspace tokens](https://app.linkbreakers.com/workspace/dashboard/api-tokens) in your dashboard. Click "Create Token" and provide a descriptive name that identifies its purpose.

Common token names include:
- "Production integration" for live systems
- "Development testing" for local development
- "Marketing automation" for specific campaign tools
- "Staging environment" for pre-production testing

The platform generates a JWT token that appears only once during creation. Store this token securely - you can't retrieve it again after the creation dialog closes.

### Token authentication

Include your token in the Authorization header for every API request:

```bash
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     -H "Content-Type: application/json" \
     https://api.linkbreakers.com/v1/links
```

Tokens remain valid indefinitely unless explicitly revoked. However, you can monitor usage and revoke tokens through the dashboard if they're compromised or no longer needed.

### Security best practices

Store tokens as environment variables rather than hardcoding them in source code. Use different tokens for different environments to limit blast radius if one gets compromised.

For production systems, consider rotating tokens periodically even if not compromised. The dashboard tracks token usage so you can verify expected activity patterns.

## Creating and managing links

Links are the foundation of Linkbreakers functionality. Every QR code, short URL, or interactive workflow starts with a link resource that defines the destination and behavior.

### Basic link creation

Create a simple link with just a destination URL:

```javascript
const response = await fetch('https://api.linkbreakers.com/v1/links', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    destination: 'https://example.com/landing-page',
    name: 'Product Launch Campaign'
  })
});

const { link } = await response.json();
console.log(`Created link: ${link.entrypoint}`);
```

This creates a link with an auto-generated shortlink that redirects to your destination. The response includes the complete link object with generated fields like `entrypoint` and `qrcode_signed_url`.

### Custom shortlinks and branding

Specify custom shortlinks for memorable URLs that reinforce your brand:

```javascript
const brandedLink = await fetch('https://api.linkbreakers.com/v1/links', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    destination: 'https://example.com/special-offer',
    name: 'Holiday Promotion',
    shortlink: 'holiday-2024',
    custom_domain_id: 'your-custom-domain-uuid'
  })
});
```

Custom shortlinks must be unique within your workspace or custom domain. The platform validates availability and returns errors for conflicts.

### Advanced link configuration

Include metadata, tags, and a purpose for campaign management:

```javascript
const advancedLink = await fetch('https://api.linkbreakers.com/v1/links', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    destination: 'https://example.com/webinar-registration',
    fallback_destination: 'https://example.com/webinar-info',
    name: 'Q4 Webinar Series',
    shortlink: 'webinar-q4',
    metadata: {
      campaign: 'Q4-2024',
      channel: 'email',
      audience: 'enterprise'
    },
    tags: ['webinar', 'lead-gen', 'q4-campaign'],
    purpose: 'Generate qualified leads for enterprise sales. Good leads: decision makers with 100+ employees.'
  })
});
```

Metadata accepts string key-value pairs with a maximum of 50 keys. Tags help organize campaigns and enable filtered dashboard views. The purpose describes what the link is for in a sentence or two, and the link's Assistant conversations read it as context.

### Links without a QR code

Every link gets a QR code by default. When a link is only ever shared as a URL, in an email, a bio or an ad, send `qrcode_enabled: false` on creation:

```javascript
await fetch('https://api.linkbreakers.com/v1/links', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    destination: 'https://example.com/newsletter',
    qrcode_enabled: false
  })
});
```

The flag works on every create endpoint (single, bulk, contact, thank you and open media links) and on update. Here is what each case does:

- **Creating with `qrcode_enabled: false`:** the link has no QR code at all. No QR code design is created and no image is generated, so `qrcode_design_id` and `qrcode_signed_url` stay empty. The shortlink and workflow work as usual. `qrcode_template_id` is still stored and used if you turn the QR code on later. It cannot be combined with `qrcode_design_id` or `wait_for_qrcode: true`.
- **Updating to `qrcode_enabled: false`:** the link's QR code image and its QR code design are deleted permanently, including any colors, shapes or logo. QR codes you already printed keep working, because they encode the shortlink, which does not change.
- **Updating to `qrcode_enabled: true`:** the link gets a new QR code design from its `qrcode_template_id`, or the default template, and the image is generated in the background. A customization made before the QR code was turned off is not restored.

While the QR code is off, the QR code design endpoints refuse the link with `LINK_QRCODE_OFF`. In the dashboard, the link shows a crossed-out QR code with a button to turn it back on.

### Link retrieval and management

Fetch existing links with optional relationship data:

```javascript
// Get a specific link with related data
const linkResponse = await fetch(
  `https://api.linkbreakers.com/v1/links/${linkId}?include=qrcodeDesign,tags,customDomain`,
  {
    headers: {
      'Authorization': 'Bearer YOUR_JWT_TOKEN'
    }
  }
);

// List all links with pagination
const linksResponse = await fetch(
  'https://api.linkbreakers.com/v1/links?page_size=50&include=qrcodeDesign',
  {
    headers: {
      'Authorization': 'Bearer YOUR_JWT_TOKEN'
    }
  }
);

const { links, next_page_token } = await linksResponse.json();
```

The `include` parameter loads related resources in a single request, reducing the number of API calls needed for complete data.

Link updates use PATCH requests to modify the link's own fields (name, tags, fallback destination) without affecting others. The destination is not one of them: it belongs to the redirect workflow step, covered in the FAQ below.

```javascript
const updateResponse = await fetch(`https://api.linkbreakers.com/v1/links/${linkId}`, {
  method: 'PATCH',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Updated Campaign Name',
    tags: ['updated', 'active', 'priority']
  })
});
```

## QR code design customization

QR code designs control the visual appearance of generated codes, from colors and shapes to logos and export formats. The design system separates visual styling from link functionality.

### Creating custom designs

Generate QR code designs with specific branding requirements:

```javascript
// Upload logo as base64 for central image
const logoBase64 = await fileToBase64(logoFile);

const designResponse = await fetch('https://api.linkbreakers.com/v1/public/qrcode-designs', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    output_file_format: 'OUTPUT_FILE_FORMAT_PNG',
    width: 500,
    height: 500,
    central_image_data: logoBase64,
    central_image_size: 0.3,
    dots_options: {
      color: '#2563eb',
      type: 'rounded'
    },
    corners_square_options: {
      color: '#1e40af',
      type: 'extra-rounded'
    },
    background_options: {
      color: '#ffffff'
    },
    shape: 'SHAPE_SQUARE',
    error_correction_level: 'M',
    hide_background_dots: true
  })
});

const { qrcode_design } = await designResponse.json();
```

Design options provide granular control over every visual element. Color options support hex codes or gradient definitions. Shape options include traditional squares or modern circular modules.

### Gradient and advanced styling

Create sophisticated designs with gradient fills and custom styling:

```javascript
const gradientDesign = {
  dots_options: {
    gradient: JSON.stringify({
      type: 'linear',
      rotation: 45,
      colorStops: [
        { offset: 0, color: '#3b82f6' },
        { offset: 1, color: '#8b5cf6' }
      ]
    }),
    type: 'dots'
  },
  corners_square_options: {
    color: '#1e293b',
    type: 'square'
  },
  background_options: {
    gradient: JSON.stringify({
      type: 'radial',
      colorStops: [
        { offset: 0, color: '#f8fafc' },
        { offset: 1, color: '#e2e8f0' }
      ]
    })
  }
};
```

Gradient definitions use the qr-code-styling library format for maximum compatibility. This enables sophisticated visual effects while maintaining scanning reliability.

### Design templates and reusability

Create reusable design templates for consistent branding across campaigns:

```javascript
// Create a template design
const templateDesign = await fetch('https://api.linkbreakers.com/v1/public/qrcode-designs', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    // ... design specifications
  })
});

// Apply template to new link
const linkWithTemplate = await fetch('https://api.linkbreakers.com/v1/links', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    destination: 'https://example.com',
    name: 'Branded Campaign',
    qrcode_design_id: templateDesign.qrcode_design.id
  })
});
```

Design templates enable brand consistency across different team members and campaigns. Update template designs to refresh all associated QR codes simultaneously.

## Analytics and event tracking

Event data provides insights into QR code performance, visitor behavior, and campaign effectiveness. The events API enables both real-time monitoring and historical analysis.

### Retrieving event data

Query events with flexible filtering options:

```javascript
const eventsResponse = await fetch(
  'https://api.linkbreakers.com/v1/events/list?' +
  new URLSearchParams({
    start_date: '2024-01-01T00:00:00Z',
    end_date: '2024-01-31T23:59:59Z',
    link_id: 'specific-link-uuid',
    page_size: '100',
    include: 'visitor,device,link,journey'
  }),
  {
    headers: {
      'Authorization': 'Bearer YOUR_JWT_TOKEN'
    }
  }
);

const { events, total_count, has_more, next_page_token } = await eventsResponse.json();
```

Events include comprehensive metadata about each scan: device information, geographic location, visitor profiles, and the [visitor journey](https://linkbreakers.com/help/article/what-is-a-visitor-journey) (how far the visitor got through the link's workflow). Filter by date ranges, specific links, or other criteria.

### CSV export for reporting

Export large datasets for analysis in spreadsheet applications or business intelligence tools:

```javascript
const csvResponse = await fetch(
  'https://api.linkbreakers.com/v1/events/list?' +
  new URLSearchParams({
    start_date: '2024-01-01T00:00:00Z',
    end_date: '2024-01-31T23:59:59Z',
    response_format: 'RESPONSE_FORMAT_CSV'
  }),
  {
    headers: {
      'Authorization': 'Bearer YOUR_JWT_TOKEN'
    }
  }
);

const csvData = await csvResponse.text();
// Save to file or process directly
```

CSV exports include all event fields with human-readable headers. Large exports use pagination to manage memory usage and transfer times.

### Real-time event processing

For real-time analytics or automation, consider using [webhooks](https://linkbreakers.com/help/article/how-do-webhooks-work-in-linkbreakers) instead of polling the events API. Webhooks deliver events as they occur with lower latency. Learn more about comprehensive [analytics capabilities](https://linkbreakers.com/help/article/analytics) and how to [integrate with your existing tech stack](https://linkbreakers.com/help/article/how-to-integrate-linkbreakers-with-existing-tech-stack).

## Advanced integrations and automation

### Bulk operations for scale

Create multiple links efficiently using concurrent requests:

```javascript
const destinations = [
  'https://example.com/product1',
  'https://example.com/product2',
  'https://example.com/product3'
];

const linkPromises = destinations.map(async (destination, index) => {
  return fetch('https://api.linkbreakers.com/v1/links', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_JWT_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      destination,
      name: `Product ${index + 1} Campaign`,
      tags: ['bulk-created', 'product-campaign']
    })
  });
});

const responses = await Promise.all(linkPromises);
const links = await Promise.all(
  responses.map(response => response.json())
);
```

Rate limits accommodate reasonable bulk operations. For very large batches, implement exponential backoff and respect rate limit headers.

### Dynamic personalization

Generate personalized QR codes based on customer data or campaign parameters:

```javascript
const customers = [
  { id: 'cust1', name: 'Acme Corp', tier: 'enterprise' },
  { id: 'cust2', name: 'Beta LLC', tier: 'professional' }
];

const personalizedLinks = await Promise.all(
  customers.map(async customer => {
    return fetch('https://api.linkbreakers.com/v1/links', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer YOUR_JWT_TOKEN',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        destination: `https://example.com/welcome?customer=${customer.id}`,
        name: `Welcome - ${customer.name}`,
        shortlink: `welcome-${customer.id}`,
        metadata: {
          customer_id: customer.id,
          customer_name: customer.name,
          tier: customer.tier
        },
        tags: ['personalized', customer.tier]
      })
    });
  })
);
```

Personalized campaigns increase engagement by delivering relevant experiences. Use metadata fields to track personalization parameters for analysis.

### Integration with marketing automation

Connect Linkbreakers with existing marketing tools using webhook notifications:

```javascript
// Create a webhook that fires when a visit ends or a step such as a form is completed
const webhookResponse = await fetch('https://api.linkbreakers.com/v1/webhooks', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Marketing automation',
    endpointUrl: 'https://your-system.com/linkbreakers-webhook',
    triggers: ['KIND_EXIT', 'KIND_INTERACTION']
  })
});

// Process webhook events in your system
// The X-Linkbreakers-Event header is always "event.completed": route on the body
app.post('/linkbreakers-webhook', async (req, res) => {
  const { type, event, workflowStep } = req.body;
  
  if (type === 'step.completed' && workflowStep?.eventAction === 'FORM_SUBMIT') {
    // Add to CRM with what the visitor submitted
    const submission = event.traces.find((t) => t.stepAction === 'FORM_SUBMIT');
    await createCRMLead(event.visitor, submission?.stepData?.form_data);
  } else if (type === 'event.completed' && event.visitor?.email) {
    // Trigger marketing automation
    await addToEmailSequence(event.visitor.email);
  }
  
  res.status(200).send('OK');
});
```

Webhooks enable real-time integration with CRM systems, email platforms, and analytics tools. Each webhook's `triggers` are workflow step kinds (`KIND_ENTRY`, `KIND_EXIT`, `KIND_INTERACTION`; empty means all), and `linkId` limits it to one link. In the delivered body, enums use short names (`FORM_SUBMIT`, `INTERACTION`) and `type` is `event.completed` or `step.completed`. See [how webhooks work](https://linkbreakers.com/help/article/how-do-webhooks-work-in-linkbreakers) for the full payload.

## Error handling and best practices

### Comprehensive error responses

The API returns structured error information for troubleshooting:

```javascript
try {
  const response = await fetch('https://api.linkbreakers.com/v1/links', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_JWT_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      destination: 'invalid-url'
    })
  });
  
  if (!response.ok) {
    const errorData = await response.json();
    console.error('API Error:', errorData.message);
    console.error('Error Code:', errorData.code);
    console.error('Details:', errorData.details);
  }
} catch (error) {
  console.error('Network Error:', error.message);
}
```

Error responses include HTTP status codes, error messages, and contextual details. Common error codes include validation failures (400), authentication issues (401), and rate limits (429).

### Rate limiting and retry logic

Implement exponential backoff for resilient integrations:

```javascript
async function apiCallWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      if (response.status === 429) {
        // Rate limited - wait and retry
        const retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt);
        await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
        continue;
      }
      
      return response;
    } catch (error) {
      if (attempt === maxRetries) throw error;
      
      // Exponential backoff for network errors
      await new Promise(resolve => 
        setTimeout(resolve, Math.pow(2, attempt) * 1000)
      );
    }
  }
}
```

Rate limits are per-workspace and reset every minute. Respect `Retry-After` headers for optimal performance. Most applications won't hit rate limits with normal usage patterns.

### Validation and data integrity

Validate data before API calls to catch issues early:

```javascript
function validateLinkData(linkData) {
  const errors = [];
  
  if (!linkData.destination) {
    errors.push('Destination is required');
  } else if (!isValidURL(linkData.destination)) {
    errors.push('Destination must be a valid URL');
  }
  
  if (linkData.metadata && Object.keys(linkData.metadata).length > 50) {
    errors.push('Maximum 50 metadata keys allowed');
  }
  
  if (linkData.shortlink && !/^[a-zA-Z0-9_-]+$/.test(linkData.shortlink)) {
    errors.push('Shortlink contains invalid characters');
  }
  
  return errors;
}

// Use before API calls
const validationErrors = validateLinkData(linkData);
if (validationErrors.length > 0) {
  console.error('Validation errors:', validationErrors);
  return;
}
```

Client-side validation prevents unnecessary API calls and provides immediate feedback. However, always handle server-side validation errors as the authoritative source.

## Code examples for popular frameworks

### Node.js with Express

```javascript
const express = require('express');
const fetch = require('node-fetch');

const app = express();
app.use(express.json());

const LINKBREAKERS_TOKEN = process.env.LINKBREAKERS_TOKEN;
const API_BASE = 'https://api.linkbreakers.com/v1';

// Create QR code endpoint
app.post('/create-qr', async (req, res) => {
  try {
    const { destination, name } = req.body;
    
    const response = await fetch(`${API_BASE}/links`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${LINKBREAKERS_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ destination, name })
    });
    
    if (!response.ok) {
      throw new Error(`API error: ${response.status}`);
    }
    
    const { link } = await response.json();
    res.json({
      qr_url: link.qrcode_signed_url,
      short_url: link.entrypoint
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});
```

### Python with Flask

```python
import requests
import os
from flask import Flask, request, jsonify

app = Flask(__name__)

LINKBREAKERS_TOKEN = os.environ['LINKBREAKERS_TOKEN']
API_BASE = 'https://api.linkbreakers.com/v1'

@app.route('/create-qr', methods=['POST'])
def create_qr():
    try:
        data = request.json
        
        response = requests.post(
            f'{API_BASE}/links',
            headers={
                'Authorization': f'Bearer {LINKBREAKERS_TOKEN}',
                'Content-Type': 'application/json'
            },
            json={
                'destination': data['destination'],
                'name': data['name']
            }
        )
        response.raise_for_status()
        
        link = response.json()['link']
        return jsonify({
            'qr_url': link['qrcode_signed_url'],
            'short_url': link['entrypoint']
        })
    except requests.exceptions.RequestException as e:
        return jsonify({'error': str(e)}), 500
```

### React frontend integration

```javascript
import { useState } from 'react';

function QRCodeGenerator() {
  const [formData, setFormData] = useState({ destination: '', name: '' });
  const [qrCode, setQrCode] = useState(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);

    try {
      const response = await fetch('/api/create-qr', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData)
      });

      if (!response.ok) throw new Error('Failed to create QR code');

      const result = await response.json();
      setQrCode(result);
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="url"
        placeholder="Destination URL"
        value={formData.destination}
        onChange={(e) => setFormData({...formData, destination: e.target.value})}
        required
      />
      <input
        type="text"
        placeholder="Campaign Name"
        value={formData.name}
        onChange={(e) => setFormData({...formData, name: e.target.value})}
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create QR Code'}
      </button>
      
      {qrCode && (
        <div>
          <img src={qrCode.qr_url} alt="Generated QR Code" />
          <p>Short URL: <a href={qrCode.short_url}>{qrCode.short_url}</a></p>
        </div>
      )}
    </form>
  );
}
```

## Testing and development workflow

### API testing with curl

Test endpoints quickly using curl commands:

```bash
# Create a test link
curl -X POST https://api.linkbreakers.com/v1/links \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "destination": "https://example.com",
    "name": "Test Link"
  }'

# Get link details
curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://api.linkbreakers.com/v1/links/LINK_UUID

# List recent events
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://api.linkbreakers.com/v1/events/list?page_size=10"
```

### Postman collection

Create Postman collections for team API testing:

1. Set up environment variables for `api_base_url` and `auth_token`
2. Create requests for common operations
3. Add test scripts to validate responses
4. Share collections with team members

### Local development setup

Use environment variables for configuration:

```javascript
// .env file
LINKBREAKERS_TOKEN=your_development_token
LINKBREAKERS_API_BASE=https://api.linkbreakers.com/v1
NODE_ENV=development

// Configuration
const config = {
  token: process.env.LINKBREAKERS_TOKEN,
  apiBase: process.env.LINKBREAKERS_API_BASE,
  isDevelopment: process.env.NODE_ENV === 'development'
};
```

Separate development and production tokens to prevent accidental data mixing.

If an AI agent will call the API for you, [How Linkbreakers works with AI agents](/help/article/linkbreakers-for-ai-agents) covers scoped tokens, auth.md self-registration, the MCP server and sandboxes.

## Frequently Asked Questions

### What are the rate limits for the API?

The API allows 1000 requests per minute per workspace token. Rate limit headers in responses show current usage and reset times.

### Can I use the API to create QR codes without links?

No, QR codes are generated as part of link creation. However, you can create links with the sole purpose of generating QR codes and ignore the shortlink functionality. The reverse works too: send `qrcode_enabled: false` to create a link with no QR code.

### How do I handle webhook signature verification?

Webhook payloads include signatures for verification. Use the shared secret from your webhook configuration to validate incoming requests.

### What happens to QR codes when I delete a link?

Deleting a link immediately stops QR code functionality. Existing QR code images continue working until their signed URLs expire, but scanning will show errors.

### Can I modify QR code destinations without regenerating codes?

Yes. The destination lives on the link's redirect workflow step, not on the link itself, so `PATCH /v1/links/{id}` does not change it. List the link's steps with `GET /v1/links/{linkId}/workflow-steps`, find the step whose `eventAction` is `EVENT_ACTION_CLIENT_REDIRECT`, and send `PATCH /v1/links/{linkId}/workflow-steps/{stepId}` with `{"payload": {"clientRedirect": {"destination": "https://new.example.com"}}}`. Existing QR codes redirect to the new destination without being regenerated.

### How do I track conversions beyond basic scans?

Use [workflow integration](https://linkbreakers.com/help/article/how-to-create-a-form) to create custom conversion events, or connect webhook data to your analytics platform for advanced tracking. For automation scenarios, explore [QR code marketing automation](https://linkbreakers.com/help/article/the-complete-guide-to-qr-code-marketing-automation) and [Zapier integration](https://linkbreakers.com/help/article/automate-your-lead-generation-with-qr-codes-zapier).

### Is there a sandbox environment for testing?

Yes. Create a [sandbox](/help/article/sandbox-test-environment) with `POST /v1/sandboxes` or from **Dashboard → API Tokens → Sandboxes**. It is a separate, empty workspace reached with its own `lb_test_` token: nothing you do there touches your live data, it is never billed, and it is deleted 30 days after creation.

### What's the maximum file size for QR code central images?

Central images have a 3MB file size limit. Use optimized PNG or JPEG formats for best results. The API automatically handles image processing and optimization.

The Linkbreakers API transforms complex QR code operations into straightforward HTTP requests that integrate with any development workflow. Start with basic link creation, then explore advanced features like custom designs and webhook integration as your needs evolve.

Focus on building reliable error handling and respecting rate limits. The API grows with your requirements while maintaining simplicity for routine operations.

## Limits and caveats

- Feature availability and limits can vary by plan and workspace setup.
- Results depend on correct implementation, attribution setup, and data quality controls.
- Regulatory and privacy obligations vary by jurisdiction and use case.
