Software Development Kit (SDK)

development

Software Development Kit (SDK)

Definition

A Software Development Kit (SDK) is a curated collection of tools, libraries, code samples, documentation, and development utilities packaged together to enable developers to build applications that integrate with a specific platform, service, or hardware. An SDK goes beyond a simple API client by providing higher-level abstractions, type-safe interfaces, error handling patterns, authentication flows, and idiomatic code structures that align with the target programming language's conventions. The goal of an SDK is to minimize the cognitive overhead required for integration: instead of manually constructing HTTP requests, parsing responses, and handling edge cases, developers work with strongly typed functions, well-documented modules, and battle-tested patterns that encapsulate the complexity of the underlying platform.

An SDK typically includes: client libraries for API communication, type definitions and data models, authentication and authorization helpers, configuration management, error handling and retry logic, logging and telemetry integration, code examples and quickstart guides, and comprehensive reference documentation. The best SDKs feel native to their target language -- an Elixir SDK uses GenServers and supervision trees, a TypeScript SDK uses Promises and type inference, a Rust SDK uses ownership and lifetimes. This idiomatic design is what separates a well-crafted SDK from a thin HTTP wrapper.

Overview

The evolution of SDKs reflects the broader maturation of the software industry. Early SDKs were primarily associated with operating systems (Windows SDK, macOS SDK) and hardware platforms (Android SDK, iOS SDK). The cloud computing era shifted SDKs toward service integration: every major cloud provider (AWS, GCP, Azure) ships SDKs for dozens of languages. The modern platform economy has made SDKs a competitive differentiator -- developer experience directly impacts platform adoption, and the quality of an SDK often determines whether developers choose one platform over another.

SDKs serve multiple audiences with different needs:

AudienceSDK RequirementsPriority
Application developersSimple API, good defaults, quickstart guidesGetting started fast
Integration engineersDetailed configuration, custom transports, middleware hooksFlexibility and control
Platform operatorsMonitoring, telemetry, health checks, circuit breakersProduction reliability
Security engineersAuthentication flows, secret management, audit loggingSecurity posture
Quality engineersTest utilities, mocking support, deterministic modesTestability

Within the Prismatic Platform, the SDK strategy follows a multi-package architecture: a core SDK for fundamental platform interaction, a Plugin Kit for extending platform capabilities, a Security SDK for specialized security operations, and a UI SDK for building frontend interfaces. Each package is independently versioned, minimally coupled, and designed for the specific concerns of its target audience.

The relationship between SDKs and APIs is complementary but distinct. An API defines the contract -- the endpoints, request formats, response schemas, and error codes. An SDK implements that contract in a specific language, adding type safety, retry logic, connection pooling, and developer ergonomics. You can use an API without an SDK (by making raw HTTP calls), but you cannot use an SDK without an underlying API. The SDK's value proposition is reducing the gap between "the API exists" and "I can productively use it."

Technical Deep Dive

SDK Architecture Patterns

A well-designed SDK follows layered architecture principles, separating concerns into distinct layers:

+--------------------------------------------------+
|  Public API Layer (Developer-Facing)              |
|  - Type-safe functions, documentation             |
|  - Idiomatic language patterns                    |
+--------------------------------------------------+
|  Business Logic Layer                             |
|  - Request building, response parsing             |
|  - Pagination, filtering, sorting                 |
+--------------------------------------------------+
|  Transport Layer                                  |
|  - HTTP client, WebSocket, gRPC                   |
|  - Connection pooling, keep-alive                 |
+--------------------------------------------------+
|  Resilience Layer                                 |
|  - Retry logic, circuit breakers                  |
|  - Rate limiting, backoff strategies              |
+--------------------------------------------------+
|  Authentication Layer                             |
|  - Token management, refresh flows                |
|  - API key rotation, OAuth2                       |
+--------------------------------------------------+
|  Telemetry Layer                                  |
|  - Request/response logging                       |
|  - Performance metrics, error tracking            |
+--------------------------------------------------+

