Skip to content

Commit b59c0df

Browse files
Mlaz-codesharp-resolver[bot]claude
authored
docs(spec): reality-audit — openapi + sitemap + account-keys sk_live examples (#277)
Machine/spec files piece of the 2026-06-26 docs reality audit (WIP tip 83fb982), applied on top of the merged family pieces and reconciled against today's main + live API. - public/openapi.json: provenance metadata refresh only (x-generated-at, x-commit-sha). Semantic body (paths + schemas) verified byte-identical to main via scripts/check-openapi-version.mjs — no unmerged-family (opportunities) endpoint changes to revert. - public/openapi-version.json: sync stale sidecar 2.1.0 -> 3.1.0 to match openapi.json info.version (stamp-openapi.mjs regenerates at build; this fixes the committed state). - public/sitemap.xml: regenerated with scripts/generate-sitemap.mjs against today's content tree (228 URLs). Supersedes the audit's 224-URL snapshot, which was already missing concepts/market-lifecycle (4 locales) added to main after the audit. - content/en/api-reference/account-keys.mdx: key-management envelope fixes from the audit ({success, data, meta}; create returns 200 not 201; rotate new_key/old_key shape + gracePeriodHours 0-72; delete envelope; real error messages), sk_live_ example keys (truncated — GitHub push protection rejects full-length fakes) replacing main's fictional "sharpapi_..." format, plus two today-reality corrections against the live handler: id_masked = "..." + last 8 chars, and max_keys = 1 for pro tier (was 5). Live-verified: GET /account/keys with an internal (no user_id) key returns exactly the documented 400 {"error":{"code":"validation_error", "message":"No user_id associated with this API key"}}; all four operation response shapes and error strings confirmed against sharp-api-go keys.go on main (1940dc1). Co-authored-by: sharp-resolver[bot] <3736210+sharp-resolver[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent a034ccd commit b59c0df

4 files changed

Lines changed: 1746 additions & 1576 deletions

File tree

content/en/api-reference/account-keys.mdx

Lines changed: 97 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: "SharpAPI API key management — create, list, rotate, and delete API keys programmatically. Manage multiple keys per account with granular permissions and usage tracking."
2+
description: "SharpAPI API key management — create, list, rotate, and delete API keys programmatically. Responses use the key-management success envelope."
33
---
44

55
import { Callout, Tabs } from 'nextra/components'
@@ -10,7 +10,7 @@ Create, list, rotate, and delete API keys for your account.
1010

1111
## Authentication
1212

13-
All key management endpoints require authentication via an existing API key. **Available to all tiers.**
13+
Key management endpoints support dashboard session auth and API-key auth. When you authenticate with an API key, that key must be associated with a SharpAPI user account; internal or service keys without `user_id` return `400 validation_error`.
1414

1515
<Callout type="warning">
1616
**Security:** API key management operations are sensitive. Only perform these from server-side code, never from a client-side application.
@@ -41,10 +41,11 @@ const response = await fetch(
4141
'https://api.sharpapi.io/api/v1/account/keys',
4242
{ headers: { 'X-API-Key': 'YOUR_API_KEY' } }
4343
);
44-
const { data } = await response.json();
44+
const { data, meta } = await response.json();
4545

46+
console.log(`${meta.count}/${meta.max_keys} key slots used`);
4647
for (const key of data) {
47-
console.log(`${key.name}: ${key.id_masked} (${key.is_active})`);
48+
console.log(`${key.name ?? 'Unnamed'}: ${key.id_masked} (${key.is_active})`);
4849
}
4950
```
5051
</Tabs.Tab>
@@ -56,10 +57,11 @@ response = requests.get(
5657
'https://api.sharpapi.io/api/v1/account/keys',
5758
headers={'X-API-Key': 'YOUR_API_KEY'}
5859
)
59-
keys = response.json()['data']
60+
body = response.json()
6061
61-
for key in keys:
62-
print(f"{key['name']}: {key['id_masked']} ({key['is_active']})")
62+
print(f"{body['meta']['count']}/{body['meta']['max_keys']} key slots used")
63+
for key in body['data']:
64+
print(f"{key['name'] or 'Unnamed'}: {key['id_masked']} ({key['is_active']})")
6365
```
6466
</Tabs.Tab>
6567
</Tabs>
@@ -68,51 +70,38 @@ for key in keys:
6870
6971
```json
7072
{
73+
"success": true,
7174
"data": [
7275
{
7376
"id": "key_abc123def456",
74-
"id_masked": "sharpapi_...f456",
77+
"id_masked": "...23def456",
7578
"name": "Production",
7679
"tier": "pro",
7780
"is_active": true,
7881
"created_at": "2025-10-15T08:30:00Z",
7982
"updated_at": "2026-02-08T14:22:10Z"
80-
},
81-
{
82-
"id": "key_xyz789ghi012",
83-
"id_masked": "sharpapi_...i012",
84-
"name": "Staging",
85-
"tier": "pro",
86-
"is_active": true,
87-
"created_at": "2026-01-05T12:00:00Z",
88-
"updated_at": "2026-02-07T09:15:30Z"
8983
}
9084
],
9185
"meta": {
92-
"count": 2,
93-
"total": 2,
94-
"pagination": {
95-
"limit": 50,
96-
"offset": 0,
97-
"has_more": false,
98-
"next_offset": null
99-
},
100-
"updated_at": "2026-02-08T14:55:00Z"
86+
"count": 1,
87+
"max_keys": 1
10188
}
10289
}
10390
```
10491
92+
`meta.max_keys` reflects your tier's key limit: Free/Hobby/Pro = 1, Sharp = 2, Enterprise = custom.
93+
10594
### Key Object Fields
10695
10796
| Field | Type | Description |
10897
|-------|------|-------------|
109-
| `id` | string | Unique key identifier |
110-
| `id_masked` | string | Masked preview of the key (first and last characters visible) |
111-
| `name` | string \| null | Human-readable key name |
112-
| `tier` | string | Subscription tier associated with the key |
113-
| `is_active` | boolean | Whether the key is currently active |
114-
| `created_at` | string | ISO 8601 timestamp of key creation |
115-
| `updated_at` | string | ISO 8601 timestamp of last key update |
98+
| `id` | string | Unique key identifier. |
99+
| `id_masked` | string | Masked preview of the key id (`...` plus the last 8 characters). |
100+
| `name` | string \| null | Human-readable key name. |
101+
| `tier` | string | Subscription tier associated with the key. |
102+
| `is_active` | boolean | Whether the key is currently active. |
103+
| `created_at` | string | ISO 8601 timestamp of key creation. |
104+
| `updated_at` | string | ISO 8601 timestamp of last key update. |
116105
117106
---
118107
@@ -128,7 +117,7 @@ POST /api/v1/account/keys
128117
129118
| Field | Type | Required | Description |
130119
|-------|------|----------|-------------|
131-
| `name` | string | Yes | A descriptive name for the key (e.g., "Production", "Mobile App") |
120+
| `name` | string | No | Descriptive name for the key, max 100 characters. If omitted, the API assigns a default name. |
132121
133122
### Example Request
134123
@@ -154,9 +143,9 @@ const response = await fetch(
154143
body: JSON.stringify({ name: 'Mobile App' })
155144
}
156145
);
157-
const { data } = await response.json();
146+
const { data, meta } = await response.json();
158147
console.log(`New key: ${data.key}`);
159-
// IMPORTANT: Store this key securely. It will not be shown again.
148+
console.warn(meta.warning);
160149
```
161150
</Tabs.Tab>
162151
<Tabs.Tab>
@@ -168,35 +157,39 @@ response = requests.post(
168157
json={'name': 'Mobile App'},
169158
headers={'X-API-Key': 'YOUR_API_KEY'}
170159
)
171-
new_key = response.json()['data']
172-
print(f"New key: {new_key['key']}")
173-
# IMPORTANT: Store this key securely. It will not be shown again.
160+
body = response.json()
161+
print(f"New key: {body['data']['key']}")
162+
print(body['meta']['warning'])
174163
```
175164
</Tabs.Tab>
176165
</Tabs>
177166
178-
### Response (201)
167+
### Response (200)
179168
180169
```json
181170
{
171+
"success": true,
182172
"data": {
183173
"id": "key_new345mno678",
184-
"key": "sharpapi_new345mno678pqr901stu234vwx567",
174+
"key": "sk_live_new345mno678...",
185175
"name": "Mobile App",
186176
"tier": "pro"
177+
},
178+
"meta": {
179+
"warning": "This is the only time the key value will be shown. Store it securely."
187180
}
188181
}
189182
```
190183
191184
<Callout type="warning">
192-
**Important:** The full `key` value is **only returned once** at creation time. Store it securely immediately. Subsequent requests will only show the `id_masked` preview.
185+
**Important:** The full `key` value is only returned once at creation time. Store it securely immediately.
193186
</Callout>
194187
195188
---
196189
197190
## Delete API Key
198191
199-
Permanently revoke and delete an API key.
192+
Permanently revoke an API key.
200193
201194
```
202195
DELETE /api/v1/account/keys/{keyId}
@@ -206,7 +199,7 @@ DELETE /api/v1/account/keys/{keyId}
206199
207200
| Parameter | Type | Description |
208201
|-----------|------|-------------|
209-
| `keyId` | string | The `key_id` of the key to delete |
202+
| `keyId` | string | The key id to delete. |
210203
211204
### Example Request
212205
@@ -226,8 +219,8 @@ const response = await fetch(
226219
headers: { 'X-API-Key': 'YOUR_API_KEY' }
227220
}
228221
);
229-
const result = await response.json();
230-
console.log(`Deleted key: ${result.key_id}`);
222+
const { data } = await response.json();
223+
console.log(`Deleted key: ${data.key_id}`);
231224
```
232225
</Tabs.Tab>
233226
<Tabs.Tab>
@@ -238,8 +231,8 @@ response = requests.delete(
238231
'https://api.sharpapi.io/api/v1/account/keys/key_xyz789ghi012',
239232
headers={'X-API-Key': 'YOUR_API_KEY'}
240233
)
241-
result = response.json()
242-
print(f"Deleted key: {result['key_id']}")
234+
data = response.json()['data']
235+
print(f"Deleted key: {data['key_id']}")
243236
```
244237
</Tabs.Tab>
245238
</Tabs>
@@ -248,9 +241,12 @@ print(f"Deleted key: {result['key_id']}")
248241
249242
```json
250243
{
251-
"deleted": true,
252-
"key_id": "key_xyz789ghi012",
253-
"message": "API key revoked successfully"
244+
"success": true,
245+
"data": {
246+
"deleted": true,
247+
"key_id": "key_xyz789ghi012",
248+
"message": "API key revoked successfully"
249+
}
254250
}
255251
```
256252
@@ -265,8 +261,7 @@ print(f"Deleted key: {result['key_id']}")
265261
{
266262
"error": {
267263
"code": "not_found",
268-
"message": "API key not found",
269-
"docs": "https://docs.sharpapi.io/en/api-reference/account-keys"
264+
"message": "Key not found or not owned by you"
270265
}
271266
}
272267
```
@@ -276,8 +271,7 @@ print(f"Deleted key: {result['key_id']}")
276271
{
277272
"error": {
278273
"code": "validation_error",
279-
"message": "Cannot delete the API key used to authenticate this request",
280-
"docs": "https://docs.sharpapi.io/en/api-reference/account-keys"
274+
"message": "Cannot delete the API key you are currently using"
281275
}
282276
}
283277
```
@@ -286,7 +280,7 @@ print(f"Deleted key: {result['key_id']}")
286280
287281
## Rotate API Key
288282
289-
Generate a new key value for an existing API key. The old key is immediately revoked and replaced with a new one.
283+
Generate a new key value and replace an existing API key. By default, the old key is revoked immediately. You may request a grace period up to 72 hours.
290284
291285
```
292286
POST /api/v1/account/keys/{keyId}/rotate
@@ -296,15 +290,24 @@ POST /api/v1/account/keys/{keyId}/rotate
296290
297291
| Parameter | Type | Description |
298292
|-----------|------|-------------|
299-
| `keyId` | string | The `key_id` of the key to rotate |
293+
| `keyId` | string | The key id to rotate. |
294+
295+
### Request Body
296+
297+
| Field | Type | Required | Description |
298+
|-------|------|----------|-------------|
299+
| `gracePeriodHours` | number | No | Keep the old key valid for this many hours, from `0` to `72`. Defaults to `0`. |
300+
| `name` | string | No | Name for the replacement key. Defaults to the old key's name. |
300301
301302
### Example Request
302303
303304
<Tabs items={['cURL', 'JavaScript', 'Python']}>
304305
<Tabs.Tab>
305306
```bash
306307
curl -X POST "https://api.sharpapi.io/api/v1/account/keys/key_abc123def456/rotate" \
307-
-H "X-API-Key: YOUR_API_KEY"
308+
-H "X-API-Key: YOUR_API_KEY" \
309+
-H "Content-Type: application/json" \
310+
-d '{"gracePeriodHours": 0}'
308311
```
309312
</Tabs.Tab>
310313
<Tabs.Tab>
@@ -313,12 +316,16 @@ const response = await fetch(
313316
'https://api.sharpapi.io/api/v1/account/keys/key_abc123def456/rotate',
314317
{
315318
method: 'POST',
316-
headers: { 'X-API-Key': 'YOUR_API_KEY' }
319+
headers: {
320+
'X-API-Key': 'YOUR_API_KEY',
321+
'Content-Type': 'application/json'
322+
},
323+
body: JSON.stringify({ gracePeriodHours: 0 })
317324
}
318325
);
319-
const { data } = await response.json();
320-
console.log(`New key value: ${data.key}`);
321-
// IMPORTANT: Update your application configuration with the new key immediately.
326+
const { data, meta } = await response.json();
327+
console.log(`New key value: ${data.new_key.key}`);
328+
console.warn(meta.warning);
322329
```
323330
</Tabs.Tab>
324331
<Tabs.Tab>
@@ -327,11 +334,12 @@ import requests
327334
328335
response = requests.post(
329336
'https://api.sharpapi.io/api/v1/account/keys/key_abc123def456/rotate',
337+
json={'gracePeriodHours': 0},
330338
headers={'X-API-Key': 'YOUR_API_KEY'}
331339
)
332-
rotated = response.json()['data']
333-
print(f"New key: {rotated['key']}")
334-
# IMPORTANT: Update your application configuration with the new key immediately.
340+
body = response.json()
341+
print(f"New key: {body['data']['new_key']['key']}")
342+
print(body['meta']['message'])
335343
```
336344
</Tabs.Tab>
337345
</Tabs>
@@ -340,51 +348,53 @@ print(f"New key: {rotated['key']}")
340348
341349
```json
342350
{
351+
"success": true,
343352
"data": {
344-
"key_id": "key_abc123def456",
345-
"name": "Production",
346-
"key": "sharpapi_rotated789abc012def345ghi678jkl",
347-
"key_preview": "sharpapi_...jkl",
348-
"status": "active",
349-
"rotated_at": "2026-02-08T15:10:00Z",
350-
"previous_key_revoked": true
353+
"new_key": {
354+
"id": "key_new789abc012",
355+
"key": "sk_live_rotated789abc012...",
356+
"name": "Production",
357+
"tier": "pro"
358+
},
359+
"old_key": {
360+
"id": "key_abc123def456",
361+
"revoked": true,
362+
"expires_at": null
363+
}
351364
},
352365
"meta": {
353-
"updated_at": "2026-02-08T15:10:00Z"
366+
"warning": "The new key value is only shown once. Store it securely.",
367+
"message": "Key rotated. Old key has been revoked immediately."
354368
}
355369
}
356370
```
357371
358372
<Callout type="warning">
359-
**Immediate effect:** The previous key value is revoked the instant rotation completes. Update your application configuration before the next API request. The new `key` value is only shown once.
360-
</Callout>
361-
362-
<Callout type="info">
363-
**Tip:** If you are rotating the key you are currently authenticating with, the rotation will succeed, but you must use the new key for all subsequent requests.
373+
**Immediate effect:** Unless you request a grace period, the previous key value is revoked the instant rotation completes. Update your application configuration before the next API request. The new `key` value is only shown once.
364374
</Callout>
365375
366376
---
367377
368378
## Response Headers
369379
370-
All key management endpoints return standard rate limit headers:
380+
Key management endpoints return standard rate-limit headers:
371381
372382
```
373383
X-RateLimit-Limit: 300
374384
X-RateLimit-Remaining: 294
375-
X-RateLimit-Reset: 1707401400
385+
X-RateLimit-Reset: 1782851940
376386
X-Data-Delay: 0
377-
X-Request-Id: req_keys123xyz
387+
X-Request-Id: 1782851943426721-511042
378388
```
379389
380390
## Best Practices
381391
382-
1. **Use descriptive names** - Name keys by their purpose (e.g., "Production Server", "Staging", "Mobile App") to easily identify them later
383-
2. **Rotate regularly** - Rotate keys periodically (e.g., every 90 days) as a security best practice
384-
3. **Use separate keys per environment** - Create distinct keys for production, staging, and development
385-
4. **Review inactive keys** - Identify and clean up unused keys
386-
5. **Store keys in secrets management** - Use environment variables or a secrets manager, never hardcode keys
387-
6. **Revoke compromised keys immediately** - If a key is exposed, delete or rotate it right away
392+
1. **Use descriptive names** - Name keys by their purpose, such as "Production Server" or "Staging".
393+
2. **Rotate regularly** - Rotate keys periodically, such as every 90 days.
394+
3. **Use separate keys per environment** - Create distinct keys for production, staging, and development.
395+
4. **Review inactive keys** - Identify and clean up unused keys.
396+
5. **Store keys in secrets management** - Use environment variables or a secrets manager, never hardcode keys.
397+
6. **Revoke compromised keys immediately** - If a key is exposed, delete or rotate it right away.
388398
389399
## Related Endpoints
390400

0 commit comments

Comments
 (0)