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.

Developer
3 min read
By Laurent Schaffner
Updated March 31, 2025

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

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("leadScore".to_string(), 85.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? 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(())
}

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 test mode API keys from your dashboard. They operate on isolated test data separate from production analytics. You can identify test vs production keys by decoding the JWT and checking the environment claim.

Sources

Last reviewed

This article was last reviewed on March 31, 2025.

About the Author

LS

Laurent Schaffner

Founder & Engineer at Linkbreakers

Passionate about building tools that help businesses track and optimize their digital marketing efforts. Laurent founded Linkbreakers to make QR code analytics accessible and actionable for companies of all sizes.