{"openapi":"3.1.0","info":{"title":"Doogle Translate API","version":"1.0.0","summary":"Translate a dog's body language from a photo, using vision AI.","description":"Given a photo of a dog, the API returns a structured analysis of mood, body language signals (eyes, ears, mouth, tail, posture, energy), what the dog likely wants, and a single-line quote in the dog's voice. Designed for agentic use — every translation is a strict JSON object with stable field names and an honest confidence score.\n\n## When to use this API\n\nUse it when a user wants to know what a dog is feeling, thinking, or trying to communicate AND a photo is available. Do **not** use it for veterinary diagnosis — direct medical/behavioral concerns to a licensed vet.\n\n## Authentication\n\nAnonymous device-based. Generate a random 32-char ID once per agent identity and pass it as `x-device-id` on every request. The server provisions an anonymous user record automatically. For higher rate limits, request an API key at info@doogletranslate.com.","contact":{"name":"Doogle Translate","email":"info@doogletranslate.com","url":"https://doogletranslate.com"},"license":{"name":"Proprietary","url":"https://doogletranslate.com/terms"}},"servers":[{"url":"https://doogle-api-production.up.railway.app","description":"Production API"}],"components":{"securitySchemes":{"DeviceId":{"type":"apiKey","in":"header","name":"x-device-id","description":"Random 32-character device ID. Generate once per agent identity and persist. Anonymous; no signup required."}},"schemas":{"BodyPartObservation":{"type":"object","required":["reading","meaning"],"properties":{"reading":{"type":"string","description":"What is observably visible in this body part (factual, no interpretation). One sentence.","maxLength":140},"meaning":{"type":"string","description":"What that signal communicates about the dog's emotional state or intent (hedged appropriately). One sentence.","maxLength":160}}},"BodyLanguageDetail":{"type":"object","required":["eyes","ears","mouth","tail","posture","energy"],"properties":{"eyes":{"$ref":"#/components/schemas/BodyPartObservation"},"ears":{"$ref":"#/components/schemas/BodyPartObservation"},"mouth":{"$ref":"#/components/schemas/BodyPartObservation"},"tail":{"$ref":"#/components/schemas/BodyPartObservation"},"posture":{"$ref":"#/components/schemas/BodyPartObservation"},"energy":{"$ref":"#/components/schemas/BodyPartObservation"}}},"TranslationResult":{"type":"object","required":["id","mood","moodEmoji","quote","bodyLanguage","bodyLanguageDetail","wantsAndNeeds","context","confidence"],"properties":{"id":{"type":"string","format":"uuid"},"mood":{"type":"string","enum":["happy","excited","playful","curious","calm","alert","anxious","scared","annoyed","guilty","sleepy","hungry","loving","zoomies","judgmental"]},"moodEmoji":{"type":"string","description":"Single emoji or short combo matching the mood. ≤8 chars."},"quote":{"type":"string","description":"Single-sentence, first-person quote in the dog's voice. Memeable, screenshot-worthy. ≤180 chars."},"bodyLanguage":{"type":"string","description":"TL;DR summary across all six body parts. ≤220 chars."},"bodyLanguageDetail":{"$ref":"#/components/schemas/BodyLanguageDetail"},"wantsAndNeeds":{"type":"string","description":"What the dog likely wants right now. Specific. ≤180 chars."},"context":{"type":"string","description":"Scene/environment description. ≤160 chars."},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"Honest confidence in the read. Lower for unclear/dim/blurry photos."},"imageUrl":{"type":"string","format":"uri","description":"URL to the stored image (private to the device)."},"isPublic":{"type":"boolean","description":"Whether this translation is shared on the public feed."},"createdAt":{"type":"string","format":"date-time"}}},"TranslateRequest":{"type":"object","required":["imageBase64"],"properties":{"imageBase64":{"type":"string","format":"byte","description":"Base64-encoded image bytes (no data URL prefix). JPEG/PNG/WebP. Max 8MB decoded."},"mimeType":{"type":"string","enum":["image/jpeg","image/png","image/webp"],"default":"image/jpeg"}}},"DogProfile":{"type":"object","properties":{"dogName":{"type":"string","maxLength":40,"nullable":true},"dogBreed":{"type":"string","maxLength":80,"nullable":true,"description":"Primary breed, e.g. 'Golden Retriever'."},"dogBreedMix":{"type":"string","maxLength":160,"nullable":true,"description":"Secondary mix, e.g. 'Poodle' — set only if the dog is mixed."},"dogAgeYears":{"type":"number","minimum":0,"maximum":40,"nullable":true,"description":"Age in years (decimals allowed; 0.5 = 6 months)."},"dogGender":{"type":"string","enum":["male","female","unknown"],"nullable":true}}},"ErrorResponse":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"message":{"type":"string"}}}}},"security":[{"DeviceId":[]}],"paths":{"/health":{"get":{"summary":"Health check","description":"Returns 200 OK when the API is reachable. No auth.","security":[],"responses":{"200":{"description":"Healthy","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}}}}},"/v1/translate":{"post":{"summary":"Translate a dog from an image","description":"**Primary endpoint.** Send a base64-encoded JPEG/PNG/WebP image; receive a structured translation. The AI analyzes six body language signals (eyes, ears, mouth, tail, posture, energy) and returns mood + a quote in the dog's voice + per-body-part breakdown + scene context + what the dog wants + a confidence score.\n\nIf no dog is detected, returns HTTP 422 with `error: 'no_dog_detected'`.\n\nIf a dog profile has been set via `PATCH /v1/me`, the translation is breed-aware and uses the dog's name naturally.","operationId":"translateDog","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranslateRequest"}}}},"responses":{"201":{"description":"Translation created","content":{"application/json":{"schema":{"type":"object","required":["translation"],"properties":{"translation":{"$ref":"#/components/schemas/TranslationResult"}}}}}},"401":{"description":"Missing or invalid x-device-id header","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Image too large (over 8MB decoded)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"No dog detected in image","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","enum":["no_dog_detected"]},"quote":{"type":"string","description":"Polite redirect quote."}}}}}}}}},"/v1/me":{"get":{"summary":"Get current user + dog profile","operationId":"getMe","responses":{"200":{"description":"Current user","content":{"application/json":{"schema":{"type":"object","required":["user"],"properties":{"user":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"handle":{"type":"string","nullable":true},"dogName":{"type":"string","nullable":true},"dogBreed":{"type":"string","nullable":true},"dogBreedMix":{"type":"string","nullable":true},"dogAgeYears":{"type":"number","nullable":true},"dogGender":{"type":"string","enum":["male","female","unknown"],"nullable":true},"streakCount":{"type":"integer"}}}}}}}}}},"patch":{"summary":"Update dog profile","description":"Set or update the dog's name, breed, breed mix, age, gender. **Improves translation quality** by making the AI breed-aware on subsequent calls.","operationId":"updateDogProfile","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DogProfile"}}}},"responses":{"200":{"description":"Profile updated","content":{"application/json":{"schema":{"type":"object","required":["user"],"properties":{"user":{"type":"object"}}}}}}}}},"/v1/translations/{id}":{"get":{"summary":"Get a public translation by ID","description":"Used for share links — returns 404 if the translation isn't marked public.","operationId":"getTranslation","security":[],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Translation found and public","content":{"application/json":{"schema":{"type":"object","properties":{"translation":{"$ref":"#/components/schemas/TranslationResult"}}}}}},"404":{"description":"Not found or not public"}}}},"/v1/feed":{"get":{"summary":"Public translation feed","description":"Browse opt-in public translations. Returns a paginated list. No auth required.","operationId":"getFeed","security":[],"parameters":[{"name":"sort","in":"query","schema":{"type":"string","enum":["recent","top"],"default":"recent"}},{"name":"cursor","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Paginated feed","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TranslationResult"}},"nextCursor":{"type":"string","format":"date-time","nullable":true}}}}}}}}},"/v1/mcp":{"post":{"summary":"Model Context Protocol endpoint","description":"JSON-RPC 2.0 endpoint implementing the Model Context Protocol (MCP). Supports `initialize`, `tools/list`, and `tools/call`. Exposes `translate_dog` as a callable tool. See https://modelcontextprotocol.io for protocol details.","operationId":"mcpJsonRpc","security":[{"DeviceId":[]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","enum":["2.0"]},"id":{"oneOf":[{"type":"string"},{"type":"integer"}]},"method":{"type":"string"},"params":{"type":"object"}}}}}},"responses":{"200":{"description":"JSON-RPC response","content":{"application/json":{"schema":{"type":"object"}}}}}}}}}