Elixir SDK Implementation

The Prismatic SDK for Elixir demonstrates how an SDK should be idiomatic to its target language -- using OTP patterns, supervision trees, and the functional programming style that Elixir developers expect:

defmodule PrismaticSDK do
  @moduledoc """
  Official Elixir SDK for the Prismatic Platform.

  Provides type-safe, OTP-native access to all Prismatic Platform
  services including OSINT, security ratings, compliance assessment,
  and asset discovery.

  ## Quick Start

      # In your mix.exs
      {:prismatic_sdk, "~> 1.0"}

      # In your application
      client = PrismaticSDK.client(api_key: System.get_env("PRISMATIC_API_KEY"))
      {:ok, rating} = PrismaticSDK.Security.get_rating(client, "example.com")

  ## Configuration

      config :prismatic_sdk,
        api_key: {:system, "PRISMATIC_API_KEY"},
        base_url: "https://api.prismatic.io/v1",
        timeout: 30_000,
        retry_max: 3,
        telemetry_prefix: [:prismatic, :sdk]
  """

  @type client :: %__MODULE__.Client{
    api_key: String.t(),
    base_url: String.t(),
    http_client: module(),
    timeout: pos_integer(),
    retry_config: retry_config()
  }

  @type retry_config :: %{
    max_attempts: pos_integer(),
    base_delay_ms: pos_integer(),
    max_delay_ms: pos_integer(),
    jitter: boolean()
  }

  @type api_response(data) ::
    {:ok, data}
    | {:error, PrismaticSDK.Error.t()}

  @doc """
  Creates a new SDK client with the given configuration.
  The client is a lightweight struct -- no processes are started.
  For connection pooling, use `PrismaticSDK.start_pool/1`.
  """
  @spec client(keyword()) :: client()
  def client(opts \\ []) do
    %PrismaticSDK.Client{
      api_key: Keyword.get(opts, :api_key, fetch_config(:api_key)),
      base_url: Keyword.get(opts, :base_url, fetch_config(:base_url)),
      http_client: Keyword.get(opts, :http_client, PrismaticSDK.HTTP.Finch),
      timeout: Keyword.get(opts, :timeout, 30_000),
      retry_config: build_retry_config(opts)
    }
  end

  defp fetch_config(key) do
    case Application.get_env(:prismatic_sdk, key) do
      {:system, env_var} -> System.get_env(env_var)
      value -> value
    end
  end

  defp build_retry_config(opts) do
    %{
      max_attempts: Keyword.get(opts, :retry_max, 3),
      base_delay_ms: Keyword.get(opts, :retry_base_delay, 1_000),
      max_delay_ms: Keyword.get(opts, :retry_max_delay, 30_000),
      jitter: Keyword.get(opts, :retry_jitter, true)
    }
  end
end

defmodule PrismaticSDK.Client do
  @moduledoc false
  defstruct [:api_key, :base_url, :http_client, :timeout, :retry_config]
end

Domain-Specific SDK Modules

Each SDK domain module provides focused, type-safe access to a specific platform capability:

