---
title: "Linkbreakers Rust SDK: Complete Integration Guide"
description: "Learn how to integrate the Linkbreakers Rust SDK in your backend application for programmatic link management, visitor tracking, QR code generation, and analytics. Includes installation, authentication, async/await patterns, and real code examples."
canonical: "https://linkbreakers.com/help/article/rust-sdk-quickstart"
last-updated: 2026-10-11
---

# Linkbreakers Rust SDK: Complete Integration Guide

> Learn how to integrate the Linkbreakers Rust SDK in your backend application for programmatic link management, visitor tracking, QR code generation, and analytics. Includes installation, authentication, async/await patterns, and real code examples.

## Short answer

The Linkbreakers Rust SDK enables programmatic link management, visitor identification, QR code generation, and analytics access from your Rust backend applications. Install it with `cargo add linkbreakers`, authenticate with a secret API key, and start creating trackable links with async/await patterns for high-performance applications.

## Installation

Add the SDK to your project using Cargo:

```bash
cargo add linkbreakers
```

Or manually add to your `Cargo.toml`:

```toml
[dependencies]
linkbreakers = "1.183.0"
tokio = { version = "1", features = ["full"] }
```

The SDK uses `tokio` for async runtime and `reqwest` for HTTP requests.

## Understanding API keys for Rust

The Rust SDK is designed for **backend/server-side use only** and requires a **secret API key** with full workspace access.

### Secret keys

**Backend-only.** These JWT tokens grant full API access and must never be exposed in public repositories or client-side code.

**What they allow:**
- Create, update, and delete links
- Generate and customize QR codes
- Identify and track visitors
- Access analytics and metrics
- Manage workspace resources
- Full API access

**Where to use:**
- Web servers (Axum, Actix, Rocket)
- Microservices
- CLI tools
- Background workers
- API gateways
- High-performance data processing

**Security note:** Rust applications typically run on servers, making them perfect for secret key usage. Never log keys, commit them to git, or expose them in error messages.

## Creating a secret API key

Create your API key from the dashboard:

