Quick Start

Get the VECU Custody .NET SDK running in your application in minutes.

What You Will Build

By the end of this guide, you'll have a working integration that can create custody authorizations, wait for credential issuance, and check vehicle releasability.

Prerequisites

Before you begin, ensure you have: - .NET 8.0 or .NET 10 SDK installed - Access to Cox Automotive Artifactory - A token provider for authentication (your auth system that can supply bearer tokens)

Step 1: Configure NuGet Source

The SDK is hosted in Cox Automotive's Artifactory. Add the NuGet source to your project.

Create or update nuget.config in your solution root:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="cai-artifactory" value="https://artifactory.coxautoinc.com/artifactory/api/nuget/cai-nuget" />
  </packageSources>
</configuration>

For authenticated access, use environment variables:

export NUGET_ARTIFACTORY_USER="your-username"
export NUGET_ARTIFACTORY_TOKEN="your-token"

Step 2: Install the SDK

dotnet add package CoxAuto.Vecu.CustodySdk

Step 3: Register Custody Client

Register the client using dependency injection. The SDK integrates natively with ASP.NET Core and hosted services.

using CoxAuto.Vecu.CustodySdk.DependencyInjection;

// Register client with token provider
builder.Services.AddCustodyClient(options =>
{
    options.Environment = CustodyEnvironment.Sandbox;
    options.TokenProvider = async (cancellationToken) =>
    {
        var token = await myAuthClient.GetTokenAsync(cancellationToken);
        return token.AccessToken;
    };
});

Or configure via appsettings.json with a code-based token provider:

{
  "CustodyClient": {
    "Environment": "Sandbox",
    "TimeoutSeconds": 30
  }
}
// Register from configuration + token provider callback
builder.Services.AddCustodyClient(
    builder.Configuration,
    tokenProvider: async (ct) =>
    {
        var token = await myAuthClient.GetTokenAsync(ct);
        return token.AccessToken;
    });

Security

Never hardcode credentials in source code. Store any credentials used by your TokenProvider callback (client IDs, secrets, certificates) in Azure Key Vault, AWS Secrets Manager, or environment variables.

Step 4: Create an Authorization

Create an authorization that permits custody transfer for a vehicle:

using CoxAuto.Vecu.CustodySdk.Client;
using CoxAuto.Vecu.CustodySdk.Models.Requests;
using CoxAuto.Vecu.CustodySdk.Models.Enums;

// Inject client via DI
public class CustodyService
{
    private readonly ICustodyServiceClient _client;

    public CustodyService(ICustodyServiceClient client)
    {
        _client = client;
    }

    public async Task CreateAuthorizationAsync()
    {
        // Create authorization
        var authorization = await _client.CreateAuthorizationAsync(new CreateAuthorizationRequest
        {
            Vin = "9HGBH41JXMN999999",
            Origin = "1 Auction Blvd, Bordentown, NJ 08505",
            Destination = "200 Motor Ave, Columbus, OH 43230",
            PersonIdentityKey = "DL-293-847-561",
            MakeModel = "Honda Accord",
            AuthorizedBy = "system-integration",
            Role = AuthorizationRole.DRIVER,
            ValidUntil = DateTimeOffset.UtcNow.AddHours(24)
        });

        Console.WriteLine($"Authorization ID: {authorization.AuthorizationId}");
        Console.WriteLine($"Status: {authorization.Status}");
    }
}

Step 5: Check Vehicle Releasability

Verify if a vehicle can be released before creating an authorization:

using CoxAuto.Vecu.CustodySdk.Models.Requests;

// Check releasability
var result = await _client.GetReleasabilityAsync(
    "9HGBH41JXMN999999",
    "1 Auction Blvd, Bordentown, NJ 08505");

if (result.IsReleasable)
{
    Console.WriteLine("Vehicle is releasable");
    Console.WriteLine($"Releasability ID: {result.ReleasabilityId}");
}
else
{
    Console.WriteLine("Vehicle cannot be released");
    foreach (var blocker in result.Blockers)
    {
        Console.WriteLine($"  - {blocker}");
    }
}

Complete Example

using CoxAuto.Vecu.CustodySdk.Client;
using CoxAuto.Vecu.CustodySdk.Configuration;
using CoxAuto.Vecu.CustodySdk.DependencyInjection;
using CoxAuto.Vecu.CustodySdk.Exceptions;
using CoxAuto.Vecu.CustodySdk.Models.Enums;
using CoxAuto.Vecu.CustodySdk.Models.Requests;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

// Build host with dependency injection
var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services.AddCustodyClient(options =>
        {
            options.Environment = CustodyEnvironment.Sandbox;
            options.TokenProvider = async (ct) =>
            {
                var token = await myAuthClient.GetTokenAsync(ct);
                return token.AccessToken;
            };
        });
    })
    .Build();

