{"openapi":"3.1.0","info":{"title":"Domain Expansion API","version":"1.0.0","description":"Versioned domain API. Money is integer minor units. Dates are YYYY-MM-DD. Null means unknown. Omitted PATCH fields stay unchanged. Explicit null clears nullable fields. Archive is isArchived. Permanent deletion requires domains:delete and If-Match. Writes require Idempotency-Key. Browser sessions sign in through Uppercut Account (https://account.uppercut-labs.com) and use an HttpOnly session cookie; automation uses personal access tokens as Bearer credentials."},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer"}},"schemas":{"DomainInput":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"name":{"type":"string","minLength":1},"registrar":{"type":["string","null"],"maxLength":200},"dnsProvider":{"type":["string","null"],"maxLength":200},"ownership":{"type":"string","enum":["owned","managed"]},"lifecycle":{"type":"string","enum":["active","inactive","transferred"]},"renewalIntent":{"type":"string","enum":["renew","let_expire"]},"autoRenew":{"type":["boolean","null"]},"registrationDate":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"billingDate":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"expirationDate":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"registrationCostMinor":{"type":["integer","null"],"minimum":0},"renewalCostMinor":{"type":["integer","null"],"minimum":0},"purchaseEmail":{"type":["string","null"],"pattern":"^(\\s*|\\s*[^\\s@]+@[^\\s@]+\\.[^\\s@]+\\s*)$","description":"Optional purchase contact email; blank values become null and surrounding whitespace is trimmed."},"paymentMethod":{"type":["string","null"],"maxLength":2000,"description":"Optional user-provided payment method description."},"currency":{"type":"string","enum":["USD","GBP","EUR","INR","CNY","JPY","CAD"]},"notes":{"type":"string"},"isArchived":{"type":"boolean"},"reminders":{"type":"object","additionalProperties":false,"required":["enabled","target","offsets"],"properties":{"enabled":{"type":"boolean"},"target":{"type":"string","enum":["default","billing","expiration"]},"offsets":{"type":"array","maxItems":16,"items":{"type":"integer","minimum":1,"maximum":365}}}}}},"DomainPatch":{"type":"object","additionalProperties":false,"properties":{"name":{"type":"string","minLength":1},"registrar":{"type":["string","null"],"maxLength":200},"dnsProvider":{"type":["string","null"],"maxLength":200},"ownership":{"type":"string","enum":["owned","managed"]},"lifecycle":{"type":"string","enum":["active","inactive","transferred"]},"renewalIntent":{"type":"string","enum":["renew","let_expire"]},"autoRenew":{"type":["boolean","null"]},"registrationDate":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"billingDate":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"expirationDate":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"registrationCostMinor":{"type":["integer","null"],"minimum":0},"renewalCostMinor":{"type":["integer","null"],"minimum":0},"purchaseEmail":{"type":["string","null"],"pattern":"^(\\s*|\\s*[^\\s@]+@[^\\s@]+\\.[^\\s@]+\\s*)$","description":"Optional purchase contact email; blank values become null and surrounding whitespace is trimmed."},"paymentMethod":{"type":["string","null"],"maxLength":2000,"description":"Optional user-provided payment method description."},"currency":{"type":"string","enum":["USD","GBP","EUR","INR","CNY","JPY","CAD"]},"notes":{"type":"string"},"isArchived":{"type":"boolean"},"reminders":{"type":"object","additionalProperties":false,"required":["enabled","target","offsets"],"properties":{"enabled":{"type":"boolean"},"target":{"type":"string","enum":["default","billing","expiration"]},"offsets":{"type":"array","maxItems":16,"items":{"type":"integer","minimum":1,"maximum":365}}}},"clearCostsOnCurrencyChange":{"type":"boolean","const":true}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","requestId","retryable"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"requestId":{"type":"string"},"retryable":{"type":"boolean"},"issues":{"type":"array","items":{"type":"object"}}}}}}}},"paths":{"/api/health":{"get":{"summary":"Process health","responses":{"200":{"description":"ok"}}}},"/api/openapi.json":{"get":{"summary":"This document","responses":{"200":{"description":"OpenAPI"}}}},"/api/auth/login":{"get":{"summary":"Start sign-in through Uppercut Account. Optional returnTo (same-origin path) and step_up=1 for a fresh sign-in.","responses":{"302":{"description":"Redirect to Uppercut Account"}}}},"/api/auth/callback":{"get":{"summary":"Finish sign-in: verifies the OIDC response and sets the session cookie.","responses":{"302":{"description":"Redirect into the app"}}}},"/api/auth/logout":{"post":{"summary":"Clear the session and return the Uppercut Account sign-out URL.","responses":{"200":{"description":"Redirect target"}}}},"/api/session":{"get":{"security":[{"bearer":[]}],"summary":"Current actor","responses":{"200":{"description":"Actor"}}}},"/api/v1/domains":{"get":{"security":[{"bearer":[]}],"summary":"List the caller portfolio. Requires domains:read.","parameters":[{"name":"q"},{"name":"cursor"},{"name":"limit"},{"name":"archived"},{"name":"sort"}],"responses":{"200":{"description":"Page"}}},"post":{"security":[{"bearer":[]}],"summary":"Create. Requires domains:write and Idempotency-Key.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainInput"}}}},"responses":{"201":{"description":"Created","headers":{"ETag":{"schema":{"type":"string"}}}},"400":{"description":"Missing or invalid Idempotency-Key"},"409":{"description":"Duplicate or idempotency conflict"},"422":{"description":"Invalid domain fields"}}}},"/api/v1/domains/batch":{"post":{"security":[{"bearer":[]}],"summary":"Atomic create of up to 100 records, or 25 when source is ai.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["records"],"properties":{"source":{"type":"string","enum":["manual","ai"]},"records":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/DomainInput"}}}}}}},"responses":{"201":{"description":"Created"},"400":{"description":"Missing or invalid Idempotency-Key"},"409":{"description":"Duplicate domain or idempotency conflict"},"413":{"description":"Batch limit exceeded"},"422":{"description":"Invalid domain fields"}}}},"/api/v1/domains/{id}":{"get":{"security":[{"bearer":[]}],"summary":"Read one record. Foreign ids look missing.","responses":{"200":{"description":"Record and ETag"},"404":{"description":"Not found"}}},"patch":{"security":[{"bearer":[]}],"summary":"Partial update. Requires If-Match.","parameters":[{"name":"If-Match","in":"header","required":true,"schema":{"type":"string","pattern":"^(?:W/)?\"[0-9]+\"$|^[0-9]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainPatch"}}}},"responses":{"200":{"description":"Updated","headers":{"ETag":{"schema":{"type":"string"}}}},"412":{"description":"Stale revision"},"422":{"description":"Invalid domain fields"},"428":{"description":"Missing If-Match"}}},"delete":{"security":[{"bearer":[]}],"summary":"Permanent delete. Requires domains:delete and If-Match.","responses":{"204":{"description":"Deleted"}}}},"/api/v1/import/preview":{"post":{"security":[{"bearer":[]}],"summary":"Validate an import and write nothing to the portfolio.","responses":{"200":{"description":"Preview"}}}},"/api/v1/import/commit":{"post":{"security":[{"bearer":[]}],"summary":"Apply a preview. Omitted records are not deleted.","responses":{"200":{"description":"Committed"}}}},"/api/v1/export":{"get":{"security":[{"bearer":[]}],"summary":"Export the caller domains. Settings require a browser session.","responses":{"200":{"description":"File"}}}},"/api/v1/summary":{"get":{"security":[{"bearer":[]}],"summary":"Dashboard summary for the caller only.","responses":{"200":{"description":"Summary"}}}},"/api/v1/settings":{"get":{"security":[{"bearer":[]}],"summary":"Caller settings. Session only.","responses":{"200":{"description":"Settings"}}},"put":{"security":[{"bearer":[]}],"summary":"Replace caller settings. Session only.","responses":{"200":{"description":"Saved"}}}},"/api/tokens":{"get":{"security":[{"bearer":[]}],"summary":"List token metadata. Secrets are not returned.","responses":{"200":{"description":"Tokens"}}},"post":{"security":[{"bearer":[]}],"summary":"Create a personal access token. Plaintext is returned once.","responses":{"201":{"description":"Token"}}}},"/api/tokens/{id}/revoke":{"post":{"security":[{"bearer":[]}],"summary":"Revoke one personal access token.","responses":{"200":{"description":"Revoked"}}}},"/api/credentials/gemini":{"get":{"security":[{"bearer":[]}],"summary":"Credential status. The raw key is never returned.","responses":{"200":{"description":"Status"}}},"put":{"security":[{"bearer":[]}],"summary":"Store a consented Gemini key. Recent session required.","responses":{"200":{"description":"Stored"}}},"delete":{"security":[{"bearer":[]}],"summary":"Remove the stored Gemini key.","responses":{"200":{"description":"Removed"}}}},"/api/ai/extract":{"post":{"security":[{"bearer":[]}],"summary":"Propose drafts. Does not write domains.","responses":{"200":{"description":"Drafts"}}}}}}