This page documents the live /api/v1/** surface, including desktop companion routes and legacy endpoints that remain reachable but should not be used for new integrations.
Base URL
| Environment | URL |
| ----------- | --------------------------------- |
| Production | https://www.devspeak.dev/api/v1 |
The machine-readable OpenAPI document is served at [/openapi.json](/openapi.json).
| Route family | Auth mechanism | Notes |
| --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------- |
| GET /api/v1/health | None | Public health check |
| GET /api/v1/me, GET /api/v1/auth/confirm, GET /api/v1/history, POST /api/v1/translate, POST /api/v1/optimize, POST /api/v1/refine | x-api-key or Bearer ID token | translate, optimize, and refine are also scope-gated |
| POST /api/v1/auth/session-key | Bearer ID token | Exchanges a signed-in session for a short-lived dsk_sess_ API key |
| POST, GET, DELETE /api/v1/user/config/providers/* | Bearer ID token or session key | Also gated by the customProvider feature |
| POST /api/v1/desktop/session | Bearer ID token or session key | Creates a desktop session token |
| Remaining /api/v1/desktop/* routes | Desktop session Bearer token | Requires a desktop session (/metadata and /download are public) |
A Bearer ID token is the short-lived token issued when you sign in to DevSpeak with Google, GitHub, or AgentID. Send it as Authorization: Bearer .
| Scope | Meaning |
| ----------------- | ----------------------------------------------------------------------------------------- |
| optimize:write | Required for POST /api/v1/translate, POST /api/v1/optimize, and POST /api/v1/refine |
| translate:write | Reserved for future use |
| history:read | Reserved for future use |
| notes:write | Required for POST /api/v1/notes/:noteId/patch |
| Scope | Meaning |
| ------------------------ | -------------------------------------------------------- |
| desktop:projects:read | Read desktop project registrations |
| desktop:projects:write | Upsert projects, reconcile manifests, and inject context |
| desktop:notes:read | List notes |
| desktop:notes:write | Patch a note |
| desktop:sync:read | Poll legacy sync events |
| Scope | Limit | Window |
| ------------------------------------------------------- | --------------- | ------------------- |
| Per API key | 100 requests | 1 minute |
| Global per IP | 180 requests | 15 minutes |
| Desktop session creation and write-heavy desktop routes | strictLimiter | Applied server-side |
Responses can include:
``text
RateLimit-Limit: 100
RateLimit-Remaining: 97
RateLimit-Reset: 1711296060
`Endpoint inventory
Core routes
| Method | Path | Auth | Status | Summary |
| -------- | --------------------------------------- | ----------------------- | --------- | ----------------------------------------------------- |
| GET | /api/v1/health | None | Supported | Public health check |
| GET | /api/v1/me | API key or ID token | Supported | Identity, tier, feature, quota, and usage summary |
| GET | /api/v1/me/entitlements | API key or ID token | Supported | Signed entitlements token for client-side caching |
| GET | /api/v1/auth/confirm | API key or ID token | Supported | Validates an active session key or signed-in session |
| POST | /api/v1/auth/session-key | ID token | Supported | Creates an ephemeral API key with requested scopes |
| POST | /api/v1/auth/session-renew | Session key | Supported | Exchanges the current session key for a successor |
| POST | /api/v1/auth/session-revoke | Session key | Supported | Revokes the presented session key |
| GET | /api/v1/history | API key or ID token | Supported | Paginated translation history |
| POST | /api/v1/translate | API key or ID token | Supported | Stage 2 full translation pipeline |
| POST | /api/v1/optimize | API key or ID token | Supported | Stage 1 copy-edit (lexical optimization) |
| POST | /api/v1/optimize/summarize | API key or ID token | Supported | Condenses over-length input (up to 15,000 characters) |
| POST | /api/v1/refine | API key or ID token | Supported | Stage 3 iterative refinement |
| POST | /api/v1/detect-language | API key or ID token | Supported | Detects the language of an input |
| POST | /api/v1/notes/:noteId/patch | API key or ID token | Supported | Surgical note edit; requires notes:write |
| GET | /api/v1/user/config/providers/current | ID token or session key | Supported | Fetch the active custom provider configuration |
| POST | /api/v1/user/config/providers/test | ID token or session key | Supported | Validate a provider draft without saving it |
| POST | /api/v1/user/config/providers | ID token or session key | Supported | Save a custom provider configuration |
| DELETE | /api/v1/user/config/providers/current | ID token or session key | Supported | Clear the active custom provider configuration |Desktop companion routes
| Method | Path | Auth | Status | Summary |
| ------- | -------------------------------- | ----------------------- | --------- | ------------------------------------------------------------------------------- |
| GET | /api/v1/desktop/appcast.xml | None | Supported | Sparkle update feed for the macOS app |
| GET | /api/v1/desktop/metadata | None | Supported | Current desktop build metadata |
| GET | /api/v1/desktop/download | None | Supported | Signed download URL for the current desktop build |
| POST | /api/v1/desktop/session | ID token or session key | Supported | Create or refresh a desktop session token |
| GET | /api/v1/desktop/projects | Desktop session | Supported | List registered desktop projects |
| POST | /api/v1/desktop/projects | Desktop session | Supported | Upsert a desktop project |
| POST | /api/v1/desktop/projects/sync | Desktop session | Supported | Reconcile a SHA-256 manifest against server state |
| GET | /api/v1/desktop/sync | Desktop session | Legacy | Poll sync events. Inventory flags this as orphaned; avoid for new integrations. |
| POST | /api/v1/desktop/context/inject | Desktop session | Supported | Build a compact workspaceContext string from local chunks |
| GET | /api/v1/desktop/notes | Desktop session | Supported | List the caller's notes |
| PATCH | /api/v1/desktop/notes/:noteId | Desktop session | Supported | Patch a note |GET /api/v1/healthCore request and response schemas
No authentication required.
`json
{
"success": true,
"data": {
"status": "ok",
"version": "v1",
"timestamp": "2026-04-13T12:00:00.000Z"
}
}
`GET /api/v1/me
Returns the authenticated caller's effective plan and usage context.
`json
{
"success": true,
"data": {
"userId": "abc123",
"tier": "DEVELOPER",
"features": ["apiAccess", "iterativeRefinement", "customProvider"],
"limits": {
"managedTokensPerMonth": 10000000,
"maxApiKeys": 3
},
"usage": {
"refinementCount": 12,
"currentPeriod": "2026-04"
},
"quotas": {
"managedTokens": 10000000,
"apiKeys": 3,
"managedTokensUnlimited": false,
"apiKeysUnlimited": false
}
}
}
`GET /api/v1/auth/confirm
Confirms that the supplied API key or signed-in session (Bearer ID token) maps to an active user.
`json
{
"success": true,
"data": {
"userId": "abc123",
"confirmed": true
}
}
`POST /api/v1/auth/session-key
Bearer ID token required.
Request body
| Field | Type | Required | Notes |
| ------------------ | ---------- | -------- | -------------------- |
| scopes | string[] | No | Defaults to [ |
| "optimize:write"] |
| label | string | No | Max 100 characters |
Response
`json
{
"success": true,
"data": {
"key": "dsk_sess_a1b2c3d4e5f6...",
"expiresAt": "2026-04-14T12:00:00.000Z",
"scopes": ["optimize:write"]
}
}
`GET /api/v1/history
Query parameters
| Param | Type | Required | Default | Notes |
| -------- | -------- | -------- | ------- | ---------------------------- |
| limit | number | No | 50 | Clamped to 1..200 |
| offset | number | No | 0 | Zero-based pagination offset |
Response
`json
{
"success": true,
"data": {
"items": [
{
"id": "translation_123",
"inputText": "We need a login page that works with Google.",
"output": "## Technical Specification...",
"audience": "Senior Dev",
"context": "Backend",
"format": "Technical Spec",
"tone": 75,
"wordCount": 847,
"latencyMs": 3200,
"model": "
"provider": "
"createdAt": "2026-04-13T12:00:00.000Z"
}
],
"total": 1,
"offset": 0,
"limit": 50
}
}
`POST /api/v1/translate
Stage 2 full generation. Requires optimize:write and the apiAccess feature.
With format: "Optimize" the response is not a document: data.output is your input improved, in the language you wrote it in, exactly as /api/v1/optimize would return it (add correctionsOnly: true for corrections only). tone, attachments, contextProjectId, webSearch, and responseFormat: "xml" do not apply to it. The result is saved to history like any other translation.
Request body
| Field | Type | Required | Notes |
| -------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| input | string | Yes | 10..10000 characters |
| audience | enum | Yes | Junior Dev, Senior Dev, Tech Lead, SRE, Data Engineer |
| context | enum | Yes | Backend, Frontend, Mobile, Data/ML, DevOps |
| format | enum | Yes | Technical Spec, Jira Tickets, API Design, RFC, Data Model, Prompt, Optimize |
| tone | integer | Yes | 0..100 |
| customInstructions | string | No | Max 10000 characters |
| workspaceContext | string | No | Max 150000 characters |
| translationId | string | No | Max 200 characters, ^[A-Za-z0-9_-]+$ |
| webSearch | boolean | No | RAG grounding with real-time web retrieval. Defaults to false |
| responseFormat | enum | No | markdown (default) or xml for a machine-readable document |
| managedModel | string | No | Pins a managed model. Rejected unless present in the server allowlist. Omit it to let the LLM router choose from the request signals |
| correctionsOnly | boolean | No | Only with format: "Optimize". false (default) improves the text; true fixes spelling, grammar and punctuation only |
`bash
curl -X POST https://www.devspeak.dev/api/v1/translate \
-H "Content-Type: application/json" \
-H "x-api-key: $DEVSPEAK_API_KEY" \
-d '{
"input": "We need a login page that works with Google and saves user data",
"audience": "Senior Dev",
"context": "Backend",
"format": "Technical Spec",
"tone": 75,
"customInstructions": "Call out persistence, observability, and rollout concerns."
}'
`
Response envelope
`json
{
"success": true,
"data": {
"output": "## Technical Specification: User Authentication System\n\n### 1. Overview\n...",
"metadata": {
"wordCount": 847,
"latencyMs": 3200,
"model": "
"provider": "
"credentialSource": "managed",
"endpointHost": null
}
}
}
`POST /api/v1/optimize
Stage 1 optimization. Requires optimize:write. It returns your text improved in the language you wrote it in. By default it improves the whole text (word choice, flow, clarity, and sentence structure, splitting or joining sentences where that reads better) while keeping your meaning, voice, facts, code, and line breaks; with correctionsOnly: true it fixes only spelling, grammar, and punctuation. In both modes, code blocks, inline code, links, tables, and placeholders such as {{name}} come back exactly as you wrote them; only the prose is edited. This endpoint is task-context-free: it accepts only the raw input, audience, context, an optional managedModel pin, and correctionsOnly. Unlike /api/v1/translate, it does not generate a formatted technical document and it does not accept Stage 2 fields such as format, tone, customInstructions, attachments, or contextProjectId — unsupported fields are logged and dropped rather than rejected. Account-level custom instructions stored in your profile still apply. Results are never persisted to translation history.
Request body
| Field | Type | Required | Notes |
| ----------------- | --------- | -------- | ---------------------------------------------------------------------------------------------- |
| input | string | Yes | 10..10000 characters |
| audience | enum | Yes | Same audience enum as /translate |
| context | enum | Yes | Same context enum as /translate |
| managedModel | string | No | Allowlisted managed model pin (optional). Omit it to let the LLM router choose |
| correctionsOnly | boolean | No | false (default) improves the whole text; true fixes spelling, grammar and punctuation only |
`bash
curl -X POST https://www.devspeak.dev/api/v1/optimize \
-H "Content-Type: application/json" \
-H "x-api-key: $DEVSPEAK_API_KEY" \
-d '{
"input": "pls make this spec more professional and clearer",
"audience": "Senior Dev",
"context": "Backend"
}'
`
The response envelope matches /api/v1/translate and returns optimized text in data.output.POST /api/v1/refine
Stage 3 refinement. Requires optimize:write and the iterativeRefinement feature.
Request body
| Field | Type | Required | Notes |
| ------------------ | --------- | -------- | ------------------------------------------------------------------- |
| originalInput | string | No | Defaults to ""; max 10000 characters |
| previousOutput | string | Yes | 1..50000 characters |
| feedback | string | Yes | 1..5000 characters |
| audience | enum | Yes | Same audience enum as /translate |
| context | enum | Yes | Same context enum as /translate |
| format | enum | Yes | Same format enum as /translate |
| tone | integer | Yes | 0..100 |
| workspaceContext | string | No | Max 150000 characters |
| translationId | string | No | Max 200 characters |
| webSearch | boolean | No | RAG grounding. Defaults to false |
| responseFormat | enum | No | markdown (default) or xml |
| managedModel | string | No | Allowlisted managed model pin. Omit it to let the LLM router choose |
`bash
curl -X POST https://www.devspeak.dev/api/v1/refine \
-H "Content-Type: application/json" \
-H "x-api-key: $DEVSPEAK_API_KEY" \
-d '{
"originalInput": "We need a login page that works with Google and saves user data",
"previousOutput": "## Technical Specification...",
"feedback": "Add a section on rate limiting and explicit error codes.",
"audience": "Senior Dev",
"context": "Backend",
"format": "Technical Spec",
"tone": 75
}'
`
Successful responses use the same envelope as /translate, with metadata.isOutOfScope appended when the refinement feedback is rejected as unrelated.Provider configuration routes
These routes accept a Bearer ID token or a session key and require the customProvider feature.GET /api/v1/user/config/providers/current
Returns either the current provider configuration or null when none is configured.
When no custom provider is stored, the route returns:
`json
{
"success": true,
"data": null,
"configured": false
}
`
`json
{
"success": true,
"data": {
"providerType": "gemini",
"providerName": "Gemini",
"apiEndpoint": null,
"metadata": {
"model": "gemini-3-flash-preview",
"label": "Gemini",
"keyPreview": "AIza...1234",
"endpointHost": null
},
"enabled": true,
"updatedAt": "2026-04-13T12:00:00.000Z",
"lastValidatedAt": "2026-04-13T12:00:00.000Z",
"status": "active",
"validationMessage": null
}
}
`POST /api/v1/user/config/providers/testPOST /api/v1/user/config/providers
Both endpoints accept the same draft schema:
| Field | Type | Required | Notes |
| ----------------- | --------- | ------------- | ---------------------------------------------------- |
| providerType | enum | Yes | gemini, openai, anthropic, openai-compatible |
| providerName | string | Yes | 1..100 characters |
| apiKey | string | Conditionally | Required unless isExistingKey is true |
| isExistingKey | boolean | No | Reuse the currently stored encrypted key |
| baseURL | string | Conditional | Required for openai-compatible |
| modelIdentifier | string | No | 1..120 characters |
/test validates a draft without persisting it. /providers validates and saves it. Both return the provider configuration envelope shown above.DELETE /api/v1/user/config/providers/current
Clears the active provider configuration.
`json
{
"success": true,
"data": null
}
`POST /api/v1/desktop/sessionDesktop companion routes
Creates a desktop session token from a request authenticated with a Bearer ID token or a session key.
Request body
| Field | Type | Required | Notes |
| ------------- | -------- | -------- | ----------------------------------------------------- |
| deviceId | string | Yes | 1..128, ^[A-Za-z0-9._:-]+$ |
| deviceLabel | string | Yes | 1..120 characters |
| runtime | enum | No | electron, tauri, unknown; defaults to unknown |
Response
`json
{
"success": true,
"data": {
"token": "desktop-session-token",
"expiresInSeconds": 900,
"scopes": [
"desktop:projects:read",
"desktop:projects:write",
"desktop:notes:read",
"desktop:notes:write",
"desktop:sync:read"
],
"device": {
"deviceId": "desktop-device-1",
"deviceLabel": "MacBook Pro",
"platform": "desktop-app",
"runtime": "electron",
"lastSeenAt": "2026-04-10T12:00:00.000Z"
}
}
}
`GET /api/v1/desktop/projects
Returns an array of registered projects for the authenticated desktop session.
`json
{
"success": true,
"data": [
{
"projectId": "project-1",
"name": "Monorepo",
"rootPath": "/Users/dev/projects/monorepo",
"createdAt": "2026-04-10T11:00:00.000Z",
"updatedAt": "2026-04-10T12:00:00.000Z",
"lastSyncedAt": "2026-04-10T12:00:00.000Z",
"lastSyncedByDeviceId": "desktop-device-1"
}
]
}
`POST /api/v1/desktop/projects
Request body
| Field | Type | Required | Notes |
| ----------- | -------- | -------- | ----------------------------------- |
| projectId | string | Yes | 1..128, desktop identifier format |
| name | string | Yes | 1..128 characters |
| rootPath | string | Yes | 1..2048, path traversal blocked |
Returns the upserted project and emits a sync event server-side.
`json
{
"success": true,
"data": {
"projectId": "project-1",
"name": "Monorepo",
"rootPath": "/Users/dev/projects/monorepo",
"createdAt": "2026-04-10T11:00:00.000Z",
"updatedAt": "2026-04-10T12:00:00.000Z",
"lastSyncedAt": "2026-04-10T12:00:00.000Z",
"lastSyncedByDeviceId": "desktop-device-1"
}
}
`POST /api/v1/desktop/projects/sync
Manifest reconciliation endpoint.
Request body
| Field | Type | Required | Notes |
| ----------- | -------- | -------- | ------------------- |
| projectId | string | Yes | Desktop identifier |
| files | array | Yes | Max 10000 entries |
Each files[] item supports:
| Field | Type | Required | Notes |
| ------------ | --------- | -------- | ---------------------------------- |
| path | string | Yes | Max 1024; path traversal blocked |
| hash | string | Yes | 64-character lowercase SHA-256 |
| sizeBytes | integer | No | 0..50000000 |
| modifiedAt | string | No | ISO 8601 date-time |
Responses return a delta with server-managed upload, delete, and unchanged lists.
`json
{
"success": true,
"data": {
"manifestId": "user-123-project-1-1713000000000",
"toUpload": ["app/main.py", "app/routes/orders.py"],
"toDelete": ["app/deprecated.py"],
"unchanged": ["pyproject.toml"],
"totalFiles": 3
}
}
`POST /api/v1/desktop/context/inject
Builds a compact workspaceContext string from local file chunks.
Request body
| Field | Type | Required | Notes |
| ----------- | -------- | -------- | ------------------------------------------ |
| projectId | string | Yes | Desktop identifier |
| label | string | No | Max 200 characters |
| query | string | No | Max 32000 characters — see ranking below |
| chunks | array | Yes | 1..200 items |
Supply query with the pending translation input. Chunks are ranked against it
— by path, filename, and extracted symbols — so the relevant files survive the
150,000-character context budget. Omit it and assembly falls back to the order the
chunks were sent in, which silently drops whatever arrives last.
Each chunks[] item supports:
| Field | Type | Required | Notes |
| --------- | ---------- | -------- | --------------------- |
| path | string | Yes | Max 1024 characters |
| content | string | Yes | Max 8192 characters |
| symbols | string[] | No | Max 100 entries |
`json
{
"success": true,
"data": {
"contextId": "ctx-project-1-1713000000000",
"contextText": "# Project Context: project-1\n# Source files: 2\n...",
"tokenEstimate": 742,
"chunkCount": 2,
"includedCount": 2
}
}
`
includedCount reports how many chunks fit the budget. A value below
chunkCount means context was dropped.GET /api/v1/desktop/sync
Legacy sync polling endpoint. Do not use it for new integrations.
Query parameters
| Param | Type | Required | Notes |
| ----------- | -------- | -------- | --------------------------- |
| projectId | string | Yes | Desktop identifier |
| after | string | No | ISO 8601 cursor |
| limit | number | No | Defaults to 50, max 100 |
The inventory flags this route as orphaned. It remains reachable for compatibility, but new desktop clients should use POST /api/v1/desktop/projects/sync instead.
`json
{
"success": true,
"data": {
"items": [
{
"eventId": "event-1",
"eventType": "translation.upserted",
"userId": "user-123",
"occurredAt": "2026-04-10T12:30:00.000Z",
"source": "api-v1",
"projectId": "project-1",
"entityType": "translation",
"entityId": "translation-1",
"payload": {
"translationId": "translation-1"
}
}
],
"nextCursor": "2026-04-10T12:30:00.000Z",
"serverTime": "2026-04-10T12:30:05.000Z"
}
}
`POST /api/v1/optimize-inputLegacy and deprecated routes
Returns 308 Permanent Redirect to /api/v1/optimize. The current sunset in the inventory is 2026-10-01.POST /api/v1/user/config/keys
Legacy BYOK upsert route. It duplicates the provider configuration flow and is gated by the same customProvider entitlement. Do not build new integrations on this endpoint.Common error envelope
`json
{
"success": false,
"error": "Human-readable error message",
"error_code": "ERR_VALIDATION_FAILED",
"details": []
}
`
| Status | Meaning |
| ------ | ---------------------------------------- |
| 400 | Validation failure |
| 401 | Missing, invalid, or expired credentials |
| 403 | Missing scopes, feature access, or quota |
| 422 | Provider validation failure |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
| 504 | Upstream timeout |CLI and route rendering notes
and /docs/cli-guide` through explicit MDX registrations, so these pages render consistently across local, staging, and production builds.