var client = host.Services.GetRequiredService<ICustodyServiceClient>();
var logger = host.Services.GetRequiredService<ILogger<Program>>();

try
{
    var vin = "9HGBH41JXMN999999";

    // Step 1: Check releasability
    logger.LogInformation("Checking vehicle releasability...");
    var releasability = await client.GetReleasabilityAsync(
        vin,
        "1 Auction Blvd, Bordentown, NJ 08505");

    if (!releasability.IsReleasable)
    {
        logger.LogWarning("Vehicle is not releasable");
        foreach (var blocker in releasability.Blockers)
        {
            logger.LogWarning($"  - {blocker}");
        }
        return;
    }

    logger.LogInformation("✓ Vehicle is releasable");

    // Step 2: Create authorization
    logger.LogInformation("Creating authorization...");
    var authorization = await client.CreateAuthorizationAsync(new()
    {
        Vin = vin,
        Origin = "1 Auction Blvd, Bordentown, NJ 08505",
        Destination = "200 Motor Ave, Columbus, OH 43230",
        PersonIdentityKey = "DL-293-847-561",
        MakeModel = "Honda Accord",
        AuthorizedBy = "console-app",
        Role = AuthorizationRole.DRIVER,
        ValidUntil = DateTimeOffset.UtcNow.AddHours(24)
    });

    logger.LogInformation($"✓ Authorization created: {authorization.AuthorizationId}");
    logger.LogInformation($"  Status: {authorization.Status}");

    // Step 3: Wait for activation
    logger.LogInformation("Waiting for authorization to become active...");
    var activeAuth = await client.WaitForAuthorizationAsync(
        authorization.AuthorizationId,
        new WaitOptions
        {
            PollingIntervalSeconds = 5,
            TimeoutSeconds = 300
        });

    if (activeAuth.Status == AuthorizationStatus.RELEASED)
    {
        logger.LogInformation("✓ Authorization is active");
    }

    logger.LogInformation("Workflow completed successfully!");
}
catch (VehicleNotReleasableException ex)
{
    logger.LogError($"Vehicle not releasable: {ex.Vin}");
    foreach (var blocker in ex.Blockers)
    {
        logger.LogError($"  - {blocker}");
    }
}
catch (ValidationException ex)
{
    logger.LogError($"Validation error: {ex.Message}");
    foreach (var (field, errors) in ex.ValidationErrors)
    {
        logger.LogError($"  {field}: {string.Join(", ", errors)}");
    }
}
catch (AuthException ex)
{
    logger.LogError($"Authentication failed: {ex.Message}");
}
catch (CustodyException ex)
{
    logger.LogError($"Custody error: {ex.Message} (Code: {ex.ErrorCode})");
}
finally
{
    await host.StopAsync();
    host.Dispose();
}

Error Handling

The SDK provides strongly typed exceptions for precise error handling:

using CoxAuto.Vecu.CustodySdk.Exceptions;

try
{
    var authorization = await client.CreateAuthorizationAsync(request);
}
catch (VehicleNotReleasableException ex)
{
    // Vehicle has blockers preventing release
    Console.WriteLine($"Blockers: {string.Join(", ", ex.Blockers)}");
}
catch (DuplicateAuthorizationException ex)
{
    // Authorization already exists for this VIN/OD pair
    Console.WriteLine($"Existing ID: {ex.ExistingAuthorizationId}");
}
catch (AuthorizationNotFoundException ex)
{
    // Authorization not found
    Console.WriteLine($"Not found: {ex.AuthorizationId}");
}
catch (ValidationException ex)
{
    // Request validation failed
    foreach (var (field, errors) in ex.ValidationErrors)
    {
        Console.WriteLine($"{field}: {string.Join(", ", errors)}");
    }
}
catch (AuthException ex)
{
    // Authentication failed
    Console.WriteLine($"Auth error: {ex.Message}");
}
catch (RateLimitException ex)
{
    // Rate limit exceeded
    Console.WriteLine($"Retry after: {ex.RetryAfter?.TotalSeconds} seconds");
}
catch (NetworkException ex)
{
    // Network or HTTP error (after retries)
    Console.WriteLine($"Network error: {ex.StatusCode}");
}
catch (CustodyException ex)
{
    // Base exception for all SDK errors
    Console.WriteLine($"Error: {ex.Message} (Code: {ex.ErrorCode})");
}

What's Next?

Now that you have a basic integration working:

  1. Installation - Detailed installation options
  2. Configuration - Full configuration reference
  3. ASP.NET Core Integration - Controllers, background services, health checks
  4. Error Handling - Handle errors gracefully
  5. Testing - Unit testing with MockCustodyServiceClient
  6. API Reference - Complete API documentation