Documentation
¶
Overview ¶
Package api provides the primary client interface for interacting with SPIKE services.
The API type serves as the main entry point for all SPIKE operations, supporting:
- Secret management (create, read, update, delete, list, and version control)
- Policy management (create, read, update, delete, and list access control policies)
- Cryptographic operations (encrypt and decrypt via streaming or JSON modes)
- Operator functions (recover and restore using Shamir secret sharing)
All operations use mutual TLS authentication with SPIFFE X.509 certificates and communicate exclusively with SPIKE Nexus servers by default.
Example usage:
// Create a new API client
api := api.New()
if api == nil {
log.Fatal("Failed to initialize SPIKE API")
}
defer api.Close()
// Store a secret
err := api.PutSecret("app/db/password", map[string]string{
"username": "admin",
"password": "secret123",
})
// Retrieve a secret
secret, err := api.GetSecret("app/db/password")
// Create an access policy
err = api.CreatePolicy(
"db-access",
"spiffe://example.org/app/*",
"app/db/*",
[]data.PolicyPermission{data.PermissionRead},
)
Index ¶
- type API
- func (a *API) CipherDecrypt(version byte, nonce, ciphertext []byte, algorithm string) ([]byte, *sdkErrors.SDKError)
- func (a *API) CipherDecryptStream(reader io.Reader) ([]byte, *sdkErrors.SDKError)
- func (a *API) CipherEncrypt(plaintext []byte, algorithm string) ([]byte, *sdkErrors.SDKError)
- func (a *API) CipherEncryptStream(reader io.Reader) ([]byte, *sdkErrors.SDKError)
- func (a *API) Close() *sdkErrors.SDKError
- func (a *API) Contribute(keeperShare secretsharing.Share, keeperID string) *sdkErrors.SDKError
- func (a *API) CreatePolicy(name string, SPIFFEIDPattern string, pathPattern string, ...) *sdkErrors.SDKError
- func (a *API) DeletePolicy(id string) *sdkErrors.SDKError
- func (a *API) DeleteSecret(path string) *sdkErrors.SDKError
- func (a *API) DeleteSecretVersions(path string, versions []int) *sdkErrors.SDKError
- func (a *API) GetPolicy(id string) (*data.Policy, *sdkErrors.SDKError)
- func (a *API) GetSecret(path string) (*data.Secret, *sdkErrors.SDKError)
- func (a *API) GetSecretMetadata(path string, version int) (*data.SecretMetadata, *sdkErrors.SDKError)
- func (a *API) GetSecretVersion(path string, version int) (*data.Secret, *sdkErrors.SDKError)
- func (a *API) ListPolicies(SPIFFEIDPattern, pathPattern string) (*[]data.Policy, *sdkErrors.SDKError)
- func (a *API) ListSecretKeys() (*[]string, *sdkErrors.SDKError)
- func (a *API) PutSecret(path string, data map[string]string) *sdkErrors.SDKError
- func (a *API) Recover() (map[int]*[32]byte, *sdkErrors.SDKError)
- func (a *API) Restore(index int, shard *[32]byte) (*data.RestorationStatus, *sdkErrors.SDKError)
- func (a *API) UndeleteSecret(path string, versions []int) *sdkErrors.SDKError
- func (a *API) Verify(randomText string, nonce, cipherText []byte) *sdkErrors.SDKError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type API ¶ added in v0.6.0
type API struct {
// contains filtered or unexported fields
}
API is the SPIKE API.
func New ¶ added in v0.1.13
New creates and returns a new instance of API configured with a SPIFFE source.
It automatically discovers and connects to the SPIFFE Workload API endpoint using the default socket path and creates an X.509 source for authentication with a configurable timeout to prevent indefinite blocking on socket issues.
The timeout can be configured using the SPIKE_SPIFFE_SOURCE_TIMEOUT environment variable (default: 30s).
The API client is configured to communicate exclusively with SPIKE Nexus.
Returns:
- *API: A configured API instance ready for use, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFEFailedToCreateX509Source: if X509Source creation fails
- ErrSPIFFEUnableToFetchX509Source: if initial SVID fetch fails
Example:
api, err := New()
if err != nil {
log.Fatalf("Failed to initialize SPIKE API: %v", err)
}
defer api.Close()
func NewWithSource ¶ added in v0.1.13
func NewWithSource(source *workloadapi.X509Source) *API
NewWithSource initializes a new API instance with a pre-configured X509Source. This constructor is useful when you already have an X.509 source or need custom source configuration. The API instance will be configured to only communicate with SPIKE Nexus servers.
Parameters:
- source: A pre-configured X509Source that provides the client's identity certificates and trusted roots for server validation
Returns:
- *API: A configured API instance using the provided source
Note: The API client created with this function is restricted to communicate only with SPIKE Nexus instances (using predicate.AllowNexus). If you need to connect to different servers, use New() with a custom predicate instead.
Example usage:
// Use with custom-configured source
source, err := workloadapi.NewX509Source(ctx,
workloadapi.WithClientOptions(...))
if err != nil {
log.Fatal("Failed to create X509Source")
}
api := NewWithSource(source)
defer api.Close()
func (*API) CipherDecrypt ¶ added in v0.15.0
func (a *API) CipherDecrypt( version byte, nonce, ciphertext []byte, algorithm string, ) ([]byte, *sdkErrors.SDKError)
CipherDecrypt decrypts data with structured parameters.
It sends version, nonce, ciphertext, and algorithm to SPIKE Nexus and returns the decrypted plaintext.
Parameters:
- version: The cipher version used during encryption
- nonce: The nonce bytes used during encryption
- ciphertext: The encrypted data to decrypt
- algorithm: The encryption algorithm used (e.g., "AES-GCM")
Returns:
- []byte: The decrypted plaintext if successful, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from httpPost(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
plaintext, err := api.CipherDecrypt(1, nonce, ciphertext, "AES-GCM")
func (*API) CipherDecryptStream ¶ added in v0.7.3
CipherDecryptStream decrypts data from a reader using streaming mode.
It sends the reader content as the request body to SPIKE Nexus for decryption. The data is treated as binary (application/octet-stream) as decryption operates on raw encrypted bytes.
Parameters:
- reader: The encrypted data source to decrypt
Returns:
- []byte: The decrypted plaintext if successful, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- Errors from streamPost(): if the streaming request fails
- ErrNetReadingResponseBody: if reading the response fails
Example:
reader := bytes.NewReader(encryptedData) plaintext, err := api.CipherDecryptStream(reader)
func (*API) CipherEncrypt ¶ added in v0.15.0
CipherEncrypt encrypts data with structured parameters.
It sends plaintext and algorithm to SPIKE Nexus and returns the encrypted ciphertext bytes.
Parameters:
- plaintext: The data to encrypt
- algorithm: The encryption algorithm to use (e.g., "AES-GCM")
Returns:
- []byte: The encrypted ciphertext if successful, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from httpPost(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
data := []byte("secret message")
encrypted, err := api.CipherEncrypt(data, "AES-GCM")
func (*API) CipherEncryptStream ¶ added in v0.7.3
CipherEncryptStream encrypts data from a reader using streaming mode.
It sends the reader content as the request body to SPIKE Nexus for encryption. The data is treated as binary (application/octet-stream) regardless of its original format, as encryption operates on raw bytes.
Parameters:
- reader: The data source to encrypt
Returns:
- []byte: The encrypted ciphertext if successful, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- Errors from streamPost(): if the streaming request fails
- ErrNetReadingResponseBody: if reading the response fails
Example:
reader := strings.NewReader("sensitive data")
encrypted, err := api.CipherEncryptStream(reader)
func (*API) Close ¶ added in v0.6.0
Close releases any resources held by the API instance.
It ensures proper cleanup of the underlying X509Source. This method should be called when the API instance is no longer needed, typically during application shutdown or cleanup.
Returns:
- *sdkErrors.SDKError: nil if successful or source is nil, ErrSPIFFEFailedToCreateX509Source if closure fails
Example:
api, err := NewAPI(ctx)
if err != nil {
log.Fatal(err)
}
defer func() {
if err := api.Close(); err != nil {
log.Printf("Failed to close API: %v", err)
}
}()
func (*API) Contribute ¶ added in v0.12.0
Contribute sends a secret share contribution to a SPIKE Keeper during the bootstrap process.
It establishes a mutual TLS connection to the specified Keeper and transmits the keeper's share of the secret. The function marshals the share value, validates its length, and sends it securely to the Keeper. After sending, the contribution is zeroed out in memory for security.
Parameters:
- keeperShare: The secret share to contribute to the Keeper
- keeperID: The unique identifier of the target Keeper
Returns:
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- Errors from net.Post(): if the HTTP request fails
Note: The function will fatally crash (via log.FatalErr) if:
- Marshal failures (ErrDataMarshalFailure)
- Share length validation fails (ErrCryptoInvalidEncryptionKeyLength)
func (*API) CreatePolicy ¶ added in v0.6.0
func (a *API) CreatePolicy( name string, SPIFFEIDPattern string, pathPattern string, permissions []data.PolicyPermission, ) *sdkErrors.SDKError
CreatePolicy creates a new policy in the system.
It establishes a mutual TLS connection using the X.509 source and sends a policy creation request to SPIKE Nexus.
Parameters:
- name: The name of the policy to be created
- SPIFFEIDPattern: The SPIFFE ID pattern that this policy will apply to
- pathPattern: The path pattern that this policy will match against
- permissions: A slice of PolicyPermission defining the access rights
Returns:
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
permissions := []data.PolicyPermission{
{Action: "read", Resource: "documents/*"},
}
err := api.CreatePolicy(
"doc-reader",
"spiffe://example.org/service/*",
"/api/documents/*",
permissions,
)
if err != nil {
log.Printf("Failed to create policy: %v", err)
}
func (*API) DeletePolicy ¶ added in v0.6.0
DeletePolicy removes an existing policy from the system using its unique ID.
Parameters:
- id: The unique identifier of the policy to be deleted
Returns:
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
err := api.DeletePolicy("policy-123")
if err != nil {
log.Printf("Failed to delete policy: %v", err)
}
func (*API) DeleteSecret ¶ added in v0.6.0
DeleteSecret deletes the entire secret at the given path.
Parameters:
- path: Path to the secret to delete
Returns:
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
err := api.DeleteSecret("secret/path")
if err != nil {
log.Printf("Failed to delete secret: %v", err)
}
func (*API) DeleteSecretVersions ¶ added in v0.6.0
DeleteSecretVersions deletes specified versions of a secret at the given path.
Parameters:
- path: Path to the secret to delete
- versions: Array of version numbers to delete
Returns:
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
err := api.DeleteSecretVersions("secret/path", []int{1, 2})
if err != nil {
log.Printf("Failed to delete secret versions: %v", err)
}
func (*API) GetPolicy ¶ added in v0.6.0
GetPolicy retrieves a policy from the system using its unique ID.
Parameters:
- id: The unique identifier of the policy to retrieve
Returns:
- *data.Policy: The policy if found, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- ErrAPINotFound: if the policy is not found
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
policy, err := api.GetPolicy("policy-123")
if err != nil {
if err.Is(sdkErrors.ErrAPINotFound) {
log.Printf("Policy not found")
return
}
log.Printf("Error retrieving policy: %v", err)
return
}
log.Printf("Found policy: %+v", policy)
func (*API) GetSecret ¶ added in v0.6.0
GetSecret retrieves the latest version of the secret at the given path.
Parameters:
- path: Path to the secret to retrieve
Returns:
- *data.Secret: Secret data if found, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- ErrAPINotFound: if the secret is not found
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
secret, err := api.GetSecret("secret/path")
if err != nil {
if err.Is(sdkErrors.ErrAPINotFound) {
log.Printf("Secret not found")
return
}
log.Printf("Error retrieving secret: %v", err)
return
}
func (*API) GetSecretMetadata ¶ added in v0.6.0
func (a *API) GetSecretMetadata( path string, version int, ) (*data.SecretMetadata, *sdkErrors.SDKError)
GetSecretMetadata retrieves metadata for a specific version of a secret at the given path.
Parameters:
- path: Path to the secret to retrieve metadata for
- version: Version number of the secret to retrieve metadata for
Returns:
- *data.SecretMetadata: Secret metadata if found, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- ErrAPINotFound: if the secret metadata is not found
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
metadata, err := api.GetSecretMetadata("secret/path", 1)
if err != nil {
if err.Is(sdkErrors.ErrAPINotFound) {
log.Printf("Metadata not found")
return
}
log.Printf("Error retrieving metadata: %v", err)
return
}
func (*API) GetSecretVersion ¶ added in v0.6.0
GetSecretVersion retrieves a specific version of a secret at the given path.
Parameters:
- path: Path to the secret to retrieve
- version: Version number of the secret to retrieve
Returns:
- *data.Secret: Secret data if found, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- ErrAPINotFound: if the secret is not found
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
secret, err := api.GetSecretVersion("secret/path", 1)
if err != nil {
if err.Is(sdkErrors.ErrAPINotFound) {
log.Printf("Secret not found")
return
}
log.Printf("Error retrieving secret: %v", err)
return
}
func (*API) ListPolicies ¶ added in v0.6.0
func (a *API) ListPolicies( SPIFFEIDPattern, pathPattern string, ) (*[]data.Policy, *sdkErrors.SDKError)
ListPolicies retrieves policies from the system, optionally filtering by SPIFFE ID and path patterns.
Parameters:
- SPIFFEIDPattern: The SPIFFE ID pattern to filter policies (empty string matches all SPIFFE IDs)
- pathPattern: The path pattern to filter policies (empty string matches all paths)
Returns:
- *[]data.Policy: Array of matching policies, empty array if none found, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails (except ErrAPINotFound)
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Note: Returns (&[]data.Policy{}, nil) if no policies are found (ErrAPINotFound)
Example:
result, err := api.ListPolicies("", "")
if err != nil {
log.Printf("Error listing policies: %v", err)
return
}
policies := *result
for _, policy := range policies {
log.Printf("Found policy: %+v", policy)
}
func (*API) ListSecretKeys ¶ added in v0.6.0
ListSecretKeys retrieves all secret keys.
Returns:
- *[]string: Array of secret keys if found, empty array if none found, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails (except ErrAPINotFound)
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Note: Returns (&[]string{}, nil) if no secrets are found (ErrAPINotFound)
Example:
keys, err := api.ListSecretKeys()
if err != nil {
log.Printf("Error listing keys: %v", err)
return
}
for _, key := range *keys {
log.Printf("Found key: %s", key)
}
func (*API) PutSecret ¶ added in v0.6.0
PutSecret creates or updates a secret at the specified path with the given values.
Parameters:
- path: Path where the secret should be stored
- data: Map of key-value pairs representing the secret data
Returns:
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
err := api.PutSecret("secret/path", map[string]string{"key": "value"})
if err != nil {
log.Printf("Failed to put secret: %v", err)
}
func (*API) Recover ¶ added in v0.6.0
Recover returns recovery partitions for SPIKE Nexus to be used in a break-the-glass recovery operation.
This should be used when the SPIKE Nexus auto-recovery mechanism isn't successful. The returned shards are sensitive and should be securely stored out-of-band in encrypted form.
Returns:
- map[int]*[32]byte: Map of shard indices to shard byte arrays if successful, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Note: The function will fatally crash (via log.FatalErr) if:
- SVID acquisition fails
- SVID is nil
- Caller is not SPIKE Pilot (security requirement)
Example:
shards, err := api.Recover()
if err != nil {
log.Fatalf("Failed to recover shards: %v", err)
}
func (*API) Restore ¶ added in v0.6.0
Restore submits a recovery shard to continue the SPIKE Nexus restoration process.
This is used when SPIKE Keepers cannot provide adequate shards and SPIKE Nexus cannot recall its root key. This is a break-the-glass superuser-only operation that a well-architected SPIKE deployment should not need.
Parameters:
- index: Index of the recovery shard
- shard: Pointer to a 32-byte array containing the recovery shard
Returns:
- *data.RestorationStatus: Status containing shards collected, remaining, and restoration state if successful, nil on error
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Note: The function will fatally crash (via log.FatalErr) if:
- SVID acquisition fails
- SVID is nil
- Caller is not SPIKE Pilot (security requirement)
Example:
status, err := api.Restore(shardIndex, shardPtr)
if err != nil {
log.Fatalf("Failed to restore shard: %v", err)
}
log.Printf("Shards collected: %d, remaining: %d",
status.ShardsCollected, status.ShardsRemaining)
func (*API) UndeleteSecret ¶ added in v0.6.0
UndeleteSecret restores previously deleted versions of a secret at the specified path.
Parameters:
- path: Path to the secret to restore
- versions: Array of version numbers to restore (empty array attempts no restoration)
Returns:
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- ErrDataMarshalFailure: if request serialization fails
- Errors from net.Post(): if the HTTP request fails
- ErrDataUnmarshalFailure: if response parsing fails
- Error from FromCode(): if the server returns an error
Example:
err := api.UndeleteSecret("secret/path", []int{1, 2})
if err != nil {
log.Printf("Failed to undelete secret: %v", err)
}
func (*API) Verify ¶ added in v0.12.0
Verify performs bootstrap verification with SPIKE Nexus by sending encrypted random text and validating that Nexus can decrypt it correctly.
This ensures that the bootstrap process completed successfully and Nexus has the correct master key. The function sends the nonce and ciphertext to Nexus, receives back a hash, and compares it against the expected hash of the original random text. A match confirms successful bootstrap.
Parameters:
- randomText: The original random text that was encrypted
- nonce: The nonce used during encryption
- cipherText: The encrypted random text
Returns:
- *sdkErrors.SDKError: nil on success, or one of the following errors:
- ErrSPIFFENilX509Source: if the X509 source is nil
- Errors from net.Post(): if the HTTP request fails
Note: The function will fatally crash (via log.FatalErr) if:
- Marshal failures (ErrDataMarshalFailure)
- Response parsing failures (ErrDataUnmarshalFailure)
- Hash verification fails (ErrCryptoCipherVerificationFailed)
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
entity
|
|
|
data
Package data provides data structures for the SPIKE mTLS REST APIs.
|
Package data provides data structures for the SPIKE mTLS REST APIs. |
|
v1/reqres
Package reqres provides request and response structures for SPIKE mTLS REST API v1.
|
Package reqres provides request and response structures for SPIKE mTLS REST API v1. |
|
internal
|
|
|
impl/acl
Package acl provides internal implementation for access control list (ACL) policy management.
|
Package acl provides internal implementation for access control list (ACL) policy management. |
|
impl/cipher
Package cipher provides internal implementation for cryptographic operations including encryption and decryption.
|
Package cipher provides internal implementation for cryptographic operations including encryption and decryption. |
|
impl/operator
Package operator provides internal implementation for operator-level functions including recovery and restoration operations using Shamir secret sharing.
|
Package operator provides internal implementation for operator-level functions including recovery and restoration operations using Shamir secret sharing. |
|
impl/secret
Package secret provides internal implementation for secret management operations.
|
Package secret provides internal implementation for secret management operations. |
|
Package url provides URL construction utilities for SPIKE API endpoints.
|
Package url provides URL construction utilities for SPIKE API endpoints. |