defmodule PrismaticSDK.Security do
  @moduledoc """
  Security assessment and rating operations.

  Provides access to the Prismatic Perimeter security rating engine,
  including domain scanning, vulnerability assessment, and compliance
  checking.
  """

  alias PrismaticSDK.{Client, Request, Response, Error}

  @type rating :: %{
    domain: String.t(),
    grade: :A | :B | :C | :D | :F,
    score: 300..900,
    industry_percentile: 0..100,
    assessed_at: DateTime.t(),
    factors: [rating_factor()]
  }

  @type rating_factor :: %{
    category: String.t(),
    score: float(),
    weight: float(),
    findings: [String.t()]
  }

  @doc """
  Retrieves the security rating for a domain.

  ## Examples

      {:ok, rating} = PrismaticSDK.Security.get_rating(client, "example.com")
      rating.grade  #=> :B
      rating.score  #=> 780

  ## Options

    * `:include_factors` - Include detailed rating factors (default: false)
    * `:cached` - Accept cached results up to N seconds old (default: 0)
  """
  @spec get_rating(Client.t(), String.t(), keyword()) ::
    {:ok, rating()} | {:error, Error.t()}
  def get_rating(%Client{} = client, domain, opts \\ []) do
    Request.new(:get, "/security/ratings/#{URI.encode(domain)}")
    |> Request.with_params(Keyword.take(opts, [:include_factors, :cached]))
    |> Request.execute(client)
    |> Response.decode(&decode_rating/1)
  end

  @doc """
  Initiates a full security scan for a domain.
  Returns a scan ID that can be polled for results.
  """
  @spec start_scan(Client.t(), String.t(), keyword()) ::
    {:ok, %{scan_id: String.t(), estimated_duration: pos_integer()}}
    | {:error, Error.t()}
  def start_scan(%Client{} = client, domain, opts \\ []) do
    body = %{
      domain: domain,
      scan_depth: Keyword.get(opts, :depth, :standard),
      include_subdomains: Keyword.get(opts, :subdomains, true)
    }

    Request.new(:post, "/security/scans")
    |> Request.with_body(body)
    |> Request.execute(client)
    |> Response.decode(&decode_scan_response/1)
  end

  defp decode_rating(data) do
    %{
      domain: data["domain"],
      grade: String.to_existing_atom(data["grade"]),
      score: data["score"],
      industry_percentile: data["industry_percentile"],
      assessed_at: DateTime.from_iso8601!(data["assessed_at"]),
      factors: Enum.map(data["factors"] || [], &decode_factor/1)
    }
  end

  defp decode_factor(data) do
    %{
      category: data["category"],
      score: data["score"],
      weight: data["weight"],
      findings: data["findings"] || []
    }
  end

  defp decode_scan_response(data) do
    %{
      scan_id: data["scan_id"],
      estimated_duration: data["estimated_duration_seconds"]
    }
  end
end

SDK Resilience Patterns

Production SDKs must handle network failures, rate limits, and service degradation gracefully. The resilience layer implements exponential backoff with jitter, circuit breakers, and structured error reporting:

defmodule PrismaticSDK.Resilience do
  @moduledoc """
  Resilience patterns for the Prismatic SDK.
  Implements retry with exponential backoff and jitter,
  circuit breaker pattern, and rate limit awareness.
  """

  alias PrismaticSDK.{Client, Error}

  @retryable_status_codes [429, 500, 502, 503, 504]

  @doc """
  Executes a request with automatic retry on transient failures.
  Uses exponential backoff with full jitter to prevent thundering herd.
  """
  @spec with_retry(Client.t(), (-> {:ok, any()} | {:error, Error.t()})) ::
    {:ok, any()} | {:error, Error.t()}
  def with_retry(%Client{retry_config: config}, request_fn) do
    do_retry(request_fn, config, 1)
  end

  defp do_retry(request_fn, %{max_attempts: max}, attempt) when attempt > max do
    case request_fn.() do
      {:ok, _} = success -> success
      {:error, _} = error -> error
    end
  end

  defp do_retry(request_fn, config, attempt) do
    case request_fn.() do
      {:ok, _} = success ->
        success

      {:error, %Error{status_code: code}} when code in @retryable_status_codes ->
        delay = calculate_delay(config, attempt)
        Process.sleep(delay)
        do_retry(request_fn, config, attempt + 1)

      {:error, %Error{reason: :timeout}} ->
        delay = calculate_delay(config, attempt)
        Process.sleep(delay)
        do_retry(request_fn, config, attempt + 1)

      {:error, _} = error ->
        error
    end
  end

  defp calculate_delay(config, attempt) do
    base = config.base_delay_ms * round(:math.pow(2, attempt - 1))
    capped = min(base, config.max_delay_ms)

    if config.jitter do
      :rand.uniform(capped)
    else
      capped
    end
  end
