diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index db00cae..0b8a7c6 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.67.0"
+ ".": "0.68.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index a164009..2919208 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 119
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-51549f813f3002e18c6ca8d850cc0c7932828d511c151e0412c73b6798d19e30.yml
-openapi_spec_hash: ee77b293c4bda91c1a32cfdd12b8739e
-config_hash: 57567e00b41af47cef1b78e51b747aa0
+configured_endpoints: 120
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-d26459bd3514237e8d757be3cbdc76ca62f6083504b85601e57db830888964f7.yml
+openapi_spec_hash: 5dd151a8099398819a97692c1c60c3c6
+config_hash: 03c7e57f268c750e2415831662e95969
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a9a1c6e..fd9d3ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 0.68.0 (2026-06-15)
+
+Full Changelog: [v0.67.0...v0.68.0](https://github.com/kernel/kernel-go-sdk/compare/v0.67.0...v0.68.0)
+
+### Features
+
+* Add API key rotate endpoint ([04d4a12](https://github.com/kernel/kernel-go-sdk/commit/04d4a12f1f98a5745b7ca706a22cab71cb625866))
+* **api:** surface deleted/expired API keys for audit trail (KERNEL-1350) ([e7f9fd8](https://github.com/kernel/kernel-go-sdk/commit/e7f9fd8aa890b660d675b22ad48f869504817b2d))
+
+
+### Refactors
+
+* **api:** align API key audit surface with browser sibling (KERNEL-1350) ([9e23506](https://github.com/kernel/kernel-go-sdk/commit/9e2350674aafa75a45c4b843d8a43b31c4656bd6))
+
## 0.67.0 (2026-06-11)
Full Changelog: [v0.66.0...v0.67.0](https://github.com/kernel/kernel-go-sdk/compare/v0.66.0...v0.67.0)
diff --git a/README.md b/README.md
index 737e769..5ddd1ff 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Or to pin the version:
```sh
-go get -u 'github.com/kernel/kernel-go-sdk@v0.67.0'
+go get -u 'github.com/kernel/kernel-go-sdk@v0.68.0'
```
diff --git a/api.md b/api.md
index f4a6c67..0c51245 100644
--- a/api.md
+++ b/api.md
@@ -427,10 +427,11 @@ Response Types:
Methods:
- client.APIKeys.New(ctx context.Context, body kernel.APIKeyNewParams) (\*kernel.CreatedAPIKey, error)
-- client.APIKeys.Get(ctx context.Context, id string) (\*kernel.APIKey, error)
+- client.APIKeys.Get(ctx context.Context, id string, query kernel.APIKeyGetParams) (\*kernel.APIKey, error)
- client.APIKeys.Update(ctx context.Context, id string, body kernel.APIKeyUpdateParams) (\*kernel.APIKey, error)
- client.APIKeys.List(ctx context.Context, query kernel.APIKeyListParams) (\*pagination.OffsetPagination[kernel.APIKey], error)
- client.APIKeys.Delete(ctx context.Context, id string) error
+- client.APIKeys.Rotate(ctx context.Context, id string, body kernel.APIKeyRotateParams) (\*kernel.CreatedAPIKey, error)
# CredentialProviders
diff --git a/apikey.go b/apikey.go
index 1fed55a..b7b4132 100644
--- a/apikey.go
+++ b/apikey.go
@@ -51,14 +51,14 @@ func (r *APIKeyService) New(ctx context.Context, body APIKeyNewParams, opts ...o
// Retrieve an API key by ID for the authenticated organization. API keys are
// masked.
-func (r *APIKeyService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *APIKey, err error) {
+func (r *APIKeyService) Get(ctx context.Context, id string, query APIKeyGetParams, opts ...option.RequestOption) (res *APIKey, err error) {
opts = slices.Concat(r.Options, opts)
if id == "" {
err = errors.New("missing required id parameter")
return nil, err
}
path := fmt.Sprintf("org/api_keys/%s", id)
- err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
return res, err
}
@@ -110,12 +110,29 @@ func (r *APIKeyService) Delete(ctx context.Context, id string, opts ...option.Re
return err
}
+// Rotate an API key. Issues a new key that copies the name and project of the
+// rotated key, and schedules the rotated key to expire after a grace period so
+// in-flight callers can swap over. The new plaintext key is returned once.
+func (r *APIKeyService) Rotate(ctx context.Context, id string, body APIKeyRotateParams, opts ...option.RequestOption) (res *CreatedAPIKey, err error) {
+ opts = slices.Concat(r.Options, opts)
+ if id == "" {
+ err = errors.New("missing required id parameter")
+ return nil, err
+ }
+ path := fmt.Sprintf("org/api_keys/%s/rotate", id)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ return res, err
+}
+
type APIKey struct {
// Unique API key identifier
ID string `json:"id" api:"required"`
// When the API key was created
CreatedAt time.Time `json:"created_at" api:"required" format:"date-time"`
CreatedBy APIKeyCreatedBy `json:"created_by" api:"required"`
+ // When the API key was deleted (soft-deleted). Null for keys that have not been
+ // deleted.
+ DeletedAt time.Time `json:"deleted_at" api:"required" format:"date-time"`
// When the API key expires
ExpiresAt time.Time `json:"expires_at" api:"required" format:"date-time"`
// Masked version of the API key
@@ -132,6 +149,7 @@ type APIKey struct {
ID respjson.Field
CreatedAt respjson.Field
CreatedBy respjson.Field
+ DeletedAt respjson.Field
ExpiresAt respjson.Field
MaskedKey respjson.Field
Name respjson.Field
@@ -208,6 +226,21 @@ func (r *APIKeyNewParams) UnmarshalJSON(data []byte) error {
return apijson.UnmarshalRoot(data, r)
}
+type APIKeyGetParams struct {
+ // When true, return the API key even if it has been deleted (soft-deleted), for
+ // audit purposes. Defaults to false, which returns 404 for a deleted key.
+ IncludeDeleted param.Opt[bool] `query:"include_deleted,omitzero" json:"-"`
+ paramObj
+}
+
+// URLQuery serializes [APIKeyGetParams]'s query parameters as `url.Values`.
+func (r APIKeyGetParams) URLQuery() (v url.Values, err error) {
+ return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
+ ArrayFormat: apiquery.ArrayQueryFormatComma,
+ NestedFormat: apiquery.NestedQueryFormatBrackets,
+ })
+}
+
type APIKeyUpdateParams struct {
// New API key name
Name string `json:"name" api:"required"`
@@ -223,6 +256,9 @@ func (r *APIKeyUpdateParams) UnmarshalJSON(data []byte) error {
}
type APIKeyListParams struct {
+ // Deprecated: use status=all instead. When true, include deleted (soft-deleted)
+ // API keys in the results for audit purposes.
+ IncludeDeleted param.Opt[bool] `query:"include_deleted,omitzero" json:"-"`
// Maximum number of results to return
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
// Number of results to skip
@@ -238,6 +274,12 @@ type APIKeyListParams struct {
//
// Any of "asc", "desc".
SortDirection APIKeyListParamsSortDirection `query:"sort_direction,omitzero" json:"-"`
+ // Filter API keys by status. "active" returns keys that are not deleted (default;
+ // expired-but-not-deleted keys are still included), "deleted" returns only
+ // soft-deleted keys, "all" returns both.
+ //
+ // Any of "active", "deleted", "all".
+ Status APIKeyListParamsStatus `query:"status,omitzero" json:"-"`
paramObj
}
@@ -265,3 +307,32 @@ const (
APIKeyListParamsSortDirectionAsc APIKeyListParamsSortDirection = "asc"
APIKeyListParamsSortDirectionDesc APIKeyListParamsSortDirection = "desc"
)
+
+// Filter API keys by status. "active" returns keys that are not deleted (default;
+// expired-but-not-deleted keys are still included), "deleted" returns only
+// soft-deleted keys, "all" returns both.
+type APIKeyListParamsStatus string
+
+const (
+ APIKeyListParamsStatusActive APIKeyListParamsStatus = "active"
+ APIKeyListParamsStatusDeleted APIKeyListParamsStatus = "deleted"
+ APIKeyListParamsStatusAll APIKeyListParamsStatus = "all"
+)
+
+type APIKeyRotateParams struct {
+ // Lifetime in days for the new key, up to 3650. Omit to reuse the rotated key's
+ // original lifetime, or never-expires if it had none.
+ DaysToExpire param.Opt[int64] `json:"days_to_expire,omitzero"`
+ // Grace period in days before the rotated key expires. Use 0 to expire it
+ // immediately. Omit for the default grace period of 7 days.
+ ExpireInDays param.Opt[int64] `json:"expire_in_days,omitzero"`
+ paramObj
+}
+
+func (r APIKeyRotateParams) MarshalJSON() (data []byte, err error) {
+ type shadow APIKeyRotateParams
+ return param.MarshalObject(r, (*shadow)(&r))
+}
+func (r *APIKeyRotateParams) UnmarshalJSON(data []byte) error {
+ return apijson.UnmarshalRoot(data, r)
+}
diff --git a/apikey_test.go b/apikey_test.go
index 946b46b..24d41c6 100644
--- a/apikey_test.go
+++ b/apikey_test.go
@@ -40,7 +40,7 @@ func TestAPIKeyNewWithOptionalParams(t *testing.T) {
}
}
-func TestAPIKeyGet(t *testing.T) {
+func TestAPIKeyGetWithOptionalParams(t *testing.T) {
t.Skip("Mock server tests are disabled")
baseURL := "http://localhost:4010"
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
@@ -53,7 +53,13 @@ func TestAPIKeyGet(t *testing.T) {
option.WithBaseURL(baseURL),
option.WithAPIKey("My API Key"),
)
- _, err := client.APIKeys.Get(context.TODO(), "id")
+ _, err := client.APIKeys.Get(
+ context.TODO(),
+ "id",
+ kernel.APIKeyGetParams{
+ IncludeDeleted: kernel.Bool(true),
+ },
+ )
if err != nil {
var apierr *kernel.Error
if errors.As(err, &apierr) {
@@ -106,11 +112,13 @@ func TestAPIKeyListWithOptionalParams(t *testing.T) {
option.WithAPIKey("My API Key"),
)
_, err := client.APIKeys.List(context.TODO(), kernel.APIKeyListParams{
- Limit: kernel.Int(100),
- Offset: kernel.Int(0),
- Query: kernel.String("query"),
- SortBy: kernel.APIKeyListParamsSortByCreatedAt,
- SortDirection: kernel.APIKeyListParamsSortDirectionAsc,
+ IncludeDeleted: kernel.Bool(true),
+ Limit: kernel.Int(100),
+ Offset: kernel.Int(0),
+ Query: kernel.String("query"),
+ SortBy: kernel.APIKeyListParamsSortByCreatedAt,
+ SortDirection: kernel.APIKeyListParamsSortDirectionAsc,
+ Status: kernel.APIKeyListParamsStatusActive,
})
if err != nil {
var apierr *kernel.Error
@@ -143,3 +151,33 @@ func TestAPIKeyDelete(t *testing.T) {
t.Fatalf("err should be nil: %s", err.Error())
}
}
+
+func TestAPIKeyRotateWithOptionalParams(t *testing.T) {
+ t.Skip("Mock server tests are disabled")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := kernel.NewClient(
+ option.WithBaseURL(baseURL),
+ option.WithAPIKey("My API Key"),
+ )
+ _, err := client.APIKeys.Rotate(
+ context.TODO(),
+ "id",
+ kernel.APIKeyRotateParams{
+ DaysToExpire: kernel.Int(30),
+ ExpireInDays: kernel.Int(7),
+ },
+ )
+ if err != nil {
+ var apierr *kernel.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
diff --git a/internal/version.go b/internal/version.go
index 1137ca0..15b7357 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -2,4 +2,4 @@
package internal
-const PackageVersion = "0.67.0" // x-release-please-version
+const PackageVersion = "0.68.0" // x-release-please-version