{"openapi":"3.1.0","info":{"title":"Impri API","version":"v1","description":"Human-in-the-loop approval API for AI agents. POST an action, get a decision back via webhook or polling. The imprimatur for your AI agents."},"servers":[{"url":"https://api.impri.dev/v1","description":"API v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"im_<key>"}},"schemas":{"CreateActionBody":{"$ref":"#/definitions/CreateActionBody","definitions":{"CreateActionBody":{"type":"object","properties":{"kind":{"type":"string","minLength":1,"maxLength":100},"title":{"type":"string","minLength":1,"maxLength":500,"pattern":"^[^\\r\\n]+$"},"preview":{"type":"object","properties":{"format":{"type":"string","enum":["markdown","plain","diff"],"default":"plain"},"body":{"type":"string","maxLength":262144}},"required":["body"],"additionalProperties":false},"payload":{},"target_url":{"type":"string","format":"uri"},"callback_url":{"type":"string","format":"uri"},"expires_in":{"type":"integer","minimum":300,"maximum":2592000,"default":259200},"idempotency_key":{"type":"string","maxLength":255},"editable":{"type":"array","items":{"type":"string"},"default":[]},"idempotent":{"type":"boolean"},"undo":{"type":"string","maxLength":2000}},"required":["kind","title","preview"],"additionalProperties":false}},"$schema":"http://json-schema.org/draft-07/schema#"},"DecisionBody":{"$ref":"#/definitions/DecisionBody","definitions":{"DecisionBody":{"type":"object","properties":{"decision":{"type":"string","enum":["approve","reject"]},"edited":{"type":"object","additionalProperties":{}},"channel":{"type":"string","pattern":"^[A-Za-z0-9_-]{0,64}$"}},"required":["decision"],"additionalProperties":false}},"$schema":"http://json-schema.org/draft-07/schema#"},"ResultBody":{"$ref":"#/definitions/ResultBody","definitions":{"ResultBody":{"type":"object","properties":{"status":{"type":"string","enum":["executed","execute_failed"]},"detail":{"type":"string"},"payload":{"type":"object","additionalProperties":{}}},"required":["status"],"additionalProperties":false}},"$schema":"http://json-schema.org/draft-07/schema#"},"ListActionsQuery":{"$ref":"#/definitions/ListActionsQuery","definitions":{"ListActionsQuery":{"type":"object","properties":{"status":{"type":"string","enum":["pending","approved","rejected","expired","executed","execute_failed"]},"since":{"type":"integer"},"kind":{"type":"string"},"q":{"type":"string","maxLength":200},"limit":{"type":"integer","minimum":1,"maximum":100,"default":50},"cursor":{"type":"string"}},"additionalProperties":false}},"$schema":"http://json-schema.org/draft-07/schema#"},"CreateKeyBody":{"$ref":"#/definitions/CreateKeyBody","definitions":{"CreateKeyBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"scopes":{"type":"array","items":{"type":"string","enum":["actions","watch","admin"]},"minItems":1}},"required":["name","scopes"],"additionalProperties":false}},"$schema":"http://json-schema.org/draft-07/schema#"},"ActionStatus":{"$ref":"#/definitions/ActionStatus","definitions":{"ActionStatus":{"type":"string","enum":["pending","approved","rejected","expired","executed","execute_failed"]}},"$schema":"http://json-schema.org/draft-07/schema#"},"CreateWatcherBody":{"$ref":"#/definitions/CreateWatcherBody","definitions":{"CreateWatcherBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"kind":{"type":"string","enum":["rss","reddit_search","url_diff"]},"config":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"query":{"type":"string","minLength":1,"maxLength":500},"subreddit":{"type":"string","minLength":1,"maxLength":100}},"additionalProperties":false},"keywords":{"type":"array","items":{"type":"object","properties":{"pattern":{"type":"string","minLength":1,"maxLength":500},"points":{"type":"integer","minimum":1,"maximum":100}},"required":["pattern","points"],"additionalProperties":false},"default":[]},"keywords_none":{"type":"array","items":{"$ref":"#/definitions/CreateWatcherBody/properties/keywords/items/properties/pattern"},"default":[]},"min_score":{"type":"integer","minimum":0,"default":1},"schedule":{"type":"object","properties":{"every":{"type":"string","pattern":"^\\d+[mhd]$"},"jitter":{"type":"string","pattern":"^\\d+[mhd]$"},"window":{"type":"string","pattern":"^\\d{2}:\\d{2}-\\d{2}:\\d{2}$"}},"required":["every"],"additionalProperties":false},"color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}]}},"required":["name","kind","config","schedule"],"additionalProperties":false}},"$schema":"http://json-schema.org/draft-07/schema#"},"UpdateWatcherBody":{"$ref":"#/definitions/UpdateWatcherBody","definitions":{"UpdateWatcherBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"config":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"query":{"type":"string","minLength":1,"maxLength":500},"subreddit":{"type":"string","minLength":1,"maxLength":100}},"additionalProperties":false},"keywords":{"type":"array","items":{"type":"object","properties":{"pattern":{"type":"string","minLength":1,"maxLength":500},"points":{"type":"integer","minimum":1,"maximum":100}},"required":["pattern","points"],"additionalProperties":false}},"keywords_none":{"type":"array","items":{"$ref":"#/definitions/UpdateWatcherBody/properties/keywords/items/properties/pattern"}},"min_score":{"type":"integer","minimum":0},"schedule":{"type":"object","properties":{"every":{"type":"string","pattern":"^\\d+[mhd]$"},"jitter":{"type":"string","pattern":"^\\d+[mhd]$"},"window":{"type":"string","pattern":"^\\d{2}:\\d{2}-\\d{2}:\\d{2}$"}},"required":["every"],"additionalProperties":false},"status":{"type":"string","enum":["active","paused"]},"color":{"anyOf":[{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},{"type":"null"}]}},"additionalProperties":false}},"$schema":"http://json-schema.org/draft-07/schema#"},"ListWatchersQuery":{"$ref":"#/definitions/ListWatchersQuery","definitions":{"ListWatchersQuery":{"type":"object","properties":{"status":{"type":"string","enum":["active","paused","degraded"]},"kind":{"type":"string","enum":["rss","reddit_search","url_diff"]},"limit":{"type":"integer","minimum":1,"maximum":100,"default":50},"cursor":{"type":"string"}},"additionalProperties":false}},"$schema":"http://json-schema.org/draft-07/schema#"}}},"paths":{"/actions":{"post":{"operationId":"createAction","summary":"Submit an action for human approval","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateActionBody"}}}},"responses":{"200":{"description":"Idempotent match or soft duplicate"},"201":{"description":"Action created"},"400":{"description":"Validation error"},"429":{"description":"Rate limit exceeded"}}},"get":{"operationId":"listActions","summary":"List actions with optional filters","parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/ActionStatus"}},{"name":"since","in":"query","schema":{"type":"integer"}},{"name":"kind","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of actions"}}}},"/actions/{id}":{"get":{"operationId":"getAction","summary":"Get a single action by ID","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Action with decision and webhook delivery status"},"404":{"description":"Not found"}}}},"/actions/{id}/decision":{"post":{"operationId":"submitDecision","summary":"Approve or reject an action","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionBody"}}}},"responses":{"200":{"description":"Decision recorded"},"409":{"description":"Already decided"}}}},"/actions/{id}/result":{"post":{"operationId":"reportResult","summary":"Report execution result after approval","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResultBody"}}}},"responses":{"200":{"description":"Result recorded"},"409":{"description":"Action not in approved state"}}}},"/keys":{"post":{"operationId":"createKey","summary":"Create a new API key (admin scope)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyBody"}}}},"responses":{"201":{"description":"Key created — key value returned once"}}},"get":{"operationId":"listKeys","summary":"List API keys (admin scope)","responses":{"200":{"description":"List of keys"}}}},"/keys/{id}":{"delete":{"operationId":"revokeKey","summary":"Revoke an API key (admin scope)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Revoked"},"404":{"description":"Not found"}}}},"/watchers":{"post":{"operationId":"createWatcher","summary":"Create a watcher (watch scope)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWatcherBody"}}}},"responses":{"201":{"description":"Watcher created"},"400":{"description":"Validation error"},"429":{"description":"Rate limit exceeded"}}},"get":{"operationId":"listWatchers","summary":"List watchers with optional filters","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["active","paused","degraded"]}},{"name":"kind","in":"query","schema":{"type":"string","enum":["rss","reddit_search","url_diff"]}},{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of watchers"}}}},"/watchers/{id}":{"get":{"operationId":"getWatcher","summary":"Get a single watcher with item count","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Watcher"},"404":{"description":"Not found"}}},"patch":{"operationId":"updateWatcher","summary":"Update a watcher (name/config/schedule/status)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWatcherBody"}}}},"responses":{"200":{"description":"Updated watcher"},"404":{"description":"Not found"}}},"delete":{"operationId":"deleteWatcher","summary":"Delete a watcher and its dedup items","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"404":{"description":"Not found"}}}},"/project":{"get":{"operationId":"getProject","summary":"Project info incl. webhook_secret (admin scope)","responses":{"200":{"description":"Project"},"404":{"description":"Not found"}}},"patch":{"operationId":"updateProject","summary":"Update project name / timezone (admin scope)","responses":{"200":{"description":"Updated project"},"400":{"description":"Invalid timezone"}}}},"/project/rotate-webhook-secret":{"post":{"operationId":"rotateWebhookSecret","summary":"Rotate the project webhook signing secret (admin scope)","responses":{"200":{"description":"New webhook secret"}}}},"/project/export":{"get":{"operationId":"exportProject","summary":"GDPR export of all project-scoped data (admin scope)","responses":{"200":{"description":"Project data dump"}}}},"/project/data":{"delete":{"operationId":"eraseProjectData","summary":"GDPR erasure — wipe content + PII, keep project & keys (admin scope)","responses":{"200":{"description":"Erased with counts"}}}},"/billing":{"get":{"operationId":"getBilling","summary":"Current tier, subscription status and usage (admin scope)","responses":{"200":{"description":"Billing state incl. usage and billing_enabled flag"}}}},"/billing/checkout":{"post":{"operationId":"createCheckout","summary":"Create a Stripe Checkout session for a plan (admin scope; billing must be enabled)","responses":{"200":{"description":"Checkout URL"},"400":{"description":"Billing disabled or plan not configured"}}}},"/billing/portal":{"post":{"operationId":"createBillingPortal","summary":"Create a Stripe customer portal session (admin scope)","responses":{"200":{"description":"Portal URL"},"400":{"description":"No customer / billing disabled"}}}},"/billing/webhook":{"post":{"operationId":"stripeWebhook","summary":"Stripe webhook (public; verified by Stripe signature, not API key)","responses":{"200":{"description":"Event processed"},"400":{"description":"Bad signature"}}}},"/push/vapid-public-key":{"get":{"operationId":"getVapidPublicKey","summary":"VAPID public key for browser push subscription (public)","responses":{"200":{"description":"{ enabled, public_key }"}}}},"/push/subscribe":{"post":{"operationId":"pushSubscribe","summary":"Register a browser push subscription (actions scope)","responses":{"201":{"description":"Subscribed"},"400":{"description":"Push disabled / invalid"}}},"delete":{"operationId":"pushUnsubscribe","summary":"Remove a browser push subscription (actions scope)","responses":{"204":{"description":"Removed"}}}}}}