end

SDK Telemetry Integration

Modern SDKs must be observable. The Prismatic SDK integrates with Telemetry to provide standardized metrics for every API call:

defmodule PrismaticSDK.Telemetry do
  @moduledoc """
  Telemetry integration for the Prismatic SDK.
  Emits standardized events for every API request,
  enabling monitoring, alerting, and performance analysis.
  """

  @doc """
  Wraps an API call with telemetry events.
  Emits start, stop, and exception events following
  the :telemetry.span/3 convention.
  """
  @spec span(atom(), map(), (-> {:ok, any()} | {:error, any()})) ::
    {:ok, any()} | {:error, any()}
  def span(operation, metadata, fun) do
    start_time = System.monotonic_time()

    :telemetry.execute(
      [:prismatic, :sdk, operation, :start],
      %{system_time: System.system_time()},
      metadata
    )

    try do
      result = fun.()
      duration = System.monotonic_time() - start_time

      :telemetry.execute(
        [:prismatic, :sdk, operation, :stop],
        %{duration: duration},
        Map.put(metadata, :result, result_type(result))
      )

      result
    rescue
      exception ->
        duration = System.monotonic_time() - start_time

        :telemetry.execute(
          [:prismatic, :sdk, operation, :exception],
          %{duration: duration},
          Map.merge(metadata, %{
            kind: :error,
            reason: exception,
            stacktrace: __STACKTRACE__
          })
        )

        reraise exception, __STACKTRACE__
    end
  end

  defp result_type({:ok, _}), do: :ok
  defp result_type({:error, _}), do: :error
  defp result_type(_), do: :unknown
end

Platform Integration

Multi-Package Architecture

The Prismatic Platform SDK follows a multi-package strategy where each package serves a distinct purpose:

PackagePurposeAudienceDependencies
prismatic_sdkCore platform client, authentication, base typesAll developersMinimal (Finch, Jason)
prismatic_plugin_kitPlugin development framework, hooks, lifecycleExtension authorsprismatic_sdk
prismatic_securitySecurity scanning, rating, compliance APIsSecurity engineersprismatic_sdk
prismatic_uiUI components, LiveView integration, themesFrontend developersprismatic_sdk, Phoenix

This separation follows the adapter pattern principle: each package adapts the underlying platform API to the specific concerns of its target audience. A security engineer installing prismatic_security should not need to understand the plugin lifecycle, and a plugin author should not need to pull in UI dependencies.

Versioning and Compatibility

SDK versioning follows semantic versioning (SemVer) with additional platform compatibility guarantees:

Version ComponentMeaningExample
Major (X.0.0)Breaking API changesNew authentication flow
Minor (0.X.0)New features, backward compatibleNew endpoint support
Patch (0.0.X)Bug fixes, performance improvementsRetry logic fix

The SDK maintains backward compatibility with at least two major versions of the platform API. Deprecated functions emit compile-time warnings for one major version before removal.

SDK and the API Gateway

The SDK communicates with the platform through the API Gateway, which provides:

  • Request routing: SDK requests are routed to the appropriate backend service
  • Rate limiting: Per-client rate limits enforced at the gateway level
  • Authentication: API keys and OAuth2 tokens validated before reaching services
  • Response caching: Frequently requested data cached at the gateway layer
  • OpenAPI validation: Request and response schemas validated against the OpenAPI specification

Industry Context

SDK Design Best Practices

The industry has converged on several best practices for SDK design:

