{"openapi":"3.1.0","info":{"title":"Ethogram API","version":"0.12","description":"Report your server's requests to Ethogram, and mark releases. Full guide: https://ethogram.si/docs/api","contact":{"email":"support@ethogram.si"}},"servers":[{"url":"https://ethogram.si"}],"security":[{"serverKey":[]}],"externalDocs":{"url":"https://ethogram.si/docs","description":"Documentation"},"paths":{"/api/e":{"post":{"operationId":"sendEvents","summary":"Send a batch of requests your server handled","externalDocs":{"url":"https://ethogram.si/docs/api"},"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["events"],"properties":{"events":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/Event"}},"collector":{"type":"string","description":"The snippet version your code follows, currently \"0.12\"."}}},{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/Event"}}]}}}},"responses":{"202":{"description":"The batch was accepted. accepted counts the events that could be used.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"accepted\": 1 }"}}}}},"400":{"description":"The body isn't JSON. Or: No events list, an empty one, or more than 50 events.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"Body must be JSON.\" }"},"example2":{"value":"{ \"error\": \"Send 1 to 50 events.\" }"}}}}},"401":{"description":"The Authorization header is missing, malformed, or holds a key no project has, or one that was revoked.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"Unknown server key.\" }"}}}}},"402":{"description":"No owner of the project has an active plan, so nothing is recorded.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"This project has no active plan, ...\" }"}}}}},"413":{"description":"The body is over 64 KB.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"Batch too large.\" }"}}}}},"429":{"description":"Over the rate limit. Comes with Retry-After: 60.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"Too many events.\" }"}}}}}}}},"/api/releases":{"post":{"operationId":"markRelease","summary":"Mark a release, so changes in agent behavior line up with what shipped","externalDocs":{"url":"https://ethogram.si/docs/releases"},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"Required. What shipped, such as \"checkout v2\" or a commit. Spaces and line breaks collapse to one space; 1 to 80 characters."},"time":{"oneOf":[{"type":"integer","description":"Epoch milliseconds"},{"type":"string","format":"date-time"}],"description":"When it shipped: epoch milliseconds or an ISO 8601 string. Leave it out for now. Up to 7 days in the past, and no more than five minutes ahead. (Up to 7 days back.)"}}}}}},"responses":{"201":{"description":"The release was recorded.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"id\": \"1\", \"name\": \"checkout v2\", \"time\": \"...\" }"}}}}},"400":{"description":"The body isn't JSON. Or: No usable name, or a time that can't be read or is out of range. The error says which.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"Body must be JSON.\" }"},"example2":{"value":"{ \"error\": \"Give the release a name.\" }"}}}}},"401":{"description":"The Authorization header is missing, malformed, or holds a key no project has, or one that was revoked.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"Unknown server key.\" }"}}}}},"402":{"description":"No owner of the project has an active plan.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"This project has no active plan, ...\" }"}}}}},"413":{"description":"The body is over 2,048 bytes.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"Body too large.\" }"}}}}},"429":{"description":"Over 200 releases in a day. Comes with Retry-After.","content":{"application/json":{"schema":{"type":"object"},"examples":{"example1":{"value":"{ \"error\": \"Too many releases today.\" }"}}}}}}}}},"components":{"securitySchemes":{"serverKey":{"type":"http","scheme":"bearer","description":"The project's server key, from its setup page or Settings, Server keys."}},"schemas":{"Event":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":4000,"description":"Required. The full URL requested, with scheme and host (http or https). Up to 4,000 characters."},"method":{"type":"string","default":"GET","description":"The HTTP method. Defaults to GET."},"time":{"oneOf":[{"type":"integer","description":"Epoch milliseconds"},{"type":"string","format":"date-time"}],"description":"When the request arrived: epoch milliseconds or an ISO 8601 string. Missing, or more than a day in the past or five minutes in the future, it becomes the time we received it."},"status":{"type":"integer","minimum":100,"maximum":599,"description":"The response status code, 100 to 599, when known."},"duration":{"type":"number","minimum":0,"maximum":600000,"description":"Milliseconds taken to respond, 0 to 600,000, when known."},"ip":{"type":"string","description":"The visitor's IPv4 or IPv6 address."},"headers":{"type":"object","additionalProperties":{"type":"string","maxLength":2000},"maxProperties":100,"description":"The request headers, name to value. Names are lowercased; up to 100 headers of up to 2,000 characters each. Leave out cookie, authorization and proxy-authorization."},"response":{"type":"object","description":"What you answered: { \"content-type\": string, \"content-length\": number or numeric string, \"cf-mitigated\": string }, all optional. Kept for agents only, to explain why they get stuck (a challenge page, a JavaScript-only page, a slow answer).","properties":{"content-type":{"type":"string"},"content-length":{"oneOf":[{"type":"integer"},{"type":"string","pattern":"^\\d+$"}]},"cf-mitigated":{"type":"string"}}},"action":{"type":"object","required":["name"],"description":"A named action: { \"name\": up to 100 characters, \"target\": up to 200 }. See Named actions.","properties":{"name":{"type":"string","maxLength":100},"target":{"type":"string","maxLength":200}}},"edge":{"type":"object","description":"What your host's edge saw of the connection: { \"asn\": number, \"org\": string, \"protocol\": \"HTTP/2\" and the like, \"tls\": \"TLSv1.3\" and the like, \"botScore\": 1 to 99 }, all optional. Read to tell people from automation, never kept. The Cloudflare Worker sends it from request.cf.","properties":{"asn":{"type":"integer"},"org":{"type":"string","maxLength":200},"protocol":{"type":"string","maxLength":20},"tls":{"type":"string","maxLength":20},"botScore":{"type":"integer","minimum":1,"maximum":99}}},"browser":{"type":"object","description":"A browser check report: the JSON body the script posted to /_ethogram/report on your site, as received. The event's ip and headers are that request's own. It adds evidence to the visit and isn't counted as a request."}}}}}}