1. Log in to [app.linkbreakers.com](https://app.linkbreakers.com)
2. Navigate to **Dashboard → API Tokens**
3. Click **Create API Token**
4. Select **Secret Key** (full API access)
5. Give it a descriptive name (e.g., "Production Backend" or "Rust Service")
6. Copy the generated JWT token immediately

**Important:** You won't see the key again after creation. Store it in environment variables or a secret management system.

## Quick start

Initialize the client and create your first shortened link:

```rust
use linkbreakers::{
    apis::configuration::Configuration,
    apis::links_api,
    models::CreateLinkRequest,
};
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Configure API client with your secret key
    let api_key = env::var("LINKBREAKERS_SECRET_KEY")?;

    let mut config = Configuration::new();
    config.bearer_access_token = Some(api_key);
    config.base_path = "https://api.linkbreakers.com".to_string();

    // Create a shortened link
    let create_req = CreateLinkRequest {
        destination: "https://example.com/product".to_string(),
        name: Some("Product Launch - Spring 2025".to_string()),
        tags: Some(vec!["marketing".to_string(), "product-launch".to_string()]),
        ..Default::default()
    };

    let response = links_api::links_service_create(&config, create_req).await?;

    if let Some(link) = response.link {
        println!("Short URL: {}", link.shortlink.unwrap_or_default());
        println!("Link ID: {}", link.id.unwrap_or_default());
        println!("QR Code: {}", link.qrcode_signed_url.unwrap_or_default());
    }

    Ok(())
}
```

## Environment variables (best practice)

Never hardcode API keys. Use environment variables:

```rust
use linkbreakers::apis::configuration::Configuration;
use std::env;

fn get_config() -> Result<Configuration, env::VarError> {
    let api_key = env::var("LINKBREAKERS_SECRET_KEY")?;

    let mut config = Configuration::new();
    config.bearer_access_token = Some(api_key);
    config.base_path = "https://api.linkbreakers.com".to_string();

    Ok(config)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = get_config()?;

    // Use the config for API operations
    // ...

    Ok(())
}
```

**Set your environment variable:**

```bash
# .env file (use dotenv crate to load)
export LINKBREAKERS_SECRET_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ3b3Jrc3BhY2VfaWQiOiIxMjM0IiwidHlwZSI6InNlY3JldCJ9...
```

**Loading .env files with dotenv:**

```toml
[dependencies]
dotenv = "0.15"
```

```rust
use dotenv::dotenv;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load .env file
    dotenv().ok();

    let api_key = std::env::var("LINKBREAKERS_SECRET_KEY")?;
    // ...

    Ok(())
}
```

## Creating and managing links

### Basic link creation

```rust
use linkbreakers::{
    apis::{configuration::Configuration, links_api},
    models::CreateLinkRequest,
};

async fn create_basic_link(config: &Configuration) -> Result<(), Box<dyn std::error::Error>> {
    let create_req = CreateLinkRequest {
        destination: "https://example.com/summer-sale".to_string(),
        name: Some("Summer Sale 2025".to_string()),
        tags: Some(vec!["campaign".to_string(), "summer".to_string(), "email".to_string()]),
        ..Default::default()
    };

    let response = links_api::links_service_create(config, create_req).await?;

    if let Some(link) = response.link {
        println!("Created: {}", link.shortlink.unwrap_or_default());
    }

    Ok(())
}
```

### Advanced link options

```rust
use linkbreakers::{
    apis::{configuration::Configuration, links_api},
    models::CreateLinkRequest,
};
use std::collections::HashMap;

async fn create_advanced_links(config: &Configuration) -> Result<(), Box<dyn std::error::Error>> {
    // Create link with custom shortlink
    let custom_req = CreateLinkRequest {
        destination: "https://example.com/docs".to_string(),
        shortlink: Some("docs".to_string()), // Creates lbr.ai/docs
        name: Some("Documentation Portal".to_string()),
        fallback_destination: Some("https://example.com/404".to_string()),
        tags: Some(vec!["internal".to_string(), "docs".to_string()]),
        ..Default::default()
    };

    let custom_link = links_api::links_service_create(config, custom_req).await?;

    // Create link with metadata for tracking
    let mut metadata = HashMap::new();
    metadata.insert("product_id".to_string(), "123".to_string());
    metadata.insert("campaign_id".to_string(), "spring-2025".to_string());
    metadata.insert("utm_source".to_string(), "email".to_string());
    metadata.insert("utm_medium".to_string(), "newsletter".to_string());
    metadata.insert("utm_campaign".to_string(), "product-launch".to_string());

    let tracked_req = CreateLinkRequest {
        destination: "https://example.com/product/123".to_string(),
        name: Some("Product 123 - Email Campaign".to_string()),
        metadata: Some(metadata),
        tags: Some(vec!["product".to_string(), "email".to_string()]),
        ..Default::default()
    };

    let tracked_link = links_api::links_service_create(config, tracked_req).await?;

    // Create link with QR code (wait for generation)
    let qr_req = CreateLinkRequest {
        destination: "https://example.com/event-registration".to_string(),
        name: Some("Conference 2025 Registration".to_string()),
        wait_for_qrcode: Some(true), // Blocks until QR is generated
        tags: Some(vec!["event".to_string(), "conference".to_string(), "qr-code".to_string()]),
        ..Default::default()
    };

    let qr_link = links_api::links_service_create(config, qr_req).await?;

    if let Some(link) = qr_link.link {
        println!("Download QR code: {}", link.qrcode_signed_url.unwrap_or_default());
    }

    Ok(())
}
```

### Listing and filtering links

```rust
use linkbreakers::{
    apis::{configuration::Configuration, links_api},
    models::{Linkbreakersv1LinksSortField, Linkbreakersv1SortDirection},
};

async fn list_links(config: &Configuration) -> Result<(), Box<dyn std::error::Error>> {
    let response = links_api::links_service_list(
        config,
        Some(50), // page_size
        None,     // page_token
        Some(vec!["marketing".to_string(), "email".to_string()]), // tags
        Some("campaign".to_string()), // search
        Some(Linkbreakersv1LinksSortField::UpdatedAt), // sort_by
        Some(Linkbreakersv1SortDirection::Desc), // sort_direction
        None,     // directory_id
        None,     // include_all_directories
        None,     // include
    )
    .await?;

    if let Some(links) = response.links {
        for link in links {
            println!("{}: {}", link.name.unwrap_or_default(), link.shortlink.unwrap_or_default());
            println!("  Tags: {:?}", link.tags.unwrap_or_default());
            println!("  Created: {}", link.created_at.unwrap_or_default());
        }
    }

    Ok(())
}
```

### Updating and deleting links

```rust
use linkbreakers::{
    apis::{configuration::Configuration, links_api},
    models::UpdateLinkRequest,
};

async fn update_and_delete_link(
    config: &Configuration,
    link_id: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    // Get a specific link
    let link_resp = links_api::links_service_get(
        config,
        link_id,
        Some(vec!["tags".to_string(), "qrcodeSignedUrl".to_string()]),
    )
    .await?;

    // Update link
    let update_req = UpdateLinkRequest {
        name: Some("Updated Campaign Name".to_string()),
        tags: Some(vec!["marketing".to_string(), "q2-2025".to_string(), "updated".to_string()]),
        ..Default::default()
    };

    links_api::links_service_update(config, link_id, update_req).await?;

    // Delete link
    links_api::links_service_delete(config, link_id).await?;

    Ok(())
}
```

## Bulk link creation

Create hundreds of links efficiently in a single API call:

```rust
use linkbreakers::{
    apis::{configuration::Configuration, links_api},
    models::{CreateBulkLinksRequest, CreateLinkRequest},
};
use std::collections::HashMap;

struct Product {
    id: String,
    name: String,
    url: String,
}

async fn create_bulk_links(config: &Configuration) -> Result<(), Box<dyn std::error::Error>> {
    // Prepare bulk links (e.g., from a product catalog)
    let products = vec![
        Product {
            id: "001".to_string(),
            name: "Widget A".to_string(),
            url: "https://example.com/products/001".to_string(),
        },
        Product {
            id: "002".to_string(),
            name: "Widget B".to_string(),
            url: "https://example.com/products/002".to_string(),
        },
        Product {
            id: "003".to_string(),
            name: "Widget C".to_string(),
            url: "https://example.com/products/003".to_string(),
        },
    ];

    let links_to_create: Vec<CreateLinkRequest> = products
        .iter()
        .map(|p| {
            let mut metadata = HashMap::new();
            metadata.insert("product_id".to_string(), p.id.clone());
            metadata.insert("sku".to_string(), format!("SKU-{}", p.id));

            CreateLinkRequest {
                destination: p.url.clone(),
                name: Some(format!("Product: {}", p.name)),
                tags: Some(vec!["product".to_string(), "catalog".to_string()]),
                metadata: Some(metadata),
                ..Default::default()
            }
        })
        .collect();

    // Create all links in one request
    let bulk_req = CreateBulkLinksRequest {
        links: links_to_create,
    };

    let response = links_api::links_service_create_bulk(config, bulk_req).await?;

    if let Some(links) = response.links {
        println!("Created {} links", links.len());
        for link in links {
            println!("  {}: {}", link.name.unwrap_or_default(), link.shortlink.unwrap_or_default());
        }
    }

    Ok(())
}
```

**When to use bulk creation:**
- Importing product catalogs
- Generating links from database records
- Campaign setup with many URLs
- Automated link generation pipelines

## Visitor identification and tracking

Track and identify visitors using their LBID (Linkbreakers ID) from click/scan events:

```rust
use linkbreakers::{
    apis::{configuration::Configuration, visitors_api},
    models::{IdentifyRequest, VisitorInput},
};
use std::collections::HashMap;

async fn identify_visitor(
    config: &Configuration,
    lbid: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    // Prepare visitor data
    let mut visitor_data: HashMap<String, serde_json::Value> = HashMap::new();

    // System fields (prefixed with "$")
    visitor_data.insert("$email".to_string(), "john.doe@example.com".into());
    visitor_data.insert("$phone".to_string(), "+14155551234".into());
    visitor_data.insert("$firstName".to_string(), "John".into());
    visitor_data.insert("$lastName".to_string(), "Doe".into());

    // Custom attributes (no "$" prefix)
    visitor_data.insert("company".to_string(), "Acme Corporation".into());
    visitor_data.insert("jobTitle".to_string(), "Product Manager".into());
    visitor_data.insert("plan".to_string(), "enterprise".into());
    visitor_data.insert("signupDate".to_string(), "2024-01-15".into());
    visitor_data.insert("industry".to_string(), "SaaS".into());
    visitor_data.insert("employees".to_string(), 250.into());

    let visitor_input = VisitorInput {
        data: Some(visitor_data),
    };

    let identify_req = IdentifyRequest {
        lbid: lbid.to_string(),
        visitor: Box::new(visitor_input),
        set_once: Some(false), // true = only update empty fields
    };

    let response = visitors_api::visitors_service_identify(config, identify_req).await?;

    if let Some(visitor) = response.visitor {
        println!("New profile created: {}", response.created.unwrap_or(false));
        println!("Visitor ID: {}", visitor.id.unwrap_or_default());
        println!("Email: {}", visitor.email.unwrap_or_default());
    }

    Ok(())
}
```

**Understanding LBID:**

When visitors click your Linkbreakers links with **conversion tracking enabled**, a `?lbid=...` parameter is added to the destination URL. This LBID connects the visitor to the specific link click, enabling attribution and analytics.

Extract LBID from incoming requests and pass it to the identification API to build visitor profiles. Learn more in our [What is LBID?](/help/article/what-is-the-linkbreakers-id-lbid) guide.

### Working with visitor data

```rust
use linkbreakers::{
    apis::{configuration::Configuration, visitors_api},
    models::{VisitorInput, VisitorsServiceUpdateBody},
};
use std::collections::HashMap;

async fn work_with_visitors(
    config: &Configuration,
    visitor_id: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    // Get visitor details
    let visitor = visitors_api::visitors_service_get(
        config,
        visitor_id,
        Some(vec!["devices".to_string(), "events".to_string(), "links".to_string()]),
    )
    .await?;

    if let Some(v) = visitor {
        println!("Visitor: {}", v.email.unwrap_or_default());
        println!("Devices: {}", v.devices.map(|d| d.len()).unwrap_or(0));
        println!("Events: {}", v.events.map(|e| e.len()).unwrap_or(0));
    }

    // List visitors with filtering
    let visitors_list = visitors_api::visitors_service_list(
        config,
        Some(100), // page_size
        None,      // page_token
        Some("user@example.com".to_string()), // email
        Some("link-uuid".to_string()), // link_id
        Some("Acme Corp".to_string()), // search
        None,      // include
        None,      // response_format
    )
    .await?;

    // Update visitor attributes
    let mut update_data: HashMap<String, serde_json::Value> = HashMap::new();
    update_data.insert("$email".to_string(), "updated@example.com".into());
    update_data.insert("plan".to_string(), "enterprise".into());
    update_data.insert("lastActivity".to_string(), "2025-03-31".into());

    let visitor_update = VisitorInput {
        data: Some(update_data),
    };

    let update_body = VisitorsServiceUpdateBody {
        visitor: Some(Box::new(visitor_update)),
    };

    visitors_api::visitors_service_update(config, visitor_id, update_body).await?;

    Ok(())
}
```

## Accessing analytics and metrics

Retrieve event data and workspace metrics:

```rust
use linkbreakers::apis::{configuration::Configuration, events_api, metrics_api};
use chrono::{Duration, Utc};

async fn get_analytics(
    config: &Configuration,
    link_id: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    // Get events for a specific link (last 30 days)
    let end_date = Utc::now();
    let start_date = end_date - Duration::days(30);

    let events_resp = events_api::events_service_list(
        config,
        Some(start_date), // start_date
        Some(end_date),   // end_date
        Some(1000),       // page_size
        None,             // page_token
        Some(link_id.to_string()), // link_id
        Some(vec!["visitor".to_string(), "device".to_string(), "link".to_string()]), // include
        None,             // response_format
    )
    .await?;

    if let Some(events) = &events_resp.events {
        println!("Total events: {}", events.len());
        for (i, event) in events.iter().enumerate() {
            if i >= 10 {
                break;
            }
            println!("  {} - {}", event.action.clone().unwrap_or_default(), event.created_at.clone().unwrap_or_default());
        }
    }

    // Get workspace metrics
    let metrics = metrics_api::metrics_service_get_workspace_metrics(config).await?;

    println!("\nWorkspace Stats:");
    println!("  Total links: {}", metrics.total_links.unwrap_or(0));
    println!("  Total clicks: {}", metrics.total_clicks.unwrap_or(0));
    println!("  Total visitors: {}", metrics.total_visitors.unwrap_or(0));

    Ok(())
}
```

## Framework integration

### Axum

```rust
use axum::{
    extract::State,
    http::StatusCode,
    routing::{get, post},
    Json, Router,
};
use linkbreakers::{
    apis::{configuration::Configuration, links_api},
    models::CreateLinkRequest,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

#[derive(Clone)]
struct AppState {
    config: Arc<Configuration>,
}

#[derive(Deserialize)]
struct CreateLinkBody {
    destination: String,
    name: Option<String>,
    tags: Option<Vec<String>>,
}

#[derive(Serialize)]
struct LinkResponse {
    success: bool,
    shortlink: String,
    qr_code: Option<String>,
}

async fn create_link(
    State(state): State<AppState>,
    Json(body): Json<CreateLinkBody>,
) -> Result<Json<LinkResponse>, StatusCode> {
    let create_req = CreateLinkRequest {
        destination: body.destination,
        name: body.name,
        tags: body.tags,
        ..Default::default()
    };

    match links_api::links_service_create(&state.config, create_req).await {
        Ok(response) => {
            if let Some(link) = response.link {
                Ok(Json(LinkResponse {
                    success: true,
                    shortlink: link.shortlink.unwrap_or_default(),
                    qr_code: link.qrcode_signed_url,
                }))
            } else {
                Err(StatusCode::INTERNAL_SERVER_ERROR)
            }
        }
        Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("LINKBREAKERS_SECRET_KEY")?;

    let mut config = Configuration::new();
    config.bearer_access_token = Some(api_key);
    config.base_path = "https://api.linkbreakers.com".to_string();

    let state = AppState {
        config: Arc::new(config),
    };

    let app = Router::new()
        .route("/api/create-link", post(create_link))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
    axum::serve(listener, app).await?;

    Ok(())
}
```

### Actix Web

```rust
use actix_web::{web, App, HttpResponse, HttpServer, Result};
use linkbreakers::{
    apis::{configuration::Configuration, links_api},
    models::CreateLinkRequest,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;

struct AppState {
    config: Arc<Configuration>,
}

#[derive(Deserialize)]
struct CreateLinkBody {
    destination: String,
    name: Option<String>,
    tags: Option<Vec<String>>,
}

#[derive(Serialize)]
struct LinkResponse {
    success: bool,
    shortlink: String,
    qr_code: Option<String>,
}

async fn create_link(
    data: web::Data<AppState>,
    body: web::Json<CreateLinkBody>,
) -> Result<HttpResponse> {
    let create_req = CreateLinkRequest {
        destination: body.destination.clone(),
        name: body.name.clone(),
        tags: body.tags.clone(),
        ..Default::default()
    };

    match links_api::links_service_create(&data.config, create_req).await {
        Ok(response) => {
            if let Some(link) = response.link {
                Ok(HttpResponse::Ok().json(LinkResponse {
                    success: true,
                    shortlink: link.shortlink.unwrap_or_default(),
                    qr_code: link.qrcode_signed_url,
                }))
            } else {
                Ok(HttpResponse::InternalServerError().finish())
            }
        }
        Err(_) => Ok(HttpResponse::InternalServerError().finish()),
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let api_key = std::env::var("LINKBREAKERS_SECRET_KEY").unwrap();

    let mut config = Configuration::new();
    config.bearer_access_token = Some(api_key);
    config.base_path = "https://api.linkbreakers.com".to_string();

    let app_state = web::Data::new(AppState {
        config: Arc::new(config),
    });

    HttpServer::new(move || {
        App::new()
            .app_data(app_state.clone())
            .route("/api/create-link", web::post().to(create_link))
    })
    .bind(("0.0.0.0", 8080))?
    .run()
    .await
}
```

## Error handling

Implement proper error handling for production:

```rust
use linkbreakers::{
    apis::{configuration::Configuration, links_api},
    models::CreateLinkRequest,
};

async fn create_link_with_error_handling(
    config: &Configuration,
) -> Result<(), Box<dyn std::error::Error>> {
    let create_req = CreateLinkRequest {
        destination: "https://example.com/page".to_string(),
        name: Some("My Link".to_string()),
        ..Default::default()
    };

    match links_api::links_service_create(config, create_req).await {
        Ok(response) => {
            if let Some(link) = response.link {
                println!("Success: {}", link.shortlink.unwrap_or_default());
            }
        }
        Err(e) => {
            eprintln!("API Error: {:?}", e);
            return Err(Box::new(e));
        }
    }

    Ok(())
}
```

To let an AI agent drive the Rust SDK, or to use the MCP server instead, see [How Linkbreakers works with AI agents](/help/article/linkbreakers-for-ai-agents).

## Frequently asked questions

**Can I use the Rust SDK with popular web frameworks?**

Yes! The SDK is framework-agnostic and works seamlessly with Axum, Actix Web, Rocket, Warp, and any Rust web framework.

**How do I handle rate limits?**

The SDK will return errors if you exceed rate limits. Implement exponential backoff retry logic for production applications.

**Does the SDK support async/await?**

Yes, the SDK is fully async and built on tokio. All API calls are async functions that return futures.

**How do I get the LBID for visitor identification?**

The LBID appears as a `?lbid=` query parameter when visitors click your links with conversion tracking enabled. Extract it from request parameters and pass it to `visitors_service_identify()`.

**Can I create bulk links efficiently?**

Yes, use `links_service_create_bulk()` to create up to 1000 links in a single API call, which is much more efficient than individual requests.

**What Rust versions are supported?**

The SDK supports Rust 1.70+ with full async/await support and modern Rust features.

**How do I test without affecting production data?**

Create a [sandbox](/help/article/sandbox-test-environment) from **Dashboard → API Tokens → Sandboxes**, or with `POST /v1/sandboxes`. A sandbox is a separate, empty workspace whose token starts with `lb_test_`, so it can never touch your live links or analytics, its usage is never billed, and it is deleted 30 days after creation.

## Sources

- [Linkbreakers Rust SDK GitHub Repository](https://github.com/linkbreakers-com/linkbreakers-rust)
- [What is LBID?](https://linkbreakers.com/help/article/what-is-the-linkbreakers-id-lbid)
- [Linkbreakers API Documentation](https://linkbreakers.com/help/article/how-to-use-the-linkbreakers-api)

## Last reviewed

This article was last reviewed on March 31, 2025.
