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 Firebase Bearer token | translate, optimize, and refine are also scope-gated |
| POST /api/v1/auth/session-key | Firebase Bearer token | Exchanges a Firebase session for a short-lived dsk_sess_ API key |
| POST, GET, DELETE /api/v1/user/config/providers/* | Firebase Bearer token | Also gated by the customProvider feature |
| POST /api/v1/desktop/session | Firebase Bearer token | Creates a desktop session token |
| Remaining /api/v1/desktop/* routes | Desktop session Bearer token | Enforced by authenticateDesktopSession |
| 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 |
| Scope | Meaning |
| ------------------------ | -------------------------------------------------------- |
| desktop:projects:read | Read desktop project registrations |
| desktop:projects:write | Upsert projects, reconcile manifests, and inject context |
| desktop:sync:read | Poll legacy sync events |
| Scope | Limit | Window |
| ------------------------------------------------------- | --------------- | ------------------- |
| Per API key | 100 requests | 1 minute |
| Global per IP | 100 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 Firebase | Supported | Identity, tier, feature, quota, and usage summary |
| GET | /api/v1/auth/confirm | API key or Firebase | Supported | Validates an active session key or Firebase-authenticated session |
| POST | /api/v1/auth/session-key | Firebase | Supported | Creates an ephemeral API key with requested scopes |
| GET | /api/v1/history | API key or Firebase | Supported | Paginated translation history |
| POST | /api/v1/translate | API key or Firebase | Supported | Stage 2 full translation pipeline |
| POST | /api/v1/optimize | API key or Firebase | Supported | Stage 1 DeepL Write-style lexical optimization |
| POST | /api/v1/refine | API key or Firebase | Supported | Stage 3 iterative refinement |
| GET | /api/v1/user/config/providers/current | Firebase | Supported | Fetch the active custom provider configuration |
| POST | /api/v1/user/config/providers/test | Firebase | Supported | Validate a provider draft without saving it |
| POST | /api/v1/user/config/providers | Firebase | Supported | Save a custom provider configuration |
| DELETE | /api/v1/user/config/providers/current | Firebase | Supported | Clear the active custom provider configuration |Desktop companion routes
| Method | Path | Auth | Status | Summary |
| ------ | -------------------------------- | --------------- | --------- | ------------------------------------------------------------------------------- |
| POST | /api/v1/desktop/session | Firebase | 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/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": {
"managedTokens": 10000000,
"apiKeys": 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 Firebase session maps to an active user.
`json
{
"success": true,
"data": {
"userId": "abc123",
"confirmed": true
}
}
`POST /api/v1/auth/session-key
Firebase Bearer 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.
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 5000 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 |
`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 lexical optimization. Requires optimize:write. This endpoint is deterministic and task-context-free: it accepts only the raw input, audience, context, and an optional managedModel pin. 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) |
`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 5000 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 |
`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 are Firebase-authenticated only 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 Firebase-authenticated request.
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: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..50 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
32k 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. The inventory marks it as a security risk because it duplicates the provider configuration flow without the customProvider entitlement gate. Do not build new integrations on this endpoint.Common error envelope
`json
{
"success": false,
"error": "Human-readable error message",
"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.