Saltar al contenido principal
Buscar herramientas
Tema

Guardado solo en este dispositivo.

Español

API reference

This page documents the public + user-shell HTTP surfaces exposed by the ToolNexus web application. Every endpoint here is cookie-authenticated and reachable from the same host that serves the UI — there is no separate API origin and no API-key bearer scheme today.

For the higher-level platform map (admin API surface, internal services, runtime worker proxy) see the project wiki: https://codecloudclub.github.io/ToolNexus-V2/18-Public-Api-Surface/.

Conventions

  • Base URL: the same origin that serves the UI (e.g. https://app.example.com).
  • Authentication: ASP.NET Core Identity session cookie. Sign in at /auth/login first; the server sets the cookie and every subsequent request must include it.
  • Anti-forgery: every state-changing request (POST / PUT / DELETE) must include the __RequestVerificationToken field issued for the current page. Posts that omit the token are rejected with 400 Bad Request. The token rotates per session — read it from the Razor @Html.AntiForgeryToken() field on the page you are calling from, or by visiting the form page first and reading the hidden input.
  • Content type: the provider endpoints take and return application/json. The profile-settings endpoints are Razor form posts (application/x-www-form-urlencoded, plus multipart/form-data for the avatar upload).
  • Errors: validation errors surface via ModelState and are re-rendered onto the originating page (200 OK with the form re-rendered, anti-forgery refreshed). Hard rejections (anti-forgery, not signed in, not found) use the standard HTTP status codes — 400, 401, 404.

Bring-your-own-key providers — /user/providers/*

Per-user provider management stores third-party AI credentials (OpenAI, Anthropic, Gemini, Groq, OpenRouter, and any other OpenAI-compatible endpoint) on a per-user basis, together with the models and combos that use them. Keys are encrypted at rest with ASP.NET Core Data Protection under a per-user purpose string (AiProvider:{userId}); the plaintext is never logged and never returned — responses expose only hasKey and a masked keyHint.

Every route below is owner-scoped: the service layer filters on OwnerUserId == userManager.GetUserId(User), so a caller can never read, edit, or delete an administrator's global provider or another user's provider.

Retired predecessor. An earlier flat vault lived at /user/vault and stored one key per provider name. It has been removed; VaultToProviderMigrationHostedService folds any legacy UserVaultCredentials rows into the provider store on startup. Requests to /user/vault now 404.

Limits: 10 providers per user, 20 models per provider, 10 keys ("accounts") per provider.

GET /user/providers

Renders the provider management page. HTML only; the data is fetched by the page from /user/providers/list.

Auth: session cookie required. Unauthenticated callers are redirected to /auth/login.

GET /user/providers/list

Returns the caller's providers with their models and accounts.

{
  "providers": [
    {
      "id": "…", "name": "My OpenAI", "type": "openai-compatible",
      "baseUrl": "https://api.openai.com", "status": "active",
      "healthStatus": "…", "hasKey": true, "roundRobinAccounts": false,
      "models": [ { "modelId": "gpt-4o-mini", "displayName": "…", "capabilities": "generation,assistant", "isEnabled": true } ],
      "accounts": [ { "id": "…", "name": "Key 1", "keyHint": "sk-…abcd", "hasKey": true, "isEnabled": true, "priority": 0, "healthStatus": "…" } ]
    }
  ]
}

POST /user/providers/create

Registers a provider. JSON body: name (required), type, baseUrl (required unless the type is a web search/fetch provider, which carries an adapter-owned base URL), apiKey (required). The server generates the id — a client-supplied id is ignored.

Responses: 200 OK with { id, name, type }; 400 Bad Request for a missing name, an invalid base URL, a missing key, or the 10-provider cap; 401 Unauthorized when not signed in.

`POST /user/providers/

Replaces the provider-level key. JSON body: apiKey (required). Returns 200 OK, or 404 Not Found when the provider is not owned by the caller.

`POST /user/providers/

Sets status to active or inactive. Any other value is 400 Bad Request.

`POST /user/providers/

Edits (name, type, baseUrl) or deletes the provider. Delete is a hard delete and cascades to the provider's models and accounts; it returns 204 No Content.

Models

MethodRoutePurpose
GET/user/providers/{providerId}/models/availableAsk the provider for its model list ({ models: [...] }; a provider error returns an empty list plus error).
POST/user/providers/{providerId}/modelsAdd a model (modelId required; capabilities defaults to generation,assistant).
POST/user/providers/{providerId}/models/enabled?modelId=…Enable / disable a model.
POST/user/providers/{providerId}/models/capabilities?modelId=…Replace the capability list.
DELETE/user/providers/{providerId}/models?modelId=…Remove a model.

modelId travels in the query string rather than the route because provider model ids contain /, and an encoded slash in a path segment is rejected by routing.

Accounts (multiple keys per provider)

POST /user/providers/{providerId}/accounts adds a named key; …/accounts/{accountId} edits it (name, priority), …/accounts/{accountId}/apikey rotates it, …/accounts/{accountId}/enabled toggles it, and DELETE …/accounts/{accountId} removes it. With roundRobinAccounts enabled the executor rotates across the enabled accounts by ascending priority.

Connectivity checks

  • POST /user/providers/{providerId}/test — sends a one-word prompt through the provider's first enabled model and returns { ok, reply, latencyMs } or { ok: false, error }.
  • POST /user/providers/test-batch — runs the same check across every active provider and returns per-provider results plus pass/fail totals.
  • POST /user/providers/playground — sends an arbitrary message (providerId, optional modelId, message, stream). With stream: true the response is text/event-stream carrying { token } frames, then { done, model }; providers without streaming support fall back to a single { content } frame.

Encryption story

  • Each user gets a distinct IDataProtector derived from the shared Data Protection key ring, with the purpose string AiProvider:{userId} (administrator-global providers use the global scope).
  • DataProtectionSecretProtector.Protect(scope, plainText) produces the ciphertext persisted in the provider / provider-account row. Even with raw database access, a ciphertext written for user A cannot be decrypted with user B's protector.
  • Decryption happens only when the execution path resolves a key for an outbound provider call. The management UI reads back keyHint and hasKey, never the ciphertext or the plaintext.
  • Data Protection key rotation is operator-controlled. Rotating the key ring without preserving the old keys invalidates every stored credential; there is no automatic re-encryption migration.

Profile settings — /user/settings/profile

The profile page exposes display-name editing, an email-change request flow with confirmation by re-clicking a link from the new address, and avatar upload. All three are cookie-authenticated and anti-forgery protected.

GET /user/settings/profile

Renders the profile settings page with the current display name, email, pending email change (if any), and avatar.

POST /user/settings/profile

Updates the display name. Trims whitespace, requires [Required], enforces StringLength(120). Refreshes the auth cookie via RefreshSignInAsync so the new display name appears in the shell without requiring a sign-out.

POST /user/settings/profile/email

Begins an email change. Validates the new address format and uniqueness, calls UserManager.GenerateChangeEmailTokenAsync, and dispatches a confirmation email to the new address (never the current address). The current email remains the authoritative login until the confirmation link is followed.

GET /user/settings/profile/confirm-email-change

Confirms the email change. Validates the user id, the new email, and the token (query-string parameters), then calls UserManager.ChangeEmailAsync. On success the email + normalized email columns are updated and a fresh confirmation cookie is issued.

POST /user/settings/profile/avatar

Uploads an avatar image. Accepts a multipart file in the Avatar field; the controller validates the MIME type (PNG or JPEG only), size (≤ 256 KiB), and persists the encrypted bytes to ApplicationUser.AvatarImage via Data Protection. The current avatar renders in _AccountMenu.cshtml and on the profile page itself.

POST /user/settings/profile/avatar/remove

Deletes the saved avatar. Clears AvatarImage and re-renders the profile page; the account menu falls back to the generated initials avatar.

Other surfaces

For the remaining public surfaces — docs search at /docs/search, the public roadmap/changelog/feedback endpoints, and the admin API — see the project wiki at https://codecloudclub.github.io/ToolNexus-V2/. The wiki is the source of truth for cross-surface reference docs; the page you are reading now is intentionally scoped to the user-shell endpoints linked from the dashboard /docs#api CTA.