PracticeDescriptionPrismatic Implementation
Idiomatic codeFollow target language conventionsOTP patterns, pipe operators, pattern matching
Minimal dependenciesReduce dependency treeOnly Finch + Jason for core SDK
Comprehensive typesFull type coverage@type and @spec on all public functions
Error transparencyStructured, actionable errorsPrismaticSDK.Error with code, message, metadata
TestabilityEasy to mock and testBehaviour-based HTTP client, test helpers
DocumentationInline docs with examples@moduledoc and @doc on everything
TelemetryObservable by default:telemetry events for all operations

Comparison with Major Platform SDKs

FeaturePrismatic SDKAWS SDK (Elixir)Stripe (Elixir)Google Cloud (Elixir)
Type safetyFull @spec coveragePartialPartialMinimal
OTP integrationGenServer, SupervisorBasicBasicBasic
Retry logicExponential + jitterBuilt-inBuilt-inBuilt-in
TelemetryNative :telemetryCustomCustomOpenTelemetry
Error typesStructured, pattern-matchableMapsMapsMaps
PaginationStream-based, lazyManualAuto-paginationManual

Anti-Patterns and Pitfalls

Common SDK Design Mistakes

Anti-PatternProblemSolution
God SDKSingle massive package with all featuresMulti-package architecture with focused concerns
Leaky abstractionsHTTP details exposed to consumersClean domain types, transport-agnostic interfaces
Silent failuresErrors swallowed or logged without notificationStructured error types, explicit {:error, _} returns
Blocking callsSynchronous HTTP blocking the callerAsync-first with explicit sync wrappers
Global stateModule attributes storing runtime configClient struct passed explicitly to all functions
Version couplingSDK breaks when API adds a fieldTolerant deserialization, ignore unknown fields
Missing telemetryNo visibility into SDK behavior:telemetry events for all operations

Evolution and Future Directions

SDK development is evolving in several directions:

  • Code generation from OpenAPI: Automatically generating type-safe SDK code from API specifications, reducing manual maintenance burden
  • Streaming and real-time: SDKs increasingly support WebSocket, Server-Sent Events, and gRPC streaming alongside traditional request-response
  • Local-first SDKs: Offline-capable SDKs that sync with the platform when connectivity returns
  • AI-assisted documentation: Using LLMs to generate contextual examples based on the developer's specific integration scenario
  • Multi-runtime support: Single SDK source targeting multiple runtimes (BEAM, Node.js, WASM) through compilation

The Prismatic Platform's SDK strategy -- with its multi-package architecture, OTP-native patterns, and comprehensive telemetry integration -- positions the platform for adoption by the Elixir developer community while maintaining the quality standards enforced by the platform's quality gates.

SDKs connect to numerous platform concepts:

  • API -- The underlying contract that the SDK implements
  • API Gateway -- The infrastructure layer that routes SDK requests
  • API Integration -- The practice of connecting systems through APIs and SDKs
  • Adapter Pattern -- The design pattern underlying SDK module architecture
  • Composability -- The principle enabling SDK modules to be combined
  • Telemetry -- Observability infrastructure integrated into every SDK call
  • Open Source -- Distribution model for the Prismatic SDK packages
  • Protocol -- Elixir protocols used for polymorphic SDK behavior

Summary

A Software Development Kit transforms a platform API from a raw interface into a productive development experience. The best SDKs are invisible -- developers focus on their application logic while the SDK handles authentication, retry, serialization, telemetry, and error handling behind the scenes. The Prismatic Platform SDK follows OTP-native patterns with supervision trees, GenServers, and telemetry integration, packaged in a multi-package architecture that serves different audiences (application developers, plugin authors, security engineers, frontend developers) without coupling their concerns. By providing type-safe, well-documented, and thoroughly tested client libraries, the SDK lowers the barrier to platform adoption while maintaining the quality and reliability standards that the Prismatic Platform enforces across its entire ecosystem.


Built with precision. Packaged for developers.

Connect & Contribute

Created by Tomas Korcak (korczis) | GitHub | GitLab | LinkedIn | Contact