# Argus Media asset manager with creative intelligence. Upload images, get AI analysis that reads creative signal — composition intent, mood, and visual fitness for purpose, not just tags. Semantic search. REST + MCP. Website: https://argus.build MCP: https://argus.build/mcp (hosted, HTTP transport — nothing to install) ## Authentication All API endpoints require: Authorization: Bearer Two token types. The prefix tells you which you have: at_ Agent session token. Short-lived (4, 8, or 24 hours), bound to exactly one workspace, revocable, and it cannot escalate: an at_ token may not mint API keys, mint or refresh another at_ token, manage members, or touch billing. This is the ONLY token that is safe to paste into a chat transcript, and it is what "Connect agent" hands you. Mint: https://argus.build/ui -> library toolbar -> "Connect agent". Renew: https://argus.build/settings/agent-sessions ak_ API key. Durable, no expiry. For environment variables and CI only. Never paste an ak_ key into a chat transcript — it does not expire, so it stays valid in the transcript for as long as the key lives. Mint: https://argus.build/ui -> Settings -> Create API Key. Session auth (cookie-based) is used by the web UI. Verify any token immediately, without restarting your session: curl -s https://argus.build/auth/me -H "Authorization: Bearer at_YOUR_TOKEN" That returns the workspace the token reaches, the scope it carries, and when it expires. Use it to confirm a connection in the same turn you were given the token — MCP tools themselves only load at the next session start. It checks this token only, NOT the credential your MCP tools will use. Those are two different things. If an Argus server is already configured in your client from an earlier session, its Authorization header wins, and it may be bound to a different workspace: the curl looks perfect and every mcp__argus__* call still reads someone else's library. So once the tools load, confirm the binding in-band before you trust a result: get_usage workspace.id + workspace.name the session is actually on list_workspaces every workspace the credential's owner can see, current flagged Treat an unexpected workspace name as a misconfiguration, not a data problem. An empty search in the wrong workspace is indistinguishable from an asset that does not exist, so search_assets and get_library_overview both return a "workspace" field and warn when they come back empty. Never conclude an asset is missing without checking which library you looked in. ## Connect agent The one-paste flow. In the Argus library toolbar the user clicks "Connect agent"; Argus mints an at_ token and copies a self-contained block. The block carries the endpoint, the real token inline, the exact command, and the verify step — so there is no second round-trip to ask for a credential. Starting from nothing — no workspace visited yet, no token in hand? Fetch https://argus.build/agent-setup/prompt.md instead and follow it; it leads with OAuth, so no credential needs to exist before you begin. What you will be pasted looks like this (with a real token in place of at_YOUR_TOKEN): Connect to my Argus media library (https://argus.build — AI-native media asset manager). Add the server: claude mcp add --transport http argus https://argus.build/mcp --header "Authorization: Bearer at_YOUR_TOKEN" Verify it now (works before any restart): curl -s https://argus.build/auth/me -H "Authorization: Bearer at_YOUR_TOKEN" Token scope: workspace "your workspace", read/write access to assets, expires 8 hours after you copy it. Flow reference for agents: https://argus.build/agents.txt (see "Connect agent") MCP tools appear on the next session start; the verify call confirms access immediately. That check covers this token only. If an Argus MCP server is already configured in your client, it keeps using that older credential, which may be bound to a different workspace. Once the tools load, call get_usage and confirm workspace.name is "your workspace" before trusting any result — including an empty one. This is a short-lived agent token. Renew: https://argus.build/settings/agent-sessions Run the add command, then the verify command, then tell the user the workspace name, scope, and expiry the verify call returned. If the token is expired or revoked the response carries a "remediation" field naming the fix — relay it rather than guessing. If "claude mcp add" reports the server already exists, do not assume the existing one is equivalent: it is the case that silently binds you to the wrong workspace. Remove it and re-add with the token you were given. Do NOT run any command on this page with at_YOUR_TOKEN left in it. That string is a placeholder, and "claude mcp add" does not validate the token: the command SUCCEEDS, registering a server that cannot authenticate. The real command then fails with "MCP server argus already exists", and the user is stuck until they run: claude mcp remove argus Wait for a block containing a token that starts with at_ and continues with random characters. If you do not have one, ask for it rather than substituting your own value. ## REST API (https://argus.build) Assets (Bearer API key): POST /assets/upload Upload image (multipart/form-data, field: file, max 20 MB). Byte-identical re-uploads return 409 with the existing asset; pass allow_duplicate=true (or ?force=1) to store anyway. Uploading a NEW VERSION of an existing asset? Send version_of= (see Versioning below) — not allow_duplicate. POST /assets/upload-from-url Upload from URL (body: { url, filename?, project?, tags?, uploadedBy?, versionOf?, allowDuplicate? }). Exact duplicates return 409 unless allowDuplicate. Use versionOf for a new revision of a known asset. Versioning — say so, don't make Argus guess: Set version_of (multipart) / versionOf (JSON body); ?versionOf= works on both endpoints. It is an assertion that this file is a new revision of that asset id, and it always wins. Without it Argus may still infer a version link from perceptual-hash similarity — a guess that misses on crops, recolors and format changes. Do not reach for allow_duplicate to work around a missed guess, and do not try to bridge revisions by similarity: versionOf is the mechanism. Sending versionOf together with allowDuplicate/?force=1 is a 400 — they ask for opposite things. Full reference: https://argus.build/agent-skills/argus (section "Versioning"). POST /assets/:id/analyze Trigger AI analysis on existing asset (costs 1 credit) POST /assets/:id/adapt-palette Recolor an SVG to a target brand palette (body: { paletteFit: , save? }). Deterministic, zero credits; saves a derived asset with derivedFrom provenance (save:false previews only). Source untouched. GET /assets/duplicates Review duplicates in the workspace (near-duplicate flags + exact-hash groups) GET /library/overview Library read model in one call: totals, counts by project/media-type/status/approval, top tags, recent uploads (query: top_tags, recent) GET /assets List/search assets (query: q, project, status, tags, limit, offset; status=deleted to list soft-deleted) GET /assets/:id Get single asset with full analysis PATCH /assets/:id Update tags, project, or uploadedBy (body: { tags?, project?, uploadedBy? }) DELETE /assets/:id Soft-delete asset (recoverable for 30 days, then permanently purged) POST /assets/:id/recover Recover a soft-deleted asset (within 30-day window) Derivative work — "this asset was made FROM that one" (BRO-2240). Distinct from versioning: a version replaces an asset's bytes under the same id; a derivative is a SEPARATE asset whose origin you are recording. Splitting one scan into 20 crops is 20 derivatives, not 20 versions. POST /assets/:id/derivatives Assert derivatives of :id. Body { derivativeAssetId | derivativeAssetIds: [...], relationType, evidenceHash?, evidenceNote? }. relationType: extracted_from (a crop/isolation of the source) | reported (a third party states it produced this, outside Argus). Batch is all-or-nothing, max 100, and re-asserting is idempotent. Requires uploader. Attribution is server-derived — assertedBy/assertedAt are never read from the body. GET /assets/:id/derivatives One direction of the graph, paginated (query: direction=children|parents, limit, offset) GET /assets/:id/lineage Ancestor chain, "A came from B came from C" (query: depth, default 5, max 20). Bounded: a cyclic graph terminates and reports truncated:true. GET /assets/:id also embeds both directions inline as derivativeLinks { parents, children }, capped at 20 each — use the endpoints above past that. Design Skills (Bearer API key) — branded-collateral recipes (skill + brand kit + assets → collateral): GET /skills List design skills ({ id, name, description, category, version }) GET /skills/:id Full skill markdown (frontmatter + instructions) as text/markdown catalog.design reference collection (Bearer API key): GET /catalog-design List 105 published design resources (query: category, q) GET /catalog-design/:id Get one resource and its creative-tool recipe when available Agent Skills (public — always-current source, npm tarball is an offline fallback): GET /agent-skills List agent skill manuals ({ name, description }) GET /agent-skills/:name Full SKILL.md (frontmatter + instructions) as text/markdown Auth (public): POST /auth/magic Request magic link (body: { email }) GET /auth/magic/verify Verify magic link token (query: token) POST /auth/magic/verify Verify magic code (body: { email, code }) POST /auth/logout Clear session GET /auth/me Current user info and workspace list Invite claim (public, agent-native — no browser/cookies needed): POST /invite/:token/claim Start invite claim (body: { email }) → sends verification code POST /invite/:token/claim/verify Verify code and join workspace (body: { email, code }) → returns { apiKey, workspaceId, workspaceName, role } Workspaces (session cookie): POST /workspaces Create workspace GET /workspaces List workspaces PUT /workspaces/:id/switch Switch active workspace GET /workspaces/:id/members List members POST /workspaces/:id/invites Create invite link (admin only) DELETE /workspaces/:id/members/:userId Remove member (admin only) API Keys (session cookie): GET /keys List active keys POST /keys Create new key DELETE /keys/:id Revoke key Billing: POST /checkout/session Retired — 410 checkout_endpoint_retired. Use Settings → Billing & usage. POST /checkout/credits Retired — 410 checkout_endpoint_retired. Use Settings → Billing & usage. GET /usage Credits and asset usage for current workspace Telemetry: POST /sync/telemetry Report skipped-filetype counts from a folder sync (body: { skipped: [{ ext, reason, count }, ...] }; extensions + counts only, never filenames/paths; reason ∈ unsupported_type|too_large|junk|duplicate) ## HTTP clients Note: raw Python stdlib `urllib` requests are blocked at the edge (Cloudflare 403, error 1010 — banned UA signature). Set any custom `User-Agent` header (e.g. `my-agent/1.0`), or use `python-requests`/curl/the Argus CLI/MCP, which all pass. ## Workflow Example Upload an image, wait for analysis, then search for it: # Upload curl -X POST https://argus.build/assets/upload \ -H "Authorization: Bearer ak_yourkey" \ -F "file=@product-photo.jpg" \ -F "project=spring-campaign" \ -F "tags=product,lifestyle" # Check status (analysis runs automatically on upload) curl https://argus.build/assets/abc123 \ -H "Authorization: Bearer ak_yourkey" # status: "pending" → "ready" once analysis completes # Search curl "https://argus.build/assets?q=lifestyle+product" \ -H "Authorization: Bearer ak_yourkey" # Upload from URL (no file download needed) curl -X POST https://argus.build/assets/upload-from-url \ -H "Authorization: Bearer ak_yourkey" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/photo.jpg","project":"imports"}' ## CLI Install the CLI for terminal workflows and folder onboarding: npm install -g argus-media-cli Bulk-onboard a folder (recursive walk, filter, dedup, sequential upload): argus-media-cli sync ./legacy-brand-folder --dry-run # preview the plan argus-media-cli sync ./legacy-brand-folder --project brand-2019 --tags legacy sync uploads JPEG/PNG/GIF/WebP/SVG ≤ 20 MB and skips junk files (.DS_Store, Thumbs.db, hidden dotfiles), unsupported types, oversize files, and duplicates (by content hash, and by filename+size already in the target project). It prints a manifest of synced/skipped/failed assets (--json for structured output) and warns before exceeding remaining credits (--yes to proceed unattended). The inverse is init — the pull-side front door. When a project's assets already live in Argus and you have a fresh or empty folder, bind the folder to that project instead of syncing: argus-media-cli init brand-2019 # → .argus/DESIGN-CONTEXT.md + AGENTS.md init is read-only (zero credits): it resolves the project, writes the project's style guide as .argus/DESIGN-CONTEXT.md, and drops agent context telling a fresh agent how to pull assets (search scoped to the project, get_asset for inline SVG logos). Route by direction: files in hand → sync; empty folder + a project name → init. ## Install the agent skills The CLI bundles two Claude agent skills: "argus" (this API/MCP operating manual) and "argus-brand-onboarding" (the legacy-folder → brand-system onboarding arc). Install them into your Claude skills directory: npx argus-media-cli skills install # → ~/.claude/skills//SKILL.md argus-media-cli skills install --dir # custom target Idempotent (overwrites with newer content). Restart the agent session afterward so it loads the skills, then trigger one explicitly for determinism, e.g. "use the argus-brand-onboarding skill to onboard this brand folder into Argus". Brand onboarding's cohesion / style-guide / creative-direction stages run over the MCP tools below, so connect the MCP server (see "MCP Tools") too. ## MCP Tools Connect your agent to Argus. The server is hosted — there is no package to install, and no npm name to resolve: claude mcp add --transport http argus https://argus.build/mcp --header "Authorization: Bearer at_YOUR_TOKEN" Two ways to authenticate that endpoint: 1. Paste a short-lived at_ token in the header, as above. This is what "Connect agent" gives you, and it works in headless sessions where a browser OAuth flow cannot run. 2. Omit the --header entirely and let the client run the OAuth flow: claude mcp add --transport http argus https://argus.build/mcp Interactive sessions should prefer this — no credential enters the transcript at all. A stdio package (argus-media-mcp) is also published. It exists for uploading files from the local filesystem, which a hosted server cannot reach. It is not the connection path — prefer the hosted endpoint above. Available tools: search_assets Natural-language semantic search over AI-analyzed assets (embeddings + vector index), relevance-ranked. Tag/status/project act as composable filters. Omit query to browse. Params: query?, project?, status?, tags?, limit?, offset? get_asset Get full asset details including AI analysis. SVG assets also carry a deterministic "vector" block (viewBox, dimensions, exact palette, inline source). Params: asset_id (required) upload_from_url Upload and auto-analyze an image from URL Params: url (required), filename?, project?, tags?, uploaded_by? argus_upload Upload a local file by path with automatic AI analysis Params: file_path (required), project?, tags?, uploaded_by? analyze_asset Trigger AI analysis on an existing asset Params: asset_id (required) delete_asset Soft-delete asset (hidden from listings, recoverable within 30 days) Params: asset_id (required) recover_asset Recover a soft-deleted asset back to the active library (within 30-day window) Params: asset_id (required) get_usage Check workspace credits, asset count, and tier limits Params: (none) create_api_key Create a new API key for the workspace (returned once — store securely) Params: name (required) list_design_skills List the Argus Design Skills — branded-collateral recipes (deck, one-pager, social-card) Params: (none) get_design_skill Get one design skill's full instructions to follow (pair with get_brand_kit + search_assets) Params: id (required) list_catalog_design_resources Browse the catalog.design reference collection and its creative-tool recipes Params: category?, query? get_catalog_design_resource Get one catalog.design resource and its recipe when available Params: id (required) ## Asset Response Shape { "id": "uuid", "filename": "photo.jpg", "mimeType": "image/jpeg", "url": "https://...", "status": "ready", "tags": ["outdoor", "nature"], "analysis": { "description": "A field of wildflowers at golden hour", "richDescription": "Detailed paragraph with mood, composition, suggested uses...", "mood": "serene", "dominantColors": [{ "oklch": "oklch(0.81 0.17 85)", "hex": "#f5c842", "name": "golden yellow" }], "useCases": ["hero image", "seasonal campaign"], "tags": ["wildflowers", "golden hour", "landscape"] } } Field notes: - tags: Merged array of user-supplied + AI-generated tags (deduplicated) - analysis.tags: AI-generated tags only ## Vector Metadata (image/svg+xml only) SVG assets additionally carry a "vector" block, parsed deterministically from the source at ingest (not sampled from a raster). This recovers exact values that rasterization loses — real viewBox/dimensions and the precise brand palette: { "vector": { "viewBox": "0 0 80 80", "intrinsicWidth": 80, "intrinsicHeight": 80, "aspectRatio": 1.0, "usesCurrentColor": true, "palette": ["#CC1A1A"], "inlineSvg": "…" } } Field notes: - palette: Exact fill/stroke/stop-color values from the source, excluding currentColor / none / url(...) paint-server refs. Hex uppercased. - usesCurrentColor: true when the source paints with currentColor (theme-adaptive). - inlineSvg: Sanitized, embed-ready source — present only when ≤ 32 KB. - sourceUrl: CORS-safe URL to the raw source — present instead of inlineSvg when the source exceeds 32 KB. - Present on both get_asset and search_assets results; absent for non-SVG assets. ## Delete Response Shape DELETE /assets/:id now performs a soft delete. Response: { "id": "uuid", "status": "deleted", "deletedAt": "2026-03-30T00:00:00.000Z", "recoversAt": "2026-04-29T00:00:00.000Z" } The asset is hidden from default listings but can be recovered within 30 days via POST /assets/:id/recover. After 30 days, a scheduled purge permanently removes the asset and its file from storage. To list soft-deleted assets, pass status=deleted on GET /assets. ## Error Responses All errors return: { "error": "Description of what went wrong" } Status codes: 400 (bad request), 401 (unauthorized), 404 (not found), 429 (tier limit), 500 (server error) Auth failures (401/403) carry two extra fields so you can act without guessing: code Stable machine-readable discriminator, e.g. "agent_session_expired", "agent_session_revoked", "invalid_agent_session", "invalid_api_key", "unrecognized_token", "agent_session_forbidden". remediation A sentence naming the fix. Relay it to the user verbatim. Expired and revoked agent sessions also carry "session_name" and "renew_url". Example: { "error": "agent_session_expired", "code": "agent_session_expired", "session_name": "Brand refresh", "expired_at": "2026-08-08T19:00:00.000Z", "remediation": "Mint a new token: Argus library toolbar -> Connect agent.", "renew_url": "https://argus.build/settings/agent-sessions" } ## Rate Limits Tier-based limits only (asset count + credits/month per plan). No per-IP rate limiting. When tier limits are hit: 429 with { "error": "...", "upgrade": true } Check GET /usage for current consumption. Credits reset monthly; one-time packs don't expire. ## Supported Formats Analyzed (AI vision): JPEG, PNG, GIF, WebP, SVG Stored, not analyzed: AVIF, JPEG 2000 · AVIF — stored and served (browsers render AVIF), but cf.image only accepts AVIF input on an Enterprise plan, so no thumbnail, dHash, or analysis path yet · JPEG 2000 — stored with the correct MIME type instead of application/octet-stream, but neither cf.image nor browsers decode JP2 — needs an ingest transcode Other file types: Uploaded and stored, but NOT analyzed (status set to "ready" immediately, no analysis data) Max upload size: 20 MB (SVG: 2 MB) Storage backend: Cloudflare R2 ## Processing Time Analysis is async. After upload, status is "pending". Typical time: 5–15 seconds How to check: Poll GET /assets/:id every 2–3 seconds until status is "ready" or "error" Webhooks: Not available — polling only ## Search Semantics The q parameter on GET /assets runs natural-language semantic search: the query is embedded and matched against a vector index built from each asset's AI-analysis fields (description, richDescription, mood, tags, useCases) plus filename. Results are relevance-ranked by semantic similarity, so a query like "hero shots, muted palette" matches conceptually related assets, not just exact term overlap. project, status, and tags remain composable filters applied on top of the ranked results. If semantic search is ever unavailable (or an asset is not yet indexed) the query transparently falls back to keyword scoring across the same fields, so search never returns worse than exact-match results. ## Pricing Free: 50 assets, 50 credits/month, $0 Starter: Unlimited assets, 500 credits/month, $19/mo Pro: Unlimited assets, 5000 credits/month, $79/mo Enterprise: Unlimited everything, custom pricing 1 credit = 1 AI image analysis Credit packs (one-time): 100/$2, 500/$8, 2500/$30 ## When NOT to Use Argus - Video files: not supported, no analysis - Real-time processing: analysis is async, not instant - Non-image documents: PDFs and docs are stored but not analyzed - Self-hosted storage: Argus uses Cloudflare R2 only, no BYO-bucket - Bulk migration: no batch upload API — the CLI's `sync ` command onboards a folder by uploading sequentially (or loop over /assets/upload / MCP yourself)