Skip to content

Commit e7f9fd8

Browse files
feat(api): surface deleted/expired API keys for audit trail (KERNEL-1350)
1 parent 3bf246f commit e7f9fd8

4 files changed

Lines changed: 59 additions & 12 deletions

File tree

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 119
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-51549f813f3002e18c6ca8d850cc0c7932828d511c151e0412c73b6798d19e30.yml
3-
openapi_spec_hash: ee77b293c4bda91c1a32cfdd12b8739e
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel/kernel-42074f2b600b0dc805377d6793e4bb30c959738b0f9cc44c409d094517e5e0ab.yml
3+
openapi_spec_hash: 81c27a833d6d9637787634180dec2abd
44
config_hash: 57567e00b41af47cef1b78e51b747aa0

api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ Response Types:
427427
Methods:
428428

429429
- <code title="post /org/api_keys">client.APIKeys.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, body <a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyNewParams">APIKeyNewParams</a>) (\*<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#CreatedAPIKey">CreatedAPIKey</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
430-
- <code title="get /org/api_keys/{id}">client.APIKeys.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (\*<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKey">APIKey</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
430+
- <code title="get /org/api_keys/{id}">client.APIKeys.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyService.Get">Get</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, query <a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyGetParams">APIKeyGetParams</a>) (\*<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKey">APIKey</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
431431
- <code title="patch /org/api_keys/{id}">client.APIKeys.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyService.Update">Update</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyUpdateParams">APIKeyUpdateParams</a>) (\*<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKey">APIKey</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
432432
- <code title="get /org/api_keys">client.APIKeys.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, query <a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyListParams">APIKeyListParams</a>) (\*<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk/packages/pagination">pagination</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk/packages/pagination#OffsetPagination">OffsetPagination</a>[<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk">kernel</a>.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKey">APIKey</a>], <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
433433
- <code title="delete /org/api_keys/{id}">client.APIKeys.<a href="https://pkg.go.dev/github.com/kernel/kernel-go-sdk#APIKeyService.Delete">Delete</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) <a href="https://pkg.go.dev/builtin#error">error</a></code>

apikey.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ func (r *APIKeyService) New(ctx context.Context, body APIKeyNewParams, opts ...o
5151

5252
// Retrieve an API key by ID for the authenticated organization. API keys are
5353
// masked.
54-
func (r *APIKeyService) Get(ctx context.Context, id string, opts ...option.RequestOption) (res *APIKey, err error) {
54+
func (r *APIKeyService) Get(ctx context.Context, id string, query APIKeyGetParams, opts ...option.RequestOption) (res *APIKey, err error) {
5555
opts = slices.Concat(r.Options, opts)
5656
if id == "" {
5757
err = errors.New("missing required id parameter")
5858
return nil, err
5959
}
6060
path := fmt.Sprintf("org/api_keys/%s", id)
61-
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
61+
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, query, &res, opts...)
6262
return res, err
6363
}
6464

@@ -116,6 +116,9 @@ type APIKey struct {
116116
// When the API key was created
117117
CreatedAt time.Time `json:"created_at" api:"required" format:"date-time"`
118118
CreatedBy APIKeyCreatedBy `json:"created_by" api:"required"`
119+
// When the API key was deleted (soft-deleted). Null for keys that have not been
120+
// deleted.
121+
DeletedAt time.Time `json:"deleted_at" api:"required" format:"date-time"`
119122
// When the API key expires
120123
ExpiresAt time.Time `json:"expires_at" api:"required" format:"date-time"`
121124
// Masked version of the API key
@@ -127,16 +130,24 @@ type APIKey struct {
127130
// Project name for project-scoped API keys. Null means the key is org-wide or the
128131
// project name is unavailable.
129132
ProjectName string `json:"project_name" api:"required"`
133+
// Derived lifecycle status of the API key. `active` means usable. `expired` means
134+
// past its expires_at. `deleted` means it was deleted (soft-deleted) and can no
135+
// longer authenticate. Deleted takes precedence over expired.
136+
//
137+
// Any of "active", "expired", "deleted".
138+
Status APIKeyStatus `json:"status" api:"required"`
130139
// JSON contains metadata for fields, check presence with [respjson.Field.Valid].
131140
JSON struct {
132141
ID respjson.Field
133142
CreatedAt respjson.Field
134143
CreatedBy respjson.Field
144+
DeletedAt respjson.Field
135145
ExpiresAt respjson.Field
136146
MaskedKey respjson.Field
137147
Name respjson.Field
138148
ProjectID respjson.Field
139149
ProjectName respjson.Field
150+
Status respjson.Field
140151
ExtraFields map[string]respjson.Field
141152
raw string
142153
} `json:"-"`
@@ -171,6 +182,17 @@ func (r *APIKeyCreatedBy) UnmarshalJSON(data []byte) error {
171182
return apijson.UnmarshalRoot(data, r)
172183
}
173184

185+
// Derived lifecycle status of the API key. `active` means usable. `expired` means
186+
// past its expires_at. `deleted` means it was deleted (soft-deleted) and can no
187+
// longer authenticate. Deleted takes precedence over expired.
188+
type APIKeyStatus string
189+
190+
const (
191+
APIKeyStatusActive APIKeyStatus = "active"
192+
APIKeyStatusExpired APIKeyStatus = "expired"
193+
APIKeyStatusDeleted APIKeyStatus = "deleted"
194+
)
195+
174196
// API key returned immediately after creation. Includes the plaintext key once.
175197
type CreatedAPIKey struct {
176198
// Plaintext API key. Only returned once when the key is created.
@@ -208,6 +230,21 @@ func (r *APIKeyNewParams) UnmarshalJSON(data []byte) error {
208230
return apijson.UnmarshalRoot(data, r)
209231
}
210232

233+
type APIKeyGetParams struct {
234+
// When true, return the API key even if it has been deleted (soft-deleted), for
235+
// audit purposes. Defaults to false, which returns 404 for a deleted key.
236+
IncludeDeleted param.Opt[bool] `query:"include_deleted,omitzero" json:"-"`
237+
paramObj
238+
}
239+
240+
// URLQuery serializes [APIKeyGetParams]'s query parameters as `url.Values`.
241+
func (r APIKeyGetParams) URLQuery() (v url.Values, err error) {
242+
return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{
243+
ArrayFormat: apiquery.ArrayQueryFormatComma,
244+
NestedFormat: apiquery.NestedQueryFormatBrackets,
245+
})
246+
}
247+
211248
type APIKeyUpdateParams struct {
212249
// New API key name
213250
Name string `json:"name" api:"required"`
@@ -223,6 +260,9 @@ func (r *APIKeyUpdateParams) UnmarshalJSON(data []byte) error {
223260
}
224261

225262
type APIKeyListParams struct {
263+
// When true, include deleted (soft-deleted) API keys in the results for audit
264+
// purposes. Defaults to false, which returns only live keys.
265+
IncludeDeleted param.Opt[bool] `query:"include_deleted,omitzero" json:"-"`
226266
// Maximum number of results to return
227267
Limit param.Opt[int64] `query:"limit,omitzero" json:"-"`
228268
// Number of results to skip

apikey_test.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestAPIKeyNewWithOptionalParams(t *testing.T) {
4040
}
4141
}
4242

43-
func TestAPIKeyGet(t *testing.T) {
43+
func TestAPIKeyGetWithOptionalParams(t *testing.T) {
4444
t.Skip("Mock server tests are disabled")
4545
baseURL := "http://localhost:4010"
4646
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
@@ -53,7 +53,13 @@ func TestAPIKeyGet(t *testing.T) {
5353
option.WithBaseURL(baseURL),
5454
option.WithAPIKey("My API Key"),
5555
)
56-
_, err := client.APIKeys.Get(context.TODO(), "id")
56+
_, err := client.APIKeys.Get(
57+
context.TODO(),
58+
"id",
59+
kernel.APIKeyGetParams{
60+
IncludeDeleted: kernel.Bool(true),
61+
},
62+
)
5763
if err != nil {
5864
var apierr *kernel.Error
5965
if errors.As(err, &apierr) {
@@ -106,11 +112,12 @@ func TestAPIKeyListWithOptionalParams(t *testing.T) {
106112
option.WithAPIKey("My API Key"),
107113
)
108114
_, err := client.APIKeys.List(context.TODO(), kernel.APIKeyListParams{
109-
Limit: kernel.Int(100),
110-
Offset: kernel.Int(0),
111-
Query: kernel.String("query"),
112-
SortBy: kernel.APIKeyListParamsSortByCreatedAt,
113-
SortDirection: kernel.APIKeyListParamsSortDirectionAsc,
115+
IncludeDeleted: kernel.Bool(true),
116+
Limit: kernel.Int(100),
117+
Offset: kernel.Int(0),
118+
Query: kernel.String("query"),
119+
SortBy: kernel.APIKeyListParamsSortByCreatedAt,
120+
SortDirection: kernel.APIKeyListParamsSortDirectionAsc,
114121
})
115122
if err != nil {
116123
var apierr *kernel.Error

0 commit comments

Comments
 (0)